Supported Languages¶
t-linter supports syntax highlighting and validation for the following embedded languages in Python template strings.
Language Detection¶
Languages are detected through direct annotations, function parameter annotations, type aliases, and supported callee inference such as tdom.html(...).
from typing import Annotated
from string.templatelib import Template
# Direct annotation
template: Annotated[Template, "html"] = t"<p>{content}</p>"
# Type alias
type html = Annotated[Template, "html"]
page: html = t"<div>{content}</div>"
Supported Languages¶
| Language | Annotation | Check | Format | Highlight | Engine |
|---|---|---|---|---|---|
| HTML | "html" |
✅ | ✅ | ✅ | tstring-html backend |
| T-HTML | "thtml" |
✅ | ✅ | ✅ | tstring-thtml backend |
| TDOM | "tdom" |
✅ | ✅ | ✅ | tstring-tdom backend |
| JSON | "json" |
✅ | ✅ | ✅ | tstring-json backend |
| YAML | "yaml", "yml" |
✅ | ✅ | ✅ | tstring-yaml backend |
| TOML | "toml" |
✅ | ✅ | ✅ | tstring-toml backend |
| CSS | "css" |
✅ | — | ✅ | Tree-sitter |
| JavaScript | "javascript", "js" |
✅ | — | ✅ | Tree-sitter |
| SQL | "sql" |
✅ | — | ✅ | Tree-sitter |
- Check — syntax validation via
t-linter checkCLI and LSP diagnostics - Format — canonical formatting via
t-linter formatCLI and LSP formatting - Highlight — semantic tokens via LSP for editor syntax highlighting
Backend-powered vs Tree-sitter Languages¶
For backend-powered languages (HTML, T-HTML, TDOM, JSON, YAML, TOML), t-linter splits responsibilities:
- Highlighting: Tree-sitter only, for low-latency semantic tokens
- Validation: Strict parsing through the dedicated Rust backends (
tstring-*crates) - Formatting: Canonical formatting through the same Rust backends
For Tree-sitter-only languages (CSS, JavaScript, SQL), t-linter uses Tree-sitter for both highlighting and validation. Formatting is not yet available for these languages.
Template Metadata Markers¶
String metadata remains the lightweight way to declare a template language:
Annotated[Template, "json"]. It needs no imports and will not be deprecated.
t-linter also recognizes Python marker classes, which let language packages
carry schema, dialect, and option metadata on normal Python objects instead of
reserving more bare strings.
The built-in json_tstring.Json marker declares the JSON language when it is
used in template metadata or as the template annotation itself. Its schema
option also enables t-linter check diagnostics for local or imported
TypedDict and dataclass models:
from typing import Annotated, TypedDict
from string.templatelib import Template
from json_tstring import Json
class Order(TypedDict):
id: int
payload: Annotated[Template, Json(schema=Order)] = t'{"id": {order_id}}'
default_payload: Annotated[Template, Json] = t'{"id": {order_id}}'
type OrderPayload = Annotated[Template, Json(schema=Order)]
aliased_payload: OrderPayload = t'{"id": {order_id}}'
Annotated[Template, Json] uses the marker class with its defaults.
Annotated[Template, Json()] uses a marker instance. Marker kwargs such as
schema=Order or future dialect/options belong to the marker, not to the core
language protocol.
Do not combine string language metadata and marker language metadata for new
code. If the string and marker agree, t-linter reports a redundant-language
warning with a fix to remove the string. If they disagree, or if one
Annotated[...] contains more than one language marker, t-linter reports a
metadata conflict.
t-linter checks required keys, unknown static keys, and static scalar value shapes against schema models. See Check Command for details.
Custom marker classes can declare a language structurally with
tstring_language; no t-linter-specific base class is required. The class may
live in source or in a .pyi stub so packages can expose language metadata
through their normal Python toolchain. Static base classes are followed, but
dynamic decorators, metaclasses, computed values, and reassignment are not part
of the marker protocol.
from typing import Annotated, Final
from string.templatelib import Template
class YamlTemplate:
tstring_language: Final = "yaml"
config: Annotated[Template, YamlTemplate()] = t"name: {name}"
SQL Notes¶
SQL templates always receive Tree-sitter syntax validation when they are annotated as "sql".
psycopg-specific t-string checks are opt-in because the rules describe psycopg semantics, not SQL in general.
Enable them with:
When enabled, t-linter reports psycopg t-string errors for unsupported conversions, unknown format specs, composable/spec mismatches, direct dict parameters that need Json or Jsonb, tuple parameters, IN ({ids}) list-parameter patterns, and multiple SQL statements in a single template.
For catalog-backed parameter narrowing, configure a PostgreSQL connection and prepare the offline cache:
The cache is written to .t-linter/sql-cache/ and should be committed with the
queries that use it. t-linter sql prepare --check reports stale or missing
cache entries when PostgreSQL is reachable. If the configured database URL
resolves but the database is unavailable and a committed cache exists, it trusts
that cache and exits successfully. When the cache is present, LSP interpolation
type checking can narrow plain psycopg parameters from PostgreSQL types even if
the editor session has no live database.
See SQL Catalog Cache for the end-to-end workflow, CI behavior, and an example diagnostic.
HTML Notes¶
For html, <title>{value}</title> is allowed and rendered as escaped text.
<script>, <style>, and <textarea> still reject interpolations.
Use these patterns:
- use
<title>{value}</title>for dynamic document titles - keep
<script>,<style>, and<textarea>static - move dynamic values into attributes
- move dynamic values into normal element content such as
<p>{content}</p>
Examples¶
from typing import Annotated
from string.templatelib import Template
def render_html(template: Annotated[Template, "html"]) -> None:
pass
def render_thtml(template: Annotated[Template, "thtml"]) -> None:
pass
def run_sql(template: Annotated[Template, "sql"]) -> None:
pass
def run_javascript(template: Annotated[Template, "javascript"]) -> None:
pass
type css = Annotated[Template, "css"]
type json_payload = Annotated[Template, "json"]
type yaml_config = Annotated[Template, "yaml"]
type toml_config = Annotated[Template, "toml"]
def load_css(template: css) -> None:
pass
def load_json(template: json_payload) -> None:
pass
def load_yaml(template: yaml_config) -> None:
pass
def load_toml(template: toml_config) -> None:
pass
def Card(*, title: str, children: str | None = None) -> object:
return None
def Badge(*, tone: str = "neutral", children: str | None = None) -> object:
return None
title = "Dashboard"
render_html(t"""
<html>
<body><h1>{title}</h1></body>
</html>
""")
status = "ready"
render_thtml(t"""
<Card title="{title}">
<Badge tone="success">{status}</Badge>
</Card>
""")
from tdom import html
page = html(t"""
<{Card} title={title}>
<span>{status}</span>
</{Card}>
""")
user_id = 42
run_sql(t"""
SELECT * FROM users WHERE id = {user_id}
""")
message = "'hello from t-linter'"
run_javascript(t"""
console.log({message});
""")
width = 1200
load_css(t"""
.container {{
max-width: {width}px;
}}
""")
name = "Ada"
age = 37
load_json(t"""
{{
"name": {name},
"age": {age}
}}
""")
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.