Skip to content

FSH Formatter

The FSH Formatter automatically formats your FHIR Shorthand files to maintain consistent code style across your project. It leverages a lossless Concrete Syntax Tree (CST) to ensure perfect preservation of comments, blank lines, and all semantic content.

Format your FSH files with a single command:

Terminal window
# Format all FSH files in a directory
maki format input/fsh/*.fsh
# Check if files are formatted without modifying them
maki format --check input/fsh/*.fsh
# Show formatting differences
maki format --diff input/fsh/*.fsh

The formatter uses Rowan-based CST (Concrete Syntax Tree) to ensure:

  • All comments preserved - Line comments, block comments, and documentation
  • Blank lines maintained - Intentional spacing between definitions
  • Perfect reconstruction - parse(format(source)) == parse(source)
  • No semantic changes - Only formatting is modified

Control formatting behavior with configuration options:

  • Indent style (spaces or tabs)
  • Line width for wrapping
  • Rule alignment
  • Spacing normalization
  • Blank line handling

Built in Rust for speed:

  • Formats files in <50ms each
  • Parallel processing for multiple files
  • Token optimization for 2-5% performance boost
  • Efficient memory usage

Control how code is indented:

[format]
indent_style = "spaces" # Options: "spaces", "tabs"
indent_size = 2 # Number of spaces (when using spaces)

Example:

// Before (mixed indentation)
Profile: MyProfile
Parent: Patient
* name 1..1
* given 1..1
* family 1..1
// After (consistent 2-space indentation)
Profile: MyProfile
Parent: Patient
* name 1..1
* given 1..1
* family 1..1

Set maximum line width before wrapping:

[format]
line_width = 120

Align rules for better readability:

[format]
align_rules = true

Example:

// Before (no alignment)
Profile: MyProfile
Parent: Patient
* name 1..1 MS
* birthDate 1..1 MS
* gender 1..1 MS
// After (aligned)
Profile: MyProfile
Parent: Patient
* name 1..1 MS
* birthDate 1..1 MS
* gender 1..1 MS

Normalize spacing around operators:

[format]
normalize_spacing = true

Example:

// Before (inconsistent spacing)
Profile:MyProfile
Parent:Patient
Id: my-profile
Title:"My Profile"
// After (normalized)
Profile: MyProfile
Parent: Patient
Id: my-profile
Title: "My Profile"

Control blank lines between sections:

[format]
preserve_blank_lines = true
max_blank_lines = 2
blank_lines_between_groups = 1

Add formatting options to your maki.json or maki.toml:

{
"format": {
"indent_style": "spaces",
"indent_size": 2,
"line_width": 120,
"align_rules": true,
"group_rules": false,
"sort_rules": false,
"normalize_spacing": true,
"preserve_blank_lines": true,
"max_blank_lines": 2,
"blank_lines_between_groups": 1
}
}

Or in TOML:

[format]
indent_style = "spaces"
indent_size = 2
line_width = 120
align_rules = true
group_rules = false
sort_rules = false
normalize_spacing = true
preserve_blank_lines = true
max_blank_lines = 2
blank_lines_between_groups = 1

The formatter uses sensible defaults if no configuration is provided:

OptionDefaultDescription
indent_style"spaces"Use spaces for indentation
indent_size22 spaces per indent level
line_width120Maximum line width
align_rulestrueAlign rule elements
group_rulesfalseDon’t group rules by type
sort_rulesfalseDon’t sort rules
normalize_spacingtrueNormalize spacing around : and =
preserve_blank_linestrueKeep intentional blank lines
max_blank_lines2Maximum consecutive blank lines
blank_lines_between_groups1Blank lines between rule groups

The formatter preserves multiline string content exactly as written:

Profile: MyProfile
Parent: Patient
Description: """
This is a multi-line
description that will be
preserved exactly as-is.
"""

All comment styles are preserved:

// Line comment before profile
Profile: MyProfile
Parent: Patient
// Comment before rule
* name 1..1 MS // Inline comment
/*
* Block comment
* spanning multiple lines
*/
* gender 1..1 MS

