Add rewriter of data members to getter/setter member functions

This CL adds a Clang rewriter that converts references to a data
member to calls of a getter or setter. The intended use-case is to
convert a struct with public members to a class with getters and
setters.

Specifically, it's intended to rewrite the structs FormData and
FormFieldData, starting with FormFieldData::value.

The rewriter does the following conversion, where `foo` is a
`FormFieldData`:

Old: `foo.value = bar`          `foo->value = bar`
New: `foo.set_value(bar)`       `foo->set_value(bar)`

Outside of the LHS of an assignment:
Old: `foo.value`                `foo->value`
New: `foo.value()`              `foo->value()`

Old: `::testing::Field(&FormFieldData::value, bar)`
New: `::testing::Property(&FormFieldData::value, bar)`

Followup CLs will extend the rewriter to other public data members
of FormFieldData and add another rewriter for its parent struct
FormData.

Bug: 40232021
Change-Id: Iaad5042514f6deafe3ee3f6a95f428bfb216508a
Cq-Do-Not-Cancel-Tryjobs: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5422009
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Christoph Schwering <schwering@google.com>
Cr-Commit-Position: refs/heads/main@{#1288878}
NOKEYCHECK=True
GitOrigin-RevId: 42c36aeee2de84c1ef21ef767114b56a7e265c0c
diff --git a/rewrite_autofill_form_data/CMakeLists.txt b/rewrite_autofill_form_data/CMakeLists.txt
new file mode 100644
index 0000000..619504d
--- /dev/null
+++ b/rewrite_autofill_form_data/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(LLVM_LINK_COMPONENTS
+  BitReader
+  MCParser
+  Option
+  X86AsmParser
+  X86CodeGen
+  )
+
+add_llvm_executable(rewrite_autofill_form_data
+  FieldToFunction.cpp
+  )
+
+target_link_libraries(rewrite_autofill_form_data
+  clangAST
+  clangASTMatchers
+  clangAnalysis
+  clangBasic
+  clangDriver
+  clangEdit
+  clangFrontend
+  clangLex
+  clangParse
+  clangSema
+  clangSerialization
+  clangTooling
+  )
+
+cr_install(TARGETS rewrite_autofill_form_data RUNTIME DESTINATION bin)
diff --git a/rewrite_autofill_form_data/FieldToFunction.cpp b/rewrite_autofill_form_data/FieldToFunction.cpp
new file mode 100644
index 0000000..9cd03c2
--- /dev/null
+++ b/rewrite_autofill_form_data/FieldToFunction.cpp
@@ -0,0 +1,413 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Clang tool to change accesses to data members to calling getters or setters.
+//
+// For example, if `foo` and `bar` are of a type whose `member` is supposed to
+// be rewritten, the rewriter replaces `foo.member = bar.member` with
+// `foo.set_member(bar.member())`.
+//
+// In particular, this is used to rewrite autofill::FormFieldData::value.
+
+#include <cassert>
+#include <iostream>
+#include <memory>
+#include <regex>
+#include <string>
+
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/TargetSelect.h"
+
+// Prints a clang::SourceLocation or clang::SourceRange.
+#define LOG(e)                                                     \
+  llvm::errs() << __FILE__ << ":" << __LINE__ << ": " << #e << " " \
+               << (e).printToString(*result.SourceManager) << '\n';
+
+namespace {
+
+llvm::cl::extrahelp common_help(
+    clang::tooling::CommonOptionsParser::HelpMessage);
+llvm::cl::extrahelp more_help(
+    "The rewriter turns references to ClassOfInterest::field_of_interest into\n"
+    "getter and setter calls.");
+llvm::cl::OptionCategory rewriter_category("Rewriter Options");
+llvm::cl::opt<std::string> classes_of_interest_option(
+    "classes-of-interest",
+    llvm::cl::desc("Comma-separated fully qualified names of the classes whose "
+                   "field are to be rewritten"),
+    llvm::cl::init("::autofill::FormFieldData"),
+    llvm::cl::cat(rewriter_category));
+llvm::cl::opt<std::string> fields_of_interest_option(
+    "fields-of-interest",
+    llvm::cl::desc("Comma-separated names of data members of any class of "
+                   "interest that are to be rewritten"),
+    llvm::cl::init("value"),
+    llvm::cl::cat(rewriter_category));
+
+// Splits a string at each comma.
+// For example, `Explode("foo,bar,")` returns `{"foo", "bar", ""}`.
+std::vector<std::string> Explode(std::string_view s) {
+  std::vector<std::string> substrings;
+  for (;;) {
+    size_t pos = s.find(',');
+    substrings.push_back(std::string(s.substr(0, pos)));
+    if (pos == std::string::npos) {
+      break;
+    }
+    s = s.substr(pos + 1);
+  }
+  return substrings;
+}
+
+// Generates substitution directives.
+// We do not use `clang::tooling::Replacements` because it's restricted to
+// same-file substitutions.
+class OutputHelper : public clang::tooling::SourceFileCallbacks {
+ public:
+  OutputHelper() = default;
+  ~OutputHelper() = default;
+
+  OutputHelper(const OutputHelper&) = delete;
+  OutputHelper& operator=(const OutputHelper&) = delete;
+
+  // Deletes `replacement_range`.
+  void Delete(const clang::CharSourceRange& replacement_range,
+              const clang::SourceManager& source_manager,
+              const clang::LangOptions& lang_opts) {
+    Replace(replacement_range, "", source_manager, lang_opts);
+  }
+
+  // Replaces `replacement_range` with `replacement_text`.
+  void Replace(const clang::CharSourceRange& replacement_range,
+               std::string replacement_text,
+               const clang::SourceManager& source_manager,
+               const clang::LangOptions& lang_opts) {
+    clang::tooling::Replacement replacement(source_manager, replacement_range,
+                                            replacement_text, lang_opts);
+    llvm::StringRef file_path = replacement.getFilePath();
+    if (file_path.empty()) {
+      return;
+    }
+    std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0');
+    Add(file_path, replacement.getOffset(), replacement.getLength(),
+        replacement_text);
+  }
+
+  // Inserts `lhs` and `rhs` to the left and right of `replacement_range`.
+  void Wrap(const clang::CharSourceRange& replacement_range,
+            std::string_view lhs,
+            std::string_view rhs,
+            const clang::SourceManager& source_manager,
+            const clang::LangOptions& lang_opts) {
+    clang::tooling::Replacement replacement(source_manager, replacement_range,
+                                            "", lang_opts);
+    llvm::StringRef file_path = replacement.getFilePath();
+    if (file_path.empty()) {
+      return;
+    }
+    Add(file_path, replacement.getOffset(), 0, lhs);
+    Add(file_path, replacement.getOffset() + replacement.getLength(), 0, rhs);
+  }
+
+ private:
+  // clang::tooling::SourceFileCallbacks:
+  bool handleBeginSource(clang::CompilerInstance& compiler) override {
+    const clang::FrontendOptions& frontend_options = compiler.getFrontendOpts();
+
+    assert((frontend_options.Inputs.size() == 1) &&
+           "run_tool.py should invoke the rewriter one file at a time");
+    const clang::FrontendInputFile& input_file = frontend_options.Inputs[0];
+    assert(input_file.isFile() &&
+           "run_tool.py should invoke the rewriter on actual files");
+
+    current_language_ = input_file.getKind().getLanguage();
+
+    if (ShouldOutput()) {
+      llvm::outs() << "==== BEGIN EDITS ====\n";
+    }
+    return true;  // Report that |handleBeginSource| succeeded.
+  }
+
+  void handleEndSource() override {
+    if (ShouldOutput()) {
+      llvm::outs() << "==== END EDITS ====\n";
+    }
+  }
+
+  void Add(llvm::StringRef file_path,
+           unsigned offset,
+           unsigned length,
+           std::string_view replacement_text) {
+    if (ShouldOutput()) {
+      llvm::outs() << "r:::" << file_path << ":::" << offset << ":::" << length
+                   << ":::" << replacement_text << "\n";
+    }
+  }
+
+  bool ShouldOutput() {
+    switch (current_language_) {
+      case clang::Language::CXX:
+      case clang::Language::OpenCLCXX:
+      case clang::Language::ObjCXX:
+        return true;
+      case clang::Language::Unknown:
+      case clang::Language::Asm:
+      case clang::Language::LLVM_IR:
+      case clang::Language::CIR:
+      case clang::Language::OpenCL:
+      case clang::Language::CUDA:
+      case clang::Language::RenderScript:
+      case clang::Language::HIP:
+      case clang::Language::HLSL:
+      case clang::Language::C:
+      case clang::Language::ObjC:
+        return false;
+    }
+    assert(false && "Unrecognized clang::Language");
+    return false;
+  }
+
+  std::vector<std::string> output_lines_;
+  clang::Language current_language_ = clang::Language::Unknown;
+};
+
+namespace matchers {
+
+auto IsClassOfInterest() {
+  using namespace clang::ast_matchers;
+  using namespace clang::ast_matchers::internal;
+  static std::vector<std::string> names = Explode(classes_of_interest_option);
+  return cxxRecordDecl(Matcher<clang::NamedDecl>(new HasNameMatcher(names)));
+}
+
+auto IsFieldOfInterest() {
+  using namespace clang::ast_matchers;
+  using namespace clang::ast_matchers::internal;
+  static std::vector<std::string> names = Explode(fields_of_interest_option);
+  return fieldDecl(Matcher<clang::NamedDecl>(new HasNameMatcher(names)));
+}
+
+// Matches `object.member` and `object->member` where `member` is a
+// member-of-interest of a class-of-interest
+auto member_expr_of_interest() {
+  using namespace clang::ast_matchers;
+  return memberExpr(allOf(member(allOf(IsFieldOfInterest(),
+                                       fieldDecl(hasParent(cxxRecordDecl(
+                                           IsClassOfInterest()))))),
+                          // Avoids matching memberExprs in defaulted
+                          // constructors and operators.
+                          unless(hasAncestor(functionDecl(isDefaulted())))))
+      .bind("member_expr");
+}
+
+// Matches `f.member = foo`.
+auto write_access() {
+  using namespace clang::ast_matchers;
+  return expr(binaryOperation(
+                  hasOperatorName("="),
+                  hasLHS(ignoringParenImpCasts(member_expr_of_interest())),
+                  hasRHS(expr().bind("rhs"))))
+      .bind("assignment");
+}
+
+}  // namespace matchers
+
+class BaseRewriter {
+ public:
+  explicit BaseRewriter(OutputHelper* output_helper)
+      : output_helper_(*output_helper) {}
+
+ protected:
+  OutputHelper& output_helper_;
+};
+
+// Replaces `object.member` with `object.member()`.
+class FieldReadAccessRewriter
+    : public BaseRewriter,
+      public clang::ast_matchers::MatchFinder::MatchCallback {
+ public:
+  using BaseRewriter::BaseRewriter;
+
+  void AddMatchers(clang::ast_matchers::MatchFinder& match_finder) {
+    using namespace clang::ast_matchers;
+    using namespace matchers;
+    // Matches `f.member` and `f->member`, except on the left-hand side of
+    // assignments.
+    auto read_access = expr(anyOf(
+        allOf(member_expr_of_interest(), unless(hasAncestor(write_access()))),
+        expr(binaryOperation(
+            hasOperatorName("="),
+            hasRHS(anyOf(member_expr_of_interest(),
+                         hasDescendant(member_expr_of_interest())))))));
+    match_finder.addMatcher(read_access, this);
+  }
+
+ private:
+  void run(
+      const clang::ast_matchers::MatchFinder::MatchResult& result) override {
+    // object.member
+    // ^-----------^  "member_expr"
+    const auto* member_expr =
+        result.Nodes.getNodeAs<clang::MemberExpr>("member_expr");
+    assert(member_expr);
+    clang::CharSourceRange range = clang::CharSourceRange::getTokenRange(
+        clang::SourceRange(member_expr->getMemberLoc()));
+    auto source_text = clang::Lexer::getSourceText(
+        range, *result.SourceManager, result.Context->getLangOpts());
+    std::string replacement_text =
+        std::string(source_text.begin(), source_text.end()) + "()";
+    output_helper_.Replace(range, replacement_text, *result.SourceManager,
+                           result.Context->getLangOpts());
+  }
+};
+
+// Replaces `object.member = something` with `object.set_value(something)`
+// (modulo whitespace).
+//
+// More precisely, it inserts `set_` before `member`, removes `=`, and puts
+// `something` into parentheses. We do it this way to avoid overlapping
+// substitutions because `something` might need substitutions on its own.
+class FieldWriteAccessRewriter
+    : public BaseRewriter,
+      public clang::ast_matchers::MatchFinder::MatchCallback {
+ public:
+  using BaseRewriter::BaseRewriter;
+
+  void AddMatchers(clang::ast_matchers::MatchFinder& match_finder) {
+    match_finder.addMatcher(matchers::write_access(), this);
+  }
+
+ private:
+  void run(
+      const clang::ast_matchers::MatchFinder::MatchResult& result) override {
+    // object.member = something;
+    // ^-----------^              "member_expr"
+    //                 ^-------^  "rhs"
+    // ^-----------------------^  "assignment"
+    const auto* assignment = result.Nodes.getNodeAs<clang::Expr>("assignment");
+    const auto* member_expr =
+        result.Nodes.getNodeAs<clang::MemberExpr>("member_expr");
+    const auto* rhs = result.Nodes.getNodeAs<clang::Expr>("rhs");
+    assert(assignment);
+    assert(member_expr);
+    assert(rhs);
+    {
+      // Replace `member` with `set_member`.
+      clang::CharSourceRange range = clang::CharSourceRange::getTokenRange(
+          clang::SourceRange(member_expr->getMemberLoc()));
+      output_helper_.Wrap(range, "set_", "", *result.SourceManager,
+                          result.Context->getLangOpts());
+    }
+    {
+      // Replace `=` with ``.
+      clang::CharSourceRange range =
+          clang::CharSourceRange::getTokenRange(assignment->getExprLoc());
+      std::string replacement_text = "";
+      output_helper_.Delete(range, *result.SourceManager,
+                            result.Context->getLangOpts());
+    }
+    {
+      // Replace `something` with `(something)`.
+      clang::CharSourceRange range =
+          clang::CharSourceRange::getTokenRange(rhs->getSourceRange());
+      output_helper_.Wrap(range, "(", ")", *result.SourceManager,
+                          result.Context->getLangOpts());
+    }
+  }
+};
+
+// Replaces `::testing::Field(..., &ClassOfInterest::field_of_interest, ...)`
+// with `::testing::Property(..., &ClassOfInterest::field_of_interest, ...)`.
+class TestingFieldRewriter
+    : public BaseRewriter,
+      public clang::ast_matchers::MatchFinder::MatchCallback {
+ public:
+  using BaseRewriter::BaseRewriter;
+
+  void AddMatchers(clang::ast_matchers::MatchFinder& match_finder) {
+    using namespace clang::ast_matchers;
+    using namespace matchers;
+    // Matches `Field(&ClassOfInterest::field_of_interest, ...)`.
+    auto testing_field =
+        callExpr(
+            callee(functionDecl(hasName("::testing::Field"))),
+            hasAnyArgument(expr(unaryOperator(
+                hasOperatorName("&"), hasUnaryOperand(declRefExpr(to(allOf(
+                                          hasParent(IsClassOfInterest()),
+                                          fieldDecl(IsFieldOfInterest())))))))))
+            .bind("call_expr");
+    match_finder.addMatcher(testing_field, this);
+  }
+
+ private:
+  void run(
+      const clang::ast_matchers::MatchFinder::MatchResult& result) override {
+    const auto* call_expr =
+        result.Nodes.getNodeAs<clang::CallExpr>("call_expr");
+    assert(call_expr);
+    // Replace `::testing::Field` with `::testing::Property`.
+    clang::CharSourceRange range = clang::CharSourceRange::getTokenRange(
+        call_expr->getCallee()->getSourceRange());
+    auto source_text = clang::Lexer::getSourceText(
+        range, *result.SourceManager, result.Context->getLangOpts());
+    std::string replacement_text =
+        std::string(source_text.begin(), source_text.end());
+    replacement_text =
+        std::regex_replace(replacement_text, std::regex("Field"), "Property");
+    output_helper_.Replace(range, replacement_text, *result.SourceManager,
+                           result.Context->getLangOpts());
+  }
+};
+
+class FieldToFunctionRewriter : public BaseRewriter {
+ public:
+  using BaseRewriter::BaseRewriter;
+
+  void AddMatchers(clang::ast_matchers::MatchFinder& match_finder) {
+    frar_.AddMatchers(match_finder);
+    fwar_.AddMatchers(match_finder);
+    tfr_.AddMatchers(match_finder);
+  }
+
+ private:
+  FieldReadAccessRewriter frar_{&output_helper_};
+  FieldWriteAccessRewriter fwar_{&output_helper_};
+  TestingFieldRewriter tfr_{&output_helper_};
+};
+
+}  // namespace
+
+int main(int argc, const char* argv[]) {
+  llvm::InitializeNativeTarget();
+  llvm::InitializeNativeTargetAsmParser();
+
+  llvm::Expected<clang::tooling::CommonOptionsParser> options =
+      clang::tooling::CommonOptionsParser::create(argc, argv,
+                                                  rewriter_category);
+  assert(static_cast<bool>(options));
+  clang::tooling::ClangTool tool(options->getCompilations(),
+                                 options->getSourcePathList());
+
+  OutputHelper output_helper;
+  clang::ast_matchers::MatchFinder match_finder;
+
+  FieldToFunctionRewriter ftf_rewriter(&output_helper);
+  ftf_rewriter.AddMatchers(match_finder);
+
+  std::unique_ptr<clang::tooling::FrontendActionFactory> factory =
+      clang::tooling::newFrontendActionFactory(&match_finder, &output_helper);
+  return tool.run(factory.get());
+}
diff --git a/rewrite_autofill_form_data/OWNERS b/rewrite_autofill_form_data/OWNERS
new file mode 100644
index 0000000..7cbf1a1
--- /dev/null
+++ b/rewrite_autofill_form_data/OWNERS
@@ -0,0 +1 @@
+schwering@google.com
diff --git a/rewrite_autofill_form_data/tests/field-to-function-expected.cc b/rewrite_autofill_form_data/tests/field-to-function-expected.cc
new file mode 100644
index 0000000..808609d
--- /dev/null
+++ b/rewrite_autofill_form_data/tests/field-to-function-expected.cc
@@ -0,0 +1,219 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+namespace autofill {
+
+struct FormFieldData {
+  FormFieldData();
+  FormFieldData(const FormFieldData&);
+  FormFieldData& operator=(const FormFieldData&);
+  std::u16string value;
+  int something_else;
+};
+
+// References in compiler-generated code must not be rewritten.
+FormFieldData::FormFieldData() = default;
+FormFieldData::FormFieldData(const FormFieldData&) = default;
+FormFieldData& FormFieldData::operator=(const FormFieldData&) = default;
+
+class AutofillField : public FormFieldData {};
+
+}  // namespace autofill
+
+namespace testing {
+
+template <typename Class, typename FieldType>
+void Field(FieldType Class::*field, int matcher) {}
+
+template <typename Class, typename FieldType>
+void Property(FieldType Class::*field(), int matcher) {}
+
+}  // namespace testing
+
+namespace base {
+
+std::u16string ASCIIToUTF16(std::string x) {
+  return u"arbitrary string";
+}
+
+}  // namespace base
+
+namespace not_autofill {
+
+struct FormFieldData {
+  std::u16string value;
+  int something_else;
+};
+
+}  // namespace not_autofill
+
+// Tests that a read reference `f.value` is replaced with `r.value()`.
+std::u16string FunRead() {
+  autofill::FormFieldData f;
+  return f.value();
+}
+
+// Tests that `f.value` at the left of an assignment is replaced with
+// `f.set_value(rhs)`.
+void FunWrite() {
+  ::autofill::FormFieldData f;
+  f.set_value(u"foo");
+}
+
+// Tests that a read reference `f->value` is replaced with `r->value()`.
+std::u16string FunReadPointer() {
+  autofill::FormFieldData f;
+  autofill::FormFieldData* g = &f;
+  return g->value();
+}
+
+// Tests that `f->value` at the left of an assignment is replaced with
+// `f->set_value(rhs)`.
+void FunWritePointer() {
+  autofill::FormFieldData f;
+  autofill::FormFieldData* g = &f;
+  g->set_value(u"foo");
+}
+
+// Tests that a read reference `f.value()` in a member function is replaced with
+// `f.value()`.
+class Class {
+  static const std::u16string& value(const autofill::FormFieldData& f) {
+    return f.value();
+  }
+};
+
+// Tests that a references at the left and right hand side of an assignment aer
+// replaced appropriately.
+void FunReadAndWrite() {
+  ::autofill::FormFieldData f;
+  ::autofill::FormFieldData g;
+  f.set_value(g.value());
+}
+
+// Like FunReadAndWrite() but additionally tests that a constness doesn't affect
+// the rewriting.
+void FunReadConstAndWrite() {
+  ::autofill::FormFieldData f;
+  const ::autofill::FormFieldData g;
+  f.set_value(g.value());
+}
+
+// Like FunReadConstAndWrite() but additionally tests that redundant
+// parentheses doesn't affect the rewriting.
+void FunReadConstAndWriteWithParentheses() {
+  ::autofill::FormFieldData f;
+  const ::autofill::FormFieldData g;
+  ((f).set_value)(((g).value()));
+}
+
+// Like FunReadConstAndWrite() but additionally tests that additional whitespace
+// doesn't affect the rewriting of a read reference and that the comments
+// survive the rewriting.
+void FunReadWithWhitespace() {
+  autofill::FormFieldData f;
+  std::u16string s = f         // comment 1
+                         .     // comment 2
+                     value();  // comment 3
+}
+
+// Like FunReadConstAndWrite() but additionally tests that additional
+// whitespace doesn't affect the rewriting of a write reference and that the
+// comments survive the rewriting.
+void FunWriteWithWhitespace() {
+  autofill::FormFieldData f;
+  f              // comment 1
+      .          // comment 2
+      set_value  // comment 3
+                 // comment 4
+      (u"foo");
+}
+
+// Tests whether explicit `operator=()` is rewritten. This is desirable but
+// currently not implemented.
+void FunWriteExplicitOperator() {
+  ::autofill::FormFieldData f;
+  f.value().operator=(u"foo");  // Currently not properly rewritten.
+}
+
+namespace autofill {
+
+// Tests that references are rewritten even if the object's type isn't fully
+// qualified.
+std::u16string FunReadImplicitNamespace() {
+  FormFieldData f;
+  return f.value();
+}
+
+// Tests that a reference at the left of an assignment is replaced with
+// set_value(), and that a more complex right-hand side experission is
+// preserved.
+void FunWriteCallExpr() {
+  std::string value;
+  FormFieldData field;
+  field.set_value(base::ASCIIToUTF16(value));
+}
+
+}  // namespace autofill
+
+// Like FunWriteCallExpr() but with a more complex expression at the right-hand
+// side. It involves UTF-16 string literals to test that the rewriter uses the
+// correct clang::LangOptions (and not the default argument).
+void FunWriteComplexCallExpr() {
+  auto g = [](const std::u16string& s) { return s; };
+  autofill::FormFieldData f;
+  f.set_value(u"bar" + g(std::u16string(u"foo") + u"qux"));
+}
+
+// Tests that the replacement does not touch classes from other namespaces.
+std::u16string FunReadNotAutofill() {
+  ::not_autofill::FormFieldData f;
+  return f.value;
+}
+
+// Tests that write references on members of a derived class are rewritten.
+void FunWriteDerived() {
+  autofill::AutofillField f;
+  f.set_value(u"bar");
+}
+
+// Tests that read references on vector elements are rewritten.
+std::u16string FunReadVector() {
+  std::vector<autofill::FormFieldData> fields;
+  fields.emplace_back();
+  return fields[0].value();
+}
+
+// Tests that write references on vector elements are rewritten.
+void FunWriteVector() {
+  std::vector<autofill::FormFieldData> fields;
+  fields.emplace_back();
+  fields[0].set_value(u"foo");
+}
+
+// Tests that pointers to members are not rewritten.
+std::u16string FunFieldPointer() {
+  using autofill::FormFieldData;
+  FormFieldData f;
+  std::u16string FormFieldData::*ptr = &FormFieldData::value;
+  return f.*ptr;
+}
+
+// Tests that Field(&Class::member, ...) is replaced with
+// Property(&Class::member, ...).
+void FunMatcher() {
+  ::testing::Property(&autofill::FormFieldData::value, {});
+  ::testing::Field(&autofill::FormFieldData::something_else, {});
+  ::testing::Field(&not_autofill::FormFieldData::value, {});
+  ::testing::Field(&not_autofill::FormFieldData::something_else, {});
+  using ::testing::Field;
+  using ::testing::Property;
+  Property(&autofill::FormFieldData::value, {});
+  Field(&autofill::FormFieldData::something_else, {});
+  Field(&not_autofill::FormFieldData::value, {});
+  Field(&not_autofill::FormFieldData::something_else, {});
+}
diff --git a/rewrite_autofill_form_data/tests/field-to-function-original.cc b/rewrite_autofill_form_data/tests/field-to-function-original.cc
new file mode 100644
index 0000000..3e5a99e
--- /dev/null
+++ b/rewrite_autofill_form_data/tests/field-to-function-original.cc
@@ -0,0 +1,219 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+namespace autofill {
+
+struct FormFieldData {
+  FormFieldData();
+  FormFieldData(const FormFieldData&);
+  FormFieldData& operator=(const FormFieldData&);
+  std::u16string value;
+  int something_else;
+};
+
+// References in compiler-generated code must not be rewritten.
+FormFieldData::FormFieldData() = default;
+FormFieldData::FormFieldData(const FormFieldData&) = default;
+FormFieldData& FormFieldData::operator=(const FormFieldData&) = default;
+
+class AutofillField : public FormFieldData {};
+
+}  // namespace autofill
+
+namespace testing {
+
+template <typename Class, typename FieldType>
+void Field(FieldType Class::*field, int matcher) {}
+
+template <typename Class, typename FieldType>
+void Property(FieldType Class::*field(), int matcher) {}
+
+}  // namespace testing
+
+namespace base {
+
+std::u16string ASCIIToUTF16(std::string x) {
+  return u"arbitrary string";
+}
+
+}  // namespace base
+
+namespace not_autofill {
+
+struct FormFieldData {
+  std::u16string value;
+  int something_else;
+};
+
+}  // namespace not_autofill
+
+// Tests that a read reference `f.value` is replaced with `r.value()`.
+std::u16string FunRead() {
+  autofill::FormFieldData f;
+  return f.value;
+}
+
+// Tests that `f.value` at the left of an assignment is replaced with
+// `f.set_value(rhs)`.
+void FunWrite() {
+  ::autofill::FormFieldData f;
+  f.value = u"foo";
+}
+
+// Tests that a read reference `f->value` is replaced with `r->value()`.
+std::u16string FunReadPointer() {
+  autofill::FormFieldData f;
+  autofill::FormFieldData* g = &f;
+  return g->value;
+}
+
+// Tests that `f->value` at the left of an assignment is replaced with
+// `f->set_value(rhs)`.
+void FunWritePointer() {
+  autofill::FormFieldData f;
+  autofill::FormFieldData* g = &f;
+  g->value = u"foo";
+}
+
+// Tests that a read reference `f.value()` in a member function is replaced with
+// `f.value()`.
+class Class {
+  static const std::u16string& value(const autofill::FormFieldData& f) {
+    return f.value;
+  }
+};
+
+// Tests that a references at the left and right hand side of an assignment aer
+// replaced appropriately.
+void FunReadAndWrite() {
+  ::autofill::FormFieldData f;
+  ::autofill::FormFieldData g;
+  f.value = g.value;
+}
+
+// Like FunReadAndWrite() but additionally tests that a constness doesn't affect
+// the rewriting.
+void FunReadConstAndWrite() {
+  ::autofill::FormFieldData f;
+  const ::autofill::FormFieldData g;
+  f.value = g.value;
+}
+
+// Like FunReadConstAndWrite() but additionally tests that redundant
+// parentheses doesn't affect the rewriting.
+void FunReadConstAndWriteWithParentheses() {
+  ::autofill::FormFieldData f;
+  const ::autofill::FormFieldData g;
+  ((f).value) = ((g).value);
+}
+
+// Like FunReadConstAndWrite() but additionally tests that additional whitespace
+// doesn't affect the rewriting of a read reference and that the comments
+// survive the rewriting.
+void FunReadWithWhitespace() {
+  autofill::FormFieldData f;
+  std::u16string s = f       // comment 1
+                         .   // comment 2
+                     value;  // comment 3
+}
+
+// Like FunReadConstAndWrite() but additionally tests that additional
+// whitespace doesn't affect the rewriting of a write reference and that the
+// comments survive the rewriting.
+void FunWriteWithWhitespace() {
+  autofill::FormFieldData f;
+  f          // comment 1
+      .      // comment 2
+      value  // comment 3
+      =      // comment 4
+      u"foo";
+}
+
+// Tests whether explicit `operator=()` is rewritten. This is desirable but
+// currently not implemented.
+void FunWriteExplicitOperator() {
+  ::autofill::FormFieldData f;
+  f.value.operator=(u"foo");  // Currently not properly rewritten.
+}
+
+namespace autofill {
+
+// Tests that references are rewritten even if the object's type isn't fully
+// qualified.
+std::u16string FunReadImplicitNamespace() {
+  FormFieldData f;
+  return f.value;
+}
+
+// Tests that a reference at the left of an assignment is replaced with
+// set_value(), and that a more complex right-hand side experission is
+// preserved.
+void FunWriteCallExpr() {
+  std::string value;
+  FormFieldData field;
+  field.value = base::ASCIIToUTF16(value);
+}
+
+}  // namespace autofill
+
+// Like FunWriteCallExpr() but with a more complex expression at the right-hand
+// side. It involves UTF-16 string literals to test that the rewriter uses the
+// correct clang::LangOptions (and not the default argument).
+void FunWriteComplexCallExpr() {
+  auto g = [](const std::u16string& s) { return s; };
+  autofill::FormFieldData f;
+  f.value = u"bar" + g(std::u16string(u"foo") + u"qux");
+}
+
+// Tests that the replacement does not touch classes from other namespaces.
+std::u16string FunReadNotAutofill() {
+  ::not_autofill::FormFieldData f;
+  return f.value;
+}
+
+// Tests that write references on members of a derived class are rewritten.
+void FunWriteDerived() {
+  autofill::AutofillField f;
+  f.value = u"bar";
+}
+
+// Tests that read references on vector elements are rewritten.
+std::u16string FunReadVector() {
+  std::vector<autofill::FormFieldData> fields;
+  fields.emplace_back();
+  return fields[0].value;
+}
+
+// Tests that write references on vector elements are rewritten.
+void FunWriteVector() {
+  std::vector<autofill::FormFieldData> fields;
+  fields.emplace_back();
+  fields[0].value = u"foo";
+}
+
+// Tests that pointers to members are not rewritten.
+std::u16string FunFieldPointer() {
+  using autofill::FormFieldData;
+  FormFieldData f;
+  std::u16string FormFieldData::*ptr = &FormFieldData::value;
+  return f.*ptr;
+}
+
+// Tests that Field(&Class::member, ...) is replaced with
+// Property(&Class::member, ...).
+void FunMatcher() {
+  ::testing::Field(&autofill::FormFieldData::value, {});
+  ::testing::Field(&autofill::FormFieldData::something_else, {});
+  ::testing::Field(&not_autofill::FormFieldData::value, {});
+  ::testing::Field(&not_autofill::FormFieldData::something_else, {});
+  using ::testing::Field;
+  using ::testing::Property;
+  Field(&autofill::FormFieldData::value, {});
+  Field(&autofill::FormFieldData::something_else, {});
+  Field(&not_autofill::FormFieldData::value, {});
+  Field(&not_autofill::FormFieldData::something_else, {});
+}