Skip to content

Diagnostics

Every ts2 diagnostic has a code. This page lists them all, with the fix.

Codes are grouped by band: TS2E0xx is syntax, TS2E1xx is meaning, TS2E2xx is markup, state and refs, TS2E3xx is interfaces and the build’s declarations. Five codes are warnings — the build succeeds — and are marked as such. Everything else is a refusal.

A declaration the language has no form for: an anonymous function, a destructuring pattern other than a tuple, a function without a body. Give the function a name — every function is a const bound to an arrow — and take parameters and tuples apart one name at a time.

A statement the language does not have. Only assignments and calls stand alone as statements; &&= and ||= are written out as an if and an assignment. Supported syntax lists what is available.

The widest code: an expression that is well-formed and cannot be built. The message names the shape. The ones you will meet:

  • === between two strings — test .length, includes, startsWith or endsWith instead.
  • ?. — a read that may miss is Value | null; discharge it with ?? or === null.
  • .length or [i] on a constant array — give the entry a name; take the count from a collection.
  • A module-level const with a type annotation, read inside a function — leave the annotation off.
  • An object literal as a value — a record belongs in a collection or a constant array; ref.props = { … } is the one other place.
  • ! on a plain value — only a parameter field (item.weight!) and a component’s geometry through a ComponentId may be asserted.
  • ?? or a collection read in a while condition — compute it inside the loop.
  • A shift, ^, ~ or ** — there is no such arithmetic.
  • intArray without a literal size — write a literal size.

try, catch and throw. There are no exceptions; a read that can fail says so in its type instead.

Use === and !==.

TS2E012 — conditional expression in a while condition

Section titled “TS2E012 — conditional expression in a while condition”

? : is fine everywhere else. Compute it into a variable inside the loop body.

Use while, the one loop. for…of and for…in are the same refusal.

Neither exists. Restructure the loop’s condition. A break at the end of a switch case is allowed and ignored — cases never fall through.

The build could not give an expression one of the language’s types — an empty array, an Int[], a value with no annotation and nothing to infer from. Annotate it with a type from Types, or declare a collection where an array was meant.

A parameter, a prop, a return type or a tuple element is declared with a type the client cannot hold: a function type, a rest parameter, an array type. Every parameter and return type is one of the language’s own types. A function is not a value; pass a Ref to the thing that should change.

if (stock) is refused; if (stock > 0) is what you meant. There is no truthiness. A Boolean value stands alone; anything else is compared.

TS2E111 — ! on something that is not a true/false value

Section titled “TS2E111 — ! on something that is not a true/false value”

!count is refused; count === 0 is what you meant. ! works on a Boolean.

The name being called is not a function the build knows: not imported from a .ts2 file, or declared inside another function. Declare functions at module level; an arrow inside a component may be a handler and nothing else. The same code covers a global called with the wrong shape — tileOf takes three axes, componentOf two halves in 0..65535, interfaceOf one module-scope ref — and param given something that is not an item, an NPC or a world object.

TS2E121 — an event field read outside a handler

Section titled “TS2E121 — an event field read outside a handler”

The event is a handler’s parameter and exists nowhere else. In markup, use state or a prop.

TS2E122 — a value that must be a constant is not one

Section titled “TS2E122 — a value that must be a constant is not one”

A state declaration’s initial value — sessionState(base() + 1) — or the default of a function parameter that a call leaves out — (a: Int, b: Int = a * 2). Use a literal, a named const or an enum member, or pass the argument. A component prop’s default is TS2E209.

A hook or an accessor was given a plain number, or the wrong kind of reference, where it needs an entry from a dictionary: useServerState a game variable, useInventory a container, param and row.int a parameter. The same code says when a container is used as a value — index it, or call one of its accessors.

useState was given a name that is not a state declaration, or a hook that belongs to a component was called outside one. Declare shared state at the top level with sessionState or persistedState, or pass a literal initial value for state private to the component. An identifier as the initial value is read as a declaration, so useState(SECONDS) asks for shared state called SECONDS.

TS2E126 — two declarations share a persisted key

Section titled “TS2E126 — two declarations share a persisted key”

Give one of them a different key — and remember that changing a key players already hold orphans the values saved under it.

TS2E128 — two names the build cannot tell apart

Section titled “TS2E128 — two names the build cannot tell apart”

Two functions in one file whose names differ only in case — Counter and counter — are refused. Rename one. The same name in two different files is fine: names are qualified by their file.

TS2E129 — a ref where a component id is expected

Section titled “TS2E129 — a ref where a component id is expected”