Mappings with multi-line comments are handled correctly (fixes SUSHI issues #1577, #1576):

Mapping: PatientMapping
Source: MyProfile
Target: "http://example.org"
* -> "Patient" """
This is a multi-line
mapping comment.
"""
Terminal window
# Format a single file
maki format profile.fsh
# Format multiple files
maki format profile.fsh extension.fsh valueset.fsh
# Format all FSH files in directory
maki format input/fsh/*.fsh
# Format recursively
maki format **/*.fsh

Check if files are formatted without modifying them:

Terminal window
maki format --check input/fsh/*.fsh

Exit codes:

  • 0 - All files are formatted
  • 1 - Some files need formatting
  • 2 - Error occurred

Show what would change without modifying files:

Terminal window
maki format --diff input/fsh/*.fsh

Example output:

--- input/fsh/profile.fsh
+++ input/fsh/profile.fsh (formatted)
@@ -1,5 +1,5 @@
-Profile:MyProfile
-Parent:Patient
+Profile: MyProfile
+Parent: Patient
* name 1..1 MS
-* birthDate 1..1 MS
-* gender 1..1 MS
+* birthDate 1..1 MS
+* gender 1..1 MS

Use a specific configuration file:

Terminal window
maki format --config custom-config.json input/fsh/*.fsh

Add formatting checks to your workflow:

name: Format Check
on: [push, pull_request]
jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install maki
run: cargo install maki
- name: Check formatting
run: maki format --check input/fsh/**/*.fsh

Add to .git/hooks/pre-commit:

#!/bin/bash
# Format FSH files before commit
maki format input/fsh/**/*.fsh
git add input/fsh/**/*.fsh
Terminal window
chmod +x .git/hooks/pre-commit

Add to your settings:

{
"[fsh]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "octofhir.maki"
}
}
  1. Open Command Palette (Cmd/Ctrl+Shift+P)
  2. Search “Format Document”
  3. Select formatter: maki

Format your code regularly to catch style issues early:

Terminal window
# Before committing
maki format --check input/fsh/**/*.fsh
# Or auto-format
maki format input/fsh/**/*.fsh

Share your maki.json configuration in version control so the entire team uses the same formatting rules.

Use formatting alongside linting for comprehensive code quality:

Terminal window
# Format first
maki format input/fsh/**/*.fsh
# Then lint
maki lint input/fsh/**/*.fsh

Automate formatting with git hooks to ensure all commits are formatted:

.git/hooks/pre-commit
#!/bin/bash
maki format --check input/fsh/**/*.fsh
if [ $? -ne 0 ]; then
echo "Files are not formatted. Run 'maki format input/fsh/**/*.fsh'"
exit 1
fi

The formatter is optimized for speed:

  • Single files: <50ms
  • Large projects: Parallel processing
  • Token optimization: 2-5% performance boost
  • Memory efficient: Streaming processing

Typical formatting performance on real-world projects:

Project SizeFilesTimeThroughput
Small10 files~50ms200 files/sec
Medium100 files~300ms330 files/sec
Large1000 files~2s500 files/sec

Formatting Doesn’t Match Expected Output

Section titled “Formatting Doesn’t Match Expected Output”

Check your configuration file:

Terminal window
# Verify config is loaded
maki format --check --verbose input/fsh/profile.fsh

For large projects, use parallel processing (automatic with multiple files):

Terminal window
# Formats files in parallel
maki format input/fsh/**/*.fsh

If you need to preserve specific formatting in a section, use comments:

// maki-format-off
* name 1..1 MS
* custom formatting here
// maki-format-on

Note: Format control comments are planned for a future release.

The formatter uses Rowan-based Concrete Syntax Tree:

  • Green Tree: Immutable, position-independent storage with all trivia
  • Red Tree: Dynamic view with parent pointers for efficient traversal
  • Lossless Property: parse(format(parse(source))) == parse(source)

Based on optimizations from Ruff and Biome formatters:

  • Token variant: Static keywords/operators (fast path)
  • Text variant: Dynamic content from source (slow path)
  • 70-85% fast path usage: High keyword density in FSH
  • 2-5% performance improvement: Proven optimization strategy

The formatter addresses known SUSHI formatting issues:

  • #1569: Preserves triple-quote endings
  • #1577, #1576: Handles mapping multi-line delimiters
  • #693: Accepts missing whitespace in input

Planned improvements for future releases:

  1. Smart line breaking: Intelligent wrapping of long lines
  2. Custom formatting rules: User-defined formatting plugins
  3. Format on save: LSP integration for automatic formatting
  4. Diff-aware formatting: Only format changed lines
  5. Format control comments: Selectively disable formatting
  6. Rule grouping: Group rules by type (metadata, constraints, flags)
  7. Rule sorting: Alphabetical or custom sorting within groups