Intelligent syntax highlighting, validation, and formatting for Python template strings (PEP 750).
📣 💼 Maintainer update: Open to opportunities. 🔗 koxudaxi.dev
- 📦 Installation - PyPI, VSCode Extension, Build from source
- 🔍 Check Command - CLI validation & output formats
- 🧹 Format Command - Canonical formatting for supported templates
- 🖥️ LSP Server - Editor integration (VSCode, Claude Code, Codex, Neovim, etc.)
- ⚙️ Configuration - pyproject.toml & ignore files
- 🌐 Supported Languages - HTML, T-HTML, TDOM, SQL, JS, CSS, JSON, YAML, TOML
t-linter validates and formats embedded languages inside Python template strings (PEP 750). Built with Rust and Tree-sitter for speed, it ships as a single binary that works as both a CLI tool and an LSP server.
t-linter check— validate template string syntaxt-linter format— canonically reformat supported template literalst-linter lsp— start the Language Server Protocol server for editor integration
- 🔍 Linting - Detect syntax errors in embedded HTML, JSON, YAML, TOML, CSS, JavaScript, SQL
- 🧹 Formatting - Canonical formatting for HTML, T-HTML, TDOM, JSON, YAML, TOML templates
- 🎨 Syntax Highlighting - Smart highlighting via LSP semantic tokens
- 🔧 Type-based Detection - Understands
Annotated[Template, "html"]and type aliases - 🧩 Callee Inference - Detects backend languages from helpers such as
tdom.html(...) - 🚀 Fast - Single Rust binary with Tree-sitter parsers
| Language | Annotation | Check | Format | Highlight |
|---|---|---|---|---|
| HTML | "html" |
✅ | ✅ | ✅ |
| T-HTML | "thtml" |
✅ | ✅ | ✅ |
| TDOM | "tdom" |
✅ | ✅ | ✅ |
| JSON | "json" |
✅ | ✅ | ✅ |
| YAML | "yaml", "yml" |
✅ | ✅ | ✅ |
| TOML | "toml" |
✅ | ✅ | ✅ |
| CSS | "css" |
✅ | — | ✅ |
| JavaScript | "javascript", "js" |
✅ | — | ✅ |
| SQL | "sql" |
✅ | — | ✅ |
For HTML, T-HTML, TDOM, JSON, YAML, and TOML, t-linter uses dedicated Rust backends (tstring-* crates) for strict validation and canonical formatting. CSS, JavaScript, and SQL use Tree-sitter for syntax validation and highlighting.
pip install t-linterOr add to your project's dependencies:
# Using uv (recommended)
uv add t-linter
# Or using pip with requirements.txt
echo "t-linter" >> requirements.txt
pip install -r requirements.txtThis provides the t-linter CLI tool and LSP server.
If you use VSCode, install the extension for seamless editor integration:
- Open VSCode → Extensions (Ctrl+Shift+X / Cmd+Shift+X)
- Search for "t-linter" → Install "T-Linter" by koxudaxi
- On Linux x64, macOS x64/arm64, and Windows x64, the extension bundles the
t-linterbinary, so no separate PyPI install is required. - On unsupported platforms, or if you want to override the bundled binary, install
t-lintervia PyPI (see above) and sett-linter.serverPathto the full executable path. - Choose one save-time formatting mode:
- Ruff coexistence mode keeps Ruff as the Python formatter and runs t-linter through
source.fixAll.t-linter:{ "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.t-linter": "explicit" } } } - t-linter formatter mode keeps the existing formatter-only workflow:
{ "[python]": { "editor.defaultFormatter": "koxudaxi.t-linter", "editor.formatOnSave": true } }
- Ruff coexistence mode keeps Ruff as the Python formatter and runs t-linter through
- If semantic highlighting conflicts with another Python extension, set the Python language server to
"None"in your workspace settings:If you need those features, you can keep the Python language server enabled. t-linter will still provide template-string diagnostics, code actions, and formatting, though semantic highlighting may conflict.{ "python.languageServer": "None" }
If you use an external t-linter binary and it is not in your PATH, set t-linter.serverPath in VSCode settings to the full path of the executable.
→ Install from VSCode Marketplace
git clone https://github.com/koxudaxi/t-linter
cd t-linter
cargo install --path crates/t-linterValidate template strings for syntax errors:
# Check a single file
t-linter check file.py
# Check a directory
t-linter check src/
# Output formats: human (default), json, github
t-linter check file.py --format json
t-linter check file.py --format github # GitHub Actions annotations
# Exit with error code if issues found (useful for CI)
t-linter check file.py --error-on-issuesExample output:
example.py:4:46: error[embedded-parse-error] Invalid json syntax in template string (language=json)
1 files scanned, 1 templates scanned, 1 diagnostics, 0 failed files
Exit codes:
| Code | Meaning |
|---|---|
0 |
Run completed successfully |
1 |
Issues were found and --error-on-issues was set |
2 |
Operational failure such as an unreadable file |
Rewrite supported template literals (HTML, T-HTML, JSON, YAML, TOML) in place:
# Format a single file
t-linter format file.py
# Format a directory
t-linter format src/
# Check whether formatting would change any files (for CI)
t-linter format --check file.py
# Override the formatter line length
t-linter format --line-length 100 file.py
# Format stdin
cat file.py | t-linter format --stdin-filename file.py -Templates in unsupported languages (CSS, JavaScript, SQL) are left unchanged.
Start the Language Server Protocol server for editor integration:
t-linter lspThe LSP server provides:
- Semantic Tokens — syntax highlighting for embedded languages
- Diagnostics — real-time validation with 250ms debouncing
- Document Formatting — full document and range formatting
- Code Actions —
source.fixAll.t-linterfor document-level rewrites andrefactor.rewrite.t-linterfor single-template selection rewrites
Add t-linter as an LSP server in your project's .claude/settings.json:
{
"lsp": {
"t-linter": {
"command": "t-linter",
"args": ["lsp"],
"languages": ["python"]
}
}
}Claude Code will then use t-linter's diagnostics when editing Python files containing template strings.
Add the LSP configuration to your project's codex.json or start t-linter's LSP server as part of your development environment. The t-linter check and t-linter format commands can also be used directly in Codex's sandbox:
t-linter check src/
t-linter format --check src/vim.lsp.start({
name = "t-linter",
cmd = { "t-linter", "lsp" },
filetypes = { "python" },
})Any editor with LSP support can use t-linter. Configure the LSP client to start t-linter lsp as the server command for Python files.
Configuration via pyproject.toml:
[tool.t-linter]
line-length = 80
extend-exclude = ["generated", "vendor"]
ignore-file = ".t-linterignore"| Key | Description |
|---|---|
line-length |
Formatter print width (applies to HTML and T-HTML only; JSON, YAML, and TOML use fixed formatting rules) |
exclude |
Override the built-in default excludes |
extend-exclude |
Add more exclude patterns on top of the defaults |
ignore-file |
Path to a gitignore-style ignore file, relative to the project root |
By default, t-linter also reads .t-linterignore from the project root if it exists.
from typing import Annotated
from string.templatelib import Template
def render_html(template: Annotated[Template, "html"]) -> None:
pass
def run_sql(template: Annotated[Template, "sql"]) -> None:
pass
type css = Annotated[Template, "css"]
type yaml_config = Annotated[Template, "yaml"]
type toml_config = Annotated[Template, "toml"]
def load_styles(template: css) -> None:
pass
def load_yaml(template: yaml_config) -> None:
pass
def load_toml(template: toml_config) -> None:
pass
title = "t-linter"
heading = "Template strings with syntax highlighting"
content = "Interpolations stay as normal Python expressions."
render_html(t"""
<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1 style="color: #007acc">{heading}</h1>
<p>{content}</p>
</body>
</html>
""")
start_date = "2026-01-01"
run_sql(t"""
SELECT u.name, u.email, p.title
FROM users u
JOIN posts p ON u.id = p.author_id
WHERE u.created_at > {start_date}
ORDER BY u.name
""")
padding = 24
load_styles(t"""
.container {{
max-width: 1200px;
margin: 0 auto;
padding: {padding}px;
}}
""")
app_name = "demo-app"
load_yaml(t"""
app:
name: {app_name}
debug: true
""")
project_name = "demo-project"
version = "0.1.0"
load_toml(t"""
[project]
name = "{project_name}"
version = "{version}"
""")Use {{ and }} when the embedded language needs literal braces, such as CSS
or JSON objects.
For html, <title>{value}</title> is allowed and treated as escaped text.
<script>, <style>, and <textarea> still reject interpolations for safety.
- ✅ Language Server Protocol (LSP) - Fully implemented
- ✅ Syntax Highlighting - Supports HTML, T-HTML, SQL, JavaScript, CSS, JSON, YAML, TOML
- ✅ Type Alias Support - Recognizes
type html = Annotated[Template, "html"] - ✅ Linting (
checkcommand) - Validate template strings for syntax errors - ✅ Formatting (
formatcommand) - Canonical formatting for HTML, T-HTML, JSON, YAML, TOML - 🚧 Statistics (
statscommand) - Analyze template string usage across codebases - 📋 Cross-file Type Resolution - Track type aliases across module boundaries
- 📋 Auto-completion - Context-aware completions within template strings