Skip to content

Supported syntax

src/chrome.ts2
const ARROW = 16;
const STEP = 8;
const WHEEL = 45;
const CAP = 5;
const MIN_THUMB = 10;
/** Puts the thumb where the content's scroll position says it should be. */
const trackScroll = (gutter: Ref, thumb: Ref, capTop: Ref, capBottom: Ref, content: Ref): void => {
const range: Int = Math.max(1, content.scrollHeight - content.height);
const travel: Int = gutter.height - thumb.height;
const y: Int = ARROW + travel * content.scrollY / range;
thumb.y = y;
capTop.y = y;
capBottom.y = y + thumb.height - CAP;
};
  • const and let. A local may carry a type or leave the build to infer it.
  • Functions, with typed parameters and a declared return type — always a const bound to an arrow, as below.
  • A function returning Component is a component; one returning UI declares an interface; anything else is an ordinary function. An exported function with typed parameters is also what the server can call — see Server entry points.

A module-level const with no type annotation is a constant. With a type — const GAP: Int = 4 — it cannot be read inside a function or a component (TS2E003). Leave the annotation off.

One form covers everything — components, helpers, handlers:

src/quest-log.ts2
const QuestEntry = ({ name, state }: { name: String; state: QuestState }): Component => (
<text x={4} w={{ fill: 8 }} h={ROW_H} text={name} font={Fonts.PLAIN}
valign="middle" shadow
color={state === QuestState.COMPLETE ? Colors.GREEN
: state === QuestState.IN_PROGRESS ? Colors.YELLOW : Colors.RED}
options={['Read journal']} optionSubject={name} />
);

Concise body when it is a single expression; braces and a return when it is not:

src/wares.ts2
const rowHeight = (ware: Ware): Int => (ware.restock ?? 0) > 0 ? 38 : 26;
src/settings.ts2
const restoreDefaults = (): void => {
const [, setRoofs] = useState(roofRemoval);
const [, setShiftDrop] = useState(shiftClickDrop);
const [, setOrbs] = useState(dataOrbs);
const [, setLevelUps] = useState(levelUpMessages);
const [, setLevel] = useState(zoom);
setRoofs(1);
setShiftDrop(0);
setOrbs(1);
setLevelUps(0);
setLevel(1);
};

A function is declared at module level. An arrow declared inside another function may be a handler and nothing else: onClick={reset} where reset is a local arrow is fine; calling reset() from another handler is refused (TS2E120). A named function is not a value either — it cannot be passed to a function, returned from one, or stored in state.

src/wares.ts2
enum Category { TOOLS, CARE, SEEDS }
src/wares.ts2
type Ware = {
item: Item;
name: String;
price: Int;
category: Category;
/** How many the stall restocks to. Absent on the one-off pieces. */
restock?: Int;
};
src/refs.ts2
export declare const SlotIcons: typeof GameEnums.ENUM_904;
  • enum — a set of named whole numbers. Always a constant. An enum whose members the server keys on is marked @wire and given written values; see Server entry points.
  • type and interface — a props type for a component, or a record type for the rows of a collection. A field may be optional.
  • declare const … : typeof … — an alias for a dictionary entry, carrying its type. See Reading game data.
  • listOf and mapOf at module level declare a collection.
src/shop.ts2
const [tab, setTab] = useState(openTab);

Tuple return with array destructuring at the call site. This is the only destructuring form, apart from props.

src/boost.ts2
const tick = useInterval(paused === 1 ? undefined : () => {
if (left === 0) {
tick.stop();
} else {
setLeft(left - 1);
}
}, { everyMs: 1000 });
src/wares.ts2
const Special = ({ i }: { i: Int }): Component => {
const ware = WARES.at(i);
if (ware === null) {
return null;
}
return (
<text x={0} y={0} w="fill" h={16} text={`Special: ${ware.name}, ${ware.price} gp`}
font={Fonts.PLAIN} valign="middle" shadow color={Colors.YELLOW} />
);
};
  • if / else if / else.
  • while. The one loop; a counter is written out — let i: Int = 0; while (i < ids.length) { … i = i + 1; } — and it is how an array the server hands an entry point is walked.
  • switch on a whole number or an enum, with constant cases. Cases never fall through, so a trailing break is allowed and does nothing.
  • return. A component may return null to draw nothing, and may return early under a guard; a statement after an early return of markup is refused (TS2E226).

null is a word the language uses in three places, and nowhere else:

  • A component returns null to draw nothing, and a ? : arm inside markup may be null for the same reason.
  • A collection read by a computed index or key is Value | null. A number or a string is discharged with ?? fallback; a record with === null. See A read that can miss.
  • A handler prop set to null takes the handler off. See Events.

=== null is the one comparison against something that is not a number.

+ - * / %Whole-number arithmetic. / truncates
+= -= *= /= %= ++ --On a local
& |Bit by bit, on whole numbers
=== !==Equality, between two numbers or against null
< > <= >=Comparison
&& ||Logic, on true/false values
!Negation, on a Boolean only
? :Conditional expression
??The fallback for a read that may miss
x!On a parameter field of an item, an NPC or a world object — item.weight! — and on a component’s geometry read through a ComponentId. Nowhere else
`…${x}…`Template literal. Any value interpolates

A comparison is a value: hide={n === 0} and const big: Boolean = n > 2 both compile.

Tags written inline, as described in Writing markup.

Markup can be bound to a const and used by name, which splices it in where the name appears — see Composing components. A name is not a tag, though: <banner /> for a const banner is refused by the type checker before the build starts. Declare a component when you want one.

src/wares.ts2
import { createRef, useInventory } from 'ts2';
import { SunkenBox, Window } from './chrome';
import { Colors, Fonts, Icons, Inventories, Items } from './refs';
import { FarmingItems } from './refs-describing';

Only from 'ts2' and from other .ts2 files in the project. import { X as Y } renames.

The Instead column is the shape to write.

Instead
for, for…of, for…inwhile (TS2E013)
break, continueRestructure the loop’s condition (TS2E014)
try, catch, throwThere are no exceptions (TS2E010)
class, newFunctions, records and components. A class cannot be built (TS2E003)
async, await, PromiseNothing is asynchronous. See Timers
Generators, yield—
==, !====, !== (TS2E011)
=== between two stringsTest .length, includes, startsWith; or interpolate (TS2E003)
typeof, instanceofTypes are known at build time
?.A read that may miss is Value | null — discharge it with ?? or === null (TS2E003)
&&=, ||=Write the assignment out (TS2E002)
<<, >>, ^, ~, ***, / and Math.pow (TS2E003)
Spread, ...Pass values individually. A spread inside a constant array is not a constant (TS2E212); on an element, TS2E232
Object literals as valuesA record in a collection or a constant array, or ref.props = { … } — nowhere else (TS2E003)
Arrays built at run timeA collection or a constant array. Int[] has no type the client holds (TS2E100)
Functions as valuesA Ref to the thing that should change (TS2E101)
A function declared inside another and called by nameDeclare it at module level (TS2E120)
TruthinessAn explicit comparison (TS2E110); ! on a number, TS2E111
? : in a while conditionCompute it inside the loop body (TS2E012)
x! on a plain valueOnly a parameter field or a component’s geometry may be asserted (TS2E003)

? :, a collection read and ?? are refused in a while condition. Compute them inside the loop body.