From c25f0585d1f063e82e5eafdda40ab6a87bf68443 Mon Sep 17 00:00:00 2001 From: Leandro Melo <leandro.melo@nokia.com> Date: Wed, 21 Dec 2011 18:24:14 +0100 Subject: [PATCH] C++: Extract function quickfix Task-number: QTCREATORBUG-5485 Change-Id: Ib6aaeaadad4b421480d22399392768d4bad85bda Reviewed-by: Roberto Raggi <roberto.raggi@nokia.com> --- src/plugins/cppeditor/cppinsertdecldef.cpp | 612 +++++++++++++++++++-- src/plugins/cppeditor/cppinsertdecldef.h | 18 +- src/plugins/cppeditor/cppquickfixes.cpp | 2 + src/plugins/cpptools/cpptoolsreuse.cpp | 16 + src/plugins/cpptools/cpptoolsreuse.h | 2 + 5 files changed, 589 insertions(+), 61 deletions(-) diff --git a/src/plugins/cppeditor/cppinsertdecldef.cpp b/src/plugins/cppeditor/cppinsertdecldef.cpp index 72bda81f481..5d1733274e4 100644 --- a/src/plugins/cppeditor/cppinsertdecldef.cpp +++ b/src/plugins/cppeditor/cppinsertdecldef.cpp @@ -38,11 +38,21 @@ #include <cplusplus/CppRewriter.h> #include <cplusplus/LookupContext.h> #include <cplusplus/Overview.h> +#include <cplusplus/ASTVisitor.h> #include <cpptools/insertionpointlocator.h> #include <cpptools/cpprefactoringchanges.h> +#include <cpptools/cpptoolsreuse.h> + +#include <utils/qtcassert.h> #include <QtCore/QCoreApplication> #include <QtCore/QDir> +#include <QtCore/QHash> +#include <QtCore/QStringBuilder> +#include <QtGui/QTextDocument> +#include <QtGui/QTextBlock> +#include <QtGui/QInputDialog> +#include <QtGui/QMessageBox> using namespace CPlusPlus; using namespace CppEditor; @@ -99,6 +109,8 @@ public: targetFile->apply(); } + static QString generateDeclaration(Function *function); + private: QString m_targetFileName; const Class *m_targetSymbol; @@ -106,6 +118,36 @@ private: QString m_decl; }; +Class *isMemberFunction(const LookupContext &context, Function *function) +{ + Q_ASSERT(function); + + Scope *enclosingScope = function->enclosingScope(); + while (! (enclosingScope->isNamespace() || enclosingScope->isClass())) + enclosingScope = enclosingScope->enclosingScope(); + Q_ASSERT(enclosingScope != 0); + + const Name *functionName = function->name(); + if (! functionName) + return 0; // anonymous function names are not valid c++ + + if (! functionName->isQualifiedNameId()) + return 0; // trying to add a declaration for a global function + + const QualifiedNameId *q = functionName->asQualifiedNameId(); + if (!q->base()) + return 0; + + if (ClassOrNamespace *binding = context.lookupType(q->base(), enclosingScope)) { + foreach (Symbol *s, binding->symbols()) { + if (Class *matchingClass = s->asClass()) + return matchingClass; + } + } + + return 0; +} + } // anonymous namespace QList<CppQuickFixOperation::Ptr> DeclFromDef::match( @@ -141,70 +183,39 @@ QList<CppQuickFixOperation::Ptr> DeclFromDef::match( if (!funDef || !funDef->symbol) return noResult(); - Function *method = funDef->symbol; - - Scope *enclosingScope = method->enclosingScope(); - while (! (enclosingScope->isNamespace() || enclosingScope->isClass())) - enclosingScope = enclosingScope->enclosingScope(); - Q_ASSERT(enclosingScope != 0); - - const Name *functionName = method->name(); - if (! functionName) - return noResult(); // warn, anonymous function names are not valid c++ - - if (! functionName->isQualifiedNameId()) - return noResult(); // warn, trying to add a declaration for a global function - - const QualifiedNameId *q = functionName->asQualifiedNameId(); - if (!q->base()) - return noResult(); - - if (ClassOrNamespace *binding = interface->context().lookupType(q->base(), enclosingScope)) { - foreach (Symbol *s, binding->symbols()) { - if (Class *matchingClass = s->asClass()) { - for (Symbol *s = matchingClass->find(q->identifier()); s; s = s->next()) { - if (! s->name()) - continue; - else if (! q->identifier()->isEqualTo(s->identifier())) - continue; - else if (! s->type()->isFunctionType()) - continue; - - if (s->type().isEqualTo(method->type())) - return noResult(); - } - - // a good candidate - - const QString fn = QString::fromUtf8(matchingClass->fileName(), - matchingClass->fileNameLength()); - const QString decl = generateDeclaration(interface, - method, - binding); - return singleResult( - new InsertDeclOperation(interface, fn, matchingClass, - InsertionPointLocator::Public, - decl)); + Function *fun = funDef->symbol; + if (Class *matchingClass = isMemberFunction(interface->context(), fun)) { + const QualifiedNameId *qName = fun->name()->asQualifiedNameId(); + for (Symbol *s = matchingClass->find(qName->base()->identifier()); s; s = s->next()) { + if (!s->name() + || !qName->identifier()->isEqualTo(s->identifier()) + || !s->type()->isFunctionType()) + continue; + + if (s->type().isEqualTo(fun->type())) { + // Declaration exists. + return noResult(); } } + QString fileName = QString::fromUtf8(matchingClass->fileName(), + matchingClass->fileNameLength()); + const QString decl = InsertDeclOperation::generateDeclaration(fun); + return singleResult(new InsertDeclOperation(interface, fileName, matchingClass, + InsertionPointLocator::Public, decl)); } return noResult(); } -QString DeclFromDef::generateDeclaration(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &, - Function *method, - ClassOrNamespace *targetBinding) +QString InsertDeclOperation::generateDeclaration(Function *function) { - Q_UNUSED(targetBinding); - Overview oo; oo.setShowFunctionSignatures(true); oo.setShowReturnTypes(true); oo.setShowArgumentNames(true); QString decl; - decl += oo(method->type(), method->unqualifiedName()); + decl += oo(function->type(), function->unqualifiedName()); decl += QLatin1String(";\n"); return decl; @@ -212,6 +223,9 @@ QString DeclFromDef::generateDeclaration(const QSharedPointer<const Internal::Cp namespace { + + + class InsertDefOperation: public CppQuickFixOperation { public: @@ -317,3 +331,501 @@ QList<CppQuickFixOperation::Ptr> DefFromDecl::match( return noResult(); } + +namespace { + +class ExtractFunctionOperation : public CppQuickFixOperation +{ +public: + ExtractFunctionOperation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, + int extractionStart, + int extractionEnd, + FunctionDefinitionAST *refFuncDef, + Symbol *funcReturn, + QList<QPair<QString, QString> > relevantDecls) + : CppQuickFixOperation(interface) + , m_extractionStart(extractionStart) + , m_extractionEnd(extractionEnd) + , m_refFuncDef(refFuncDef) + , m_funcReturn(funcReturn) + , m_relevantDecls(relevantDecls) + { + setDescription(QCoreApplication::translate("QuickFix::ExtractFunction", "Extract Function")); + } + + void performChanges(const CppTools::CppRefactoringFilePtr ¤tFile, + const CppTools::CppRefactoringChanges &refactoring) + { + QTC_ASSERT(!m_funcReturn || !m_relevantDecls.isEmpty(), return); + + const QString &funcName = getFunctionName(); + if (funcName.isEmpty()) + return; + + Function *refFunc = m_refFuncDef->symbol; + + // We don't need to rewrite the type for declarations made inside the reference function, + // since their scope will remain the same. Then we preserve the original spelling style. + // However, we must do so for the return type in the definition. + SubstitutionEnvironment env; + env.setContext(assistInterface()->context()); + env.switchScope(refFunc); + ClassOrNamespace *targetCoN = + assistInterface()->context().lookupType(refFunc->enclosingScope()); + if (!targetCoN) + targetCoN = assistInterface()->context().globalNamespace(); + UseMinimalNames subs(targetCoN); + env.enter(&subs); + + Overview printer; + Control *control = assistInterface()->context().control().data(); + QString funcDef; + QString funcDecl; // We generate a declaration only in the case of a member function. + QString funcCall; + + Class *matchingClass = isMemberFunction(assistInterface()->context(), refFunc); + + // Write return type. + if (!m_funcReturn) { + funcDef.append(QLatin1String("void ")); + if (matchingClass) + funcDecl.append(QLatin1String("void ")); + } else { + const FullySpecifiedType &fullType = rewriteType(m_funcReturn->type(), &env, control); + funcDef.append(printer.prettyType(fullType) + QLatin1Char(' ')); + funcDecl.append(printer.prettyType(m_funcReturn->type()) + QLatin1Char(' ')); + } + + // Write class qualification, if any. + if (matchingClass) { + const Name *name = rewriteName(matchingClass->name(), &env, control); + funcDef.append(printer.prettyName(name)); + funcDef.append(QLatin1String("::")); + } + + // Write the extracted function itself and its call. + funcDef.append(funcName); + if (matchingClass) + funcDecl.append(funcName); + funcCall.append(funcName); + funcDef.append(QLatin1Char('(')); + if (matchingClass) + funcDecl.append(QLatin1Char('(')); + funcCall.append(QLatin1Char('(')); + for (int i = m_funcReturn ? 1 : 0; i < m_relevantDecls.length(); ++i) { + QPair<QString, QString> p = m_relevantDecls.at(i); + funcCall.append(p.first); + funcDef.append(p.second); + if (matchingClass) + funcDecl.append(p.second); + if (i < m_relevantDecls.length() - 1) { + funcCall.append(QLatin1String(", ")); + funcDef.append(QLatin1String(", ")); + if (matchingClass) + funcDecl.append(QLatin1String(", ")); + } + } + funcDef.append(QLatin1Char(')')); + if (matchingClass) + funcDecl.append(QLatin1Char(')')); + funcCall.append(QLatin1Char(')')); + if (refFunc->isConst()) { + funcDef.append(QLatin1String(" const")); + funcDecl.append(QLatin1String(" const")); + } + funcDef.append(QLatin1String("\n{\n") + % currentFile->textOf(m_extractionStart, m_extractionEnd) + % QLatin1Char('\n')); + if (matchingClass) + funcDecl.append(QLatin1String(";\n")); + if (m_funcReturn) { + funcDef.append(QLatin1String("return ") + % m_relevantDecls.at(0).first + % QLatin1String(";\n")); + funcCall.prepend(m_relevantDecls.at(0).second % QLatin1String(" = ")); + } + funcDef.append(QLatin1String("}\n\n")); + funcDef.replace(QChar::ParagraphSeparator, QLatin1String("\n")); + funcCall.append(QLatin1Char(';')); + + Utils::ChangeSet change; + int position = currentFile->startOf(m_refFuncDef); + change.insert(position, funcDef); + change.replace(m_extractionStart, m_extractionEnd, funcCall); + currentFile->setChangeSet(change); + currentFile->appendIndentRange(Utils::ChangeSet::Range(position, position + funcDef.length())); + currentFile->apply(); + + // Write declaration, if necessary. + if (matchingClass) { + InsertionPointLocator locator(refactoring); + const InsertionLocation &location = + locator.methodDeclarationInClass(matchingClass->fileName(), + matchingClass, + InsertionPointLocator::Public); + CppTools::CppRefactoringFilePtr declFile = refactoring.file(matchingClass->fileName()); + change.clear(); + position = declFile->position(location.line(), location.column()); + change.insert(position, funcDecl); + declFile->setChangeSet(change); + declFile->appendIndentRange(Utils::ChangeSet::Range(position, + position + funcDecl.length())); + declFile->apply(); + } + } + + QString getFunctionName() const + { + bool ok; + QString name = + QInputDialog::getText(0, + QCoreApplication::translate("QuickFix::ExtractFunction", + "Extract Function Refactoring"), + QCoreApplication::translate("QuickFix::ExtractFunction", + "Enter function name"), + QLineEdit::Normal, + QString(), + &ok); + name = name.trimmed(); + if (!ok || name.isEmpty()) + return QString(); + + if (!isValidIdentifier(name)) { + QMessageBox::critical(0, + QCoreApplication::translate("QuickFix::ExtractFunction", + "Extract Function Refactoring"), + QCoreApplication::translate("QuickFix::ExtractFunction", + "Invalid function name")); + return QString(); + } + + return name; + } + + int m_extractionStart; + int m_extractionEnd; + FunctionDefinitionAST *m_refFuncDef; + Symbol *m_funcReturn; + QList<QPair<QString, QString> > m_relevantDecls; +}; + +QPair<QString, QString> assembleDeclarationData(const QString &specifiers, + DeclaratorAST *decltr, + const CppRefactoringFilePtr &file, + const Overview &printer) +{ + if (decltr->core_declarator + && decltr->core_declarator->asDeclaratorId() + && decltr->core_declarator->asDeclaratorId()->name) { + QString decltrText = file->textOf(file->startOf(decltr), + file->endOf(decltr->core_declarator)); + if (!decltrText.isEmpty()) { + const QString &name = printer.prettyName( + decltr->core_declarator->asDeclaratorId()->name->name); + QString completeDecl = specifiers; + if (!decltrText.contains(QLatin1Char(' '))) + completeDecl.append(QLatin1Char(' ') + decltrText); + else + completeDecl.append(decltrText); + return qMakePair(name, completeDecl); + } + } + return QPair<QString, QString>(); +} + + +class FunctionExtractionAnalyser : public ASTVisitor +{ +public: + FunctionExtractionAnalyser(TranslationUnit *unit, + const int selStart, + const int selEnd, + const CppRefactoringFilePtr &file, + const Overview &printer) + : ASTVisitor(unit) + , m_done(false) + , m_failed(false) + , m_selStart(selStart) + , m_selEnd(selEnd) + , m_extractionStart(0) + , m_extractionEnd(0) + , m_file(file) + , m_printer(printer) + {} + + bool operator()(FunctionDefinitionAST *refFunDef) + { + accept(refFunDef); + + if (!m_failed && m_extractionStart == m_extractionEnd) + m_failed = true; + + return !m_failed; + } + + bool preVisit(AST *) + { + if (m_done) + return false; + return true; + } + + void statement(StatementAST *stmt) + { + if (!stmt) + return; + + const int stmtStart = m_file->startOf(stmt); + const int stmtEnd = m_file->endOf(stmt); + + if (stmtStart >= m_selEnd + || m_extractionStart && stmtEnd > m_selEnd) { + m_done = true; + return; + } + + if (stmtStart >= m_selStart && !m_extractionStart) + m_extractionStart = stmtStart; + if (stmtEnd > m_extractionEnd && m_extractionStart) + m_extractionEnd = stmtEnd; + + accept(stmt); + } + + bool visit(CaseStatementAST *stmt) + { + statement(stmt->statement); + return false; + } + + bool visit(CompoundStatementAST *stmt) + { + for (StatementListAST *it = stmt->statement_list; it; it = it->next) { + statement(it->value); + if (m_done) + break; + } + return false; + } + + bool visit(DoStatementAST *stmt) + { + statement(stmt->statement); + return false; + } + + bool visit(ForeachStatementAST *stmt) + { + statement(stmt->statement); + return false; + } + + bool visit(ForStatementAST *stmt) + { + statement(stmt->initializer); + if (!m_done) + statement(stmt->statement); + return false; + } + + bool visit(IfStatementAST *stmt) + { + statement(stmt->statement); + if (!m_done) + statement(stmt->else_statement); + return false; + } + + bool visit(TryBlockStatementAST *stmt) + { + statement(stmt->statement); + for (CatchClauseListAST *it = stmt->catch_clause_list; it; it = it->next) { + statement(it->value); + if (m_done) + break; + } + return false; + } + + bool visit(WhileStatementAST *stmt) + { + statement(stmt->statement); + return false; + } + + bool visit(DeclarationStatementAST *declStmt) + { + // We need to collect the declarations we see before the extraction or even inside it. + // They might need to be used as either a parameter or return value. Actually, we could + // still obtain their types from the local uses, but it's good to preserve the original + // typing style. + if (declStmt + && declStmt->declaration + && declStmt->declaration->asSimpleDeclaration()) { + SimpleDeclarationAST *simpleDecl = declStmt->declaration->asSimpleDeclaration(); + if (simpleDecl->decl_specifier_list + && simpleDecl->declarator_list) { + const QString &specifiers = + m_file->textOf(m_file->startOf(simpleDecl), + m_file->endOf(simpleDecl->decl_specifier_list->lastValue())); + for (DeclaratorListAST *decltrList = simpleDecl->declarator_list; + decltrList; + decltrList = decltrList->next) { + const QPair<QString, QString> p = + assembleDeclarationData(specifiers, decltrList->value, m_file, m_printer); + if (!p.first.isEmpty()) + m_knownDecls.insert(p.first, p.second); + } + } + } + + return false; + } + + bool visit(ReturnStatementAST *) + { + if (m_extractionStart) { + m_done = true; + m_failed = true; + } + + return false; + } + + bool m_done; + bool m_failed; + const int m_selStart; + const int m_selEnd; + int m_extractionStart; + int m_extractionEnd; + QHash<QString, QString> m_knownDecls; + CppRefactoringFilePtr m_file; + const Overview &m_printer; +}; + +} // anonymous namespace + +QList<CppQuickFixOperation::Ptr> ExtractFunction::match( + const QSharedPointer<const CppQuickFixAssistInterface> &interface) +{ + CppRefactoringFilePtr file = interface->currentFile(); + + QTextCursor cursor = file->cursor(); + if (!cursor.hasSelection()) + return noResult(); + + const QList<AST *> &path = interface->path(); + FunctionDefinitionAST *refFuncDef = 0; // The "reference" function, which we will extract from. + for (int i = path.size() - 1; i >= 0; --i) { + refFuncDef = path.at(i)->asFunctionDefinition(); + if (refFuncDef) + break; + } + + if (!refFuncDef + || !refFuncDef->function_body + || !refFuncDef->function_body->asCompoundStatement() + || !refFuncDef->function_body->asCompoundStatement()->statement_list + || !refFuncDef->symbol + || !refFuncDef->symbol->name() + || refFuncDef->symbol->enclosingScope()->isTemplate() /* TODO: Templates... */) { + return noResult(); + } + + // Adjust selection ends. + int selStart = cursor.selectionStart(); + int selEnd = cursor.selectionEnd(); + if (selStart > selEnd) + qSwap(selStart, selEnd); + + Overview printer; + + // Analyse the content to be extracted, which consists of determining the statements + // which are complete and collecting the declarations seen. + FunctionExtractionAnalyser analyser(interface->semanticInfo().doc->translationUnit(), + selStart, selEnd, + file, + printer); + if (!analyser(refFuncDef)) + return noResult(); + + // We also need to collect the declarations of the parameters from the reference function. + QSet<QString> refFuncParams; + if (refFuncDef->declarator->postfix_declarator_list + && refFuncDef->declarator->postfix_declarator_list->value + && refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator()) { + FunctionDeclaratorAST *funcDecltr = + refFuncDef->declarator->postfix_declarator_list->value->asFunctionDeclarator(); + if (funcDecltr->parameter_declaration_clause + && funcDecltr->parameter_declaration_clause->parameter_declaration_list) { + for (ParameterDeclarationListAST *it = + funcDecltr->parameter_declaration_clause->parameter_declaration_list; + it; + it = it->next) { + ParameterDeclarationAST *paramDecl = it->value->asParameterDeclaration(); + const QString &specifiers = + file->textOf(file->startOf(paramDecl), + file->endOf(paramDecl->type_specifier_list->lastValue())); + const QPair<QString, QString> &p = + assembleDeclarationData(specifiers, paramDecl->declarator, file, printer); + if (!p.first.isEmpty()) { + analyser.m_knownDecls.insert(p.first, p.second); + refFuncParams.insert(p.first); + } + } + } + } + + // Identify what would be parameters for the new function and its return value, if any. + Symbol *funcReturn = 0; + QList<QPair<QString, QString> > relevantDecls; + SemanticInfo::LocalUseIterator it(interface->semanticInfo().localUses); + while (it.hasNext()) { + it.next(); + + bool usedBeforeExtraction = false; + bool usedAfterExtraction = false; + bool usedInsideExtraction = false; + const QList<SemanticInfo::Use> &uses = it.value(); + foreach (const SemanticInfo::Use &use, uses) { + const int position = file->position(use.line, use.column); + if (position < analyser.m_extractionStart) + usedBeforeExtraction = true; + else if (position >= analyser.m_extractionEnd) + usedAfterExtraction = true; + else + usedInsideExtraction = true; + } + + const QString &name = printer.prettyName(it.key()->name()); + + if (usedBeforeExtraction && usedInsideExtraction + || usedInsideExtraction && refFuncParams.contains(name)) { + QTC_ASSERT(analyser.m_knownDecls.contains(name), return noResult()); + relevantDecls.append(qMakePair(name, analyser.m_knownDecls.value(name))); + } + + // We assume that the first use of a local corresponds to its declaration. + if (usedInsideExtraction && usedAfterExtraction && !usedBeforeExtraction) { + if (!funcReturn) { + QTC_ASSERT(analyser.m_knownDecls.contains(name), return noResult()); + // The return, if any, is stored as the first item in the list. + relevantDecls.prepend(qMakePair(name, analyser.m_knownDecls.value(name))); + funcReturn = it.key(); + } else { + // Would require multiple returns. (Unless we do fancy things, as pointed below.) + return noResult(); + } + } + } + + // The current implementation doesn't try to be too smart since it preserves the original form + // of the declarations. This might be or not the desired effect. An improvement would be to + // let the user somehow customize the function interface. + return singleResult(new ExtractFunctionOperation(interface, + analyser.m_extractionStart, + analyser.m_extractionEnd, + refFuncDef, + funcReturn, + relevantDecls)); +} diff --git a/src/plugins/cppeditor/cppinsertdecldef.h b/src/plugins/cppeditor/cppinsertdecldef.h index 8507f87186b..4f2f97e1687 100644 --- a/src/plugins/cppeditor/cppinsertdecldef.h +++ b/src/plugins/cppeditor/cppinsertdecldef.h @@ -35,12 +35,6 @@ #include "cppquickfix.h" -#include <CPlusPlusForwardDeclarations.h> - -namespace CPlusPlus { -class ClassOrNamespace; -} // namespace CPlusPlus - namespace CppEditor { namespace Internal { @@ -49,11 +43,6 @@ class DeclFromDef: public CppQuickFixFactory public: virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface); - -protected: - static QString generateDeclaration(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface, - CPlusPlus::Function *method, - CPlusPlus::ClassOrNamespace *targetBinding); }; class DefFromDecl: public CppQuickFixFactory @@ -63,6 +52,13 @@ public: match(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface); }; +class ExtractFunction : public CppQuickFixFactory +{ + virtual QList<CppQuickFixOperation::Ptr> + match(const QSharedPointer<const CppQuickFixAssistInterface> &interface); +}; + + } // namespace Internal } // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index 29bb1750ae0..0c683a3b479 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -1948,6 +1948,7 @@ private: }; }; + } // end of anonymous namespace void registerQuickFixes(ExtensionSystem::IPlugin *plugIn) @@ -1973,4 +1974,5 @@ void registerQuickFixes(ExtensionSystem::IPlugin *plugIn) plugIn->addAutoReleasedObject(new DefFromDecl); plugIn->addAutoReleasedObject(new ApplyDeclDefLinkChanges); plugIn->addAutoReleasedObject(new IncludeAdder); + plugIn->addAutoReleasedObject(new ExtractFunction); } diff --git a/src/plugins/cpptools/cpptoolsreuse.cpp b/src/plugins/cpptools/cpptoolsreuse.cpp index 27b09656e73..f9744ff14ed 100644 --- a/src/plugins/cpptools/cpptoolsreuse.cpp +++ b/src/plugins/cpptools/cpptoolsreuse.cpp @@ -102,5 +102,21 @@ bool isOwnershipRAIIType(CPlusPlus::Symbol *symbol, const LookupContext &context return false; } +bool isValidIdentifier(const QString &s) +{ + const int length = s.length(); + for (int i = 0; i < length; ++i) { + const QChar &c = s.at(i); + if (i == 0) { + if (!c.isLetter() && c != QLatin1Char('_')) + return false; + } else { + if (!c.isLetterOrNumber() && c != QLatin1Char('_')) + return false; + } + } + return true; +} + } // CppTools diff --git a/src/plugins/cpptools/cpptoolsreuse.h b/src/plugins/cpptools/cpptoolsreuse.h index a290905bda3..f6b4d42a265 100644 --- a/src/plugins/cpptools/cpptoolsreuse.h +++ b/src/plugins/cpptools/cpptoolsreuse.h @@ -49,6 +49,8 @@ void CPPTOOLS_EXPORT moveCursorToEndOfIdentifier(QTextCursor *tc); bool CPPTOOLS_EXPORT isOwnershipRAIIType(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context); +bool CPPTOOLS_EXPORT isValidIdentifier(const QString &s); + } // CppTools #endif // CPPTOOLSREUSE_H -- GitLab