references edge in the dependency graph, and the text() function, which retrieves a node’s requirement text as a string at runtime and records an embeds edge. Both are valid TypeScript — no custom runtime or loader is required — and both are the primary mechanism by which xspec builds its queryable graph of coverage and impact.
Markers
A marker is a bare node expression used as an expression statement — that is, appearing on its own line in statement position, not assigned, not passed anywhere:xspec build, not at runtime.
In the graph, the marker records a references edge from the code location of the enclosing function to the node AUTH.auth.login.valid. This edge participates in coverage analysis (which requirements are referenced by your code) and impact analysis (which code is affected when a requirement changes).
Placing a marker on the root node (e.g.
AUTH alone as a statement) records a graph edge, but the root node never participates in coverage paths. Marker to root is valid but has no coverage effect.The text() function
When you need the actual requirement text as a string at runtime — to display it, include it in a prompt, or embed it in output — use the text() function imported from the same spec module:
text(node) returns the node’s full subtree text as a string. It also records an embeds edge from the calling code location to the node, indicating that this code site directly embeds the requirement content.
text() is the only way to obtain requirement text at runtime. You cannot read text off a node directly, and the string-path form that is available in MDX is not valid in TypeScript:
Rules for using spec bindings
xspec enforces a strict set of rules about how node bindings and thetext binding may be used in TypeScript. These rules exist so that the static analyzer can reliably track all edges and so that rename and move refactors work correctly.
Permitted uses
- A node expression as a marker — alone as an expression statement.
- A node expression as the sole argument to
text()— wheretextis the binding imported from the same module. - Dot access and string-literal bracket access to traverse the node tree.
Invalid uses (build errors)
Shadowing
If a local declaration shadows an imported spec binding, xspec treats the identifier as an ordinary local variable and does not record any graph edge:Type-only references
Using a node in a type position — for exampletypeof AUTH.auth.login — is unrestricted. Type references record nothing in the graph and are not rewritten by rename or move operations.
Module branding and aliasing text
Each spec module’s node types are branded to that module. You cannot pass a node from one module to another module’s text function — this is both a TypeScript type error and a runtime throw:
text export to avoid ambiguity and to make the pairing explicit, as shown above.
Code locations and edge attribution
Every edge recorded in the graph is attributed to a code location — either a whole file or a named unit within a file. A file-level location looks like:# and the unit’s qualified name:
What counts as a named unit
The following constructs create named units:- Function and class declarations
- Class members with identifier names: methods, getters, setters, and function-valued properties
const,let, orvarbindings whose initializer is a function or class expression- Namespaces —
namespace A.Byields the unit nameA.B - Default exports — an anonymous default export uses the unit name
default
text() call is attributed to the innermost enclosing named unit. If no named unit encloses the reference, it is attributed to the file as a whole.