[clang][ssaf] Add YAML source-edit format (#204491)

Adds the built-in `SourceEditFormat`, registered under the file
extension `yaml`. The writer drives `llvm::yaml::Output` against the
existing `clang::tooling::TranslationUnitReplacements` `MappingTraits`
from `clang/Tooling/ReplacementsYaml.h`, so the resulting document is
byte-for-byte consumable by `clang-apply-replacements`.

Anchored via `SSAFYAMLSourceEditFormatAnchorSource` so static builds
keep the registration.

Assisted-By: Claude Opus 4.7
This commit is contained in:
Jan Korous authored and GitHub committed 2026-07-08 10:53:15 -07:00
1 parent eb37dfcad1
commit 01846f68e5
5 files changed
+164

No files matched your search

@@ -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
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangScalableStaticAnalysisSourceTransformation
TransformationRegistry.cpp
YAMLSourceEditFormat.cpp
LINK_LIBS
clangAST
@@ -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();
}
@@ -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
@@ -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