|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Obsidian Linter is an Obsidian plugin that formats and styles markdown notes with configurable rules. The plugin enforces consistent markdown styling for YAML frontmatter, headings, footnotes, spacing, content formatting, and paste behavior. |
| 8 | + |
| 9 | +## Build & Development Commands |
| 10 | + |
| 11 | +### Building |
| 12 | +- `npm run build` - Production build using esbuild |
| 13 | +- `npm run dev` - Development build with watch mode |
| 14 | +- `npm run compile` - Full compile (build + docs + lint + test) |
| 15 | + |
| 16 | +### Testing |
| 17 | +- `npm test` - Run all Jest tests |
| 18 | +- `npm run test-suite` - Run specific test suite (set $1 to test name) |
| 19 | +- `npm run clear-jest` - Clear Jest cache |
| 20 | + |
| 21 | +### Linting & Docs |
| 22 | +- `npm run lint` - Run ESLint with auto-fix |
| 23 | +- `npm run docs` - Generate documentation (runs docs.js) |
| 24 | +- `npm run translate` - Build and run translation helper |
| 25 | + |
| 26 | +### Other |
| 27 | +- `npm run minify-css` - Minify CSS using PostCSS |
| 28 | + |
| 29 | +## Architecture Overview |
| 30 | + |
| 31 | +### Core Components |
| 32 | + |
| 33 | +**Main Plugin (`src/main.ts`)** |
| 34 | +- Entry point: `LinterPlugin` class extends Obsidian's `Plugin` |
| 35 | +- Manages settings, commands, event handlers, and file linting operations |
| 36 | +- Handles paste events, file change detection, and custom commands |
| 37 | +- Uses `AsyncLock` for custom command synchronization |
| 38 | + |
| 39 | +**Rules System (`src/rules.ts` & `src/rules/`)** |
| 40 | +- Rules are organized by type: YAML, Heading, Footnote, Content, Spacing, Paste |
| 41 | +- Each rule is a class in `src/rules/` that extends `RuleBuilder<TOptions>` |
| 42 | +- Rules are registered via decorator: `@RuleBuilder.register` |
| 43 | +- Rule execution order respects `hasSpecialExecutionOrder` flag for dependencies |
| 44 | +- Rules implement `apply(text: string, options: TOptions): string` |
| 45 | + |
| 46 | +**Rule Builder Pattern (`src/rules/rule-builder.ts`)** |
| 47 | +- Base class: `RuleBuilderBase` - manages static rule registry |
| 48 | +- Main class: `RuleBuilder<TOptions>` - template for creating rules |
| 49 | +- Each rule defines: |
| 50 | + - `OptionsClass` - typed options interface |
| 51 | + - `apply()` - core transformation logic |
| 52 | + - `exampleBuilders` - documentation examples |
| 53 | + - `optionBuilders` - UI option controls |
| 54 | + |
| 55 | +**Rules Runner (`src/rules-runner.ts`)** |
| 56 | +- `RulesRunner` class orchestrates rule execution |
| 57 | +- Handles disabled rules from YAML frontmatter (`disabled rules:`) |
| 58 | +- Runs rules in specific order: pre-rules → regular rules → post-rules |
| 59 | +- Special handling for YAML timestamp, title, and escape rules |
| 60 | + |
| 61 | +**Options System (`src/option.ts`)** |
| 62 | +- Option types: `BooleanOption`, `TextOption`, `DropdownOption`, `TextAreaOption`, `MomentFormatOption`, `MdFilePickerOption` |
| 63 | +- Options generate UI controls in settings tab |
| 64 | +- Each rule has an 'enabled' option as first option |
| 65 | + |
| 66 | +**Ignore Types (`src/utils/ignore-types.ts`)** |
| 67 | +- Rules can ignore specific markdown elements during processing |
| 68 | +- Types: code blocks, inline code, math, links, wikilinks, tags, YAML, custom ignore sections |
| 69 | +- Custom ignore syntax: `<!-- linter-disable -->` / `<!-- linter-enable -->` |
| 70 | + |
| 71 | +**YAML Processing (`src/utils/yaml.ts`)** |
| 72 | +- Uses `yaml` package for parsing/stringifying frontmatter |
| 73 | +- Handles YAML extraction, modification, and disabled rules detection |
| 74 | +- Special handling for timestamp values and escape characters |
| 75 | + |
| 76 | +**Markdown AST (`src/utils/mdast.ts`)** |
| 77 | +- Uses `mdast` (markdown abstract syntax tree) for parsing |
| 78 | +- Supports footnotes, math blocks, GFM task lists via micromark extensions |
| 79 | +- Provides utilities for finding headings, links, tables, etc. |
| 80 | + |
| 81 | +### Testing Architecture |
| 82 | + |
| 83 | +**Test Structure (`__tests__/`)** |
| 84 | +- One test file per rule: `{rule-name}.test.ts` |
| 85 | +- Common utilities in `__tests__/common.ts` |
| 86 | +- Mock Obsidian API in `__mocks__/obsidian.ts` |
| 87 | +- Helper function: `ruleTest()` runs rule before/after comparisons |
| 88 | + |
| 89 | +**Test Pattern** |
| 90 | +```typescript |
| 91 | +ruleTest({ |
| 92 | + RuleBuilderClass: YourRule, |
| 93 | + testCases: [ |
| 94 | + { testName: '...', before: '...', after: '...', options: {...} } |
| 95 | + ] |
| 96 | +}); |
| 97 | +``` |
| 98 | + |
| 99 | +### Settings & UI |
| 100 | + |
| 101 | +**Settings (`src/settings-data.ts` & `src/ui/settings.ts`)** |
| 102 | +- `LinterSettings` interface defines all plugin settings |
| 103 | +- `SettingTab` class creates Obsidian settings UI |
| 104 | +- Rule configs stored in `settings.ruleConfigs[ruleAlias]` |
| 105 | + |
| 106 | +**UI Components (`src/ui/`)** |
| 107 | +- `modals/` - Confirmation modals, parse results |
| 108 | +- `linter-components/` - Custom UI controls (file pickers, replacements, commands) |
| 109 | +- `helpers.ts` - Settings UI helpers |
| 110 | + |
| 111 | +### Language Support |
| 112 | + |
| 113 | +**Internationalization (`src/lang/`)** |
| 114 | +- Language files: `en.ts`, `zh.ts`, `zh-TW.ts`, etc. |
| 115 | +- Helper: `getTextInLanguage(key)` retrieves translated strings |
| 116 | +- All user-facing text uses language keys |
| 117 | + |
| 118 | +## Creating a New Rule |
| 119 | + |
| 120 | +1. Create `src/rules/{rule-name}.ts` using `_rule-template.ts.txt` as reference |
| 121 | +2. Define options interface extending `Options` |
| 122 | +3. Extend `RuleBuilder<YourOptions>` with `@RuleBuilder.register` decorator |
| 123 | +4. Implement required properties: |
| 124 | + - `nameKey`, `descriptionKey` - language keys |
| 125 | + - `type` - RuleType enum value |
| 126 | + - `OptionsClass` - options class reference |
| 127 | + - `apply()` - transformation logic |
| 128 | + - `exampleBuilders` - documentation examples |
| 129 | + - `optionBuilders` - UI controls |
| 130 | +5. Create test file: `__tests__/{rule-name}.test.ts` |
| 131 | +6. Test using `ruleTest()` helper with before/after cases |
| 132 | +7. Run `npm run compile` to verify build, docs, lint, and tests |
| 133 | + |
| 134 | +## Common Patterns |
| 135 | + |
| 136 | +**YAML Regex** |
| 137 | +Use `yamlRegex` from `src/utils/regex.ts` to match frontmatter. |
| 138 | + |
| 139 | +**Ignore Sections** |
| 140 | +Set `ruleIgnoreTypes` in constructor to skip code blocks, links, etc. |
| 141 | + |
| 142 | +**String Utilities** |
| 143 | +- `stripCr()` - normalize line endings |
| 144 | +- `parseCustomReplacements()` - parse replacement rules |
| 145 | + |
| 146 | +**Moment Locale** |
| 147 | +Plugin sets moment locale based on Obsidian language via `langToMomentLocale` mapping. |
| 148 | + |
| 149 | +## Build System |
| 150 | + |
| 151 | +**esbuild (`esbuild.config.mjs`)** |
| 152 | +- Bundles TypeScript to `main.js` |
| 153 | +- Production mode removes example code |
| 154 | +- Mocked banner for docs.js (replaces Obsidian imports) |
| 155 | +- Import glob plugin for dynamic rule loading |
| 156 | + |
| 157 | +**TypeScript Config** |
| 158 | +- Target: ES6, module: ESNext |
| 159 | +- Decorators enabled (`experimentalDecorators: true`) |
| 160 | +- Inline source maps for debugging |
| 161 | + |
| 162 | +## CI/CD |
| 163 | + |
| 164 | +GitHub Actions (`.github/workflows/main.yml`): |
| 165 | +- Runs on Node 16.x |
| 166 | +- Steps: install → build → test → lint |
| 167 | +- Triggered on push/PR to master |
| 168 | + |
| 169 | +## File Linting Modes |
| 170 | + |
| 171 | +1. **Editor Lint** - Uses CodeMirror editor API for live editing |
| 172 | +2. **File Lint** - Direct file content modification via Vault API |
| 173 | +3. **Paste Rules** - Special rules triggered on paste events |
| 174 | + |
| 175 | +## Notes |
| 176 | + |
| 177 | +- Rules with `hasSpecialExecutionOrder: true` run before/after regular rules |
| 178 | +- Paste rules (RuleType.PASTE) only run on paste events |
| 179 | +- Custom commands can be configured per-file in settings |
| 180 | +- YAML disabled rules: Add `disabled rules: [rule-alias]` to frontmatter |
| 181 | +- Plugin supports auto-correct misspellings with 939KB default dictionary |
0 commit comments