Formatteur de code
Outil de formatage automatique du code Catnip avec style opinionated.
Vue d'ensemble
Le formatteur catnip format applique un style de code cohérent et uniforme sur l'ensemble du code Catnip.
Contrairement à un pretty-printer qui reconstruit le code depuis l'AST, le formatteur utilise une approche
token-based qui préserve :
- Les commentaires (inline et standalone)
- Les newlines intentionnelles (pas de reformatage destructif)
- La structure du code (seuls les espaces et l'indentation sont ajustés)
Le style appliqué s'inspire de Black (Python) : un seul style, pas de configuration, zéro débat.
Utilisation CLI
Formater un fichier
# Formater et afficher sur stdout
catnip format script.cat
# Formater et sauvegarder (redirection)
catnip format script.cat > script_formatted.cat
# Formater en place (écrase le fichier)
catnip format script.cat > /tmp/temp.cat && mv /tmp/temp.cat script.cat
Formater depuis stdin
# Lecture stdin avec --
echo 'x=1+2*3' | catnip format --
# Pipe
cat script.cat | catnip format --
# Formater multiple fichiers
for f in *.cat; do catnip format "$f" > "${f}.tmp" && mv "${f}.tmp" "$f"; done
Règles de formatage
Espaces
Opérateurs binaires : espace avant et après
# Avant
x=1+2*3
y==42
# Après
x = 1 + 2 * 3
y == 42
Opérateurs unaires : pas d'espace
# Avant
- x
not y
# Après
-x
not y
Assignation et lambda
# Avant
x=42
f=(n)=>{n*2}
# Après
x = 42
f = (n) => {n * 2}
Virgules : espace après, pas avant
# Avant
list(1,2,3)
func(a,b,c)
# Après
list(1, 2, 3)
func(a, b, c)
Parenthèses : pas d'espace intérieur
# Avant
func( x , y )
( a + b )
# Après
func(x, y)
(a + b)
Broadcasting : pas d'espace après .[
# Avant
numbers.[ * 2 ]
data.[ if > 10 ]
# Après
numbers.[* 2]
data.[if > 10]
Indentation
Blocs : 4 espaces par niveau
# Avant
if x{
y=1
}
# Après
if x {
y = 1
}
Structures imbriquées
# Avant
match x{
1=>{print("one")}
_=>{
if y{print("other")}
}
}
# Après
match x {
1 => {print("one")}
_ => {
if y {print("other")}
}
}
Commentaires
Commentaires inline : minimum 2 espaces avant
# Avant
x = 42# important
# Après
x = 42 # important
Commentaires standalone : indentation normale
# Avant
if x {
# case one
y = 1
}
# Après
if x {
# case one
y = 1
}
Newlines
Maximum 2 newlines consécutives
# Avant
x = 1
y = 2
# Après
x = 1
y = 2
Une seule newline en fin de fichier
# Avant
x = 1
# Après
x = 1
Exemples
Code mal formaté
# Factorial
factorial =( n ) => {
if n<=1{return 1}
else{return n*factorial(n-1)} # recursive
}
x=factorial( 5 )
print(x)
Code formaté
# Factorial
factorial = (n) => {
if n <= 1 { return 1}
else { return n * factorial(n - 1)} # recursive
}
x = factorial(5)
print(x)
Pattern matching
# Avant
check=(x)=>{
match x{
1|2|3=>{print("small")}
n if n>10=>{print("big")} # guard
_=>{print("other")}
}
}
# Après
check = (x) => {
match x {
1 | 2 | 3 => {print("small")}
n if n > 10 => {print("big")} # guard
_ => {print("other")}
}
}
Broadcasting et listes
# Avant
numbers=list(1,2,3,4,5)
doubled=numbers.[*2]
filtered=numbers.[if>3]
# Après
numbers = list(1, 2, 3, 4, 5)
doubled = numbers.[* 2]
filtered = numbers.[if > 3]
Architecture interne
Le formatteur utilise une approche token-based en 3 phases :
1. Tokenisation avec préservation des commentaires
from catnip._rs import TreeSitterParser
# Parser Rust Tree-sitter
parser = TreeSitterParser()
tokens = parser.tokenize(source)
# Chaque token contient : type, value, line, column, end_line, end_column
# Les commentaires sont extraits automatiquement depuis l'arbre de syntaxe
Le tokenizer Rust extrait directement les tokens depuis l'arbre Tree-sitter, préservant naturellement les commentaires comme nœuds de l'arbre syntaxique.
2. Application des règles de formatage
Le formatteur parcourt les tokens et applique les règles localement :
for token in tokens:
# Indentation en début de ligne
if at_line_start:
result.append(' ' * (indent_level * 4))
# Espace avant opérateur binaire ?
if needs_space_before(token, prev_token):
result.append(' ')
# Token lui-même
result.append(token.value)
# Espace après virgule, opérateur, etc. ?
if needs_space_after(token, next_token):
result.append(' ')
Règles principales :
_needs_space_before(): opérateurs binaires, keywords_needs_space_after(): virgules,=>, keywords- Gestion de l'indentation via
LBRACE/RBRACE - Préservation des commentaires avec espacement minimal
3. Normalisation finale
# Max 2 newlines consécutives
formatted = re.sub(r'\n{3,}', '\n\n', formatted)
# Une seule newline en fin
formatted = formatted.rstrip('\n') + '\n'
Différences avec un pretty-printer AST
| Critère | Pretty-printer AST | Formatteur token-based |
|---|---|---|
| Commentaires | ✗ Perdus (ignorés par parser) | ✓ Préservés |
| Newlines | ✗ Reconstruites arbitrairement | ✓ Respectées |
| Structure | ✗ Reformatée complètement | ✓ Ajustée localement |
| Fidélité | ✗ Code reconstruit | ✓ Code original préservé |
| Performance | Plus lent (parse + transform) | Plus rapide (lex only) |
Le formatteur token-based est un formatter respectueux comme Black, pas un uglifier qui détruit le code.
Utilisation programmatique
from catnip.formatter import format_code
source = """
x=1+2*3 # no spaces
y = 42 # too many
"""
formatted = format_code(source)
print(formatted)
# Output:
# x = 1 + 2 * 3 # no spaces
# y = 42 # too many
Formatteur avec options
from catnip.formatter import TokenBasedFormatter
formatter = TokenBasedFormatter(indent_size=2) # 2 espaces au lieu de 4
formatted = formatter.format(source)
Intégration éditeurs
VSCode
Créer .vscode/settings.json :
{
"[catnip]": {
"editor.defaultFormatter": "catnip-formatter",
"editor.formatOnSave": true
}
}
Vim/Neovim
" Format on save
autocmd BufWritePre *.cat !catnip format % > %.tmp && mv %.tmp %
" Format selection
vnoremap <leader>f :!catnip format --<CR>
Pre-commit hook
.git/hooks/pre-commit :
#!/bin/bash
for file in $(git diff --cached --name-only --diff-filter=ACM | grep '\.cat$'); do
catnip format "$file" > "$file.tmp" && mv "$file.tmp" "$file"
git add "$file"
done
Limitations connues
- Pas de mode "check" : le formatteur reformate toujours, pas de vérification sans modification
- Pas d'option
--in-place: nécessite redirection manuelle - Broadcasting edge cases :
.[peut avoir un espace superflu dans certains cas complexes - Blocs single-line : pas d'optimisation pour
{ return 1 }vs{\n return 1\n}
Ces limitations reflètent le principe : un seul style, pas de config, comportement prévisible.