[clang] [llvm] Convert clang-format to Opt and add multicall support (PR #201730)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 4 18:48:09 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-format
Author: David Zbarsky (dzbarsky)
<details>
<summary>Changes</summary>
---
Patch is 30.37 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/201730.diff
5 Files Affected:
- (modified) clang/tools/clang-format/CMakeLists.txt (+12-1)
- (modified) clang/tools/clang-format/ClangFormat.cpp (+226-206)
- (added) clang/tools/clang-format/Opts.td (+154)
- (modified) utils/bazel/llvm-project-overlay/clang/BUILD.bazel (+19-3)
- (modified) utils/bazel/llvm-project-overlay/llvm/driver.bzl (+1)
``````````diff
diff --git a/clang/tools/clang-format/CMakeLists.txt b/clang/tools/clang-format/CMakeLists.txt
index 1c61a3c8fb803..5a1e53d3aa99b 100644
--- a/clang/tools/clang-format/CMakeLists.txt
+++ b/clang/tools/clang-format/CMakeLists.txt
@@ -1,7 +1,18 @@
-set(LLVM_LINK_COMPONENTS support)
+set(LLVM_LINK_COMPONENTS
+ Option
+ Support
+ )
+
+set(LLVM_TARGET_DEFINITIONS Opts.td)
+tablegen(LLVM Opts.inc -gen-opt-parser-defs)
+add_public_tablegen_target(ClangFormatOptsTableGen)
add_clang_tool(clang-format
ClangFormat.cpp
+
+ DEPENDS
+ ClangFormatOptsTableGen
+ GENERATE_DRIVER
)
set(CLANG_FORMAT_LIB_DEPS
diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp
index 37d0eb83414f4..f4bf01fbc4e39 100644
--- a/clang/tools/clang-format/ClangFormat.cpp
+++ b/clang/tools/clang-format/ClangFormat.cpp
@@ -21,198 +21,80 @@
#include "clang/Format/Format.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "llvm/ADT/StringSwitch.h"
-#include "llvm/Support/CommandLine.h"
+#include "llvm/Option/Arg.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/Option.h"
+#include "llvm/Support/Allocator.h"
#include "llvm/Support/FileSystem.h"
-#include "llvm/Support/InitLLVM.h"
-#include "llvm/Support/Process.h"
+#include "llvm/Support/LLVMDriver.h"
+#include "llvm/Support/StringSaver.h"
#include <fstream>
+#include <optional>
+#include <string>
+#include <vector>
using namespace llvm;
using clang::tooling::Replacements;
-static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
-
-// Mark all our options with this category, everything else (except for -version
-// and -help) will be hidden.
-static cl::OptionCategory ClangFormatCategory("Clang-format options");
-
-static cl::list<unsigned>
- Offsets("offset",
- cl::desc("Format a range starting at this byte offset.\n"
- "Multiple ranges can be formatted by specifying\n"
- "several -offset and -length pairs.\n"
- "Can only be used with one input file."),
- cl::cat(ClangFormatCategory));
-static cl::list<unsigned>
- Lengths("length",
- cl::desc("Format a range of this length (in bytes).\n"
- "Multiple ranges can be formatted by specifying\n"
- "several -offset and -length pairs.\n"
- "When only a single -offset is specified without\n"
- "-length, clang-format will format up to the end\n"
- "of the file.\n"
- "Can only be used with one input file."),
- cl::cat(ClangFormatCategory));
-static cl::list<std::string>
- LineRanges("lines",
- cl::desc("<start line>:<end line> - format a range of\n"
- "lines (both 1-based).\n"
- "Multiple ranges can be formatted by specifying\n"
- "several -lines arguments.\n"
- "Can't be used with -offset and -length.\n"
- "Can only be used with one input file."),
- cl::cat(ClangFormatCategory));
-static cl::opt<std::string>
- Style("style", cl::desc(clang::format::StyleOptionHelpDescription),
- cl::init(clang::format::DefaultFormatStyle),
- cl::cat(ClangFormatCategory));
-static cl::opt<std::string>
- FallbackStyle("fallback-style",
- cl::desc("The name of the predefined style used as a\n"
- "fallback in case clang-format is invoked with\n"
- "-style=file, but can not find the .clang-format\n"
- "file to use. Defaults to 'LLVM'.\n"
- "Use -fallback-style=none to skip formatting."),
- cl::init(clang::format::DefaultFallbackStyle),
- cl::cat(ClangFormatCategory));
-
-static cl::opt<std::string> AssumeFileName(
- "assume-filename",
- cl::desc("Set filename used to determine the language and to find\n"
- ".clang-format file.\n"
- "Only used when reading from stdin.\n"
- "If this is not passed, the .clang-format file is searched\n"
- "relative to the current working directory when reading stdin.\n"
- "Unrecognized filenames are treated as C++.\n"
- "supported:\n"
- " CSharp: .cs\n"
- " Java: .java\n"
- " JavaScript: .js .mjs .cjs .ts\n"
- " JSON: .json .ipynb\n"
- " Objective-C: .m .mm\n"
- " Proto: .proto .protodevel\n"
- " TableGen: .td\n"
- " TextProto: .txtpb .textpb .pb.txt .textproto .asciipb\n"
- " Verilog: .sv .svh .v .vh"),
- cl::init("<stdin>"), cl::cat(ClangFormatCategory));
-
-static cl::opt<bool> Inplace("i",
- cl::desc("Inplace edit <file>s, if specified."),
- cl::cat(ClangFormatCategory));
-
-static cl::opt<bool> OutputXML("output-replacements-xml",
- cl::desc("Output replacements as XML."),
- cl::cat(ClangFormatCategory));
-static cl::opt<bool>
- DumpConfig("dump-config",
- cl::desc("Dump configuration options to stdout and exit.\n"
- "Can be used with -style option."),
- cl::cat(ClangFormatCategory));
-static cl::opt<unsigned>
- Cursor("cursor",
- cl::desc("The position of the cursor when invoking\n"
- "clang-format from an editor integration"),
- cl::init(0), cl::cat(ClangFormatCategory));
-
-static cl::opt<bool>
- SortIncludes("sort-includes",
- cl::desc("If set, overrides the include sorting behavior\n"
- "determined by the SortIncludes style flag"),
- cl::cat(ClangFormatCategory));
-
-static cl::opt<std::string> QualifierAlignment(
- "qualifier-alignment",
- cl::desc("If set, overrides the qualifier alignment style\n"
- "determined by the QualifierAlignment style flag"),
- cl::init(""), cl::cat(ClangFormatCategory));
-
-static cl::opt<std::string> Files(
- "files",
- cl::desc("A file containing a list of files to process, one per line."),
- cl::value_desc("filename"), cl::init(""), cl::cat(ClangFormatCategory));
-
-static cl::opt<bool>
- Verbose("verbose", cl::desc("If set, shows the list of processed files"),
- cl::cat(ClangFormatCategory));
-
-// Use --dry-run to match other LLVM tools when you mean do it but don't
-// actually do it
-static cl::opt<bool>
- DryRun("dry-run",
- cl::desc("If set, do not actually make the formatting changes"),
- cl::cat(ClangFormatCategory));
-
-// Use -n as a common command as an alias for --dry-run. (git and make use -n)
-static cl::alias DryRunShort("n", cl::desc("Alias for --dry-run"),
- cl::cat(ClangFormatCategory), cl::aliasopt(DryRun),
- cl::NotHidden);
-
-// Emulate being able to turn on/off the warning.
-static cl::opt<bool>
- WarnFormat("Wclang-format-violations",
- cl::desc("Warnings about individual formatting changes needed. "
- "Used only with --dry-run or -n"),
- cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
-
-static cl::opt<bool>
- NoWarnFormat("Wno-clang-format-violations",
- cl::desc("Do not warn about individual formatting changes "
- "needed. Used only with --dry-run or -n"),
- cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
-
-static cl::opt<unsigned> ErrorLimit(
- "ferror-limit",
- cl::desc("Set the maximum number of clang-format errors to emit\n"
- "before stopping (0 = no limit).\n"
- "Used only with --dry-run or -n"),
- cl::init(0), cl::cat(ClangFormatCategory));
-
-static cl::opt<bool>
- WarningsAsErrors("Werror",
- cl::desc("If set, changes formatting warnings to errors"),
- cl::cat(ClangFormatCategory));
-
namespace {
-enum class WNoError { Unknown };
-}
+enum ID {
+ OPT_INVALID = 0,
+#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
+#include "Opts.inc"
+#undef OPTION
+};
+
+#define OPTTABLE_STR_TABLE_CODE
+#include "Opts.inc"
+#undef OPTTABLE_STR_TABLE_CODE
+
+#define OPTTABLE_PREFIXES_TABLE_CODE
+#include "Opts.inc"
+#undef OPTTABLE_PREFIXES_TABLE_CODE
+
+using namespace llvm::opt;
+static constexpr opt::OptTable::Info InfoTable[] = {
+#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
+#include "Opts.inc"
+#undef OPTION
+};
-static cl::bits<WNoError> WNoErrorList(
- "Wno-error",
- cl::desc("If set, don't error out on the specified warning type."),
- cl::values(
- clEnumValN(WNoError::Unknown, "unknown",
- "If set, unknown format options are only warned about.\n"
- "This can be used to enable formatting, even if the\n"
- "configuration contains unknown (newer) options.\n"
- "Use with caution, as this might lead to dramatically\n"
- "differing format depending on an option being\n"
- "supported or not.")),
- cl::cat(ClangFormatCategory));
-
-static cl::opt<bool>
- ShowColors("fcolor-diagnostics",
- cl::desc("If set, and on a color-capable terminal controls "
- "whether or not to print diagnostics in color"),
- cl::init(true), cl::cat(ClangFormatCategory), cl::Hidden);
-
-static cl::opt<bool>
- NoShowColors("fno-color-diagnostics",
- cl::desc("If set, and on a color-capable terminal controls "
- "whether or not to print diagnostics in color"),
- cl::init(false), cl::cat(ClangFormatCategory), cl::Hidden);
-
-static cl::list<std::string> FileNames(cl::Positional,
- cl::desc("[@<file>] [<file> ...]"),
- cl::cat(ClangFormatCategory));
-
-static cl::opt<bool> FailOnIncompleteFormat(
- "fail-on-incomplete-format",
- cl::desc("If set, fail with exit code 1 on incomplete format."),
- cl::init(false), cl::cat(ClangFormatCategory));
-
-static cl::opt<bool> ListIgnored("list-ignored",
- cl::desc("List ignored files."),
- cl::cat(ClangFormatCategory), cl::Hidden);
+class ClangFormatOptTable : public opt::GenericOptTable {
+public:
+ ClangFormatOptTable()
+ : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {
+ setDashDashParsing(true);
+ }
+};
+
+static std::vector<unsigned> Offsets;
+static std::vector<unsigned> Lengths;
+static std::vector<std::string> LineRanges;
+static std::string Style;
+static std::string FallbackStyle;
+static std::string AssumeFileName;
+static bool Inplace;
+static bool OutputXML;
+static bool DumpConfig;
+static unsigned Cursor;
+static bool CursorSpecified;
+static bool SortIncludes;
+static bool SortIncludesSpecified;
+static std::string QualifierAlignment;
+static std::string Files;
+static bool Verbose;
+static bool DryRun;
+static bool WarnFormat;
+static bool NoWarnFormat;
+static unsigned ErrorLimit;
+static bool WarningsAsErrors;
+static bool WNoErrorUnknown;
+static bool ShowColors;
+static bool NoShowColors;
+static std::vector<std::string> FileNames;
+static bool FailOnIncompleteFormat;
+static bool ListIgnored;
+} // namespace
namespace clang {
namespace format {
@@ -370,7 +252,6 @@ static bool emitReplacementWarnings(const Replacements &Replaces,
static void outputXML(const Replacements &Replaces,
const Replacements &FormatChanges,
const FormattingAttemptStatus &Status,
- const cl::opt<unsigned> &Cursor,
unsigned CursorPosition) {
outs() << "<?xml version='1.0'?>\n<replacements "
"xml:space='preserve' incomplete_format='"
@@ -378,7 +259,7 @@ static void outputXML(const Replacements &Replaces,
if (!Status.FormatComplete)
outs() << " line='" << Status.Line << "'";
outs() << ">\n";
- if (Cursor.getNumOccurrences() != 0) {
+ if (CursorSpecified) {
outs() << "<cursor>" << FormatChanges.getShiftedCodePosition(CursorPosition)
<< "</cursor>\n";
}
@@ -444,7 +325,7 @@ static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
Expected<FormatStyle> FormatStyle =
getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer(),
- nullptr, WNoErrorList.isSet(WNoError::Unknown));
+ nullptr, WNoErrorUnknown);
if (!FormatStyle) {
llvm::errs() << toString(FormatStyle.takeError()) << "\n";
return true;
@@ -471,7 +352,7 @@ static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
FormatStyle->QualifierOrder = {Qualifiers.begin(), Qualifiers.end()};
}
- if (SortIncludes.getNumOccurrences() != 0) {
+ if (SortIncludesSpecified) {
FormatStyle->SortIncludes = {};
if (SortIncludes)
FormatStyle->SortIncludes.Enabled = true;
@@ -507,7 +388,7 @@ static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
emitReplacementWarnings(Replaces, AssumedFileName, std::move(Code));
}
if (OutputXML) {
- outputXML(Replaces, FormatChanges, Status, Cursor, CursorPosition);
+ outputXML(Replaces, FormatChanges, Status, CursorPosition);
} else {
auto InMemoryFileSystem =
makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
@@ -526,7 +407,7 @@ static bool format(StringRef FileName, bool ErrorOnIncompleteFormat = false) {
if (Rewrite.overwriteChangedFiles())
return true;
} else {
- if (Cursor.getNumOccurrences() != 0) {
+ if (CursorSpecified) {
outs() << "{ \"Cursor\": "
<< FormatChanges.getShiftedCodePosition(CursorPosition)
<< ", \"IncompleteFormat\": "
@@ -548,6 +429,138 @@ static void PrintVersion(raw_ostream &OS) {
OS << clang::getClangToolFullVersion("clang-format") << '\n';
}
+static bool parseUnsignedArgs(const opt::InputArgList &Args, OptSpecifier ID,
+ std::vector<unsigned> &Values) {
+ for (const opt::Arg *A : Args.filtered(ID)) {
+ unsigned Value;
+ if (!StringRef(A->getValue()).getAsInteger(0, Value)) {
+ Values.push_back(Value);
+ continue;
+ }
+ errs() << "clang-format: invalid value '" << A->getValue()
+ << "' for option '" << A->getSpelling() << "'\n";
+ return false;
+ }
+ return true;
+}
+
+static bool parseBoolArg(const opt::Arg *A, unsigned ValueID, bool &Value) {
+ if (!A->getOption().matches(ValueID)) {
+ Value = true;
+ return true;
+ }
+ std::optional<bool> Parsed = StringSwitch<std::optional<bool>>(A->getValue())
+ .CaseLower("true", true)
+ .Case("1", true)
+ .CaseLower("false", false)
+ .Case("0", false)
+ .Default(std::nullopt);
+ if (Parsed) {
+ Value = *Parsed;
+ return true;
+ }
+ errs() << "clang-format: invalid value '" << A->getValue() << "' for option '"
+ << A->getSpelling() << "'\n";
+ return false;
+}
+
+static bool parseBoolArg(const opt::InputArgList &Args, unsigned FlagID,
+ unsigned ValueID, bool Default, bool &Value,
+ bool *Specified = nullptr) {
+ const opt::Arg *A = Args.getLastArg(FlagID, ValueID);
+ if (Specified)
+ *Specified = A != nullptr;
+ if (!A) {
+ Value = Default;
+ return true;
+ }
+ return parseBoolArg(A, ValueID, Value);
+}
+
+static bool parseArgs(int argc, char **argv, StringSaver &Saver,
+ ClangFormatOptTable &Tbl, opt::InputArgList &Args) {
+ bool HasError = false;
+ Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) {
+ errs() << "clang-format: " << Msg << '\n';
+ HasError = true;
+ });
+ if (HasError)
+ return false;
+
+ Offsets.clear();
+ Lengths.clear();
+ LineRanges = Args.getAllArgValues(OPT_lines_EQ);
+ Style = Args.getLastArgValue(OPT_style_EQ, clang::format::DefaultFormatStyle);
+ FallbackStyle = Args.getLastArgValue(OPT_fallback_style_EQ,
+ clang::format::DefaultFallbackStyle);
+ AssumeFileName = Args.getLastArgValue(OPT_assume_filename_EQ, "<stdin>");
+ Cursor = 0;
+ CursorSpecified = Args.hasArg(OPT_cursor_EQ);
+ QualifierAlignment = Args.getLastArgValue(OPT_qualifier_alignment_EQ);
+ Files = Args.getLastArgValue(OPT_files_EQ);
+ ErrorLimit = 0;
+ WNoErrorUnknown = false;
+ FileNames = Args.getAllArgValues(OPT_INPUT);
+
+ if (!parseBoolArg(Args, OPT_i, OPT_i_EQ, false, Inplace) ||
+ !parseBoolArg(Args, OPT_output_replacements_xml,
+ OPT_output_replacements_xml_EQ, false, OutputXML) ||
+ !parseBoolArg(Args, OPT_dump_config, OPT_dump_config_EQ, false,
+ DumpConfig) ||
+ !parseBoolArg(Args, OPT_sort_includes, OPT_sort_includes_EQ, false,
+ SortIncludes, &SortIncludesSpecified) ||
+ !parseBoolArg(Args, OPT_verbose, OPT_verbose_EQ, false, Verbose) ||
+ !parseBoolArg(Args, OPT_dry_run, OPT_dry_run_EQ, false, DryRun) ||
+ !parseBoolArg(Args, OPT_Wclang_format_violations,
+ OPT_Wclang_format_violations_EQ, true, WarnFormat) ||
+ !parseBoolArg(Args, OPT_Wno_clang_format_violations,
+ OPT_Wno_clang_format_violations_EQ, false, NoWarnFormat) ||
+ !parseBoolArg(Args, OPT_Werror, OPT_Werror_EQ, false, WarningsAsErrors) ||
+ !parseBoolArg(Args, OPT_fcolor_diagnostics, OPT_fcolor_diagnostics_EQ,
+ true, ShowColors) ||
+ !parseBoolArg(Args, OPT_fno_color_diagnostics,
+ OPT_fno_color_diagnostics_EQ, false, NoShowColors) ||
+ !parseBoolArg(Args, OPT_fail_on_incomplete_format,
+ OPT_fail_on_incomplete_format_EQ, false,
+ FailOnIncompleteFormat) ||
+ !parseBoolArg(Args, OPT_list_ignored, OPT_list_ignored_EQ, false,
+ ListIgnored)) {
+ return false;
+ }
+
+ if (!parseUnsignedArgs(Args, OPT_offset_EQ, Offsets) ||
+ !parseUnsignedArgs(Args, OPT_length_EQ, Lengths)) {
+ return false;
+ }
+
+ if (const opt::Arg *A = Args.getLastArg(OPT_cursor_EQ)) {
+ if (StringRef(A->getValue()).getAsInteger(0, Cursor)) {
+ errs() << "clang-format: invalid value '" << A->getValue()
+ << "' for option '" << A->getSpelling() << "'\n";
+ return false;
+ }
+ }
+
+ if (const opt::Arg *A = Args.getLastArg(OPT_ferror_limit_EQ)) {
+ if (StringRef(A->getValue()).getAsInteger(0, ErrorLimit)) {
+ errs() << "clang-format: invalid value '" << A->getValue()
+ << "' for option '" << A->getSpelling() << "'\n";
+ return false;
+ }
+ }
+
+ for (StringRef Warning : Args.getAllArgValues(OPT_Wno_error_EQ)) {
+ if (Warning == "unknown") {
+ WNoErrorUnknown = true;
+ continue;
+ }
+ errs() << "clang-format: invalid value '" << Warning
+ << "' for option '-Wno-error'\n";
+ return false;
+ }
+ return true;
+}
+
// Dump the configuration.
static int dumpConfig() {
std::unique_ptr<llvm::MemoryBuffer> Code;
@@ -664,24 +677,31 @@ static bool isIgnored(StringRef FilePath) {
return false;
}
-int main(int argc, const char **argv) {
- InitLLVM X(argc, argv);
-
- cl::HideUnrelatedOptions(ClangFormatCategory);
+int clang_format_main(int argc, char **argv, const llvm::ToolContext &) {
+ BumpPtrAllocator Alloc;
+ StringSaver Saver(Alloc);
+ ClangFormatOptTable Tbl;
+ opt::InputArgList Args;
+ if (!parseArgs(argc, argv, Saver, Tbl, Args))
+ return 1;
- cl::SetVersionPrinter(PrintVersion);
- cl::ParseCommandLineOptions(
- argc, argv,
- "A tool to format C/C++/Java/JavaScript/JSON/Objective-C/Protobuf/C# "
- "code.\n\n"
- "If no arguments are specified, it formats the code from standard input\n"
- "and writes the result to the standard output.\n"
- "If <file>s are given, it reformats the files. If -i is specifi...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/201730
More information about the cfe-commits
mailing list