Add refactoring callbacks to make common kinds of refactorings easy.
llvm-svn: 160255
This commit is contained in:
		
							parent
							
								
									3dd6c81492
								
							
						
					
					
						commit
						7e22282b68
					
				| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					//===--- RefactoringCallbacks.h - Structural query framework ----*- C++ -*-===//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//                     The LLVM Compiler Infrastructure
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This file is distributed under the University of Illinois Open Source
 | 
				
			||||||
 | 
					// License. See LICENSE.TXT for details.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//===----------------------------------------------------------------------===//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Provides callbacks to make common kinds of refactorings easy.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  The general idea is to construct a matcher expression that describes a
 | 
				
			||||||
 | 
					//  subtree match on the AST and then replace the corresponding source code
 | 
				
			||||||
 | 
					//  either by some specific text or some other AST node.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Example:
 | 
				
			||||||
 | 
					//  int main(int argc, char **argv) {
 | 
				
			||||||
 | 
					//    ClangTool Tool(argc, argv);
 | 
				
			||||||
 | 
					//    MatchFinder Finder;
 | 
				
			||||||
 | 
					//    ReplaceStmtWithText Callback("integer", "42");
 | 
				
			||||||
 | 
					//    Finder.AddMatcher(id("integer", expression(integerLiteral())), Callback);
 | 
				
			||||||
 | 
					//    return Tool.run(newFrontendActionFactory(&Finder));
 | 
				
			||||||
 | 
					//  }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  This will replace all integer literals with "42".
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//===----------------------------------------------------------------------===//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef LLVM_CLANG_AST_MATCHERS_REFACTORING_CALLBACKS_H
 | 
				
			||||||
 | 
					#define LLVM_CLANG_AST_MATCHERS_REFACTORING_CALLBACKS_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "clang/ASTMatchers/ASTMatchFinder.h"
 | 
				
			||||||
 | 
					#include "clang/Tooling/Refactoring.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace clang {
 | 
				
			||||||
 | 
					namespace ast_matchers {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// \brief Base class for RefactoringCallbacks.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Collects \c tooling::Replacements while running.
 | 
				
			||||||
 | 
					class RefactoringCallback : public MatchFinder::MatchCallback {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  RefactoringCallback();
 | 
				
			||||||
 | 
					  tooling::Replacements &getReplacements();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					  tooling::Replacements Replace;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// \brief Replace the text of the statement bound to \c FromId with the text in
 | 
				
			||||||
 | 
					/// \c ToText.
 | 
				
			||||||
 | 
					class ReplaceStmtWithText : public RefactoringCallback {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  ReplaceStmtWithText(StringRef FromId, StringRef ToText);
 | 
				
			||||||
 | 
					  virtual void run(const MatchFinder::MatchResult &Result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  std::string FromId;
 | 
				
			||||||
 | 
					  std::string ToText;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// \brief Replace the text of the statement bound to \c FromId with the text of
 | 
				
			||||||
 | 
					/// the statement bound to \c ToId.
 | 
				
			||||||
 | 
					class ReplaceStmtWithStmt : public RefactoringCallback {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  ReplaceStmtWithStmt(StringRef FromId, StringRef ToId);
 | 
				
			||||||
 | 
					  virtual void run(const MatchFinder::MatchResult &Result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  std::string FromId;
 | 
				
			||||||
 | 
					  std::string ToId;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// \brief Replace an if-statement bound to \c Id with the outdented text of its
 | 
				
			||||||
 | 
					/// body, choosing the consequent or the alternative based on whether
 | 
				
			||||||
 | 
					/// \c PickTrueBranch is true.
 | 
				
			||||||
 | 
					class ReplaceIfStmtWithItsBody : public RefactoringCallback {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  ReplaceIfStmtWithItsBody(StringRef Id, bool PickTrueBranch);
 | 
				
			||||||
 | 
					  virtual void run(const MatchFinder::MatchResult &Result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  std::string Id;
 | 
				
			||||||
 | 
					  const bool PickTrueBranch;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // end namespace ast_matchers
 | 
				
			||||||
 | 
					} // end namespace clang
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif // LLVM_CLANG_AST_MATCHERS_REFACTORING_CALLBACKS_H
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,8 @@ FrontendActionFactory *newFrontendActionFactory();
 | 
				
			||||||
/// FrontendActionFactory *FactoryAdapter =
 | 
					/// FrontendActionFactory *FactoryAdapter =
 | 
				
			||||||
///   newFrontendActionFactory(&Factory);
 | 
					///   newFrontendActionFactory(&Factory);
 | 
				
			||||||
template <typename FactoryT>
 | 
					template <typename FactoryT>
 | 
				
			||||||
FrontendActionFactory *newFrontendActionFactory(FactoryT *ConsumerFactory);
 | 
					inline FrontendActionFactory *newFrontendActionFactory(
 | 
				
			||||||
 | 
					    FactoryT *ConsumerFactory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// \brief Runs (and deletes) the tool on 'Code' with the -fsyntax-only flag.
 | 
					/// \brief Runs (and deletes) the tool on 'Code' with the -fsyntax-only flag.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
| 
						 | 
					@ -202,7 +203,8 @@ FrontendActionFactory *newFrontendActionFactory() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename FactoryT>
 | 
					template <typename FactoryT>
 | 
				
			||||||
FrontendActionFactory *newFrontendActionFactory(FactoryT *ConsumerFactory) {
 | 
					inline FrontendActionFactory *newFrontendActionFactory(
 | 
				
			||||||
 | 
					    FactoryT *ConsumerFactory) {
 | 
				
			||||||
  class FrontendActionFactoryAdapter : public FrontendActionFactory {
 | 
					  class FrontendActionFactoryAdapter : public FrontendActionFactory {
 | 
				
			||||||
  public:
 | 
					  public:
 | 
				
			||||||
    explicit FrontendActionFactoryAdapter(FactoryT *ConsumerFactory)
 | 
					    explicit FrontendActionFactoryAdapter(FactoryT *ConsumerFactory)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ set(LLVM_USED_LIBS clangBasic clangAST)
 | 
				
			||||||
add_clang_library(clangASTMatchers
 | 
					add_clang_library(clangASTMatchers
 | 
				
			||||||
  ASTMatchFinder.cpp
 | 
					  ASTMatchFinder.cpp
 | 
				
			||||||
  ASTMatchersInternal.cpp
 | 
					  ASTMatchersInternal.cpp
 | 
				
			||||||
 | 
					  RefactoringCallbacks.cpp
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_dependencies(clangASTMatchers
 | 
					add_dependencies(clangASTMatchers
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					//===--- RefactoringCallbacks.cpp - Structural query framework ------------===//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//                     The LLVM Compiler Infrastructure
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This file is distributed under the University of Illinois Open Source
 | 
				
			||||||
 | 
					// License. See LICENSE.TXT for details.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//===----------------------------------------------------------------------===//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//===----------------------------------------------------------------------===//
 | 
				
			||||||
 | 
					#include "clang/Lex/Lexer.h"
 | 
				
			||||||
 | 
					#include "clang/ASTMatchers/RefactoringCallbacks.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace clang {
 | 
				
			||||||
 | 
					namespace ast_matchers {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RefactoringCallback::RefactoringCallback() {}
 | 
				
			||||||
 | 
					tooling::Replacements &RefactoringCallback::getReplacements() {
 | 
				
			||||||
 | 
					  return Replace;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static tooling::Replacement replaceStmtWithText(SourceManager &Sources,
 | 
				
			||||||
 | 
					                                                const Stmt &From,
 | 
				
			||||||
 | 
					                                                StringRef Text) {
 | 
				
			||||||
 | 
					  return tooling::Replacement(Sources, CharSourceRange::getTokenRange(
 | 
				
			||||||
 | 
					      From.getSourceRange()), Text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					static tooling::Replacement replaceStmtWithStmt(SourceManager &Sources,
 | 
				
			||||||
 | 
					                                                const Stmt &From,
 | 
				
			||||||
 | 
					                                                const Stmt &To) {
 | 
				
			||||||
 | 
					  return replaceStmtWithText(Sources, From, Lexer::getSourceText(
 | 
				
			||||||
 | 
					      CharSourceRange::getTokenRange(To.getSourceRange()),
 | 
				
			||||||
 | 
					      Sources, LangOptions()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ReplaceStmtWithText::ReplaceStmtWithText(StringRef FromId, StringRef ToText)
 | 
				
			||||||
 | 
					    : FromId(FromId), ToText(ToText) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ReplaceStmtWithText::run(const MatchFinder::MatchResult &Result) {
 | 
				
			||||||
 | 
					  if (const Stmt *FromMatch = Result.Nodes.getStmtAs<Stmt>(FromId)) {
 | 
				
			||||||
 | 
					    Replace.insert(tooling::Replacement(
 | 
				
			||||||
 | 
					        *Result.SourceManager,
 | 
				
			||||||
 | 
					        CharSourceRange::getTokenRange(FromMatch->getSourceRange()),
 | 
				
			||||||
 | 
					        ToText));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ReplaceStmtWithStmt::ReplaceStmtWithStmt(StringRef FromId, StringRef ToId)
 | 
				
			||||||
 | 
					    : FromId(FromId), ToId(ToId) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ReplaceStmtWithStmt::run(const MatchFinder::MatchResult &Result) {
 | 
				
			||||||
 | 
					  const Stmt *FromMatch = Result.Nodes.getStmtAs<Stmt>(FromId);
 | 
				
			||||||
 | 
					  const Stmt *ToMatch = Result.Nodes.getStmtAs<Stmt>(ToId);
 | 
				
			||||||
 | 
					  if (FromMatch && ToMatch)
 | 
				
			||||||
 | 
					    Replace.insert(replaceStmtWithStmt(
 | 
				
			||||||
 | 
					        *Result.SourceManager, *FromMatch, *ToMatch));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ReplaceIfStmtWithItsBody::ReplaceIfStmtWithItsBody(StringRef Id,
 | 
				
			||||||
 | 
					                                                   bool PickTrueBranch)
 | 
				
			||||||
 | 
					    : Id(Id), PickTrueBranch(PickTrueBranch) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ReplaceIfStmtWithItsBody::run(const MatchFinder::MatchResult &Result) {
 | 
				
			||||||
 | 
					  if (const IfStmt *Node = Result.Nodes.getStmtAs<IfStmt>(Id)) {
 | 
				
			||||||
 | 
					    const Stmt *Body = PickTrueBranch ? Node->getThen() : Node->getElse();
 | 
				
			||||||
 | 
					    if (Body) {
 | 
				
			||||||
 | 
					      Replace.insert(replaceStmtWithStmt(*Result.SourceManager, *Node, *Body));
 | 
				
			||||||
 | 
					    } else if (!PickTrueBranch) {
 | 
				
			||||||
 | 
					      // If we want to use the 'else'-branch, but it doesn't exist, delete
 | 
				
			||||||
 | 
					      // the whole 'if'.
 | 
				
			||||||
 | 
					      Replace.insert(replaceStmtWithText(*Result.SourceManager, *Node, ""));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // end namespace ast_matchers
 | 
				
			||||||
 | 
					} // end namespace clang
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
add_clang_unittest(ASTMatchersTests
 | 
					add_clang_unittest(ASTMatchersTests
 | 
				
			||||||
  ASTMatchersTest.cpp)
 | 
					  ASTMatchersTest.cpp
 | 
				
			||||||
 | 
					  RefactoringCallbacksTest.cpp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
target_link_libraries(ASTMatchersTests
 | 
					target_link_libraries(ASTMatchersTests
 | 
				
			||||||
  gtest gtest_main clangASTMatchers clangTooling)
 | 
					  gtest gtest_main clangASTMatchers clangTooling)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,98 @@
 | 
				
			||||||
 | 
					//===- unittest/ASTMatchers/RefactoringCallbacksTest.cpp ------------------===//
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//                     The LLVM Compiler Infrastructure
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This file is distributed under the University of Illinois Open Source
 | 
				
			||||||
 | 
					// License. See LICENSE.TXT for details.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//===----------------------------------------------------------------------===//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "clang/ASTMatchers/ASTMatchers.h"
 | 
				
			||||||
 | 
					#include "clang/ASTMatchers/ASTMatchFinder.h"
 | 
				
			||||||
 | 
					#include "clang/ASTMatchers/RefactoringCallbacks.h"
 | 
				
			||||||
 | 
					#include "../Tooling/RewriterTestContext.h"
 | 
				
			||||||
 | 
					#include "gtest/gtest.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace clang {
 | 
				
			||||||
 | 
					namespace ast_matchers {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T>
 | 
				
			||||||
 | 
					void expectRewritten(const std::string &Code,
 | 
				
			||||||
 | 
					                     const std::string &Expected,
 | 
				
			||||||
 | 
					                     const T &AMatcher,
 | 
				
			||||||
 | 
					                     RefactoringCallback &Callback) {
 | 
				
			||||||
 | 
					  MatchFinder Finder;
 | 
				
			||||||
 | 
					  Finder.addMatcher(AMatcher, &Callback);
 | 
				
			||||||
 | 
					  OwningPtr<tooling::FrontendActionFactory> Factory(
 | 
				
			||||||
 | 
					      tooling::newFrontendActionFactory(&Finder));
 | 
				
			||||||
 | 
					  ASSERT_TRUE(tooling::runToolOnCode(Factory->create(), Code))
 | 
				
			||||||
 | 
					      << "Parsing error in \"" << Code << "\"";
 | 
				
			||||||
 | 
					  RewriterTestContext Context;
 | 
				
			||||||
 | 
					  FileID ID = Context.createInMemoryFile("input.cc", Code);
 | 
				
			||||||
 | 
					  EXPECT_TRUE(tooling::applyAllReplacements(Callback.getReplacements(),
 | 
				
			||||||
 | 
					                                            Context.Rewrite));
 | 
				
			||||||
 | 
					  EXPECT_EQ(Expected, Context.getRewrittenText(ID));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST(RefactoringCallbacksTest, ReplacesStmtsWithString) {
 | 
				
			||||||
 | 
					  std::string Code = "void f() { int i = 1; }";
 | 
				
			||||||
 | 
					  std::string Expected = "void f() { ; }";
 | 
				
			||||||
 | 
					  ReplaceStmtWithText Callback("id", ";");
 | 
				
			||||||
 | 
					  expectRewritten(Code, Expected, id("id", declarationStatement()), Callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST(RefactoringCallbacksTest, ReplacesStmtsInCalledMacros) {
 | 
				
			||||||
 | 
					  std::string Code = "#define A void f() { int i = 1; }\nA";
 | 
				
			||||||
 | 
					  std::string Expected = "#define A void f() { ; }\nA";
 | 
				
			||||||
 | 
					  ReplaceStmtWithText Callback("id", ";");
 | 
				
			||||||
 | 
					  expectRewritten(Code, Expected, id("id", declarationStatement()), Callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST(RefactoringCallbacksTest, IgnoresStmtsInUncalledMacros) {
 | 
				
			||||||
 | 
					  std::string Code = "#define A void f() { int i = 1; }";
 | 
				
			||||||
 | 
					  std::string Expected = "#define A void f() { int i = 1; }";
 | 
				
			||||||
 | 
					  ReplaceStmtWithText Callback("id", ";");
 | 
				
			||||||
 | 
					  expectRewritten(Code, Expected, id("id", declarationStatement()), Callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST(RefactoringCallbacksTest, ReplacesInteger) {
 | 
				
			||||||
 | 
					  std::string Code = "void f() { int i = 1; }";
 | 
				
			||||||
 | 
					  std::string Expected = "void f() { int i = 2; }";
 | 
				
			||||||
 | 
					  ReplaceStmtWithText Callback("id", "2");
 | 
				
			||||||
 | 
					  expectRewritten(Code, Expected, id("id", expression(integerLiteral())),
 | 
				
			||||||
 | 
					                  Callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST(RefactoringCallbacksTest, ReplacesStmtWithStmt) {
 | 
				
			||||||
 | 
					  std::string Code = "void f() { int i = false ? 1 : i * 2; }";
 | 
				
			||||||
 | 
					  std::string Expected = "void f() { int i = i * 2; }";
 | 
				
			||||||
 | 
					  ReplaceStmtWithStmt Callback("always-false", "should-be");
 | 
				
			||||||
 | 
					  expectRewritten(Code, Expected,
 | 
				
			||||||
 | 
					      id("always-false", conditionalOperator(
 | 
				
			||||||
 | 
					          hasCondition(boolLiteral(equals(false))),
 | 
				
			||||||
 | 
					          hasFalseExpression(id("should-be", expression())))),
 | 
				
			||||||
 | 
					      Callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST(RefactoringCallbacksTest, ReplacesIfStmt) {
 | 
				
			||||||
 | 
					  std::string Code = "bool a; void f() { if (a) f(); else a = true; }";
 | 
				
			||||||
 | 
					  std::string Expected = "bool a; void f() { f(); }";
 | 
				
			||||||
 | 
					  ReplaceIfStmtWithItsBody Callback("id", true);
 | 
				
			||||||
 | 
					  expectRewritten(Code, Expected,
 | 
				
			||||||
 | 
					      id("id", ifStmt(
 | 
				
			||||||
 | 
					          hasCondition(implicitCast(hasSourceExpression(
 | 
				
			||||||
 | 
					              declarationReference(to(variable(hasName("a"))))))))),
 | 
				
			||||||
 | 
					      Callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST(RefactoringCallbacksTest, RemovesEntireIfOnEmptyElse) {
 | 
				
			||||||
 | 
					  std::string Code = "void f() { if (false) int i = 0; }";
 | 
				
			||||||
 | 
					  std::string Expected = "void f() {  }";
 | 
				
			||||||
 | 
					  ReplaceIfStmtWithItsBody Callback("id", false);
 | 
				
			||||||
 | 
					  expectRewritten(Code, Expected,
 | 
				
			||||||
 | 
					      id("id", ifStmt(hasCondition(boolLiteral(equals(false))))),
 | 
				
			||||||
 | 
					      Callback);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // end namespace ast_matchers
 | 
				
			||||||
 | 
					} // end namespace clang
 | 
				
			||||||
		Loading…
	
		Reference in New Issue