t-linter ๐โจ
July 10, 2026 ยท View on GitHub
Intelligent syntax highlighting, validation, and formatting for Python template strings (PEP 750).
๐ฃ ๐ผ Maintainer update: Open to opportunities. ๐ koxudaxi.dev
๐ Documentation
- ๐ฆ 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.)
- ๐งช Interpolation Type Checking - Optional type checker-backed diagnostics
- ๐๏ธ SQL Catalog Cache - PostgreSQL-backed psycopg parameter narrowing
- โ๏ธ Configuration - pyproject.toml & ignore files
- ๐ Supported Languages - HTML, T-HTML, TDOM, SQL, JS, CSS, JSON, YAML, TOML
Overview
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 sql prepareโ cache PostgreSQL parameter metadata for psycopg SQL type narrowingt-linter lspโ start the Language Server Protocol server for editor integration
Features
- ๐ 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"], type aliases, and marker classes withtstring_language - ๐งช Interpolation Type Checking - Optional LSP diagnostics for JSON, YAML, TOML, psycopg SQL, and TDOM interpolations through Ty, Pyright, or Pyrefly
- ๐๏ธ SQL Catalog Cache - Narrows psycopg SQL parameters from PostgreSQL metadata, even when the editor session has no live database
- ๐ JSON Schema Binding - Checks JSON template keys and static value shapes against
TypedDictor dataclass models withJson(schema=...) - ๐งฉ Callee Inference - Detects backend languages from helpers such as
tdom.html(...) - ๐ Fast - Single Rust binary with Tree-sitter parsers
Supported Languages
| 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.
Installation
PyPI (Recommended)
pip install t-linter
Or 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.txt
This provides the t-linter CLI tool and LSP server.
VSCode Extension
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:
Set{ "[python]": { "editor.defaultFormatter": "koxudaxi.t-linter", "editor.formatOnSave": true } }"t-linter.format.runRuffPipeline": trueif you want this formatter mode to run Ruff fixAll, import organization, Ruff formatting, and then t-linter in one composed formatting transaction. VSCode only allows one default formatter per language, which is why t-linter also exposes a save-time code action lane for template-string formatting.
- 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
Build from Source
git clone https://github.com/koxudaxi/t-linter
cd t-linter
cargo install --path crates/t-linter
Usage
Check
Validate 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, sarif
t-linter check file.py --format json
t-linter check file.py --format github # GitHub Actions annotations
t-linter check file.py --format sarif # SARIF 2.1.0
# Exit with error code if issues found (useful for CI)
t-linter check file.py --error-on-issues
# Apply or preview suggested edits
t-linter check file.py --fix
t-linter check file.py --diff
Example output:
example.py:4:47: error[embedded-parse-error] Expected a JSON value. (language=json)
1 files scanned, 1 templates scanned, 1 diagnostics, 0 failed files
JSON templates can also be bound to a Python schema marker so t-linter check
validates required keys, unknown keys, and static scalar shapes:
from typing import Annotated, NotRequired, TypedDict
from string.templatelib import Template
from json_tstring import Json
class Order(TypedDict):
id: int
name: str
note: NotRequired[str]
payload: Annotated[Template, Json(schema=Order)] = (
t'{{"id": "abc", "nme": "Ada"}}'
)
Json(schema=Order) declares the JSON template language and binds the schema.
payload: Json[Order] = t"..." is also accepted as a shorthand. Plain string
metadata such as Annotated[Template, "json"] remains supported for
language-only detection, but new code should not combine it with Json(...);
t-linter reports redundant or conflicting language metadata. t-linter reads the
annotation text only; the template is still a normal Python template string.
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 |
Format
Rewrite supported template literals (HTML, T-HTML, TDOM, 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.
For CI pipelines that also use Ruff, run the tools explicitly in sequence:
ruff check --fix . && ruff format . && t-linter format .
ruff check . && ruff format --check . && t-linter format --check .
Stats
Count template strings without running diagnostics:
t-linter stats .
t-linter stats . --format json
SQL Catalog Cache
For psycopg SQL templates, t-linter can cache PostgreSQL describe metadata and use it later for LSP interpolation type diagnostics:
[tool.t-linter.sql]
library = "psycopg"
database-url = "env:DATABASE_URL"
search-path = "public"
DATABASE_URL=postgresql://postgres:postgres@localhost/app t-linter sql prepare .
DATABASE_URL=postgresql://postgres:postgres@localhost/app t-linter sql prepare --check .
Commit .t-linter/sql-cache/ with the code. When PostgreSQL is reachable,
--check reports stale or missing cache entries with exit code 2; when the
configured database URL resolves but the database is unreachable and a committed
cache exists, it trusts that cache and exits successfully. The LSP reads the
cache, narrows plain parameters such as {user_id} from PostgreSQL types, and
reports diagnostics on the original interpolation expression.
Set T_LINTER_SQL_PYTHON if python3 on PATH does not have psycopg
installed. See SQL Catalog Cache for the full
workflow.
LSP Server
Start the Language Server Protocol server for editor integration:
t-linter lsp
# Start the LSP server with the composed Ruff pipeline -> t-linter document formatting
t-linter lsp --ruff-pipeline
The LSP server provides:
- Semantic Tokens โ syntax highlighting for embedded languages
- Diagnostics โ real-time validation with 250ms debouncing
- Interpolation Type Checking โ optional JSON, YAML, TOML, psycopg SQL interpolation value diagnostics and TDOM component prop interpolation diagnostics through Ty, Pyright, or Pyrefly
- Document Formatting โ full document and range formatting
- Code Actions โ
source.fixAll.t-linterfor document-level rewrites andrefactor.rewrite.t-linterfor single-template selection rewrites
Interpolation value type checking is opt-in and applies to JSON, YAML, TOML, psycopg SQL templates and TDOM component props. Enable it from an LSP client with initialization options:
{
"typeChecking": {
"enabled": true,
"checker": "pyright",
"command": "/path/to/pyright-langserver",
"args": ["--stdio"]
}
}
When command and args are omitted, t-linter uses the selected checker defaults: ty server, pyright-langserver --stdio, or pyrefly lsp. It searches active virtual environments, workspace .venv/venv, uv projects, and then the checker on PATH.
See Interpolation Type Checking for the architecture, behavior, and implementation details.
Claude Code
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.
Codex
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/
Neovim
vim.lsp.start({
name = "t-linter",
cmd = { "t-linter", "lsp" },
filetypes = { "python" },
})
Other Editors
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
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, T-HTML, and TDOM 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.
Quick Start Example
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.
Roadmap
Planned Features
- โ Language Server Protocol (LSP) - Fully implemented
- โ Syntax Highlighting - Supports HTML, T-HTML, TDOM, SQL, JavaScript, CSS, JSON, YAML, TOML
- โ
Type Alias and Marker Support - Recognizes
type html = Annotated[Template, "html"]and marker classes withtstring_language - โ
Linting (
checkcommand) - Validate template strings for syntax errors - โ
Formatting (
formatcommand) - Canonical formatting for HTML, T-HTML, TDOM, JSON, YAML, TOML - โ
Statistics (
statscommand) - Analyze template string usage across codebases - ๐ Cross-file Type Resolution - Track type aliases across module boundaries