Analyzer
analyze is parse plus semantic analysis. You get the same program,
comments, and diagnostics, and one field more: a SemanticView over the
references, scopes, and symbols the analyzer resolved.
analyze(source: string | Uint8Array, options?: ParseOptions): AnalyzeResultimport { analyze } from "yuku-tsrx";
const result = analyze(source, { lang: "tsx" });
for (let i = 0; i < result.semantic.reference.count; i++) {
const name = result.semantic.reference.name(i);
const symbol = result.semantic.reference.symbolId(i);
// symbol === null means this reference resolved to nothing in the file
}It takes the same ParseOptions as parse.
AnalyzeResult#
export interface AnalyzeResult extends ParseResult {
readonly semantic: SemanticView;
}semantic is readonly and is built the first time you read it, so a caller
that only wants the diagnostics or the program does not pay for it.
SemanticView#
export interface SemanticView {
reference: { count: number; name(index: number): string; symbolId(index: number): number | null };
scope: { count: number; kind(index: number): string };
symbol: { count: number; name(index: number): string };
}Three collections, each an index-addressed accessor rather than an array of
objects. You read count, then call the accessors with an index from 0 to
count - 1. Nothing is materialized until you ask for it, which is what keeps
the analyzer cheap for a tool that only needs one of the three.
| Collection | Read it for |
|---|---|
reference |
Every identifier use. name(i) is the text, symbolId(i) is the symbol it resolved to, or null if it resolved to nothing declared in this file. |
scope |
Every scope the analyzer created. kind(i) names it. |
symbol |
Every declared binding. name(i) is its text. |
An unresolved reference is symbolId(i) === null. That is the normal answer for
a name this file never declares, a global for instance, and not an error.
Why the analyzer is dialect work#
Semantic analysis is Yuku's job, not yuku-tsrx's. The dialect's part is making sure Yuku's analyzer sees the right tree.
TSRX puts bindings in places plain JavaScript does not have: a const declared
in a @{ } code block, the loop variable of a @for, the error and reset
parameters of a @catch. Each of those has to become a real scope with real
symbols, or a reference to it resolves to nothing and every downstream tool that
asks "where is this defined" gets the wrong answer.
Click a symbol below to light its declaration and every reference the analyzer
resolved to it, and hover a scope to outline the span it covers. The status
line counts what did not resolve too. In this sample exactly one reference is
unresolved: reset. The analyzer creates a symbol for the first @catch
parameter (error) but not yet for the second, so the onClick={reset}
reference has nothing to point at, and the figure shows that rather than hiding
it. A const declared in a @{ } block is block scoped, so the sample reads
total inside that block.
Source
export function Cart({ items }) {
@{
const total = items.length;
const label = total === 1 ? "item" : "items";
}
return (
<ul class="cart">
@try {
@for (const item of items; index i; key item.id) {
<li>{item.label}</li>
}
} @catch (error, reset) {
<li><button onClick={reset}>{error.message}</button></li>
}
</ul>
);
}Symbols
The analyzer runs when this figure scrolls into view.
Two files own that:
src/dialect/semantic.zighands the dialect's tree to Yuku's analyzer. TSRX records are attached to host nodes, so before the analysis runs it swaps in the node data the analyzer expects for each associated node and restores it afterwards. The result is that Yuku's scope and symbol resolution walks TSRX constructs as the ordinary constructs they stand for, with no TSRX-specific code in Yuku.src/dialect/semantic_transfer.zigis the wire format for the result. It packs scopes, symbols, references, imports, and exports into the same buffer the parse result travels in, appended after the tree, so one call across the native boundary carries both.
The consequence worth knowing: the contents of a <style> element are not
JavaScript, so the CSS text inside it produces no references and no symbols.
Selector and property names never show up in reference.
semanticErrors is a separate thing#
Do not confuse analyze with the semanticErrors parse option. They are not
the same switch.
analyzegives you the semantic view: what resolved to what.semanticErrorsadds the scope-dependent early errors todiagnostics: the ones a grammar check alone cannot find, like an export of a binding that was never declared.
semanticErrors is opt-in on parse and on by default in parseModule, for
the reason spelled out in Parser. Which of
those errors are fatal and which are lowered to warnings is decided in
src/dialect/diagnostics.zig.
Reading the buffer yourself#
decodeAnalyzer(buffer: ArrayBuffer, source: string): unknowndecodeAnalyzer is the decoder analyze uses internally, exported so you can
decode an analyzer buffer you obtained some other way. It is typed unknown in
index.d.ts because the analyzer buffer carries more than the typed
SemanticView promises; treat AnalyzeResult as the supported shape and
anything beyond it as subject to change.