Variables
Variables are declared with the let keyword. Every variable must be initialized at declaration.
let x = 42
let y = x + 1
let z = x * y - 10
Variable names must start with a letter or underscore and may contain letters, digits, and underscores.
Rules
- A variable can hold any Jade value. See Types for the full list.
- A variable may be referenced in any expression written after it.
- Referencing an undeclared name is a compile-time error.
jade checkcatches it before the program runs, so nothing before the mistake executes. - Variables declared inside a function body are local to that call and are not visible outside.
Blocks
A name first introduced inside an if, while, or for block cannot be used after the block ends:
if true {
let inner = 9
}
print(inner) // error: undefined variable 'inner'
A let that reuses a name from outside the block is a different case: it overwrites the outer variable rather than shadowing it, and the new value survives the block.
let x = 1
if true {
let x = 99
}
print(x) // 99, not 1
Pick a fresh name inside a block when you want the outer one left alone.
Statements end at the line break
There is no statement separator to write. A statement ends where its line does, and Jade fills in the break for you.
Jade does not accept a semicolon you type yourself — let x = 1; is a lexer error, not a harmless extra. Leave the line ending bare:
let x = 1
let y = 2
A line break inside ( … ) or [ … ] does not end a statement, so a long call or array can span several lines.
Reassigning
Assignment without let changes a variable that already exists:
let count = 1
count = count + 1
print(count) // 2
A second let on the same name is allowed too, and simply rebinds it. Either way the new value need not be the same type as the old one — Jade infers a variable's type from what it currently holds rather than fixing it at declaration:
let value = "text"
value = 7
print(value) // 7
Decorators
A let may carry a decorator, which wraps the value in a function call:
fn shout(s) {
return s.upper()
}
@shout
let greeting = "hello" // same as: let greeting = shout("hello")
print(greeting) // HELLO
The point is not brevity — it is that the wrapper sits above the declaration instead of around it, so what the value actually is stays readable.
A decorator may take its own arguments. The decorated value goes first:
fn fence(s, tag) {
return f"<{tag}>{s}</{tag}>"
}
@fence("note")
let body = "keep it short" // same as: let body = fence("keep it short", "note")
Decorators stack, and the one written first is applied first:
@shout
@fence("p")
let loud = "hello" // fence(shout("hello"), "p") → <p>HELLO</p>
That is the same order fn decorators use, and the reverse of Python's.
A decorator can also be namespaced, using :: like an import:
@style::tagged
let body = "keep it short"
The same syntax works on a prompt declaration, which is where it earns its keep — see Prompts and Inference.