void tst_ChangeSet::doubleInsert() { { Utils::ChangeSet cs; QVERIFY(cs.insert(1, "01")); QVERIFY(cs.insert(1, "234")); QString test("abcdef"); cs.apply(&test); QCOMPARE(test, QLatin1String("a01234bcdef")); } { Utils::ChangeSet cs; QVERIFY(cs.insert(1, "234")); QVERIFY(cs.insert(1, "01")); QString test("abcdef"); cs.apply(&test); QCOMPARE(test, QLatin1String("a23401bcdef")); } { Utils::ChangeSet cs; QVERIFY(cs.insert(1, "01")); QVERIFY(cs.remove(1, 2)); QVERIFY(cs.insert(2, "234")); QString test("abcdef"); cs.apply(&test); QCOMPARE(test, QLatin1String("a01234cdef")); } }
void tst_ChangeSet::singleRemove() { { Utils::ChangeSet cs; QString test("abcdef"); QVERIFY(cs.remove(0, 1)); cs.apply(&test); QCOMPARE(test, QLatin1String("bcdef")); } { Utils::ChangeSet cs; QString test("abcdef"); QVERIFY(cs.remove(3, 6)); cs.apply(&test); QCOMPARE(test, QLatin1String("abc")); } { Utils::ChangeSet cs; QString test("abcdef"); QVERIFY(cs.remove(4, 14)); cs.apply(&test); QCOMPARE(test, QLatin1String("abcd")); } { Utils::ChangeSet cs; QString test("abcdef"); QVERIFY(cs.remove(2, 2)); cs.apply(&test); QCOMPARE(test, QLatin1String("abcdef")); } { Utils::ChangeSet cs; QString test("abcdef"); QVERIFY(cs.remove(7, 8)); cs.apply(&test); QCOMPARE(test, QLatin1String("abcdef")); } }
Utils::ChangeSet FunctionDeclDefLink::changes(const Snapshot &snapshot, int targetOffset) { Utils::ChangeSet changes; // parse the current source declaration TypeOfExpression typeOfExpression; // ### just need to preprocess... typeOfExpression.init(sourceDocument, snapshot); QString newDeclText = linkSelection.selectedText(); for (int i = 0; i < newDeclText.size(); ++i) { if (newDeclText.at(i).toAscii() == 0) newDeclText[i] = QLatin1Char('\n'); } newDeclText.append(QLatin1String("{}")); const QString newDeclTextPreprocessed = typeOfExpression.preprocess(newDeclText); Document::Ptr newDeclDoc = Document::create(QLatin1String("<decl>")); newDeclDoc->setSource(newDeclTextPreprocessed.toUtf8()); newDeclDoc->parse(Document::ParseDeclaration); newDeclDoc->check(); // extract the function symbol if (!newDeclDoc->translationUnit()->ast()) return changes; FunctionDefinitionAST *newDef = newDeclDoc->translationUnit()->ast()->asFunctionDefinition(); if (!newDef) return changes; Function *newFunction = newDef->symbol; if (!newFunction) return changes; Overview overview; overview.setShowReturnTypes(true); overview.setShowTemplateParameters(true); overview.setShowArgumentNames(true); overview.setShowFunctionSignatures(true); // abort if the name of the newly parsed function is not the expected one DeclaratorIdAST *newDeclId = getDeclaratorId(newDef->declarator); if (!newDeclId || !newDeclId->name || !newDeclId->name->name || overview(newDeclId->name->name) != nameInitial) { return changes; } LookupContext sourceContext(sourceDocument, snapshot); LookupContext targetContext(targetFile->cppDocument(), snapshot); // sync return type { // set up for rewriting return type SubstitutionEnvironment env; env.setContext(sourceContext); env.switchScope(sourceFunction->enclosingScope()); ClassOrNamespace *targetCoN = targetContext.lookupType(targetFunction->enclosingScope()); if (!targetCoN) targetCoN = targetContext.globalNamespace(); UseMinimalNames q(targetCoN); env.enter(&q); Control *control = sourceContext.control().data(); // get return type start position and declarator info from declaration DeclaratorAST *declarator = 0; SpecifierAST *firstReplaceableSpecifier = 0; TranslationUnit *targetTranslationUnit = targetFile->cppDocument()->translationUnit(); if (SimpleDeclarationAST *simple = targetDeclaration->asSimpleDeclaration()) { declarator = simple->declarator_list->value; firstReplaceableSpecifier = findFirstReplaceableSpecifier( targetTranslationUnit, simple->decl_specifier_list); } else if (FunctionDefinitionAST *def = targetDeclaration->asFunctionDefinition()) { declarator = def->declarator; firstReplaceableSpecifier = findFirstReplaceableSpecifier( targetTranslationUnit, def->decl_specifier_list); } int returnTypeStart = 0; if (firstReplaceableSpecifier) returnTypeStart = targetFile->startOf(firstReplaceableSpecifier); else returnTypeStart = targetFile->startOf(declarator); if (!newFunction->returnType().isEqualTo(sourceFunction->returnType()) && !newFunction->returnType().isEqualTo(targetFunction->returnType())) { FullySpecifiedType type = rewriteType(newFunction->returnType(), &env, control); const QString replacement = overview(type, targetFunction->name()); changes.replace(returnTypeStart, targetFile->startOf(targetFunctionDeclarator->lparen_token), replacement); } } // sync parameters { // set up for rewriting parameter types SubstitutionEnvironment env; env.setContext(sourceContext); env.switchScope(sourceFunction); ClassOrNamespace *targetCoN = targetContext.lookupType(targetFunction); if (!targetCoN) targetCoN = targetContext.globalNamespace(); UseMinimalNames q(targetCoN); env.enter(&q); Control *control = sourceContext.control().data(); Overview overview; const unsigned sourceArgCount = declaredArgumentCount(sourceFunction); const unsigned newArgCount = declaredArgumentCount(newFunction); const unsigned targetArgCount = declaredArgumentCount(targetFunction); // check if parameter types or names have changed const unsigned existingArgs = qMin(targetArgCount, newArgCount); ParameterDeclarationClauseAST *targetParameterDecl = targetFunctionDeclarator->parameter_declaration_clause; ParameterDeclarationListAST *firstTargetParameterDeclIt = targetParameterDecl ? targetParameterDecl->parameter_declaration_list : 0; ParameterDeclarationListAST *targetParameterDeclIt = firstTargetParameterDeclIt; for (unsigned i = 0; i < existingArgs && targetParameterDeclIt; ++i, targetParameterDeclIt = targetParameterDeclIt->next) { Symbol *targetParam = targetFunction->argumentAt(i); Symbol *newParam = newFunction->argumentAt(i); // if new's name and type are the same as source's, forbid changes bool allowChangeType = true; const Name *replacementName = newParam->name(); if (i < sourceArgCount) { Symbol *sourceParam = sourceFunction->argumentAt(i); if (newParam->type().isEqualTo(sourceParam->type())) allowChangeType = false; if (namesEqual(replacementName, sourceParam->name())) replacementName = targetParam->name(); } // don't change the name if it's in a comment if (hasCommentedName(targetFile->cppDocument()->translationUnit(), targetFile->cppDocument()->source(), targetFunctionDeclarator, i)) replacementName = 0; // find the end of the parameter declaration ParameterDeclarationAST *targetParamAst = targetParameterDeclIt->value; int parameterNameEnd = 0; if (targetParamAst->declarator) parameterNameEnd = targetFile->endOf(targetParamAst->declarator); else if (targetParamAst->type_specifier_list) parameterNameEnd = targetFile->endOf(targetParamAst->type_specifier_list->lastToken() - 1); else parameterNameEnd = targetFile->startOf(targetParamAst); // change name and type? if (allowChangeType && !targetParam->type().isEqualTo(newParam->type())) { FullySpecifiedType replacementType = rewriteType(newParam->type(), &env, control); const QString replacement = overview(replacementType, replacementName); changes.replace(targetFile->startOf(targetParamAst), parameterNameEnd, replacement); } // change the name only? else if (!namesEqual(targetParam->name(), replacementName)) { DeclaratorIdAST *id = getDeclaratorId(targetParamAst->declarator); QString replacementNameStr = overview(replacementName); if (id) { changes.replace(targetFile->range(id), replacementNameStr); } else { // add name to unnamed parameter replacementNameStr.prepend(QLatin1Char(' ')); int end; if (targetParamAst->equal_token) { end = targetFile->startOf(targetParamAst->equal_token); replacementNameStr.append(QLatin1Char(' ')); } else { // one past end on purpose end = targetFile->startOf(targetParamAst->lastToken()); } changes.replace(parameterNameEnd, end, replacementNameStr); } } } // remove some parameters? if (newArgCount < targetArgCount) { targetParameterDeclIt = firstTargetParameterDeclIt; if (targetParameterDeclIt) { if (newArgCount == 0) { changes.remove(targetFile->startOf(targetParameterDeclIt->firstToken()), targetFile->endOf(targetParameterDeclIt->lastToken() - 1)); } else { // get the last valid argument for (unsigned i = 0; i < newArgCount - 1 && targetParameterDeclIt; ++i) targetParameterDeclIt = targetParameterDeclIt->next; if (targetParameterDeclIt) { const int start = targetFile->endOf(targetParameterDeclIt->value); const int end = targetFile->endOf(targetParameterDecl->lastToken() - 1); changes.remove(start, end); } } } } // add some parameters? else if (newArgCount > targetArgCount) { QString newParams; for (unsigned i = targetArgCount; i < newArgCount; ++i) { Symbol *param = newFunction->argumentAt(i); FullySpecifiedType type = rewriteType(param->type(), &env, control); if (i != 0) newParams += QLatin1String(", "); newParams += overview(type, param->name()); } targetParameterDeclIt = firstTargetParameterDeclIt; if (targetParameterDeclIt) { while (targetParameterDeclIt->next) targetParameterDeclIt = targetParameterDeclIt->next; changes.insert(targetFile->endOf(targetParameterDeclIt->value), newParams); } else { changes.insert(targetFile->endOf(targetFunctionDeclarator->lparen_token), newParams); } } } // sync cv qualification if (targetFunction->isConst() != newFunction->isConst() || targetFunction->isVolatile() != newFunction->isVolatile()) { QString cvString; if (newFunction->isConst()) cvString += QLatin1String("const"); if (newFunction->isVolatile()) { if (!cvString.isEmpty()) cvString += QLatin1Char(' '); cvString += QLatin1String("volatile"); } const int rparenEnd = targetFile->endOf(targetFunctionDeclarator->rparen_token); if (targetFunctionDeclarator->cv_qualifier_list) { const int cvEnd = targetFile->endOf(targetFunctionDeclarator->cv_qualifier_list->lastToken() - 1); // if the qualifies changed, replace if (!cvString.isEmpty()) { changes.replace(targetFile->startOf(targetFunctionDeclarator->cv_qualifier_list->firstToken()), cvEnd, cvString); } else { // remove changes.remove(rparenEnd, cvEnd); } } else { // otherwise add cvString.prepend(QLatin1Char(' ')); changes.insert(rparenEnd, cvString); } } if (targetOffset != -1) { // move all change operations to have the right start offset const int moveAmount = targetOffset - targetFile->startOf(targetDeclaration); QList<Utils::ChangeSet::EditOp> ops = changes.operationList(); for (int i = 0; i < ops.size(); ++i) { ops[i].pos1 += moveAmount; ops[i].pos2 += moveAmount; } changes = Utils::ChangeSet(ops); } return changes; }