A handle from useRef() was passed to a prop or a parameter typed ComponentId that wants an absolute address, or the other way round. Declare the parameter ComponentId when a function should take either, and pass event.component where an address is wanted. See Refs.

TS2E130 — a function that returns nothing returned a value

Section titled “TS2E130 — a function that returns nothing returned a value”

A function declared : void has a return x;. Declare the return type, or drop the value. Several values are returned as a tuple: return [x, y]; with a return type of [Int, Int].

event.subId is an address, not a row number. To learn which row of a .map() fired, name the callback’s index and use that. Passing subId on whole, or comparing it to -1, is fine.

TS2E133 — private state cannot hold a string

Section titled “TS2E133 — private state cannot hold a string”

useState('') is refused: a private cell holds a whole number. Declare the string at module level with sessionState('') or persistedState('', key) and pass the declaration to useState. See Interface state.

A function compares a parameter it was handed against another component, and the caller passed event.self. Pass event.component, and event.subId beside it if the position is part of the question.

TS2E136 — a component polls more than one string cell

Section titled “TS2E136 — a component polls more than one string cell”

A component may read one string cell. Split the component, or derive one string from the other.

A lowercase tag that is not one of the eight elements, or a const holding markup used as a tag. Check the spelling and the capitalisation: your own components start with a capital letter, and a named piece of markup is spliced in as {banner}, not <banner />.

The element does not accept that prop. Its reference page lists what it takes; shared props are on Common props.

TS2E202 — a prop cannot fall through to this component’s root

Section titled “TS2E202 — a prop cannot fall through to this component’s root”

A prop the component does not declare is applied to its root element, and this one cannot be: the root does not take it (a handler never falls through — bind it on the layer that carries the click), the component has no single root, or you gave one axis of a pair whose partner the root sets as something other than a constant. Declare the prop, or give both axes. See Props.

Something appeared among an element’s children that cannot be there: a value that is not markup, a .filter() whose test is not a comparison, a .map() callback that is not an arrow. Children are elements, fragments, .map() and .scan() walks, and conditionals.

Text goes in the text prop of a <text>; it is not a component.

TS2E205 — this element cannot have children

Section titled “TS2E205 — this element cannot have children”

Only <layer> may contain other elements, and only a component that declares children can be given any. Wrap the group in a layer.

A handler is an arrow function or a named .ts2 function, taking the event whole or nothing. Refused here: an expression that is not a call, a destructured event parameter, onX={null} on an element being created (null is for a ref write or one arm of a conditional), and onLoad under a component that reads state. See Events.

Three shapes. A tag whose function does not return Component — give it that return type. A component that reads state, placed as the root of a conditional inside a component that rebuilds — wrap the conditional in a <layer>. A root with no place of its own in the layout, which a rebuild could not tear down — the same fix. A component that reads state may otherwise be rooted at another component freely.

The message names it. A component prop with no default was left out — give it a value, or give the prop a literal default. A default that is not a literal counts as none: color = Colors.WHITE is reported here at the call that leaves color out; write color = 0xffffff. The same code says when a pair is half-written: x needs y beside it in ref.props, an options record needs its label, a timer needs a literal everyMs.

x, y, w or h was given something that is not a number, a keyword, a percentage or an anchor object; an anchor object with two anchors; a size anchor with a percentage inside it ({ fill: '10%' } — write "10%" for the proportion or { fill: n } for the inset); or a keyword arriving as a run-time string. See Layout and geometry.

TS2E211 — flow container child has no size

Section titled “TS2E211 — flow container child has no size”

A direction-set layer places each child after the one before, so every child needs a fixed size on the flow axis, and no child may be a conditional, a loop or a component. Give the child a number, or drop direction and position the children yourself.

TS2E212 — a walk over something that is not constant

Section titled “TS2E212 — a walk over something that is not constant”

Every field of a mapped constant array’s entries must be a constant — no spread, computed field or row that is not a record. A collection handed in as a parameter cannot be iterated. Iterate a collection by the name it was declared under; read single values from one you were handed.

{children} appears more than once — a component has one slot — or a component was given two timers, where it carries one.

ref must be a plain name declared with useRef() or createRef(), bound to exactly one element; a <ui> or a static element takes a module-scope createRef only. draggable, stopTimer, resumePauseButton and the last argument of a timer or a watch each want something that names a component — a bound ref, a ComponentId parameter, event.self. As a warning, it says a module-scope ref inside a .map() row cannot be named from outside the build; bind it on an element the tree builds once if something outside needs it.

TS2E215 — ref not bound, or read too early

Section titled “TS2E215 — ref not bound, or read too early”

