[clang] [clang][ssaf] Add YAML source-edit format (PR #204491)
Jan Korous via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 7 16:52:36 PDT 2026
https://github.com/jkorous-apple updated https://github.com/llvm/llvm-project/pull/204491
>From 1950f4bb676941c2e732681ad3bdd93a6064aff6 Mon Sep 17 00:00:00 2001
From: Jan Korous <jkorous at apple.com>
Date: Fri, 12 Jun 2026 19:15:00 -0700
Subject: [PATCH] [clang][ssaf] Add YAML source-edit format
Adds `writeYAMLSourceEdits`, which serializes
`clang::tooling::TranslationUnitReplacements` via the existing
`clang/Tooling/ReplacementsYaml.h` `MappingTraits`, so the resulting
document is byte-for-byte consumable by `clang-apply-replacements`.
Assisted-By: Claude Opus 4.7
---
.../YAMLSourceEditFormat.h | 32 ++++++
.../SourceTransformation/CMakeLists.txt | 1 +
.../YAMLSourceEditFormat.cpp | 33 +++++++
.../ScalableStaticAnalysis/CMakeLists.txt | 1 +
.../SourceTransformation/YAMLFormatTest.cpp | 97 +++++++++++++++++++
5 files changed, 164 insertions(+)
create mode 100644 clang/include/clang/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.h
create mode 100644 clang/lib/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.cpp
create mode 100644 clang/unittests/ScalableStaticAnalysis/SourceTransformation/YAMLFormatTest.cpp
diff --git a/clang/include/clang/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.h b/clang/include/clang/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.h
new file mode 100644
index 0000000000000..0676d6b361158
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.h
@@ -0,0 +1,32 @@
+//===- YAMLSourceEditFormat.h -----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Built-in YAML source-edit writer. The on-disk layout is the existing
+// `clang::tooling::TranslationUnitReplacements` YAML schema, byte-for-byte
+// consumable by `clang-apply-replacements`.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSIS_SOURCETRANSFORMATION_YAMLSOURCEEDITFORMAT_H
+#define LLVM_CLANG_SCALABLESTATICANALYSIS_SOURCETRANSFORMATION_YAMLSOURCEEDITFORMAT_H
+
+#include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+namespace clang::ssaf {
+
+/// Writes \p Doc to \p Path as a YAML document compatible with
+/// `clang-apply-replacements`.
+llvm::Error
+writeYAMLSourceEdits(const clang::tooling::TranslationUnitReplacements &Doc,
+ llvm::StringRef Path);
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSIS_SOURCETRANSFORMATION_YAMLSOURCEEDITFORMAT_H
diff --git a/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt
index 3761ee7463f86..f46b6df1d0bdb 100644
--- a/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangScalableStaticAnalysisSourceTransformation
TransformationRegistry.cpp
+ YAMLSourceEditFormat.cpp
LINK_LIBS
clangAST
diff --git a/clang/lib/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.cpp b/clang/lib/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.cpp
new file mode 100644
index 0000000000000..3bdbdb148e5cd
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.cpp
@@ -0,0 +1,33 @@
+//===- YAMLSourceEditFormat.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.h"
+#include "clang/Tooling/ReplacementsYaml.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/YAMLTraits.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace ssaf;
+
+llvm::Error ssaf::writeYAMLSourceEdits(
+ const clang::tooling::TranslationUnitReplacements &Doc,
+ llvm::StringRef Path) {
+ std::error_code EC;
+ llvm::raw_fd_ostream OS(Path, EC, llvm::sys::fs::OF_None);
+ if (EC)
+ return llvm::createStringError(EC, "failed to open '" + Path + "'");
+
+ // llvm::yaml::Output's stream operator binds to a non-const reference.
+ clang::tooling::TranslationUnitReplacements Mutable = Doc;
+ llvm::yaml::Output YAMLOut(OS);
+ YAMLOut << Mutable;
+
+ return llvm::Error::success();
+}
diff --git a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
index 387f03dc76fc6..12ee90f3cbebf 100644
--- a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
@@ -26,6 +26,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
Serialization/JSONFormatTest/TUSummaryTest.cpp
SourceTransformation/EmitterTest.cpp
SourceTransformation/RegistryTest.cpp
+ SourceTransformation/YAMLFormatTest.cpp
SummaryData/SummaryDataTest.cpp
SummaryNameTest.cpp
TestFixture.cpp
diff --git a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/YAMLFormatTest.cpp b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/YAMLFormatTest.cpp
new file mode 100644
index 0000000000000..d654a0436ae8b
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/YAMLFormatTest.cpp
@@ -0,0 +1,97 @@
+//===- YAMLFormatTest.cpp -------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/ReplacementsYaml.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/YAMLTraits.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+
+// Materializes a unique temporary file path under the system temp dir and
+// removes it on destruction.
+struct TempPath {
+ SmallString<128> Path;
+
+ TempPath(StringRef Suffix) {
+ sys::fs::createUniquePath("ssaf-yaml-%%%%%%." + Suffix, Path,
+ /*MakeAbsolute=*/true);
+ }
+ ~TempPath() { sys::fs::remove(Path); }
+};
+
+TEST(WriteYAMLSourceEditsTest, RoundTripsTwoReplacements) {
+ clang::tooling::TranslationUnitReplacements Doc;
+ Doc.MainSourceFile = "main.cpp";
+ Doc.Replacements.emplace_back("a.cpp", 0, 0, "/*1*/");
+ Doc.Replacements.emplace_back("b.cpp", 10, 3, "/*2*/");
+
+ TempPath TP("yaml");
+ ASSERT_THAT_ERROR(writeYAMLSourceEdits(Doc, TP.Path), Succeeded());
+
+ auto BufferOrErr = MemoryBuffer::getFile(TP.Path);
+ ASSERT_TRUE(static_cast<bool>(BufferOrErr))
+ << "Failed to read back '" << TP.Path << "'";
+
+ clang::tooling::TranslationUnitReplacements Parsed;
+ yaml::Input YIn((*BufferOrErr)->getBuffer());
+ YIn >> Parsed;
+ ASSERT_FALSE(YIn.error()) << YIn.error().message();
+
+ EXPECT_EQ(Parsed.MainSourceFile, "main.cpp");
+ ASSERT_EQ(Parsed.Replacements.size(), 2u);
+ EXPECT_EQ(Parsed.Replacements[0].getFilePath(), "a.cpp");
+ EXPECT_EQ(Parsed.Replacements[0].getOffset(), 0u);
+ EXPECT_EQ(Parsed.Replacements[0].getLength(), 0u);
+ EXPECT_EQ(Parsed.Replacements[0].getReplacementText(), "/*1*/");
+ EXPECT_EQ(Parsed.Replacements[1].getFilePath(), "b.cpp");
+ EXPECT_EQ(Parsed.Replacements[1].getOffset(), 10u);
+ EXPECT_EQ(Parsed.Replacements[1].getLength(), 3u);
+ EXPECT_EQ(Parsed.Replacements[1].getReplacementText(), "/*2*/");
+}
+
+TEST(WriteYAMLSourceEditsTest, EmptyReplacementsWritesValidDocument) {
+ clang::tooling::TranslationUnitReplacements Doc;
+ Doc.MainSourceFile = "main.cpp";
+
+ TempPath TP("yaml");
+ ASSERT_THAT_ERROR(writeYAMLSourceEdits(Doc, TP.Path), Succeeded());
+
+ auto BufferOrErr = MemoryBuffer::getFile(TP.Path);
+ ASSERT_TRUE(static_cast<bool>(BufferOrErr));
+
+ clang::tooling::TranslationUnitReplacements Parsed;
+ yaml::Input YIn((*BufferOrErr)->getBuffer());
+ YIn >> Parsed;
+ ASSERT_FALSE(YIn.error()) << YIn.error().message();
+ EXPECT_EQ(Parsed.MainSourceFile, "main.cpp");
+ EXPECT_TRUE(Parsed.Replacements.empty());
+}
+
+TEST(WriteYAMLSourceEditsTest, OpenErrorReturnsError) {
+ clang::tooling::TranslationUnitReplacements Doc;
+ Doc.MainSourceFile = "main.cpp";
+
+ // Path under a directory that does not exist.
+ SmallString<128> BadPath;
+ sys::fs::createUniquePath("ssaf-missing-%%%%%%/edits.yaml", BadPath,
+ /*MakeAbsolute=*/true);
+
+ ASSERT_THAT_ERROR(writeYAMLSourceEdits(Doc, BadPath), Failed());
+}
+
+} // namespace
More information about the cfe-commits
mailing list