CppEditor: Add QuickFix to generate a constructor

Change-Id: Iba2ce3bfa1a1d1a325626a21f46b485d12cbb060
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Leander Schulten
2021-01-04 04:41:36 +01:00
parent d03f723060
commit c960f279e6
7 changed files with 1057 additions and 222 deletions

View File

@@ -60,6 +60,7 @@ static int ordering(InsertionPointLocator::AccessSpec xsSpec)
struct AccessRange
{
int start = 0;
int beforeStart = 0;
unsigned end = 0;
InsertionPointLocator::AccessSpec xsSpec = InsertionPointLocator::Invalid;
unsigned colonToken = 0;
@@ -81,16 +82,14 @@ struct AccessRange
class FindInClass: public ASTVisitor
{
public:
FindInClass(const Document::Ptr &doc, const Class *clazz, InsertionPointLocator::AccessSpec xsSpec)
: ASTVisitor(doc->translationUnit())
, _doc(doc)
FindInClass(TranslationUnit *tu, const Class *clazz)
: ASTVisitor(tu)
, _clazz(clazz)
, _xsSpec(xsSpec)
{}
InsertionLocation operator()()
ClassSpecifierAST *operator()()
{
_result = InsertionLocation();
_result = nullptr;
AST *ast = translationUnit()->ast();
accept(ast);
@@ -107,146 +106,120 @@ protected:
return true;
if (!ast->symbol || !ast->symbol->match(_clazz))
return true;
QList<AccessRange> ranges = collectAccessRanges(
ast->member_specifier_list,
tokenKind(ast->classkey_token) == T_CLASS ? InsertionPointLocator::Private : InsertionPointLocator::Public,
ast->lbrace_token,
ast->rbrace_token);
unsigned beforeToken = 0;
bool needsLeadingEmptyLine = false;
bool needsPrefix = false;
bool needsSuffix = false;
findMatch(ranges, _xsSpec, beforeToken, needsLeadingEmptyLine, needsPrefix, needsSuffix);
int line = 0, column = 0;
getTokenStartPosition(beforeToken, &line, &column);
QString prefix;
if (needsLeadingEmptyLine)
prefix += QLatin1String("\n");
if (needsPrefix)
prefix += InsertionPointLocator::accessSpecToString(_xsSpec) + QLatin1String(":\n");
QString suffix;
if (needsSuffix)
suffix = QLatin1Char('\n');
_result = InsertionLocation(_doc->fileName(), prefix, suffix,
line, column);
_result = ast;
return false;
}
static void findMatch(const QList<AccessRange> &ranges,
InsertionPointLocator::AccessSpec xsSpec,
unsigned &beforeToken,
bool &needsLeadingEmptyLine,
bool &needsPrefix,
bool &needsSuffix)
{
QTC_ASSERT(!ranges.isEmpty(), return);
const int lastIndex = ranges.size() - 1;
needsLeadingEmptyLine = false;
// try an exact match, and ignore the first (default) access spec:
for (int i = lastIndex; i > 0; --i) {
const AccessRange &range = ranges.at(i);
if (range.xsSpec == xsSpec) {
beforeToken = range.end;
needsPrefix = false;
needsSuffix = (i != lastIndex);
return;
}
}
// try to find a fitting access spec to insert XXX:
for (int i = lastIndex; i > 0; --i) {
const AccessRange &current = ranges.at(i);
if (ordering(xsSpec) > ordering(current.xsSpec)) {
beforeToken = current.end;
needsPrefix = true;
needsSuffix = (i != lastIndex);
return;
}
}
// otherwise:
beforeToken = ranges.first().end;
needsLeadingEmptyLine = !ranges.first().isEmpty();
needsPrefix = true;
needsSuffix = (ranges.size() != 1);
}
QList<AccessRange> collectAccessRanges(DeclarationListAST *decls,
InsertionPointLocator::AccessSpec initialXs,
int firstRangeStart,
int lastRangeEnd) const
{
QList<AccessRange> ranges;
ranges.append(AccessRange(firstRangeStart, lastRangeEnd, initialXs, 0));
for (DeclarationListAST *iter = decls; iter; iter = iter->next) {
DeclarationAST *decl = iter->value;
if (AccessDeclarationAST *xsDecl = decl->asAccessDeclaration()) {
const unsigned token = xsDecl->access_specifier_token;
InsertionPointLocator::AccessSpec newXsSpec = initialXs;
bool isSlot = xsDecl->slots_token
&& tokenKind(xsDecl->slots_token) == T_Q_SLOTS;
switch (tokenKind(token)) {
case T_PUBLIC:
newXsSpec = isSlot ? InsertionPointLocator::PublicSlot
: InsertionPointLocator::Public;
break;
case T_PROTECTED:
newXsSpec = isSlot ? InsertionPointLocator::ProtectedSlot
: InsertionPointLocator::Protected;
break;
case T_PRIVATE:
newXsSpec = isSlot ? InsertionPointLocator::PrivateSlot
: InsertionPointLocator::Private;
break;
case T_Q_SIGNALS:
newXsSpec = InsertionPointLocator::Signals;
break;
case T_Q_SLOTS: {
newXsSpec = (InsertionPointLocator::AccessSpec)
(ranges.last().xsSpec | InsertionPointLocator::SlotBit);
break;
}
default:
break;
}
if (newXsSpec != ranges.last().xsSpec || ranges.size() == 1) {
ranges.last().end = token;
AccessRange r(token, lastRangeEnd, newXsSpec, xsDecl->colon_token);
ranges.append(r);
}
}
}
ranges.last().end = lastRangeEnd;
return ranges;
}
private:
Document::Ptr _doc;
const Class *_clazz;
InsertionPointLocator::AccessSpec _xsSpec;
InsertionLocation _result;
ClassSpecifierAST *_result;
};
void findMatch(const QList<AccessRange> &ranges,
InsertionPointLocator::AccessSpec xsSpec,
InsertionPointLocator::Position positionInAccessSpec,
unsigned &beforeToken,
bool &needsLeadingEmptyLine,
bool &needsPrefix,
bool &needsSuffix)
{
QTC_ASSERT(!ranges.isEmpty(), return );
const int lastIndex = ranges.size() - 1;
const bool atEnd = positionInAccessSpec == InsertionPointLocator::AccessSpecEnd;
needsLeadingEmptyLine = false;
// try an exact match, and ignore the first (default) access spec:
for (int i = lastIndex; i > 0; --i) {
const AccessRange &range = ranges.at(i);
if (range.xsSpec == xsSpec) {
beforeToken = atEnd ? range.end : range.beforeStart;
needsLeadingEmptyLine = !atEnd;
needsPrefix = false;
needsSuffix = (i != lastIndex);
return;
}
}
// try to find a fitting access spec to insert XXX:
for (int i = lastIndex; i > 0; --i) {
const AccessRange &current = ranges.at(i);
if (ordering(xsSpec) > ordering(current.xsSpec)) {
beforeToken = atEnd ? current.end : current.end - 1;
needsLeadingEmptyLine = !atEnd;
needsPrefix = true;
needsSuffix = (i != lastIndex);
return;
}
}
// otherwise:
beforeToken = atEnd ? ranges.first().end : ranges.first().end - 1;
needsLeadingEmptyLine = !ranges.first().isEmpty();
needsPrefix = true;
needsSuffix = (ranges.size() != 1);
}
QList<AccessRange> collectAccessRanges(const CPlusPlus::TranslationUnit *tu,
DeclarationListAST *decls,
InsertionPointLocator::AccessSpec initialXs,
int firstRangeStart,
int lastRangeEnd)
{
QList<AccessRange> ranges;
ranges.append(AccessRange(firstRangeStart, lastRangeEnd, initialXs, 0));
for (DeclarationListAST *iter = decls; iter; iter = iter->next) {
DeclarationAST *decl = iter->value;
if (AccessDeclarationAST *xsDecl = decl->asAccessDeclaration()) {
const unsigned token = xsDecl->access_specifier_token;
InsertionPointLocator::AccessSpec newXsSpec = initialXs;
bool isSlot = xsDecl->slots_token && tu->tokenKind(xsDecl->slots_token) == T_Q_SLOTS;
switch (tu->tokenKind(token)) {
case T_PUBLIC:
newXsSpec = isSlot ? InsertionPointLocator::PublicSlot
: InsertionPointLocator::Public;
break;
case T_PROTECTED:
newXsSpec = isSlot ? InsertionPointLocator::ProtectedSlot
: InsertionPointLocator::Protected;
break;
case T_PRIVATE:
newXsSpec = isSlot ? InsertionPointLocator::PrivateSlot
: InsertionPointLocator::Private;
break;
case T_Q_SIGNALS:
newXsSpec = InsertionPointLocator::Signals;
break;
case T_Q_SLOTS: {
newXsSpec = (InsertionPointLocator::AccessSpec)(ranges.last().xsSpec
| InsertionPointLocator::SlotBit);
break;
}
default:
break;
}
if (newXsSpec != ranges.last().xsSpec || ranges.size() == 1) {
ranges.last().end = token;
AccessRange r(token, lastRangeEnd, newXsSpec, xsDecl->colon_token);
r.beforeStart = xsDecl->lastToken() - 1;
ranges.append(r);
}
}
}
ranges.last().end = lastRangeEnd;
return ranges;
}
} // end of anonymous namespace
InsertionLocation::InsertionLocation() = default;
@@ -301,13 +274,118 @@ InsertionLocation InsertionPointLocator::methodDeclarationInClass(
{
const Document::Ptr doc = m_refactoringChanges.file(fileName)->cppDocument();
if (doc) {
FindInClass find(doc, clazz, xsSpec);
return find();
FindInClass find(doc->translationUnit(), clazz);
ClassSpecifierAST *classAST = find();
return methodDeclarationInClass(doc->translationUnit(), classAST, xsSpec);
} else {
return InsertionLocation();
}
}
InsertionLocation InsertionPointLocator::methodDeclarationInClass(
const TranslationUnit *tu,
const ClassSpecifierAST *clazz,
InsertionPointLocator::AccessSpec xsSpec,
Position pos) const
{
if (!clazz)
return {};
QList<AccessRange> ranges = collectAccessRanges(tu,
clazz->member_specifier_list,
tu->tokenKind(clazz->classkey_token) == T_CLASS
? InsertionPointLocator::Private
: InsertionPointLocator::Public,
clazz->lbrace_token,
clazz->rbrace_token);
unsigned beforeToken = 0;
bool needsLeadingEmptyLine = false;
bool needsPrefix = false;
bool needsSuffix = false;
findMatch(ranges, xsSpec, pos, beforeToken, needsLeadingEmptyLine, needsPrefix, needsSuffix);
int line = 0, column = 0;
if (pos == InsertionPointLocator::AccessSpecEnd)
tu->getTokenStartPosition(beforeToken, &line, &column);
else
tu->getTokenEndPosition(beforeToken, &line, &column);
QString prefix;
if (needsLeadingEmptyLine)
prefix += QLatin1String("\n");
if (needsPrefix)
prefix += InsertionPointLocator::accessSpecToString(xsSpec) + QLatin1String(":\n");
QString suffix;
if (needsSuffix)
suffix = QLatin1Char('\n');
const QString fileName = QString::fromUtf8(tu->fileName(), tu->fileNameLength());
return InsertionLocation(fileName, prefix, suffix, line, column);
}
InsertionPointLocator::AccessSpec symbolsAccessSpec(Symbol *symbol)
{
if (symbol->isPrivate())
return InsertionPointLocator::Private;
if (symbol->isProtected())
return InsertionPointLocator::Protected;
if (symbol->isPublic())
return InsertionPointLocator::Public;
return InsertionPointLocator::Invalid;
}
InsertionLocation InsertionPointLocator::constructorDeclarationInClass(
const CPlusPlus::TranslationUnit *tu,
const ClassSpecifierAST *clazz,
InsertionPointLocator::AccessSpec xsSpec,
int constructorArgumentCount) const
{
std::map<int, std::pair<DeclarationAST *, DeclarationAST *>> constructors;
for (DeclarationAST *rootDecl : clazz->member_specifier_list) {
if (SimpleDeclarationAST *ast = rootDecl->asSimpleDeclaration()) {
if (!ast->symbols)
continue;
if (symbolsAccessSpec(ast->symbols->value) != xsSpec)
continue;
if (ast->symbols->value->name() != clazz->name->name)
continue;
for (DeclaratorAST *d : ast->declarator_list) {
for (PostfixDeclaratorAST *decl : d->postfix_declarator_list) {
if (FunctionDeclaratorAST *func = decl->asFunctionDeclarator()) {
int params = 0;
if (func->parameter_declaration_clause) {
params = size(
func->parameter_declaration_clause->parameter_declaration_list);
}
auto &entry = constructors[params];
if (!entry.first)
entry.first = rootDecl;
entry.second = rootDecl;
}
}
}
}
}
if (constructors.empty())
return methodDeclarationInClass(tu, clazz, xsSpec, AccessSpecBegin);
auto iter = constructors.lower_bound(constructorArgumentCount);
if (iter == constructors.end()) {
// we have a constructor with x arguments but there are only ones with < x arguments
--iter; // select greatest one (in terms of argument count)
}
const QString fileName = QString::fromUtf8(tu->fileName(), tu->fileNameLength());
int line, column;
if (iter->first <= constructorArgumentCount) {
tu->getTokenEndPosition(iter->second.second->lastToken() - 1, &line, &column);
return InsertionLocation(fileName, "\n", "", line, column);
}
// before iter
// end pos of firstToken-1 instead of start pos of firstToken to skip leading commend
tu->getTokenEndPosition(iter->second.first->firstToken() - 1, &line, &column);
return InsertionLocation(fileName, "\n", "", line, column);
}
namespace {
template <class Key, class Value>
class HighestValue