GritQL Custom Rules
GritQL is a powerful pattern-matching language that allows you to write custom lint rules for your FSH project.
What is GritQL?
Section titled “What is GritQL?”GritQL is a query language designed for code analysis and transformation. It lets you:
- Match code patterns using intuitive syntax
- Capture matched values in variables
- Apply complex predicates and conditions
- Generate diagnostic messages and fixes
Basic Syntax
Section titled “Basic Syntax”Simple Pattern Matching
Section titled “Simple Pattern Matching”Match a profile definition:
Profile: $nameThis matches any profile and captures its name in $name.
Field Matching
Section titled “Field Matching”Match profiles with specific properties:
Profile: $nameParent: PatientPattern Variables
Section titled “Pattern Variables”Variables capture matched content:
$name- Captures an identifier$_- Matches anything (anonymous)$...rest- Captures remaining items
Writing Custom Rules
Section titled “Writing Custom Rules”Rule File Structure
Section titled “Rule File Structure”Create .grit files in your custom rules directory:
// Rule metadatalanguage fshdescription "Profile constraints should have MS flag"severity warning
// Pattern to matchpattern { Profile: $profile_name * $path $card}
// Conditionwhere { // Check if MS flag is missing !contains($card, "MS")}
// Messagemessage "Add MS (Must Support) flag to constraint"Loading Custom Rules
Section titled “Loading Custom Rules”Configure custom rule directories in maki.json:
{ "linter": { "ruleDirectories": [ "./custom-rules", "./org-rules" ] }}Example Rules
Section titled “Example Rules”Require Must Support Flags
Section titled “Require Must Support Flags”language fshdescription "Profile constraints should use MS flag"severity warning
pattern { Profile: $_ * $path 1..1}
where { !contains($path, "MS")}
message "Add MS flag to required elements"Enforce Naming Patterns
Section titled “Enforce Naming Patterns”language fshdescription "ValueSets must end with 'VS'"severity error
pattern { ValueSet: $name}
where { !ends_with($name, "VS")}
message "ValueSet names should end with 'VS'"fix { ValueSet: `${name}VS`}Detect Missing Descriptions
Section titled “Detect Missing Descriptions”language fshdescription "Profiles must have descriptions"severity warning
pattern { Profile: $name $...body}
where { !contains($body, "Description:")}
message "Profile '${name}' is missing a description"Advanced Patterns
Section titled “Advanced Patterns”Using Wildcards
Section titled “Using Wildcards”// Match any resource type$resource_type: $namewhere { $resource_type in ["Profile", "Extension", "ValueSet"]}Nested Patterns
Section titled “Nested Patterns”Profile: $name* $path from $valueset (required)where { !exists($valueset)}message "ValueSet '${valueset}' not found"Multiple Conditions
Section titled “Multiple Conditions”Profile: $name* $path $cardwhere { $card == "1..1" and !contains($path, "MS") and !contains($path, "^")}Variable Binding with Nested Where Clauses
Section titled “Variable Binding with Nested Where Clauses”New in Phase 3: You can now use nested where clauses with variable constraints to create more powerful rules:
// Match profiles where the name starts with uppercaseprofile_declaration: $name where { $name <: r"^[A-Z]"}This syntax allows you to:
- Bind a variable to a field value (e.g.,
$namecaptures the profile name) - Apply constraints to that variable using nested predicates
- Use the captured value in messages and fixes
Examples
Section titled “Examples”Enforce naming conventions:
// Profile names must start with uppercaseprofile_declaration: $name where { $name <: r"^[A-Z]"}Combine with logical operators:
// Profile names must be PascalCase (uppercase start, no underscores/hyphens)profile_declaration: $name where { and { $name <: r"^[A-Z]", not $name <: r"[_-]" }}Check multiple variables:
// Both profile name and ID must follow naming rulesProfile: $name where { and { $name <: r"^[A-Z][a-zA-Z0-9]*$", $parent == "Patient" }}Syntax Requirements
Section titled “Syntax Requirements”⚠️ Important: Nested where clauses require braces { }:
// ✅ Correct - braces after 'where'$name where { $name <: r"^[A-Z]" }
// ❌ Wrong - missing braces$name where $name <: r"^[A-Z]"This distinguishes variable patterns from regular predicates:
$var where { predicate }- Variable pattern with constraintswhere $var == value- Regular predicate
See the GritQL Getting Started Guide for more examples.
Testing Custom Rules
Section titled “Testing Custom Rules”Test your rules before deploying:
# Run only custom rulesmaki lint --only-custom-rules **/*.fsh
# Test specific rulemaki lint --rule custom/require-ms-flag test.fshBest Practices
Section titled “Best Practices”- Start Simple - Begin with basic pattern matching
- Test Thoroughly - Test on various FSH files
- Provide Clear Messages - Help users understand the issue
- Include Fixes - Provide automated fixes when possible
- Document Rules - Explain why the rule exists
Built-in Functions
Section titled “Built-in Functions”MAKI provides 12 specialized built-in functions for FSH pattern matching:
Node Type Checking Functions
Section titled “Node Type Checking Functions”Check the type of a matched node:
is_profile($node) // Returns true if node is a Profileis_extension($node) // Returns true if node is an Extensionis_value_set($node) // Returns true if node is a ValueSetis_code_system($node) // Returns true if node is a CodeSystemExample:
Profile: $name where { is_profile($name) }Node Property Functions
Section titled “Node Property Functions”Check if a node has specific fields or properties:
has_comment($node) // Node has commentshas_title($node) // Node has Title fieldhas_description($node) // Node has Description fieldhas_parent($node) // Node has Parent field (Profiles only)Example:
Profile where { has_title($p) and has_description($p) }Extension where { not has_comment($e) }String Validation Functions
Section titled “String Validation Functions”Validate if a string follows a specific naming convention:
is_kebab_case($text) // lowercase-with-dashesis_pascal_case($text) // PascalCaseis_camel_case($text) // camelCaseis_screaming_snake_case($text) // SCREAMING_SNAKE_CASEPatterns:
- kebab-case:
my-profile,patient-id,value-set-name - PascalCase:
MyProfile,PatientRecord,ValueSetName - camelCase:
myProfile,patientRecord,valueSetName - SCREAMING_SNAKE_CASE:
MY_PROFILE,PATIENT_ID,VALUE_SET_NAME
Example:
// Enforce PascalCase naming for profilesProfile: $name where { is_pascal_case($name)}
// Enforce kebab-case IDsValueSet where { is_kebab_case($id)}Field Conditions
Section titled “Field Conditions”Check for field existence and compare field values:
// Check if field existsProfile where { title }
// Check if field doesn't existProfile where { not title }
// Compare field valuesProfile where { parent == "Patient" }
// String operations on fieldsExtension where { url contains "hl7.org" }
// Multiple conditions with ANDProfile where { title and description and parent}
// Multiple conditions with ORProfile where { not title or not description}Common Built-in Function Examples
Section titled “Common Built-in Function Examples”Enforce Documentation Standards
Section titled “Enforce Documentation Standards”// Find profiles without titlesProfile where { not title }
// Find profiles missing descriptionsProfile where { not description }
// Find profiles with complete documentationProfile where { title and description and parent}Enforce Naming Conventions
Section titled “Enforce Naming Conventions”// Validate profile names use PascalCaseProfile: $name where { is_pascal_case($name)}
// Find IDs that don't use kebab-caseValueSet where { not is_kebab_case($id)}
// Enforce consistent extension namingExtension: $ext where { is_pascal_case($ext) and title and description}Enforce Metadata Requirements
Section titled “Enforce Metadata Requirements”// Extensions must have titles and descriptionsExtension where { has_title($e) and has_description($e)}
// Profiles with commentsProfile where { has_comment($p)}
// ValueSets with parent relationshipsValueSet where { has_parent($vs)}See Also
Section titled “See Also”- GritQL Documentation - Complete GritQL reference
- Built-in Rules - Examples of rule implementation
- Custom Rules Guide - Detailed guide