formatpp
v0.1.0
Published
A professional code formatter for 26 languages, from JS to Zig to Vue to Python to Ruby
Maintainers
Readme
fmtpp — Format++
A professional-grade, multi-language code formatter written in C++. One static binary, twenty-six languages, zero runtime dependencies.
- Supported languages
- Install
- Formatting files
- Configuration
- CLI reference
- Running the tests
- Building from source
- Project layout
- How it works
- Troubleshooting
- Current status and limitations
- License
Full guides with the same content live on the docs site: docs/
(run npm run docs:dev inside docs/).
Supported languages
The formatter is picked from the file extension. Unknown extensions fall back to JavaScript.
| Language | Extensions | Formatter | Reference style |
|------------|-----------------------------|--------------------|--------------------|
| JavaScript | .js .mjs .cjs .jsx | JSFormatter | Prettier |
| TypeScript | .ts .tsx .mts .cts | TSFormatter | Prettier |
| Java | .java | JavaFormatter | google-java-format |
| C | .c .h | CFormatter | clang-format |
| C++ | .cpp .cc .cxx .hpp .hh .hxx .ino .cu .cuh | CppFormatter | clang-format |
| C# | .cs | CSharpFormatter | dotnet format |
| Go | .go | GoFormatter | gofmt |
| Rust | .rs | RustFormatter | rustfmt |
| Kotlin | .kt .kts | KotlinFormatter | ktfmt |
| Swift | .swift | SwiftFormatter | swift-format |
| Dart | .dart | DartFormatter | dart format |
| PHP | .php .phtml | PhpFormatter | php-cs-fixer |
| Scala | .scala .sc | ScalaFormatter | scalafmt |
| Objective-C| .m .mm | ObjectiveCFormatter | clang-format |
| Zig | .zig .zon | ZigFormatter | zig fmt |
| D | .d .di | DFormatter | dfmt |
| Groovy | .groovy .gvy | GroovyFormatter | Spotless |
| Solidity | .sol | SolidityFormatter| prettier-plugin-solidity |
| V | .v .vsh .vv | VFormatter | v fmt |
| GLSL | .glsl .vert .frag .geom .comp .tesc .tese .vs .fs | GlslFormatter | clang-format |
| HTML/XML | .html .htm .xhtml .svg .xml | HtmlFormatter | Prettier |
| CSS | .css .scss .less | CssFormatter | Prettier |
| JSON | .json | JSFormatter | Prettier |
| Vue SFC | .vue | VueFormatter | Prettier/Volar |
| Python | .py .pyw .pyi | PythonFormatter | black |
| Ruby | .rb | RubyFormatter | rubocop |
Install
Option A — npm (prebuilt binary, no compilation)
npm install fmtpp
npx fmtpp --versionPrebuilt static binaries ship for linux-x64, linux-arm64,
macos-x64, macos-arm64 and win-x64 under npm/. The fmtpp
launcher (index.js) picks the right one for your OS and CPU.
Two escape hatches:
# Use an exact binary (e.g. your own build)
FMTPP_BINARY=/path/to/fmtpp npx fmtpp format main.js
# No matching prebuilt binary? The launcher compiles from source
# automatically using zig cc — no action needed.Option B — build from source
You need CMake ≥ 3.16, Ninja (or Make) and a C++11-capable compiler
(GCC 5+, Clang 6+, MSVC 2019+). CLI11 and toml++ are vendored under
lib/, nothing else to install. Details in
Building from source.
cmake -S . -B build
cmake --build build --config Release
./build/fmtpp --version # Linux / macOS
.\build\fmtpp.exe --version # WindowsFormatting files
Format one file (prints to stdout)
fmtpp format main.js
fmtpp main.js # shorthand, same thingThe formatted code goes to stdout, the original file is never touched. To rewrite a file in place, redirect:
# POSIX
fmtpp format main.js > main.js.tmp && mv main.js.tmp main.js
# PowerShell
fmtpp format main.js | Set-Content main.js.tmp; Move-Item main.js.tmp main.js -ForceFormat several files at once
fmtpp format a.js b.go c.rsFormatting 3 files with 16 threads...
Formatted: a.js
Formatted: b.go
Formatted: c.rs
Done: 3 succeeded, 0 failedThis path is parallel (one file per thread) and prints a status
report, not the formatted code. Single-file mode prints code;
multi-file mode prints progress. Files that cannot be read or parsed
are listed as Failed: and the command exits with code 1.
Which config is used?
format looks for .fmtpp.toml starting in the formatted file's own
directory, then each parent directory (same rule as Prettier). The
first config found wins; if none exists (or it is broken), built-in
defaults apply. See Configuration.
Benchmark a file
fmtpp bench app.js # 10 iterations + warmup
fmtpp bench app.js --runs 100 # custom iteration count
fmtpp bench app.js --runs 20 --compare oxfmt # also time Oxfmt/PrettierPrints total/average/min/max/median/P95 time, throughput and a verdict against the 12 ms budget.
Configuration
fmtpp reads .fmtpp.toml (TOML). Three ways to get one:
fmtpp learn example.js # detect style, write ./.fmtpp.toml
fmtpp learn example.js --output custom.toml
fmtpp config # interactive TUI editor (needs a terminal)learn runs StyleDetector over the file (indent, brace placement,
semicolons, trailing commas, print width, spacing, final newline) and
writes the result as TOML:
printWidth = 80
useTabs = false
tabWidth = 2
semi = true
singleQuote = false
trailingComma = "es5" # "none", "es5" or "all"
bracketSpacing = true
arrowParens = "always"
endOfLine = "lf" # "lf", "crlf", "cr" or "auto"
braceStyle = "same-line" # "same-line" (K&R) or "next-line" (Allman)
spaceBeforeBrace = true
spaceAfterKeyword = true # space after if/for/while
spaceAroundOperators = trueDefaults mirror Prettier. Unknown keys are ignored; a missing or unparseable file silently falls back to defaults.
CLI reference
fmtpp [OPTIONS] [FILE]
fmtpp <command> [options]
fmtpp format <file...> format files (code to stdout for 1 file, report for N)
fmtpp <file> shorthand for formatting a single file
fmtpp bench <file> [--runs N] [--compare <oxfmt|prettier>]
fmtpp learn <file> [--output .fmtpp.toml]
fmtpp config interactive TUI configurator
fmtpp --version, -v
fmtpp --help, -hHidden flags (just for fun): --easter-egg, --cowbell, --kittens,
--unicorn. Running fmtpp with no arguments prints usage.
Exit codes: 0 on success, 1 when a file cannot be read/parsed or any
file in a multi-file run fails.
Running the tests
C++ suites (the real coverage)
33 suites under ctest: one formatter suite per language plus infra
(pool, buffers, parser, thread pool, config, benchmark).
# From the repo root:
cmake -S . -B build
cmake --build build --config Release
ctest --test-dir build --output-on-failureUseful variants (run from build/):
ctest -R javaformatter --output-on-failure # one suite only
ctest -R "formatter" # all 23 formatter suites
./test_goformatter.exe # run a suite binary directlyEach language suite is tests/test_<lang>formatter.cpp, with a matching
real-world sample in tests/fixtures/sample.<ext>:
| Suite | Fixture | What it checks |
|---|---|---|
| test_jsformatter | sample.js | let/const, functions, imports, classes, arrows, arrays, objects, exports |
| test_tsformatter | sample.ts | return types, interfaces, enums, type aliases, extends |
| test_javaformatter | sample.java | public methods, extends/implements, imports, switch+break, try/catch |
| test_cformatter | sample.c | decls, pointers, #include/#define, if/for/while, comments |
| test_cppformatter | sample.cpp | Ret name(), : public Base, namespace, forward decls |
| test_csharpformatter | sample.cs | public class : Base, IFace, methods, namespace, interfaces |
| test_goformatter | sample.go | func, :=, no semicolons, structs, iota, panic, nil |
| test_rustformatter | sample.rs | fn ->, let [mut], use, match, panic!, vec!, closures, traits |
| test_kotlinformatter | sample.kt | fun, classes, when, package/import without semicolons |
| test_swiftformatter | sample.swift | func ->, structs, for-in/switch without parens |
| test_dartformatter | sample.dart | C-style functions, classes, quoted imports |
| test_phpformatter | sample.php | $ sigils, use/namespace, <?php |
| test_scalaformatter | sample.scala | def, classes/objects, match, package/import |
| test_objectivecformatter | sample.m | @interface/@protocol/@end, raw methods |
| test_zigformatter | sample.zig | fn, struct containers, @import, captures |
| test_dformatter | sample.d | : bases, dotted imports, module |
| test_groovyformatter | sample.groovy | def, package/import without semicolons |
| test_solidityformatter | sample.sol | contract is, modifiers/returns, pragmas |
| test_vformatter | sample.v | mut, real enums, module, unquoted imports |
| test_glslformatter | sample.glsl | struct;, uniforms, #version |
| test_htmlformatter | sample.html | inline/block elements, voids, comments |
| test_cssformatter | sample.css | rules, prop: value;, at-rules |
| test_vueformatter | sample.vue | template/script/style delegation |
Suites share one pattern: build ASTs with ASTNodeFactory on a local
MemoryPool, format into an OutputBuffer, assert on the output,
print N passed, M failed, return nonzero on failure. To add coverage,
copy the closest suite and register its name in FMTPP_TESTS in
CMakeLists.txt — no other wiring needed.
npm packaging tests
npm test # runs tests/test_npm_packaging.jsChecks package.json fields, platform detection, binary-path
resolution, binary slots for all five targets, the FMTPP_BINARY
override and the compile-from-source fallback.
Building from source
cmake -S . -B build
cmake --build build --config ReleaseTested compilers: GCC 5+, Clang 6+, MSVC 2019+. The project targets
C++11; the three toml++-including files compile as C++17 via per-file
flags (-std=c++17 / /std:c++17).
Three portability details, all automatic:
- Linking. Fully-static
-staticapplies on Linux only (the macOS linker forbids it; it breakslld-linkon Windows). Windows/MinGW gets-static-libstdc++ -static-libgcc;ws2_32links on Windows. - SIMD. toml++ uses SSE2 intrinsics on x86-64. A configure-time
compile+link probe (
FMTPP_HAVE_WORKING_SSE2) detects toolchains that cannot link them and definesTOML_ENABLE_SIMD=0(scalar fallback) for those builds only. Healthy toolchains keep SIMD with no flags. - UB sanitizer.
cmake -S . -B build-sanitize -DFMTPP_SANITIZE_UB=ONbuilds with-fsanitize=undefinedfor QA runs.
Cross-compiling the npm binaries
bash scripts/build-all.sh # needs zig cc; fills npm/<platform-arch>/Single target:
cmake -S . -B build-cross -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DTARGET=linux-arm64
cmake --build build-cross --config ReleaseTargets: linux-x64, linux-arm64, macos-x64, macos-arm64,
windows-x64 (see toolchain.cmake).
Docs site
cd docs
npm install
npm run docs:dev # local preview
npm run docs:build # static buildProject layout
include/fmtpp/ public headers (formatters, AST, infra)
src/ implementation (C++11, Java-style OOP)
tests/ CTest suites + tests/fixtures/ language samples
docs/ VitePress documentation site
lib/ vendored CLI11 + toml++ mirrors
npm/ prebuilt binary slots per platform-arch
scripts/ cross-compilation (zig cc) helpersHow it works
SourceBuffer → Parser → AST → FormatterVisitor → OutputBuffer → stdout
(one per language) ↑
ThreadPool fans multi-file runs out; each job owns its buffer and parser.Parser— a real language-aware parser: comment/string/regex aware scanning, brace matching, per-language profiles. Declarations, control flow, imports and comments become structured AST nodes; leaf expressions stay verbatim and anything unrecognized passes through asRawTextNode. Formatting restructures only what it understands, so output is never invented and code cannot be corrupted.MemoryPool— one 10 MB bump-allocated chunk per thread; every AST node comes fromASTNodeFactoryvia placement new. No per-nodenew/delete, freed in one shot.CstyleFormatterBase— the shared master for all C-like languages (indent, braces, semicolons, spacing, wrapping, comments). Subclasses override only what differs (visitFunctionDecl,visitClassDecl, …). Go and Rust extendFormatterVisitordirectly.- Resources — every acquisition (
fopen,malloc,mmap) lives in a RAII wrapper (FileHandle,MallocBuffer,MmapRegion) or agoto-cleanup function with a single exit label. - Config precedence:
.fmtpp.tomlfound from the file upward > built-in defaults.StyleDetector(learn) andTuiConfigurator(config) both read and write that file.
Troubleshooting
- Fresh configure fails oddly / stale errors after editing
CMakeLists.txt. Delete the build dir and reconfigure:cmake -S . -B buildre-runs automatically under Ninja, but a cleanRemove-Item -Recurse build; cmake -S . -B buildnever hurts. ctestsays "No tests were found". You ran it from the repo root without--test-dir build, or from insidebuild/withcmake --build build(that path only exists at the repo root). From the root:ctest --test-dir build. From insidebuild/: plainctest.test_threadpoolsegfaults once, passes on re-run. Known flake: workers share the globalMemoryPoolwhileParser::parsecallsg_pool->reset()— a data race. Re-run; the proper fix is per-thread pools (pendingThreadPool/Parserrework).- Link errors about
_mm_*/ SSE intrinsics. Your toolchain cannot link them (seen with mismatched Clang/MSVC installs). The build detects this at configure time (fmtpp: toolchain cannot link SSE2 intrinsics…) and falls back to the scalar TOML path automatically — no action needed. - Formatted output looks odd for an exotic construct. By design the parser passes through what it cannot model (method signatures in class bodies, complex attributes, switch arms) verbatim instead of restructuring it. Structure (braces, indentation, semicolons) still applies around it. If output ever drops or rewrites code, that is a bug — file an issue with the input.
.fmtpp.tomlseems ignored. It is resolved from the formatted file's directory upward, not from your shell's cwd. Put it next to (or above) the file you format; checklearnoutput parses with no typos in key names.
Current status and limitations
Implemented and tested: a real parser for all 26 languages, all
formatters, config load/merge, learn, TUI config, bench
(+--compare), thread-pool multi-file runs, npm packaging with five
prebuilt targets.
Known fidelity limits (valid output, lighter formatting): JS/TS class
method signatures, complex decorators/attributes and switch-arm bodies
pass through verbatim; leaf expressions (conditions, arguments,
initializers) print as written. The full phase plan lives in the
engineering blueprint (fmtpp.txt, gitignored) and
docs/guide/architecture.md.
License
MIT — see package.json (fmtpp on npm).
