Writing Custom Rules
Learn how to write custom lint rules using GritQL for project-specific validation.
Getting Started
Section titled “Getting Started”Prerequisites
Section titled “Prerequisites”- Understanding of FSH syntax
- Basic pattern matching concepts
- GritQL syntax basics (see GritQL Rules)
Create a directory for custom rules:
mkdir custom-rulesConfigure FSH Lint to load custom rules:
{ "linter": { "ruleDirectories": ["./custom-rules"] }}Rule Structure
Section titled “Rule Structure”A GritQL rule file (.grit) contains:
- Metadata (language, description, severity)
- Pattern to match
- Conditions (optional)
- Message
- Fix (optional)
Example: Enforce Profile Naming
Section titled “Example: Enforce Profile Naming”Create custom-rules/profile-naming.grit:
language fshdescription "Profiles must end with 'Profile'"severity error
pattern { Profile: $name}
where { !ends_with($name, "Profile")}
message "Profile name '${name}' should end with 'Profile'"
fix { Profile: `${name}Profile`}Example: Require MS Flags
Section titled “Example: Require MS Flags”Create custom-rules/require-ms.grit:
language fshdescription "Required fields must have MS flag"severity warning
pattern { Profile: $_ * $path 1..1 $flags}
where { !contains($flags, "MS")}
message "Add MS flag to required field: ${path}"
fix { * $path 1..1 MS}Example: Enforce Descriptions
Section titled “Example: Enforce Descriptions”Create custom-rules/require-description.grit:
language fshdescription "All profiles must have descriptions"severity warning
pattern { Profile: $name $...content}
where { !any_match($content, "Description:")}
message "Profile '${name}' is missing a description"Testing Custom Rules
Section titled “Testing Custom Rules”Test your rules before deploying:
# Test on specific filesmaki lint --rule custom/profile-naming test.fsh
# Run only custom rulesmaki lint --only-custom **/*.fshUsing Built-in Functions
Section titled “Using Built-in Functions”MAKI provides 12 built-in functions for powerful pattern matching. Here are practical examples:
Example: Enforce PascalCase Profile Names
Section titled “Example: Enforce PascalCase Profile Names”language fshdescription "Profile names must use PascalCase"severity error
pattern { Profile: $name}
where { not is_pascal_case($name)}
message "Profile name '${name}' should use PascalCase (e.g., MyProfile)"Example: Enforce kebab-case ValueSet IDs
Section titled “Example: Enforce kebab-case ValueSet IDs”language fshdescription "ValueSet IDs must use kebab-case"severity error
pattern { ValueSet: $vs_name where { id }}
where { not is_kebab_case($id)}
message "ValueSet ID should use kebab-case (e.g., my-value-set)"Example: Require Complete Documentation
Section titled “Example: Require Complete Documentation”language fshdescription "Profiles must have title, description, and parent"severity warning
pattern { Profile: $name where { not (title and description and parent) }}
message "Profile '${name}' is missing required documentation (title, description, parent)"Example: Check Profile Properties
Section titled “Example: Check Profile Properties”language fshdescription "Profiles must inherit from Patient"severity error
pattern { Profile: $name where { parent != "Patient" and parent != "DomainResource" }}
message "Profile '${name}' should inherit from Patient or DomainResource"Example: Validate Extension Documentation
Section titled “Example: Validate Extension Documentation”language fshdescription "Extensions must have title, description, and URL"severity warning
pattern { Extension: $ext where { not (has_title($e) and has_description($e) and url) }}
message "Extension '${ext}' is missing documentation"Example: Find Undocumented Elements
Section titled “Example: Find Undocumented Elements”language fshdescription "All definitions should have descriptions"severity info
pattern { (Profile or Extension or ValueSet or CodeSystem): $name where { not has_description($d) }}
message "Element '${name}' should include a description"Advanced Patterns
Section titled “Advanced Patterns”Complex Conditions with Built-ins
Section titled “Complex Conditions with Built-ins”language fshdescription "Profile naming and documentation validation"severity error
pattern { Profile: $name where { is_pascal_case($name) and title and description and parent }}
message "Profile '${name}' meets all requirements"Multiple Type Validation
Section titled “Multiple Type Validation”language fshdescription "Validate naming across definition types"severity warning
pattern { or { { Profile: $p where { is_pascal_case($p) } } { Extension: $e where { is_pascal_case($e) } } { ValueSet: $vs where { not is_kebab_case($vs) } } }}
message "Definition naming convention mismatch"Composite Rules
Section titled “Composite Rules”language fshdescription "Complete validation of profiles"severity error
pattern { Profile: $name where { is_pascal_case($name) and has_title($p) and has_description($p) and has_parent($p) and has_comment($p) }}
message "Profile '${name}' is well-documented and follows conventions"Best Practices
Section titled “Best Practices”- Start Simple - Begin with basic patterns
- Test Thoroughly - Test on various FSH files
- Clear Messages - Help users understand violations
- Provide Fixes - Automate fixes when possible
- Document Rules - Explain the reasoning
- Performance - Avoid overly complex patterns
Organization-Wide Rules
Section titled “Organization-Wide Rules”Share rules across projects:
# Create shared rules repositorygit clone https://github.com/yourorg/maki-rules.git
# Reference in config{ "linter": { "ruleDirectories": [ "./maki-rules" ] }}Quick Reference: Built-in Functions
Section titled “Quick Reference: Built-in Functions”MAKI provides 12 specialized functions for FSH validation:
| Category | Functions |
|---|---|
| Node Type Checks | is_profile(), is_extension(), is_value_set(), is_code_system() |
| Node Properties | has_comment(), has_title(), has_description(), has_parent() |
| String Validation | is_kebab_case(), is_pascal_case(), is_camel_case(), is_screaming_snake_case() |
See GritQL Rules for complete documentation of all built-in functions.
See Also
Section titled “See Also”- GritQL Rules - Complete built-in functions reference
- GritQL Documentation
- Built-in Rules - Examples