A useRef() that no ref={…} ever binds; a ref’s geometry read above the markup that binds it, when the component does not exist yet; a property that cannot be set through a ref; interfaceOf on a ref that is not bound on a <ui> or a static element. Bind the ref, move the read below the binding or into a handler, and write paired props through ref.props. See Refs.

options takes a list of labels or option records, written literally, named as a const, or chosen between two of those with ? :. An empty entry skips a slot; [] through a ref clears the menu.

hover is an object of plain prop overrides for this element. It cannot be combined with onMouseEnter/onMouseLeave, which bind the same events; its releaseAfterMs is a literal and cannot share a component with onTimer.

A subscription that cannot fire: a watch on a kind of thing the hook does not watch, or with nothing to watch, or a second watch of the same kind on one component; onVarTransmit and its siblings written as props (a subscription is a hook — useWatch); a useDelay in a component that rebuilds; a cell read only inside onUpdated. See Watching and reacting and Timers.

A <text> or <input> with no font draws nothing at all — an invisible, clickable gap. Add one.

A .map() in a form that cannot be built: as the root of a component that rebuilds (wrap it in a <layer>), over a component that renders itself, over an array or a container whose size is not known here. The message names the missing fact.

continueOp, dragTarget, spellTarget and allowRunScript must be literal true or false; spellTargets a literal list of target kinds; interactionDepth a literal 0–7; an option’s slot within the ten the menu can show.

The project is set to "rift-standard". Either target the Rift client, or use something else. See dialect.

contentType takes one of the named surfaces or a plain number, written directly.

A static element sits under something that is built when the interface opens. The message names the part that kept its builder — a component that reads state, has a conditional, or holds a ref. Move the slot above that part.

A component declared inside another function cannot read state. Declare the component at module level.

TS2E226 — markup returned from the wrong place

Section titled “TS2E226 — markup returned from the wrong place”

A statement after an early return of markup, or markup returned from somewhere the tree cannot be planned. See Conditional rendering.

.scan() given the wrong number of by callbacks or starting values, a by that is not a single-expression arrow, a callback naming a total the walk does not carry, or a .scan() over a constant array. See scan.

const [, setSeen] = useState(0) writes a cell nothing reads. Read the value in the render, or reach for the hook that has the read built in — useOnResize for a resize, useRerenderOn for an occasion the client announces. See Watching and reacting.

TS2E229 — a record read beside its test (warning)

Section titled “TS2E229 — a record read beside its test (warning)”

row !== null && row.name reads the field beside the test rather than under it; on a missing row the interface stops drawing. Put the read under the test — if (row === null) { return null; } and then read.

A component bound ref={target} where target is a Ref it was handed. Write through the ref; bind only your own useRef(). See Refs.

target?: Ref is refused. Make the prop required, or take a ComponentId and pass one that always exists.

{...props} on an element. Write each prop out.

TS2E233 — a caller’s prop overrides the component’s own (warning)

Section titled “TS2E233 — a caller’s prop overrides the component’s own (warning)”

<Row color={…} /> where Row sets color on its own root. The caller wins. Declare the prop on the component when it should decide.

TS2E234 — an option handler on a component that never carries an option

Section titled “TS2E234 — an option handler on a component that never carries an option”

An option handler written on useUniverse() never runs. Give the options and their handler to the same element, in markup.

TS2E235 — a hand-written interface number

Section titled “TS2E235 — a hand-written interface number”

componentOf(n, …) where n is a number this build assigned. It moves whenever an interface is added or renamed. Address the component through the module-scope ref its interface declares. See componentOf.

TS2E236 — useServerStateOnce outside a render

Section titled “TS2E236 — useServerStateOnce outside a render”

Use the hook in a component’s render body. See Read once and derived.

TS2E237 — a subscribing hook inside a read-once body

Section titled “TS2E237 — a subscribing hook inside a read-once body”

useState, useWatch, useDerived, useInventory, useOnResize, useRerenderOn or a nested useServerStateOnce inside the body. Move it out.

TS2E238 — useServerStateOnce in a .map() row

Section titled “TS2E238 — useServerStateOnce in a .map() row”

The rows would share one value. Make the row a component, so each instance has its own.

TS2E239 — a read-once capture in a cell with no setter

Section titled “TS2E239 — a read-once capture in a cell with no setter”

const [v] = useState(useServerStateOnce(…)) polls a value that cannot change. Drop the useState and use the value directly; keep the cell only when something calls its setter.

TS2E251 — a list that rebuilds instead of updating in place (warning)

Section titled “TS2E251 — a list that rebuilds instead of updating in place (warning)”

