mir-linter 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mir_linter-0.1.0/PKG-INFO +288 -0
- mir_linter-0.1.0/README.md +274 -0
- mir_linter-0.1.0/mir/__init__.py +1 -0
- mir_linter-0.1.0/mir/engine/config.py +243 -0
- mir_linter-0.1.0/mir/engine/disabler.py +272 -0
- mir_linter-0.1.0/mir/engine/rule_interface.py +99 -0
- mir_linter-0.1.0/mir/engine/rules_help.py +510 -0
- mir_linter-0.1.0/mir/engine/rules_loader.py +190 -0
- mir_linter-0.1.0/mir/engine/runner.py +707 -0
- mir_linter-0.1.0/mir/main.py +22 -0
- mir_linter-0.1.0/mir/rules/java/__init__.py +6 -0
- mir_linter-0.1.0/mir/rules/sql/__init__.py +7 -0
- mir_linter-0.1.0/mir/rules/sql/alias_as.py +126 -0
- mir_linter-0.1.0/mir/rules/sql/between.py +137 -0
- mir_linter-0.1.0/mir/rules/sql/blank_lines.py +86 -0
- mir_linter-0.1.0/mir/rules/sql/boolean_comparison.py +140 -0
- mir_linter-0.1.0/mir/rules/sql/case_layout.py +300 -0
- mir_linter-0.1.0/mir/rules/sql/clause_alignment.py +279 -0
- mir_linter-0.1.0/mir/rules/sql/column_layout.py +576 -0
- mir_linter-0.1.0/mir/rules/sql/comment_spacing.py +76 -0
- mir_linter-0.1.0/mir/rules/sql/count_star.py +91 -0
- mir_linter-0.1.0/mir/rules/sql/create_view_indent.py +120 -0
- mir_linter-0.1.0/mir/rules/sql/cte_format.py +305 -0
- mir_linter-0.1.0/mir/rules/sql/datatype_case.py +84 -0
- mir_linter-0.1.0/mir/rules/sql/datatype_format.py +81 -0
- mir_linter-0.1.0/mir/rules/sql/distinct_parentheses.py +99 -0
- mir_linter-0.1.0/mir/rules/sql/dollar_quote_alignment.py +94 -0
- mir_linter-0.1.0/mir/rules/sql/empty_command.py +107 -0
- mir_linter-0.1.0/mir/rules/sql/eof_newline.py +66 -0
- mir_linter-0.1.0/mir/rules/sql/expression_split.py +403 -0
- mir_linter-0.1.0/mir/rules/sql/from_multi.py +208 -0
- mir_linter-0.1.0/mir/rules/sql/from_paren_layout.py +195 -0
- mir_linter-0.1.0/mir/rules/sql/from_single.py +124 -0
- mir_linter-0.1.0/mir/rules/sql/function_body_indent.py +113 -0
- mir_linter-0.1.0/mir/rules/sql/function_case.py +199 -0
- mir_linter-0.1.0/mir/rules/sql/function_header_layout.py +306 -0
- mir_linter-0.1.0/mir/rules/sql/in_exists.py +77 -0
- mir_linter-0.1.0/mir/rules/sql/indent.py +143 -0
- mir_linter-0.1.0/mir/rules/sql/is_null.py +108 -0
- mir_linter-0.1.0/mir/rules/sql/join_on_multi.py +205 -0
- mir_linter-0.1.0/mir/rules/sql/join_parens.py +134 -0
- mir_linter-0.1.0/mir/rules/sql/keyword_case.py +254 -0
- mir_linter-0.1.0/mir/rules/sql/line_length.py +134 -0
- mir_linter-0.1.0/mir/rules/sql/null_coalesce.py +230 -0
- mir_linter-0.1.0/mir/rules/sql/operator_spacing.py +152 -0
- mir_linter-0.1.0/mir/rules/sql/paren_content_indent.py +265 -0
- mir_linter-0.1.0/mir/rules/sql/paren_multi.py +159 -0
- mir_linter-0.1.0/mir/rules/sql/paren_same_op.py +228 -0
- mir_linter-0.1.0/mir/rules/sql/paren_single.py +200 -0
- mir_linter-0.1.0/mir/rules/sql/plpgsql_assignment.py +181 -0
- mir_linter-0.1.0/mir/rules/sql/plpgsql_block_indent.py +219 -0
- mir_linter-0.1.0/mir/rules/sql/plpgsql_keyword_case.py +122 -0
- mir_linter-0.1.0/mir/rules/sql/raise_layout.py +197 -0
- mir_linter-0.1.0/mir/rules/sql/redundant_keywords.py +128 -0
- mir_linter-0.1.0/mir/rules/sql/sql_utils.py +667 -0
- mir_linter-0.1.0/mir/rules/sql/statement_blank_lines.py +158 -0
- mir_linter-0.1.0/mir/rules/sql/statement_semicolon.py +126 -0
- mir_linter-0.1.0/mir/rules/sql/subquery_compact.py +178 -0
- mir_linter-0.1.0/mir/rules/sql/subquery_depth_limit.py +109 -0
- mir_linter-0.1.0/mir/rules/sql/subquery_indent.py +262 -0
- mir_linter-0.1.0/mir/rules/sql/table_alias_as.py +107 -0
- mir_linter-0.1.0/mir/rules/sql/table_field_spacing.py +271 -0
- mir_linter-0.1.0/mir/rules/sql/trailing_semicolon.py +115 -0
- mir_linter-0.1.0/mir/rules/sql/trigger_layout.py +215 -0
- mir_linter-0.1.0/mir/rules/sql/unresolved_alias.py +176 -0
- mir_linter-0.1.0/mir/rules/sql/update_layout.py +195 -0
- mir_linter-0.1.0/mir/rules/sql/where_multi.py +175 -0
- mir_linter-0.1.0/mir/rules/sql/where_single.py +113 -0
- mir_linter-0.1.0/mir/rules/xml/__init__.py +6 -0
- mir_linter-0.1.0/mir_linter.egg-info/PKG-INFO +288 -0
- mir_linter-0.1.0/mir_linter.egg-info/SOURCES.txt +81 -0
- mir_linter-0.1.0/mir_linter.egg-info/dependency_links.txt +1 -0
- mir_linter-0.1.0/mir_linter.egg-info/entry_points.txt +3 -0
- mir_linter-0.1.0/mir_linter.egg-info/requires.txt +1 -0
- mir_linter-0.1.0/mir_linter.egg-info/top_level.txt +1 -0
- mir_linter-0.1.0/pyproject.toml +28 -0
- mir_linter-0.1.0/setup.cfg +4 -0
- mir_linter-0.1.0/tests/test_config.py +67 -0
- mir_linter-0.1.0/tests/test_disabler.py +74 -0
- mir_linter-0.1.0/tests/test_dynamic_loader.py +193 -0
- mir_linter-0.1.0/tests/test_meta_rules.py +94 -0
- mir_linter-0.1.0/tests/test_runner.py +486 -0
- mir_linter-0.1.0/tests/test_statement_blank_lines.py +48 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mir-linter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Mitchell's Ineffable Rules (IR) Linter - A modular linter with customizable rules for SQL, Java, and XML.
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: pyyaml>=6.0
|
|
14
|
+
|
|
15
|
+
# Mitchell's Ineffable Rules (IR) Linter
|
|
16
|
+
|
|
17
|
+
A highly modular, extensible linting and auto-fixing utility for **SQL, Java, and XML** codebase assets. The linter can be run locally via `uv` / `python`, inside a containerized **Docker** execution loop, or integrated into your lifecycle as a **Git Pre-commit Hook**.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Extensible Rules Architecture**: Add language rules simply by adding Python rule modules under `mir/rules/<language>/`.
|
|
24
|
+
- **User Styling Guides**: Detailed style guides explaining each rule, its configuration parameters, auto-fixability, and clean vs. violating examples under `docs/`.
|
|
25
|
+
- **Precedence-Aware Configuration**: Merges CLI flags, environment variables, and YAML config file settings:
|
|
26
|
+
$$\text{CLI arguments} > \text{Environment variables} > \text{Config file} > \text{Defaults}$$
|
|
27
|
+
- **Granular Disable Comments**:
|
|
28
|
+
- **Single-line disable** (disables a rule for the next non-comment code line):
|
|
29
|
+
- SQL: `-- IR-rule-name`
|
|
30
|
+
- Java: `// IR-rule-name`
|
|
31
|
+
- XML: `<!-- IR-rule-name -->`
|
|
32
|
+
- **Block disable** (disables a rule for a range of lines):
|
|
33
|
+
- SQL: `-- IR-start-rule-name` ... `-- IR-end-rule-name`
|
|
34
|
+
- Java: `// IR-start-rule-name` ... `// IR-end-rule-name`
|
|
35
|
+
- XML: `<!-- IR-start-rule-name -->` ... `<!-- IR-end-rule-name -->`
|
|
36
|
+
- **Execution Modes**:
|
|
37
|
+
- `check`: Validates files and reports all errors (default).
|
|
38
|
+
- `fix`: Applies automatic corrections to fixable rules directly to files.
|
|
39
|
+
- `dry-run`: Runs checks and generates unified diffs showing proposed fixes without modifying the source.
|
|
40
|
+
- `verbose`: Prints rule descriptions and the offending lines along with errors.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Directory Structure
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
.
|
|
48
|
+
├── docs/ # Language-specific Style Guides
|
|
49
|
+
│ ├── sql.md # SQL style guide & rules
|
|
50
|
+
│ ├── java.md # Java style guide (placeholder)
|
|
51
|
+
│ └── xml.md # XML style guide (placeholder)
|
|
52
|
+
├── Dockerfile # Containerization
|
|
53
|
+
├── README.md # This instruction manual
|
|
54
|
+
├── requirements.txt # Python dependencies (PyYAML)
|
|
55
|
+
├── pre-commit-hook.sh # Shell hook executing linter via Docker
|
|
56
|
+
├── mir/
|
|
57
|
+
│ ├── main.py # Core CLI Entrypoint
|
|
58
|
+
│ ├── engine/ # Core engine mechanics
|
|
59
|
+
│ │ ├── config.py # Precedence-aware configuration loader
|
|
60
|
+
│ │ ├── disabler.py # Parsing and handling disable comments
|
|
61
|
+
│ │ ├── rule_interface.py # BaseRule & Violation class structures
|
|
62
|
+
│ │ ├── rules_loader.py # Dynamic imports of language rules
|
|
63
|
+
│ │ └── runner.py # Processing workflow (Check, Fix, Dry-Run)
|
|
64
|
+
│ └── rules/ # Language specific rules
|
|
65
|
+
│ └── sql/
|
|
66
|
+
│ ├── line_length.py # Checks lines exceeding maximum characters
|
|
67
|
+
│ └── keyword_case.py # Enforces and fixes uppercase SQL keywords
|
|
68
|
+
└── tests/ # Suite of unit & integration tests
|
|
69
|
+
├── test_config.py # Tests config parser
|
|
70
|
+
├── test_disabler.py # Tests comment disabler logic
|
|
71
|
+
├── test_runner.py # Tests CLI runner integration
|
|
72
|
+
└── rules/ # Rule specific tests
|
|
73
|
+
└── sql/
|
|
74
|
+
├── test_line_length.py
|
|
75
|
+
└── test_keyword_case.py
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Setup & Local Usage
|
|
81
|
+
|
|
82
|
+
Ensure you have [uv](https://github.com/astral-sh/uv) or `python` installed.
|
|
83
|
+
|
|
84
|
+
### 1. Running Tests
|
|
85
|
+
You can run the full test suite using `uv`:
|
|
86
|
+
```bash
|
|
87
|
+
uv run --with pyyaml python -m unittest discover -s tests -p "test_*.py"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. Style Guides & Documentation
|
|
91
|
+
|
|
92
|
+
- **Custom Rules Guide**: Refer to the [Custom Rules Guide](docs/custom_rules.md) for details on writing your own custom linting rules, parameter interfaces, and execution validation.
|
|
93
|
+
- **Language Style Guides**: The other language-specific files in the `docs/` folder (such as `docs/sql.md`, `docs/java.md`, and `docs/xml.md`) are **automatically generated by the package** from code metadata. To compile and update these guides in sync with rule source definitions, run:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Generate style guides directly inside the 'docs/' directory
|
|
97
|
+
uv run mir/main.py --help docs
|
|
98
|
+
|
|
99
|
+
# Generate style guides inside a custom directory name
|
|
100
|
+
uv run mir/main.py --help docs:my_rules_docs
|
|
101
|
+
```
|
|
102
|
+
This scans all rule modules under `mir/rules/` and compiles the style guide markdown files directly.
|
|
103
|
+
|
|
104
|
+
### 2. Linting execution
|
|
105
|
+
```bash
|
|
106
|
+
# Check files (default path: current directory)
|
|
107
|
+
uv run mir/main.py
|
|
108
|
+
|
|
109
|
+
# Verbose check on a specific file path
|
|
110
|
+
uv run mir/main.py -v path/to/file.sql
|
|
111
|
+
|
|
112
|
+
# Safe auto-fixing rules on a directory (rewrites matching files)
|
|
113
|
+
uv run mir/main.py --fix path/to/file.sql
|
|
114
|
+
|
|
115
|
+
# Show dry-run diffs
|
|
116
|
+
uv run mir/main.py --dry-run path/to/file.sql
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 3. Rules CLI Documentation & Help
|
|
120
|
+
You can query and inspect the list of rules and details directly from the command line:
|
|
121
|
+
```bash
|
|
122
|
+
# List all rules across all languages
|
|
123
|
+
uv run mir/main.py --help rules
|
|
124
|
+
|
|
125
|
+
# List only SQL rules
|
|
126
|
+
uv run mir/main.py --help rules:sql
|
|
127
|
+
|
|
128
|
+
# Show detailed documentation & examples for a specific rule across all languages
|
|
129
|
+
uv run mir/main.py --help IR-line-length
|
|
130
|
+
|
|
131
|
+
# Show detailed documentation for a specific rule inside a specific language
|
|
132
|
+
uv run mir/main.py --help sql:IR-keyword-case
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Docker Usage
|
|
138
|
+
|
|
139
|
+
Build and execute the linter entirely inside a containerized sandbox.
|
|
140
|
+
|
|
141
|
+
### 1. Build the Docker Image
|
|
142
|
+
```bash
|
|
143
|
+
docker build -t mitchells-ineffable-rules:latest .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 2. Lint Files via Docker
|
|
147
|
+
Mount your workspace volume into `/workspace` in the container:
|
|
148
|
+
```bash
|
|
149
|
+
# Basic check on the current directory
|
|
150
|
+
docker run --rm -v "$(pwd):/workspace" mitchells-ineffable-rules:latest .
|
|
151
|
+
|
|
152
|
+
# Run auto-fixes
|
|
153
|
+
docker run --rm -v "$(pwd):/workspace" mitchells-ineffable-rules:latest --fix .
|
|
154
|
+
|
|
155
|
+
# Verbose dry-run on a file
|
|
156
|
+
docker run --rm -v "$(pwd):/workspace" mitchells-ineffable-rules:latest --dry-run -v query.sql
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Git Pre-commit Hook Integration
|
|
162
|
+
|
|
163
|
+
Integrate the linter to block bad code from being committed:
|
|
164
|
+
|
|
165
|
+
1. Copy the hook script into your repository's hook directory:
|
|
166
|
+
```bash
|
|
167
|
+
cp pre-commit-hook.sh .git/hooks/pre-commit
|
|
168
|
+
```
|
|
169
|
+
2. Make sure it is executable:
|
|
170
|
+
```bash
|
|
171
|
+
chmod +x .git/hooks/pre-commit
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
When committing files, the hook automatically detects staged SQL, Java, and XML changes, running them through the Docker container before allowing the commit to succeed.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Configuration
|
|
179
|
+
|
|
180
|
+
You can create an `.ir-config.yaml` file in the root of your project:
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
# Rules to globally disable
|
|
184
|
+
disable:
|
|
185
|
+
- IR-some-disabled-rule
|
|
186
|
+
|
|
187
|
+
# Global flags
|
|
188
|
+
verbose: true
|
|
189
|
+
fix: false
|
|
190
|
+
|
|
191
|
+
# External rule directories (to extend or replace built-in rulesets)
|
|
192
|
+
include_dirs:
|
|
193
|
+
- /path/to/my/custom_rules
|
|
194
|
+
|
|
195
|
+
# Rule mode for common languages: "extend" (default, built-in + custom) or "replace" (custom only)
|
|
196
|
+
rule_mode: extend
|
|
197
|
+
|
|
198
|
+
# Individual rule configuration
|
|
199
|
+
rules:
|
|
200
|
+
IR-line-length:
|
|
201
|
+
max_length: 120
|
|
202
|
+
IR-in-exists:
|
|
203
|
+
severity: warning # Override severity to warning (defaults to error if not specified)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Configurable Severity (`severity`)
|
|
207
|
+
|
|
208
|
+
By default, any unfixable violation (or check-mode violation) is treated as an **error** and causes the linter to return a non-zero exit code. You can configure any rule to be treated as a **warning** instead by setting the `severity` config option to `warning` in `.ir-config.yaml`. Warnings are still printed to output, but they do not cause the linter to return a failure exit code.
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
### Rule Configuration Fallbacks
|
|
212
|
+
|
|
213
|
+
To keep rules independent and decoupled, certain rules can dynamically inherit configuration parameters from other rules if they are not explicitly set.
|
|
214
|
+
|
|
215
|
+
For example, the SQL Column Layout formatter (`IR-column-layout`) dynamically falls back to query its parameters from the canonical rule configurations:
|
|
216
|
+
- `max_length` is inherited from the Line Length rule (`IR-line-length`)
|
|
217
|
+
- `indent_size` is inherited from the Indentation rule (`IR-indent`)
|
|
218
|
+
|
|
219
|
+
You can override these parameters specifically for the dependent rule or set them once on the canonical rule:
|
|
220
|
+
```yaml
|
|
221
|
+
rules:
|
|
222
|
+
# Sets line length limit to 80 (affects both IR-line-length and IR-column-layout fallback)
|
|
223
|
+
IR-line-length:
|
|
224
|
+
max_length: 80
|
|
225
|
+
|
|
226
|
+
# Specifically overrides column layout to use 100 instead
|
|
227
|
+
IR-column-layout:
|
|
228
|
+
max_length: 100
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Order of Precedence
|
|
232
|
+
|
|
233
|
+
Configuration parameters are merged with the following priority hierarchy (highest overrides lowest):
|
|
234
|
+
1. **Command Line Arguments** (e.g. `--fix`, `--disable IR-line-length`, `--include-dir /path`, `--rule-mode replace`)
|
|
235
|
+
2. **Environment Variables** (e.g. `IR_FIX=true`, `IR_DISABLE=IR-line-length`, `IR_INCLUDE_DIRS=/path`, `IR_RULE_MODE=replace`)
|
|
236
|
+
3. **YAML Config File** (e.g. `disable: [IR-line-length]`, `include_dirs: [/path]`, `rule_mode: replace` inside `.ir-config.yaml`)
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Writing Custom Rules
|
|
241
|
+
|
|
242
|
+
You can write custom rules to run against your codebase, either placing them inside the built-in rules directory or in an external custom rules directory. For full details on class specifications, default parameters, and strict execution validation, refer to the [Custom Rules Guide](docs/custom_rules.md).
|
|
243
|
+
|
|
244
|
+
### 1. Built-in Directory
|
|
245
|
+
Create your rule Python module under `mir/rules/<language>/my_rule_name.py`.
|
|
246
|
+
|
|
247
|
+
### 2. External Directory
|
|
248
|
+
1. Create a directory structure, e.g. `/my-custom-rules/<language>/my_rule_name.py`.
|
|
249
|
+
2. Provide the path to the linter via CLI (`--include-dir /my-custom-rules`), Env (`IR_INCLUDE_DIRS`), or config (`include_dirs: ["/my-custom-rules"]`).
|
|
250
|
+
3. Set the `--rule-mode` to specify whether custom rules should **extend** (default) or **replace** the built-in rule sets.
|
|
251
|
+
|
|
252
|
+
### Custom Rule Implementation Example
|
|
253
|
+
Each custom rule must subclass `BaseRule` (from `mir.engine.rule_interface`):
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from typing import List, Dict, Any
|
|
257
|
+
from mir.engine.rule_interface import BaseRule, Violation
|
|
258
|
+
|
|
259
|
+
class MyXmlRule(BaseRule):
|
|
260
|
+
rule_id = "IR-xml-rule"
|
|
261
|
+
description = "A custom description of my rule."
|
|
262
|
+
category = "general"
|
|
263
|
+
is_fixable = "yes" # "yes", "no", or "sometimes"
|
|
264
|
+
|
|
265
|
+
def check(self, content: str, file_path: str, rule_config: Dict[str, Any]) -> List[Violation]:
|
|
266
|
+
violations = []
|
|
267
|
+
# Implement check logic...
|
|
268
|
+
# If a violation is found, append Violation(...) to list.
|
|
269
|
+
return violations
|
|
270
|
+
|
|
271
|
+
def fix(self, content: str, file_path: str, rule_config: Dict[str, Any]) -> str:
|
|
272
|
+
# If is_fixable is "yes" or "sometimes", return the modified content string
|
|
273
|
+
return content
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Publishing to PyPI
|
|
279
|
+
|
|
280
|
+
For instructions on building distribution archives, TestPyPI validation, and publishing to the live Python Package Index, refer to the [PyPI Publishing Guide](docs/publishing.md).
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
*This entire package was built using **Antigravity**, a powerful agentic AI coding assistant designed by the Google DeepMind team.*
|
|
285
|
+
|
|
286
|
+
*Total Cumulative Development Time: ~11.5 hours*
|
|
287
|
+
|
|
288
|
+
*Last Developer Session: 2026-07-13T10:57:00Z*
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Mitchell's Ineffable Rules (IR) Linter
|
|
2
|
+
|
|
3
|
+
A highly modular, extensible linting and auto-fixing utility for **SQL, Java, and XML** codebase assets. The linter can be run locally via `uv` / `python`, inside a containerized **Docker** execution loop, or integrated into your lifecycle as a **Git Pre-commit Hook**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Extensible Rules Architecture**: Add language rules simply by adding Python rule modules under `mir/rules/<language>/`.
|
|
10
|
+
- **User Styling Guides**: Detailed style guides explaining each rule, its configuration parameters, auto-fixability, and clean vs. violating examples under `docs/`.
|
|
11
|
+
- **Precedence-Aware Configuration**: Merges CLI flags, environment variables, and YAML config file settings:
|
|
12
|
+
$$\text{CLI arguments} > \text{Environment variables} > \text{Config file} > \text{Defaults}$$
|
|
13
|
+
- **Granular Disable Comments**:
|
|
14
|
+
- **Single-line disable** (disables a rule for the next non-comment code line):
|
|
15
|
+
- SQL: `-- IR-rule-name`
|
|
16
|
+
- Java: `// IR-rule-name`
|
|
17
|
+
- XML: `<!-- IR-rule-name -->`
|
|
18
|
+
- **Block disable** (disables a rule for a range of lines):
|
|
19
|
+
- SQL: `-- IR-start-rule-name` ... `-- IR-end-rule-name`
|
|
20
|
+
- Java: `// IR-start-rule-name` ... `// IR-end-rule-name`
|
|
21
|
+
- XML: `<!-- IR-start-rule-name -->` ... `<!-- IR-end-rule-name -->`
|
|
22
|
+
- **Execution Modes**:
|
|
23
|
+
- `check`: Validates files and reports all errors (default).
|
|
24
|
+
- `fix`: Applies automatic corrections to fixable rules directly to files.
|
|
25
|
+
- `dry-run`: Runs checks and generates unified diffs showing proposed fixes without modifying the source.
|
|
26
|
+
- `verbose`: Prints rule descriptions and the offending lines along with errors.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Directory Structure
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
.
|
|
34
|
+
├── docs/ # Language-specific Style Guides
|
|
35
|
+
│ ├── sql.md # SQL style guide & rules
|
|
36
|
+
│ ├── java.md # Java style guide (placeholder)
|
|
37
|
+
│ └── xml.md # XML style guide (placeholder)
|
|
38
|
+
├── Dockerfile # Containerization
|
|
39
|
+
├── README.md # This instruction manual
|
|
40
|
+
├── requirements.txt # Python dependencies (PyYAML)
|
|
41
|
+
├── pre-commit-hook.sh # Shell hook executing linter via Docker
|
|
42
|
+
├── mir/
|
|
43
|
+
│ ├── main.py # Core CLI Entrypoint
|
|
44
|
+
│ ├── engine/ # Core engine mechanics
|
|
45
|
+
│ │ ├── config.py # Precedence-aware configuration loader
|
|
46
|
+
│ │ ├── disabler.py # Parsing and handling disable comments
|
|
47
|
+
│ │ ├── rule_interface.py # BaseRule & Violation class structures
|
|
48
|
+
│ │ ├── rules_loader.py # Dynamic imports of language rules
|
|
49
|
+
│ │ └── runner.py # Processing workflow (Check, Fix, Dry-Run)
|
|
50
|
+
│ └── rules/ # Language specific rules
|
|
51
|
+
│ └── sql/
|
|
52
|
+
│ ├── line_length.py # Checks lines exceeding maximum characters
|
|
53
|
+
│ └── keyword_case.py # Enforces and fixes uppercase SQL keywords
|
|
54
|
+
└── tests/ # Suite of unit & integration tests
|
|
55
|
+
├── test_config.py # Tests config parser
|
|
56
|
+
├── test_disabler.py # Tests comment disabler logic
|
|
57
|
+
├── test_runner.py # Tests CLI runner integration
|
|
58
|
+
└── rules/ # Rule specific tests
|
|
59
|
+
└── sql/
|
|
60
|
+
├── test_line_length.py
|
|
61
|
+
└── test_keyword_case.py
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Setup & Local Usage
|
|
67
|
+
|
|
68
|
+
Ensure you have [uv](https://github.com/astral-sh/uv) or `python` installed.
|
|
69
|
+
|
|
70
|
+
### 1. Running Tests
|
|
71
|
+
You can run the full test suite using `uv`:
|
|
72
|
+
```bash
|
|
73
|
+
uv run --with pyyaml python -m unittest discover -s tests -p "test_*.py"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Style Guides & Documentation
|
|
77
|
+
|
|
78
|
+
- **Custom Rules Guide**: Refer to the [Custom Rules Guide](docs/custom_rules.md) for details on writing your own custom linting rules, parameter interfaces, and execution validation.
|
|
79
|
+
- **Language Style Guides**: The other language-specific files in the `docs/` folder (such as `docs/sql.md`, `docs/java.md`, and `docs/xml.md`) are **automatically generated by the package** from code metadata. To compile and update these guides in sync with rule source definitions, run:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Generate style guides directly inside the 'docs/' directory
|
|
83
|
+
uv run mir/main.py --help docs
|
|
84
|
+
|
|
85
|
+
# Generate style guides inside a custom directory name
|
|
86
|
+
uv run mir/main.py --help docs:my_rules_docs
|
|
87
|
+
```
|
|
88
|
+
This scans all rule modules under `mir/rules/` and compiles the style guide markdown files directly.
|
|
89
|
+
|
|
90
|
+
### 2. Linting execution
|
|
91
|
+
```bash
|
|
92
|
+
# Check files (default path: current directory)
|
|
93
|
+
uv run mir/main.py
|
|
94
|
+
|
|
95
|
+
# Verbose check on a specific file path
|
|
96
|
+
uv run mir/main.py -v path/to/file.sql
|
|
97
|
+
|
|
98
|
+
# Safe auto-fixing rules on a directory (rewrites matching files)
|
|
99
|
+
uv run mir/main.py --fix path/to/file.sql
|
|
100
|
+
|
|
101
|
+
# Show dry-run diffs
|
|
102
|
+
uv run mir/main.py --dry-run path/to/file.sql
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Rules CLI Documentation & Help
|
|
106
|
+
You can query and inspect the list of rules and details directly from the command line:
|
|
107
|
+
```bash
|
|
108
|
+
# List all rules across all languages
|
|
109
|
+
uv run mir/main.py --help rules
|
|
110
|
+
|
|
111
|
+
# List only SQL rules
|
|
112
|
+
uv run mir/main.py --help rules:sql
|
|
113
|
+
|
|
114
|
+
# Show detailed documentation & examples for a specific rule across all languages
|
|
115
|
+
uv run mir/main.py --help IR-line-length
|
|
116
|
+
|
|
117
|
+
# Show detailed documentation for a specific rule inside a specific language
|
|
118
|
+
uv run mir/main.py --help sql:IR-keyword-case
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Docker Usage
|
|
124
|
+
|
|
125
|
+
Build and execute the linter entirely inside a containerized sandbox.
|
|
126
|
+
|
|
127
|
+
### 1. Build the Docker Image
|
|
128
|
+
```bash
|
|
129
|
+
docker build -t mitchells-ineffable-rules:latest .
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 2. Lint Files via Docker
|
|
133
|
+
Mount your workspace volume into `/workspace` in the container:
|
|
134
|
+
```bash
|
|
135
|
+
# Basic check on the current directory
|
|
136
|
+
docker run --rm -v "$(pwd):/workspace" mitchells-ineffable-rules:latest .
|
|
137
|
+
|
|
138
|
+
# Run auto-fixes
|
|
139
|
+
docker run --rm -v "$(pwd):/workspace" mitchells-ineffable-rules:latest --fix .
|
|
140
|
+
|
|
141
|
+
# Verbose dry-run on a file
|
|
142
|
+
docker run --rm -v "$(pwd):/workspace" mitchells-ineffable-rules:latest --dry-run -v query.sql
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Git Pre-commit Hook Integration
|
|
148
|
+
|
|
149
|
+
Integrate the linter to block bad code from being committed:
|
|
150
|
+
|
|
151
|
+
1. Copy the hook script into your repository's hook directory:
|
|
152
|
+
```bash
|
|
153
|
+
cp pre-commit-hook.sh .git/hooks/pre-commit
|
|
154
|
+
```
|
|
155
|
+
2. Make sure it is executable:
|
|
156
|
+
```bash
|
|
157
|
+
chmod +x .git/hooks/pre-commit
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
When committing files, the hook automatically detects staged SQL, Java, and XML changes, running them through the Docker container before allowing the commit to succeed.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
|
|
166
|
+
You can create an `.ir-config.yaml` file in the root of your project:
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
# Rules to globally disable
|
|
170
|
+
disable:
|
|
171
|
+
- IR-some-disabled-rule
|
|
172
|
+
|
|
173
|
+
# Global flags
|
|
174
|
+
verbose: true
|
|
175
|
+
fix: false
|
|
176
|
+
|
|
177
|
+
# External rule directories (to extend or replace built-in rulesets)
|
|
178
|
+
include_dirs:
|
|
179
|
+
- /path/to/my/custom_rules
|
|
180
|
+
|
|
181
|
+
# Rule mode for common languages: "extend" (default, built-in + custom) or "replace" (custom only)
|
|
182
|
+
rule_mode: extend
|
|
183
|
+
|
|
184
|
+
# Individual rule configuration
|
|
185
|
+
rules:
|
|
186
|
+
IR-line-length:
|
|
187
|
+
max_length: 120
|
|
188
|
+
IR-in-exists:
|
|
189
|
+
severity: warning # Override severity to warning (defaults to error if not specified)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Configurable Severity (`severity`)
|
|
193
|
+
|
|
194
|
+
By default, any unfixable violation (or check-mode violation) is treated as an **error** and causes the linter to return a non-zero exit code. You can configure any rule to be treated as a **warning** instead by setting the `severity` config option to `warning` in `.ir-config.yaml`. Warnings are still printed to output, but they do not cause the linter to return a failure exit code.
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
### Rule Configuration Fallbacks
|
|
198
|
+
|
|
199
|
+
To keep rules independent and decoupled, certain rules can dynamically inherit configuration parameters from other rules if they are not explicitly set.
|
|
200
|
+
|
|
201
|
+
For example, the SQL Column Layout formatter (`IR-column-layout`) dynamically falls back to query its parameters from the canonical rule configurations:
|
|
202
|
+
- `max_length` is inherited from the Line Length rule (`IR-line-length`)
|
|
203
|
+
- `indent_size` is inherited from the Indentation rule (`IR-indent`)
|
|
204
|
+
|
|
205
|
+
You can override these parameters specifically for the dependent rule or set them once on the canonical rule:
|
|
206
|
+
```yaml
|
|
207
|
+
rules:
|
|
208
|
+
# Sets line length limit to 80 (affects both IR-line-length and IR-column-layout fallback)
|
|
209
|
+
IR-line-length:
|
|
210
|
+
max_length: 80
|
|
211
|
+
|
|
212
|
+
# Specifically overrides column layout to use 100 instead
|
|
213
|
+
IR-column-layout:
|
|
214
|
+
max_length: 100
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Order of Precedence
|
|
218
|
+
|
|
219
|
+
Configuration parameters are merged with the following priority hierarchy (highest overrides lowest):
|
|
220
|
+
1. **Command Line Arguments** (e.g. `--fix`, `--disable IR-line-length`, `--include-dir /path`, `--rule-mode replace`)
|
|
221
|
+
2. **Environment Variables** (e.g. `IR_FIX=true`, `IR_DISABLE=IR-line-length`, `IR_INCLUDE_DIRS=/path`, `IR_RULE_MODE=replace`)
|
|
222
|
+
3. **YAML Config File** (e.g. `disable: [IR-line-length]`, `include_dirs: [/path]`, `rule_mode: replace` inside `.ir-config.yaml`)
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Writing Custom Rules
|
|
227
|
+
|
|
228
|
+
You can write custom rules to run against your codebase, either placing them inside the built-in rules directory or in an external custom rules directory. For full details on class specifications, default parameters, and strict execution validation, refer to the [Custom Rules Guide](docs/custom_rules.md).
|
|
229
|
+
|
|
230
|
+
### 1. Built-in Directory
|
|
231
|
+
Create your rule Python module under `mir/rules/<language>/my_rule_name.py`.
|
|
232
|
+
|
|
233
|
+
### 2. External Directory
|
|
234
|
+
1. Create a directory structure, e.g. `/my-custom-rules/<language>/my_rule_name.py`.
|
|
235
|
+
2. Provide the path to the linter via CLI (`--include-dir /my-custom-rules`), Env (`IR_INCLUDE_DIRS`), or config (`include_dirs: ["/my-custom-rules"]`).
|
|
236
|
+
3. Set the `--rule-mode` to specify whether custom rules should **extend** (default) or **replace** the built-in rule sets.
|
|
237
|
+
|
|
238
|
+
### Custom Rule Implementation Example
|
|
239
|
+
Each custom rule must subclass `BaseRule` (from `mir.engine.rule_interface`):
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
from typing import List, Dict, Any
|
|
243
|
+
from mir.engine.rule_interface import BaseRule, Violation
|
|
244
|
+
|
|
245
|
+
class MyXmlRule(BaseRule):
|
|
246
|
+
rule_id = "IR-xml-rule"
|
|
247
|
+
description = "A custom description of my rule."
|
|
248
|
+
category = "general"
|
|
249
|
+
is_fixable = "yes" # "yes", "no", or "sometimes"
|
|
250
|
+
|
|
251
|
+
def check(self, content: str, file_path: str, rule_config: Dict[str, Any]) -> List[Violation]:
|
|
252
|
+
violations = []
|
|
253
|
+
# Implement check logic...
|
|
254
|
+
# If a violation is found, append Violation(...) to list.
|
|
255
|
+
return violations
|
|
256
|
+
|
|
257
|
+
def fix(self, content: str, file_path: str, rule_config: Dict[str, Any]) -> str:
|
|
258
|
+
# If is_fixable is "yes" or "sometimes", return the modified content string
|
|
259
|
+
return content
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Publishing to PyPI
|
|
265
|
+
|
|
266
|
+
For instructions on building distribution archives, TestPyPI validation, and publishing to the live Python Package Index, refer to the [PyPI Publishing Guide](docs/publishing.md).
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
*This entire package was built using **Antigravity**, a powerful agentic AI coding assistant designed by the Google DeepMind team.*
|
|
271
|
+
|
|
272
|
+
*Total Cumulative Development Time: ~11.5 hours*
|
|
273
|
+
|
|
274
|
+
*Last Developer Session: 2026-07-13T10:57:00Z*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0a16"
|