From: <net...@us...> - 2002-12-28 10:44:43
|
Update of /cvsroot/cpptool/rfta/src/rfta In directory sc8-pr-cvs1:/tmp/cvs-serv25280/src/rfta Added Files: InlineTempRefactoringTest.h InlineTempRefactoringTest.cpp InlineTempRefactoring.h InlineTempRefactoring.cpp Log Message: -- inlinetemprefactoring implementation & test --- NEW FILE: InlineTempRefactoringTest.h --- // ////////////////////////////////////////////////////////////////////////// // (c)Copyright 2002, Baptiste Lepilleur. // Created: 2002/12/25 // ////////////////////////////////////////////////////////////////////////// #ifndef RFTA_INLINETEMPREFACTORINGTEST_H #define RFTA_INLINETEMPREFACTORINGTEST_H #include "SourceBasedTestBase.h" #include <rfta/refactoring/RefactoringError.h> namespace Refactoring { class PlainTextDocument; /// Unit tests for InlineTempRefactoringTest class InlineTempRefactoringTest : public SourceBasedTestBase { CPPUNIT_TEST_SUITE( InlineTempRefactoringTest ); CPPUNIT_TEST( testEasiestCase ); CPPUNIT_TEST( testThrowNoInitializer ); CPPUNIT_TEST( testThrowInitializerNotSupported ); CPPUNIT_TEST( testThrowVariableAssigned ); CPPUNIT_TEST( testDetectComplexVariableAssignments ); CPPUNIT_TEST( testMultipleDeclarationInitLast ); CPPUNIT_TEST( testMultipleDeclarationInitFirst ); CPPUNIT_TEST( testMultipleDeclarationInitMiddle ); CPPUNIT_TEST( testInitExpression ); CPPUNIT_TEST_SUITE_END(); public: /*! Constructs a InlineTempRefactoringTest object. */ InlineTempRefactoringTest(); /// Destructor. virtual ~InlineTempRefactoringTest(); void setUp(); void tearDown(); void testEasiestCase(); void testThrowNoInitializer(); void testThrowInitializerNotSupported(); void testThrowVariableAssigned(); void testDetectComplexVariableAssignments(); void testMultipleDeclarationInitLast(); void testMultipleDeclarationInitFirst(); void testMultipleDeclarationInitMiddle(); void testInitExpression(); private: /// Prevents the use of the copy constructor. InlineTempRefactoringTest( const InlineTempRefactoringTest &other ); /// Prevents the use of the copy operator. void operator =( const InlineTempRefactoringTest &other ); private: void applyRefactoring( const std::string &LocaleVariableName ); void applyRefactoringExpectThrow( const std::string &LocaleVariableName , RefactoringError::Cause _cause ,const CppUnit::SourceLine &sourceLine ); void applyRefactoringExpectThrow( const std::string &assigment , const std::string &LocaleVariableName , RefactoringError::Cause _cause ,const CppUnit::SourceLine &sourceLine ); void applyRefactoringNoThrow ( const std::string &assignment , const std::string &LocaleVariableName , const CppUnit::SourceLine &sourceLine ); boost::shared_ptr<PlainTextDocument> document_; }; // Inlines methods for InlineTempRefactoringTest: // ---------------------------------------------- } // namespace Refactoring #endif // RFTA_INLINETEMPREFACTORINGTEST_H --- NEW FILE: InlineTempRefactoringTest.cpp --- // ////////////////////////////////////////////////////////////////////////// // (c)Copyright 2002, Baptiste Lepilleur. // Created: 2002/12/25 // ////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "InlineTempRefactoringTest.h" #include "InlineTempRefactoring.h" #include <rfta/refactoring/PlainTextDocument.h> namespace Refactoring { RFTA_TEST_SUITE_REGISTRATION( InlineTempRefactoringTest ); InlineTempRefactoringTest::InlineTempRefactoringTest() { } InlineTempRefactoringTest::~InlineTempRefactoringTest() { } void InlineTempRefactoringTest::setUp() { SourceBasedTestBase::setUp(); } void InlineTempRefactoringTest::tearDown() { document_.reset(); SourceBasedTestBase::tearDown(); } /** * Help function used for applying refactoring algorithm to one example. */ void InlineTempRefactoringTest::applyRefactoring( const std::string &LocaleVariableName ) { document_.reset( new PlainTextDocument( source_ ) ); int selectionIndex = builder_->getStartIndex( "selection" ); InlineTempRefactoring refactoring( *document_, selectionIndex ); refactoring.apply( ); CPPUNIT_ASSERT_EQUAL( LocaleVariableName, refactoring.getVariableName() ); } /** * Help function to test if refactoring implementation throws the expected exception, * with the correct 'exception-reason' */ void InlineTempRefactoringTest::applyRefactoringExpectThrow( const std::string &LocaleVariableName , RefactoringError::Cause _cause, const CppUnit::SourceLine &sourceLine ) { bool failed = true; RefactoringError expected(_cause); document_.reset( new PlainTextDocument( source_ ) ); int selectionIndex = builder_->getStartIndex( "selection" ); try { InlineTempRefactoring refactoring( *document_, selectionIndex ); refactoring.apply( ); std::string failure = std::string("Test fails since it expects the refactoring error '") + expected.what() + "'."; CppUnit::Asserter::fail( CppUnit::Message( failure ), sourceLine ); } catch (RefactoringError& err) { // todo@: compare the 'cause' if ( err.getCause() != expected.getCause() ) { std::string failure = std::string("Test fails since it throws Exception'")+err.what()+"' instead of expected '" + expected.what() + "'."; CppUnit::Asserter::fail( CppUnit::Message( failure ), sourceLine ); } } } void InlineTempRefactoringTest::applyRefactoringExpectThrow( const std::string &assignment , const std::string &LocaleVariableName , RefactoringError::Cause _cause ,const CppUnit::SourceLine &sourceLine ) { SourceBasedTestBase::setUp(); builder_->add( "{ double y = 2;" ); builder_->addKeyingMid( " double ", LocaleVariableName ,"= 5;", "selection" ); builder_->add( assignment ); builder_->add( "}" ); applyRefactoringExpectThrow( LocaleVariableName , RefactoringError::variableIsAssigned , sourceLine ); SourceBasedTestBase::tearDown(); } void InlineTempRefactoringTest::applyRefactoringNoThrow( const std::string &assignment , const std::string &LocaleVariableName , const CppUnit::SourceLine &sourceLine ) { SourceBasedTestBase::setUp(); builder_->add( "{ double y = 2;" ); builder_->addKeyingMid( " double ", LocaleVariableName ,"= 5;", "selection" ); builder_->add( assignment ); builder_->add( "}" ); try { applyRefactoring( LocaleVariableName ); } catch (RefactoringError& err) { std::string failure = std::string("Test fails since it throws Exception '")+err.what()+"'. This was not expected."; CppUnit::Asserter::fail( CppUnit::Message( failure ), sourceLine ); } SourceBasedTestBase::tearDown(); } void InlineTempRefactoringTest::testEasiestCase() { builder_->add( "{" " double y;" ); builder_->addKeyingMid( " double ", "x"," = 4;", "selection" ); builder_->add( "y += y * x;"); builder_->add( " return x * getQuantity();" "}" ); applyRefactoring( "x" ); std::string expectedSource( "{" " double y;" " y += y * 4;" " return 4 * getQuantity();" "}" ); std::string actualSource( document_->getAllText() ); RFTA_ASSERT_EQUAL( expectedSource, actualSource ); } void InlineTempRefactoringTest::testThrowNoInitializer() { builder_->add( "{" " double y;" ); builder_->addKeyingMid( " double ", "x",";", "selection" ); builder_->add( "y += y * x;"); builder_->add( " return x * getQuantity();" "}" ); applyRefactoringExpectThrow( "x" , RefactoringError::identifierIsNotInitialized , CPPUNIT_SOURCELINE() ); } void InlineTempRefactoringTest::testThrowInitializerNotSupported() { builder_->add( "{" " double y = 2;" ); builder_->addKeyingMid( " double ", "x","= 5 * y;", "selection" ); builder_->add( "y += y * x;"); builder_->add( " return x * getQuantity();" "}" ); applyRefactoringExpectThrow( "x" , RefactoringError::initializerValueNotSupported , CPPUNIT_SOURCELINE() ); } void InlineTempRefactoringTest::testThrowVariableAssigned() { applyRefactoringExpectThrow( "x = 2;", "x" , RefactoringError::variableIsAssigned , CPPUNIT_SOURCELINE() ); } void InlineTempRefactoringTest::testDetectComplexVariableAssignments() { applyRefactoringExpectThrow( "*c += x = 1;" , "x" , RefactoringError::variableIsAssigned , CPPUNIT_SOURCELINE() ); applyRefactoringExpectThrow( "if (x=1) ;" , "x" , RefactoringError::variableIsAssigned , CPPUNIT_SOURCELINE() ); applyRefactoringNoThrow ( "if (x==1) ;" , "x" , CPPUNIT_SOURCELINE() ); applyRefactoringExpectThrow( "y=2,x=1;" , "x" , RefactoringError::variableIsAssigned , CPPUNIT_SOURCELINE() ); applyRefactoringNoThrow ( "y=2,x==1;" , "x" , CPPUNIT_SOURCELINE() ); applyRefactoringExpectThrow( "int j = x = 2;", "x" , RefactoringError::variableIsAssigned , CPPUNIT_SOURCELINE() ); // the following will fail because of incomplete expression parser: /* applyRefactoringExpectThrow( "*c += ( x += 1 );" , "x" , RefactoringError::variableIsAssigned , CPPUNIT_SOURCELINE() ); applyRefactoringExpectThrow( "fct(x = 1);" , "x" , RefactoringError::variableIsAssigned , CPPUNIT_SOURCELINE() ); applyRefactoringExpectThrow( "x++;" , "x" , RefactoringError::variableIsAssigned , CPPUNIT_SOURCELINE() ); */ } void InlineTempRefactoringTest::testMultipleDeclarationInitLast() { builder_->add( "{" " double y;" ); builder_->addKeyingMid( " double z , ", "x"," = 4;" , "selection" ); builder_->add( " y += y * x;"); builder_->add( " return x * getQuantity();" "}" ); applyRefactoring( "x" ); std::string expectedSource( "{" " double y; double z;" " y += y * 4;" " return 4 * getQuantity();" "}" ); std::string actualSource( document_->getAllText() ); RFTA_ASSERT_EQUAL( expectedSource, actualSource ); } void InlineTempRefactoringTest::testMultipleDeclarationInitFirst() { builder_->add( "{" " double y;" ); builder_->addKeyingMid( " double ", "x"," = 4, z;", "selection" ); builder_->add( " y += y * x;"); builder_->add( " return x * getQuantity();" "}" ); applyRefactoring( "x" ); std::string expectedSource( "{" " double y; double z;" " y += y * 4;" " return 4 * getQuantity();" "}" ); std::string actualSource( document_->getAllText() ); RFTA_ASSERT_EQUAL( expectedSource, actualSource ); } void InlineTempRefactoringTest::testMultipleDeclarationInitMiddle() { builder_->add( "{" " double y;" ); builder_->addKeyingMid( " double v , ", "x"," = 4, z;", "selection" ); builder_->add( " y += y * x;"); builder_->add( " return x * getQuantity();" "}" ); applyRefactoring( "x" ); std::string expectedSource( "{" " double y; double v , z;" " y += y * 4;" " return 4 * getQuantity();" "}" ); std::string actualSource( document_->getAllText() ); RFTA_ASSERT_EQUAL( expectedSource, actualSource ); } void InlineTempRefactoringTest::testInitExpression() { builder_->add( "{" " double y;" ); builder_->addKeyingMid( " double ", "x"," = 4 + 3;", "selection" ); builder_->add( "y += y * x;"); builder_->add( " return x * getQuantity();" "}" ); applyRefactoring( "x" ); std::string expectedSource( "{" " double y;" " y += y * ( 4 + 3 );" " return ( 4 + 3 ) * getQuantity();" "}" ); std::string actualSource( document_->getAllText() ); RFTA_ASSERT_EQUAL( expectedSource, actualSource ); } } // namespace Refactoring --- NEW FILE: InlineTempRefactoring.h --- // ////////////////////////////////////////////////////////////////////////// // (c)Copyright 2002, Andre Baresel // Created: 2002/12/25 // ////////////////////////////////////////////////////////////////////////// #ifndef RFTA_RFTA_INLINETEMPREFACTORING_H #define RFTA_RFTA_INLINETEMPREFACTORING_H #include <rfta/parser/ASTNode.h> #include <rfta/refactoring/RefactoringBase.h> #include <rfta/refactoring/ToolsBox.h> namespace Refactoring { /// This class implements the algorithm for InlineTemp refactoring. class RFTA_API InlineTempRefactoring : public RefactoringBase { public: /** * constructs on object and prepares the inlining */ InlineTempRefactoring(TextDocument &document,int temporaryLocation); /** * destroys object data. */ virtual ~InlineTempRefactoring(); /** * function executes the inlining. */ void apply( ); /** * function returns the name of the temporary which is about to be inlined */ const std::string getVariableName() const; /** * returns the number of occurences of the variable. */ int getVariableOccurrenceCount() const; private: void prepare(); //< does prepare the inlining (check if it's possible and find the locations for inlining) void findLocaleVariableOccurrences(); //< find the locations of variable uses void calculateTempDeclRange(); //< finds the source range of the temporary declaration bool isInitializerAtomic(); //< does check the initializer for being an atomic value or not void checkForVariableAssignments(); int temporaryLocation_; //< location where refactoring was asked for SourceASTNodePtr sourceNode_; //< parsed source of the location ToolsBox::ASTNodes occurrences_; //< occurences of the temporary variable ASTNodePtr localeVariableNode_; //< ast node of the identifier at selection ASTNodePtr temporaryDecl; //< ast node of the declaration of the selected identifier ASTNodePtr initValue_; //< ast node of the initializer that initializes the selected variable SourceRange rangeOfTempDeclaration; //< Range of temporary declaration that is inlined (this range can be erased after inlining) bool tempDeclaredInMiddle; //< true if the temporary is declared between other variables. }; // Inlines methods for InlineTempRefactoring: // ------------------------------------------ } // namespace Refactoring #endif // RFTA_RFTA_INLINETEMPREFACTORING_H --- NEW FILE: InlineTempRefactoring.cpp --- // ////////////////////////////////////////////////////////////////////////// // (c)Copyright 2002, Baptiste Lepilleur. // Created: 2002/12/25 // ////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "InlineTempRefactoring.h" #include <rfta/refactoring/RefactoringError.h> #include <rfta/refactoring/TextDocument.h> #include <rfta/parser/NonSemanticBlanker.h> #include <rfta/parser/ParseContext.h> #include <rfta/parser/StatementParser.h> #include <rfta/parser/MaxLODMutator.h> #include <rfta/parser/ExpressionOperationMutator.h> #include <rfta/parser/ASTNodes.h> #include "IdentifierResolverContext.h" #include "IdentifierResolver.h" #include "ReplaceTextTransform.h" #include "TransformList.h" namespace Refactoring { InlineTempRefactoring::InlineTempRefactoring( TextDocument &document, int temporaryLocation ) : RefactoringBase( document ) , temporaryLocation_( temporaryLocation ) { prepare(); } InlineTempRefactoring::~InlineTempRefactoring() { } /** * this might not work anymore after inlining has been done. */ const std::string InlineTempRefactoring::getVariableName() const { if ( !localeVariableNode_ ) throw RefactoringError( RefactoringError::identifierIsNotLocalVariable ); return localeVariableNode_->getBlankedText(); } int InlineTempRefactoring::getVariableOccurrenceCount() const { if ( !localeVariableNode_ ) throw RefactoringError( RefactoringError::identifierIsNotLocalVariable ); return occurrences_.size(); } void InlineTempRefactoring::prepare() { // get the source code: std::string source( getDocument().getAllText() ); if ( temporaryLocation_ >= source.length() ) throw RefactoringError( RefactoringError::selectionNotInSource ); // blank the source code NullPPDirectiveListener nullListener; std::string blankedSource; NonSemanticBlanker blanker( source, blankedSource, nullListener ); blanker.blank(); // find the compound statement where the temporary is located: int compoundStartIndex = ToolsBox::findCompoundBefore( blankedSource, temporaryLocation_ ); if ( compoundStartIndex < 0 ) throw RefactoringError( RefactoringError::temporaryNotInFunctionBody ); // 'lazy' parse the source sourceNode_ = SourceASTNode::create( blankedSource, source ); ParseContext context( sourceNode_ ); StatementParser parser( context, sourceNode_->getBlankedSourceStart() + compoundStartIndex, sourceNode_->getBlankedSourceEnd() ); parser.parse(); // do mutate to highest level until we know how to do it 'lazy' MaxLODMutator mutator; mutator.mutate( sourceNode_, sourceNode_ ); // find all occurrences of the temporary. findLocaleVariableOccurrences(); // check for initializer property and throw exception if not found if (!temporaryDecl->hasProperty(ASTNodeProperties::variableInitializerProperty)) throw RefactoringError( RefactoringError::identifierIsNotInitialized ); ASTNodePtr initializerNode_; initializerNode_ = temporaryDecl->getProperty(ASTNodeProperties::variableInitializerProperty); // check for value expression: if (!initializerNode_->hasProperty(ASTNodeProperties::valueProperty)) throw RefactoringError( RefactoringError::initializerValueNotSupported ); initValue_ = initializerNode_->getProperty(ASTNodeProperties::valueProperty); // @todo: EXTEND THIS BY CHECKING IF local-scope-identifiers can be inlined int numberOfChilds = initValue_->getChildCount(); if (numberOfChilds > 0) { for (int idx = 0; idx < numberOfChilds; idx++ ) { ASTNodePtr child = initValue_->getChildAt(idx); if ( child->getType() == ASTNodeTypes::localScopeIdentifier ) throw RefactoringError( RefactoringError::initializerValueNotSupported ); } } checkForVariableAssignments(); } /** * check all occurences for: * not being assignments to the identifier * * function throws an exception in case of a detected assignment. */ void InlineTempRefactoring::checkForVariableAssignments() { for ( ToolsBox::ASTNodes::const_iterator it = occurrences_.begin(); it != occurrences_.end(); ++it ) { if ( *it != localeVariableNode_) { // get the expression this variable belongs to: ASTNodePtr parentExpr = boost::make_shared((*it)->getParentNode()); bool go_deeper = false; do { go_deeper = false; if (!parentExpr->hasProperty(ASTNodeProperties::expressionTypeProperty)) { // call mutator on this expression: SourceASTNodePtr sourceNode = boost::make_shared(parentExpr->getSourceNode()); ParseContext context( sourceNode ); ExpressionOperationMutator mutator( context, parentExpr, sourceNode ); mutator.mutate(); // check if no expression type was added: if (!parentExpr->hasProperty(ASTNodeProperties::expressionTypeProperty)) break; } // check if the expression is an assignment: ASTNodePtr exprType = parentExpr->getProperty(ASTNodeProperties::expressionTypeProperty); if (exprType->getType() == ASTNodeTypes::assignmentExpression) { // find the first child that is <expression> int idx; for (idx=0;idx<parentExpr->getChildCount();idx++) if (parentExpr->getChildAt(idx)->getType()==ASTNodeTypes::expression) break; // check if localVariable is on the left hand side: ASTNodePtr left = parentExpr->getChildAt(idx); SourceRange varRange = (*it)->getRange(); SourceRange leftRange = left->getRange(); if (leftRange.contains(varRange)) throw RefactoringError( RefactoringError::variableIsAssigned ); else { parentExpr = parentExpr->getChildAt(idx+1); go_deeper = true; } } } while (go_deeper); } } } void InlineTempRefactoring::findLocaleVariableOccurrences() { SourceRange temporaryRange( temporaryLocation_, 1 ); localeVariableNode_ = ToolsBox::findIdentifierNodeAt( temporaryRange, sourceNode_ ); if ( !localeVariableNode_ ) throw RefactoringError( RefactoringError::selectionIsNotAnIdentifier ); IdentifierResolverContext context( sourceNode_ ); IdentifierResolver resolver( context ); resolver.visitNode( sourceNode_ ); try { temporaryDecl = context.getLocalVariableIdentifierDeclaration( localeVariableNode_ ); ASTNodePtr compoundNode = sourceNode_->getChildAt(0); ToolsBox::getLocalVariableOccurrences( compoundNode, temporaryDecl, sourceNode_, context, occurrences_ ); } catch ( NotLocalVariableIdentifierError & ) { throw RefactoringError( RefactoringError::identifierIsNotLocalVariable ); } } /** * function does check if the temporary is declared in its own declaration expression * or if it's part of a declaration for more than one variable. */ void InlineTempRefactoring::calculateTempDeclRange() { tempDeclaredInMiddle = false; // remove the declaration of the temporary: ASTNodePtr decl_expr = boost::make_shared(temporaryDecl->getParentNode()); int countVarDecl = 0; for (int j = 0; j < decl_expr->getChildCount(); j++ ) { if (decl_expr->getChildAt(j)->getType() == ASTNodeTypes::variableDeclExpression ) countVarDecl++; } // check number of variables declared in this declaration: if ( countVarDecl == 1) // Temp is declared in its own declaration expression, range is the full expression rangeOfTempDeclaration = decl_expr->getRange(); else { // Temp is declared together with other variables, we need to calculate the range ASTNodePtr tempVarNameNode = temporaryDecl->getProperty(ASTNodeProperties::variableNameProperty); // find out the declaration just before the temporary is declared: int tempVarBegin = tempVarNameNode->getRange().getStartIndex(); int tempVarEnd = initValue_->getRange().getEndIndex(); int lastChildEndBefore = 0; int firstChildStartAfter = decl_expr->getRange().getEndIndex() - 1; // ends just before the semicolon bool childDeclaredAfterTemp = false; for (int j = 0; j < decl_expr->getChildCount(); j++ ) { ASTNodePtr child = decl_expr->getChildAt(j); if ( temporaryDecl == child ) continue; ASTNodePtr childNameNode = child->getProperty(ASTNodeProperties::variableNameProperty); int childEndIndex = child->getRange().getEndIndex(); int childStartIndex = childNameNode->getRange().getStartIndex(); // check if the current child is the last before the temporary: if ( childEndIndex < tempVarBegin && childEndIndex > lastChildEndBefore ) lastChildEndBefore = childEndIndex; // check if the current child is declared after the temporary: if ( childStartIndex > tempVarEnd && firstChildStartAfter > childStartIndex ) { childDeclaredAfterTemp = true; firstChildStartAfter = childStartIndex; } } // temp is declared between other in case: // there're declaration after its declaration && it is not the first tempDeclaredInMiddle = childDeclaredAfterTemp && ( lastChildEndBefore != 0 ); // check if temporary is declared as first: if ( lastChildEndBefore==0 ) lastChildEndBefore = tempVarBegin; // in this case delete range beginning at the temp-var-name // calculate the range: SourceRange merged ( lastChildEndBefore , firstChildStartAfter - lastChildEndBefore ); rangeOfTempDeclaration = merged; } } bool InlineTempRefactoring::isInitializerAtomic() { SourceRange range = initValue_->getRange(); const char * start = &sourceNode_->getBlankedSourceStart()[range.getStartIndex()]; const char * end = &sourceNode_->getBlankedSourceStart()[range.getEndIndex()]; const char * c = start; while (c != end) { switch (*c) { case '+': case '-': case '*': case '/': case '%': case '&': case '|': case ',': case '~': case '^': return false; } c++; } return true; } void InlineTempRefactoring::apply() { TransformList transforms; calculateTempDeclRange(); if (tempDeclaredInMiddle) transforms.add( *new ReplaceTextTransform( rangeOfTempDeclaration , " , " ) ); else transforms.add( *new ReplaceTextTransform( rangeOfTempDeclaration , "" ) ); std::string newtext; if (isInitializerAtomic()) { newtext = initValue_->getOriginalText(); while (isspace(newtext[0])) newtext.erase(0,1); while (isspace(newtext[newtext.length()-1])) newtext.erase(newtext.length()-1,1); } else newtext = std::string("(") + initValue_->getOriginalText() + " )"; for ( ToolsBox::ASTNodes::const_iterator it = occurrences_.begin(); it != occurrences_.end(); ++it ) { if ( *it != localeVariableNode_) { SourceRange range = (*it)->getRange(); transforms.add( *new ReplaceTextTransform( range, newtext ) ); } } transforms.apply( getDocument() ); } } // namespace Refactoring |