Rules
CWF comes with a set of built-in formatting rules that can be combined to enforce consistent code style across your projects.
Core Rules
indentation
Purpose: Normalizes code indentation to either spaces or tabs with configurable size.
Options:
style:"space"|"tab"(default:"space")size: number (default:2, only applicable whenstyleis"space")
How it works:
- Detects existing indentation patterns in the code
- Converts all indentation to the specified style
- Maintains relative indentation levels
- Preserves empty lines
Examples:
// Input (mixed indentation)
function hello() {
if (true) {
console.log('world');
}
}
// Output with { style: 'space', size: 2 }
function hello() {
if (true) {
console.log('world');
}
}
// Output with { style: 'tab' }
function hello() {
if (true) {
console.log('world');
}
}Configuration:
{
"name": "indentation",
"enabled": true,
"options": {
"style": "space",
"size": 2
}
}line-ending
Purpose: Normalizes line endings to a consistent format (LF, CRLF, or CR).
Options:
style:"lf"|"crlf"|"cr"(default:"lf")
How it works:
- Scans file for all line ending characters
- Replaces all line endings with the specified style
- Handles mixed line endings in the same file
Examples:
// Input (mixed line endings)
line1\r\n
line2\n
line3\r
// Output with { style: 'lf' } (Unix)
line1\n
line2\n
line3\n
// Output with { style: 'crlf' } (Windows)
line1\r\n
line2\r\n
line3\r\nConfiguration:
{
"name": "line-ending",
"enabled": true,
"options": {
"style": "lf"
}
}Use cases:
- Cross-platform projects (normalize to LF for git)
- Windows-specific projects (use CRLF)
- Legacy Mac projects (use CR, rarely needed)
trailing-whitespace
Purpose: Removes whitespace characters at the end of lines.
Options: None
How it works:
- Processes each line independently
- Removes all space and tab characters from line endings
- Preserves intentional empty lines
- Does not affect whitespace within lines
Examples:
// Input (• represents spaces)
const name = 'John';••
const age = 30;•••••
// Output
const name = 'John';
const age = 30;Configuration:
{
"name": "trailing-whitespace",
"enabled": true
}Benefits:
- Cleaner diffs in version control
- Reduces file size
- Prevents editor warnings
- Industry best practice
final-newline
Purpose: Ensures files end with exactly one newline character (or removes it).
Options:
insert: boolean (default:true)
How it works:
- Checks if file ends with a newline
- Adds or removes final newline based on
insertoption - Removes multiple trailing newlines when
insert: true - Ensures clean file ending
Examples:
// Input (no final newline)
const name = 'John';[EOF]
// Output with { insert: true }
const name = 'John';
[EOF]
// Input (multiple final newlines)
const name = 'John';
[EOF]
// Output with { insert: true }
const name = 'John';
[EOF]Configuration:
{
"name": "final-newline",
"enabled": true,
"options": {
"insert": true
}
}Standards:
- POSIX standard requires final newline
- Many linters and editors enforce this
- Prevents issues with tools that read files line-by-line
max-line-length
Purpose: Validates or wraps lines exceeding a maximum length.
Options:
length: number (default:80)action:"warn"|"wrap"(default:"warn")
How it works:
- Measures each line's character count
- With
action: "warn": Logs warnings but doesn't modify content - With
action: "wrap": Attempts to break long lines at spaces
Examples:
// Input (long line)
const message = 'This is a very long string that exceeds the maximum line length configured in the formatter';
// Output with { length: 80, action: 'warn' }
// ⚠️ Warning: Line 1 exceeds 80 characters (90)
// (content unchanged)
// Output with { length: 80, action: 'wrap' }
const message = 'This is a very long string that exceeds the maximum line
length configured in the formatter';Configuration:
{
"name": "max-line-length",
"enabled": true,
"options": {
"length": 80,
"action": "warn"
}
}Notes:
wrapmode is basic and may not work well with all code structures- Use
warnmode for linting,wrapmode with caution - Common lengths: 80 (classic), 100 (modern), 120 (widescreen)
Rule Application Order
Rules are applied sequentially in the order they appear in your configuration. Each rule receives the output of the previous rule as input.
Example execution flow:
Original Content
↓
Indentation Rule (normalize spaces)
↓
Line Ending Rule (normalize to LF)
↓
Trailing Whitespace Rule (remove)
↓
Final Newline Rule (insert)
↓
Max Line Length Rule (validate/wrap)
↓
Final Formatted ContentBest Practice: Order rules from most to least invasive:
indentation- Affects structureline-ending- Affects line breakstrailing-whitespace- Cleanupfinal-newline- Final touchmax-line-length- Validation last
Creating Custom Rules
CWF's plugin system allows you to create custom formatting rules.
Rule Interface
import { BaseFormattingRule } from '@codewaveinnovation/formatter';
import { FormatContext } from '@codewaveinnovation/formatter';
export class MyCustomRule extends BaseFormattingRule {
readonly name = 'my-custom-rule';
readonly description = 'Description of what this rule does';
protected format(context: FormatContext): string {
// Access current content
const { content } = context;
// Access configuration for this rule
const ruleConfig = context.config.rules.find(r => r.name === this.name);
const options = ruleConfig?.options || {};
// Transform content
const transformed = content; // Your transformation logic
// Return new content (immutable)
return transformed;
}
}Creating a Plugin
import { BasePlugin } from '@codewaveinnovation/formatter';
import { IFormattingRule } from '@codewaveinnovation/formatter';
import { MyCustomRule } from './MyCustomRule';
export class MyPlugin extends BasePlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
getRules(): IFormattingRule[] {
return [
new MyCustomRule()
];
}
}Using Custom Plugin
import { createFormatter, RuleRegistry } from '@codewaveinnovation/formatter';
import { PluginManager } from '@codewaveinnovation/formatter';
import { MyPlugin } from './MyPlugin';
const registry = new RuleRegistry();
const pluginManager = new PluginManager(registry);
// Load custom plugin
pluginManager.loadPlugin(new MyPlugin());
// Create formatter
const formatter = new CodeFormatter(registry);
// Use with configuration
const config = {
rules: [
{ name: 'my-custom-rule', enabled: true, options: {} }
]
};
const result = await formatter.format(content, config);Rule Best Practices
1. Keep Rules Simple
Each rule should do one thing well. Avoid combining multiple concerns.
// ✅ Good - Single responsibility
export class TrailingWhitespaceRule extends BaseFormattingRule {
protected format(context: FormatContext): string {
return context.content
.split('\n')
.map(line => line.replace(/\s+$/, ''))
.join('\n');
}
}
// ❌ Bad - Multiple responsibilities
export class WhitespaceRule extends BaseFormattingRule {
protected format(context: FormatContext): string {
// Removes trailing whitespace AND normalizes spaces AND fixes indentation
// Too much in one rule!
}
}2. Make Rules Immutable
Always return new content instead of modifying the input.
// ✅ Good - Immutable
protected format(context: FormatContext): string {
return context.content.replace(/foo/g, 'bar');
}
// ❌ Bad - Mutates input
protected format(context: FormatContext): string {
context.content = context.content.replace(/foo/g, 'bar'); // Never do this!
return context.content;
}3. Handle Edge Cases
protected format(context: FormatContext): string {
const { content } = context;
// Handle empty content
if (!content || content.length === 0) {
return content;
}
// Handle single line
if (!content.includes('\n')) {
return this.processLine(content);
}
// Process multiple lines
return content
.split('\n')
.map(line => this.processLine(line))
.join('\n');
}4. Document Options
/**
* Controls quote style in code
*
* @options
* - style: 'single' | 'double' - Type of quotes to use (default: 'single')
* - avoidEscape: boolean - Prefer quotes that minimize escaping (default: true)
*
* @example
* // Input with double quotes
* const name = "John";
*
* // Output with { style: 'single' }
* const name = 'John';
*/
export class QuoteStyleRule extends BaseFormattingRule {
// Implementation
}Next Steps
- Configuration Guide - Learn how to configure rules
- API Reference - Programmatic usage
- Getting Started - Quick start guide
- Plugins - Create custom rules with plugins