A component that reads state holds a loop whose rows can move — a .filter() on state, or a loop inside a conditional — so the component rebuilds its rows on every change. Move the test inside the row. See Rendering lists.

TS2E252 — a listener inside an empty box

Section titled “TS2E252 — a listener inside an empty box”

A state read, a timer or a watch inside an element sized 0 on either axis, with no ref to size it later. It never updates. Give the box w={1} h={1}, or a ref if a handler sizes it.

TS2E253 — a store into an array you were handed

Section titled “TS2E253 — a store into an array you were handed”

ids[0] = 1 where ids is a parameter. Copy it first — const mine = intArray(ids.length); — and write into that.

TS2E254 — .map() over an array of unknown size

Section titled “TS2E254 — .map() over an array of unknown size”

.map() over an array that arrived as a parameter. Walk it with while (i < ids.length).

TS2E255 — a region that can never be re-created (warning)

Section titled “TS2E255 — a region that can never be re-created (warning)”

A conditional region in a component that updates in place, whose condition is true for every larger value of a counter that only goes up — build > 0. Put rebuildOn={build} on the region’s container. See How updates happen.

rebuildOn on an element with no conditional among its children gates nothing. Put it on the element whose children include the {cond && …} to re-create.

TS2E257 — rebuildOn in a component with no in-place update

Section titled “TS2E257 — rebuildOn in a component with no in-place update”

The component rebuilds whole on every change already — usually TS2E251 says why. Restore the in-place update first.

TS2E258 — rebuildOn key is not a whole number

Section titled “TS2E258 — rebuildOn key is not a whole number”

The key is remembered as a whole number. Keep the list in its string cell and give the region a companion Int cell whose only job is to be the key.

TS2E259 — rebuildOn on a component invocation

Section titled “TS2E259 — rebuildOn on a component invocation”

Put rebuildOn on the element inside the component that holds the conditional.

TS2E300 — static is neither an assertion nor a name

Section titled “TS2E300 — static is neither an assertion nor a name”

Write static on its own for an anonymous slot, static ref={x} to name it with a module-scope createRef, or static="name".

An interface must be returned from a function taking no parameters; a <ui> inside a handler or a .map() is not one. Its w, h, x and y must be constants the build can see — a literal, an enum member, a const or an object field, not arithmetic. A static layer holds one component and other static layers, is named once, and no two in one interface share a name. An interface with nothing in it draws nothing. See <ui>.

TS2E302 — static on a non-layer directly inside <ui>

Section titled “TS2E302 — static on a non-layer directly inside <ui>”

Only a <layer> may be static directly inside <ui>. Put the rect inside the component the slot hosts.

An enum marked @wire names values the server keys on, so every member must have a written whole-number value in range, no two members or enums may share a name or a value, and the tag must be spelled exactly. See Server entry points.

TS2E350 — engine hooks need the extended dialect

Section titled “TS2E350 — engine hooks need the extended dialect”

defineEngineHooks names the Rift client’s hooks, which a stock client does not have. Target rift-extended, or drop the declaration.

TS2E351 — a second engine-hooks declaration

Section titled “TS2E351 — a second engine-hooks declaration”

The client reads one configuration per game. Merge the two declarations.

A key in defineEngineHooks that is not one of the hooks or assets. The message lists the hooks.

An asset name that is not a literal string, or null on an asset the client cannot start without. Name the asset, or leave the key out for the default.

TS2E354 — mouse position in an option hook

Section titled “TS2E354 — mouse position in an option hook”

e.x and e.y are supplied to the enter, hover and leave hooks only; an option hook is not told where the mouse is.

TS2E355 — an engine hook that behaves like a component

Section titled “TS2E355 — an engine hook that behaves like a component”

Markup, useState, event.self or another hook inside an engine hook. No component runs it: call a function that addresses components by ref, or set state a rendered component reads.

The declaration is export default defineEngineHooks({ … }) at module scope, with an object literal and inline arrows. See Engine hooks.

The compiler hit something it did not expect. Please report it with the source that caused it.

Four numbers are spent and will never be reused.

  • TS2E125 refused a state write from outside the component that read the state.
  • TS2E127 refused a component that owned private state and appeared more than once. Each instance holds its own private state.
  • TS2E140 was reserved for a missing type annotation and never reported; TS2E100 covers it.
  • TS2E207 refused a component with nowhere to attach a timer.
  • TS2E131 — refused where a value is handed to a low-level client operation.
  • TS2E134 — refused where a value is handed to a low-level client operation.
  • TS2E250 — refused where a value is handed to a low-level client operation.