Diagnostics
July 30, 2026 · View on GitHub
Sight reports problems in your Stata code as you type — syntax errors, unresolved macros, suspicious operators, and style issues. This page lists every diagnostic, shows how to silence individual reports, and documents the configuration keys that control severity.
In VS Code, Sight keeps Problems entries only for files represented by real
editor tabs or visible peek editors. A diff tab owns its modified side, not the
original side. Text models opened invisibly by another extension remain
synchronized and participate in cross-file analysis, but do not add background
Problems entries. Closing and re-adding a tab starts a fresh diagnostic
lifecycle, so an older in-flight result cannot reappear after the clear. Other
LSP clients that do not send editor ownership metadata retain standard
didOpen-scoped diagnostics.
Diagnostics are deferred until the workspace scan completes, so
cross-file warnings reflect the full project rather than just the open
buffer. Each diagnostic carries a symbolic code (the rule IDs below)
and source: "sight". LSP diagnostics, JSON, and SARIF use the
canonical uppercase rule IDs. sight check text output lowercases the
same rule IDs in its bracketed suffixes, for example [undefined_macro].
Quick reference
- Silence one site — add
// sight: ignoreor// sight: ignore-nexton its own line above the offending statement, or add// sight: ignoreas a trailing comment on the offending line. Suppresses undefined-symbol (UNDEFINED_MACRO,UNDEFINED_VARIABLE,OUT_OF_SCOPE_SYMBOL) and operator-style diagnostics on the targeted line. Lexer, parser / brace-style, and indentation diagnostics are not silenced this way — fix lexer/parser issues at the source, or turn indentation off viasight.diagnostics.indentation(it ships off by default). - Declare a symbol the analyzer can't see — use
sight: local,sight: global,sight: variables,sight: scalar,sight: matrix,sight: program. Forward-only: effective for the whole directive line and following lines. Declaration directives may also be trailing//comments. - Bring a sibling file's symbols into scope — usually nothing to do:
Sight auto-discovers
do/run/includerelationships and inherits the parent's symbols. Add a header directive (sight: done-by,sight: included-by) only when auto-discovery can't see the link — for example, when the path is built from macros. See Cross-File Awareness. - Turn a category off globally — set the matching severity to
"off"(see Configuration). - Disable everything — set
sight.diagnostics.enabledtofalse.
Lexical errors
Reported by the lexer. Most are hard errors; CONTINUATION_NO_SPACE is
gated by the styleWarnings severity.
Parse and structural errors
Reported by the parser and context tracker. Severity is fixed (no configuration key); two entries are warnings rather than errors because Stata still runs the code.
Embedded language context diagnostics
Reported by the context tracker for Mata and Python block structure.
Semantic / scope diagnostics
The analyzer flags references it cannot resolve to a definition in the
current scope. All four codes are gated by the undefinedMacro or
undefinedVariable severity (see How scope is decided).
| Rule ID | Default | Severity key | Trigger |
|---|---|---|---|
UNDEFINED_MACRO | warning | undefinedMacro | `name', $name, or ${name} references a macro that is not in scope at the reference line. Also covers undefined scalars, matrices, and programs. |
UNDEFINED_VARIABLE | off | undefinedVariable | A varlist position references a name Sight has not seen defined. See What counts as a variable definition. |
OUT_OF_SCOPE_SYMBOL | inherits | matches the underlying symbol's key | A reference resolves to a symbol that exists in the workspace but is not reachable from this file's scope chain. The diagnostic message names the file the symbol was found in. |
MISSING_VARIABLE_NAME | error | n/a | A command position requires a variable name and the parser found no token (e.g., gen = 1). |
A second class — forward references — also surfaces under
UNDEFINED_MACRO when a macro is used earlier in the same file than its
first definition (see Forward references).
For statically expanded foreach / forvalues constructed macro names,
the definition line is the local or global statement inside the loop
body that creates the concrete name:
foreach g in age sex {
display `total_`g'' // UNDEFINED_MACRO: used before its definition
local total_`g' = r(N) // defines total_age, total_sex from here on
display `total_`g'' // no warning — at or after the definition line
}
Positional macro arguments (`0', `1', …) bypass position
and scope checks: they are bound by the caller, not by lexical position.
Operator style diagnostics
Each operator diagnostic has its own severity key, and each can be turned off independently.
Compact, top-level || levelvar: random-equation separators are not treated as
logical operators for these exact-case commands: mixed, mecloglog, meglm,
meintreg, melogit, menbreg, meologit, meoprobit, mepoisson,
meprobit, meqrlogit, meqrpoisson, mestreg, metobit, xtmixed,
xtmelogit, and xtmepoisson. A plausible levelvar: must be the next
significant content immediately after the compact bars. Fixed-effects model
content must precede the first separator; only mestreg may omit it. Thus
mixed || id: is flagged, while mestreg || id: is accepted. The same
exception applies after exact-case simple prefix abbreviations from their
documented minimum through the full command: cap–capture, qui–quietly,
and noi–noisily. The exact-case, colon-required xi: prefix is also
supported; bare xi is not. Mixed-command abbreviations, wrong-case commands,
argument-bearing prefixes, user programs, and commands such as menl, gsem,
twoway, and svyset fail closed. The exception also fails closed when any
top-level comma appears before the first ||, including a comma that introduces
valid fixed-equation options. Sight deliberately does not parse command-specific
fe_options for this exception. Within one logical statement, whitespace,
block comments, line comments under #delimit ;, and ///-swallowed newlines
preserve adjacency between the bars and therefore preserve
INVALID_OPERATOR_SEQUENCE. A real statement terminator, including a newline
under #delimit cr, breaks adjacency.
These operator/expression rules are heuristic token-stream checks. Apart from
the narrow mixed-effects separator exception, they do not parse per-command
semantics, so they can fire inside text-storing commands
(e.g. notes, char, local x <text>) and do not cover every
bare-expression command. LITERAL_MACRO_ADJACENCY additionally recognizes
assert as a bare-boolean-expression command when it appears in command
position (optionally after capture / quietly / noisily prefixes and
their colons), so assert 1\b'is flagged likeif 1`b'`; other
bare-expression commands remain uncovered. This matches the scope of the
existing operator diagnostics; see
#268 for the known edge cases
and the command-context improvement tracked for later.
Indentation diagnostics
Off by default. Enable with sight.diagnostics.indentation: true.
This is an on/off toggle — there is no severity key; both codes are
emitted at information severity.
| Rule ID | Trigger |
|---|---|
UNNECESSARY_INDENTATION | A line is indented past the AST-computed depth. |
MISSING_INDENTATION | A line is at a shallower indent than its block depth. |
Cross-file diagnostics
Reported during cross-file scope resolution. Severity is configurable
via the crossFile.diagnostics.* keys in sight.toml.
PATH_CASE_MISMATCH
Trigger: A static path in a do, run, or include command — or
in a forward directive (sight: do, sight: run, sight: include) or
backward header directive (sight: done-by, sight: run-by,
sight: included-by) — resolves to an on-disk file that has different
letter casing. The file is found and symbols are inherited (no
undefined-symbol cascade), but the diagnostic asks you to fix the
spelling.
Forward message (do/run/include commands and forward
directives): notes that Stata will not find the file on case-sensitive
filesystems and shows both spellings:
Path "helpers/clean" does not match the file on disk
"helpers/Clean.do"; Stata will not find it on case-sensitive
filesystems (Linux). Update the path to match.
Backward message (sight: done-by, sight: run-by,
sight: included-by): backward directives are not Stata commands, so the
message makes no execution claim:
Directive path "parent" does not match the file on disk
"Parent.do"; update the directive to match the file's casing.
Default severity ("auto"): information on case-insensitive
filesystems (macOS/Windows), warning on case-sensitive ones (Linux /
CI). This means the same code is quiet during local development on a Mac
but surfaces as a build warning in Linux CI — the intended asymmetry.
Not suppressible by sight: ignore or sight: ignore-next. Suppress
project-wide with crossFile.diagnostics.caseMismatch = "off", or fix
the path casing. Setting crossFile.diagnostics.missingFile = "off"
does not silence this diagnostic — the two settings are independent.
Out of scope: data-file commands (use, save, merge, import,
export); paths with macro interpolation; paths outside all workspace
folders. Only static paths in the cross-file execution graph
(do/run/include and their directive equivalents) are covered.
File-level check diagnostics
sight check can also emit diagnostics for files that could not be
analyzed. These use the same symbolic code field as analyzer
diagnostics.
Configuration
Diagnostics keys live under sight.* in VS Code's settings.json:
{
"sight.diagnostics.enabled": true, // master switch
"sight.diagnostics.indentation": false, // enable indentation diagnostics
"sight.diagnostics.severity.undefinedMacro": "warning",
"sight.diagnostics.severity.undefinedVariable": "off",
"sight.diagnostics.severity.styleWarnings": "information",
"sight.diagnostics.severity.malformedOperator": "warning",
"sight.diagnostics.severity.spacedCompoundOperator": "information",
"sight.diagnostics.severity.invalidOperatorSequence": "error",
"sight.diagnostics.severity.cStyleLogicalInControlFlow": "information",
"sight.diagnostics.severity.mixedLogicalOperators": "warning",
"sight.diagnostics.severity.chainedComparison": "warning",
"sight.diagnostics.severity.literalMacroAdjacency": "hint"
}
Project-wide diagnostic settings can also be stored in sight.toml:
[diagnostics]
enabled = true
indentation = false
[diagnostics.severity]
undefinedMacro = "warning"
undefinedVariable = "off"
styleWarnings = "information"
See the Project Configuration File
section for the full sight.toml schema and precedence rules.
Each severity key accepts "error", "warning", "information",
"hint", or "off". Notes:
undefinedMacroalso governs undefined scalars, matrices, programs, and theOUT_OF_SCOPE_SYMBOLvariants of those categories.undefinedVariableis experimental and ships off — see Why undefined-variable is off by default.styleWarningscurrently gatesCONTINUATION_NO_SPACE.- Parse and brace-style diagnostics have no per-category control and
sight: ignoredoes not silence them — fix the underlying issue. - Indentation diagnostics ship off; turn them on with the boolean
sight.diagnostics.indentation. Both indentation rule IDs emit atinformationseverity (there is no severity key), andsight: ignoredoes not silence individual sites.
Suppressing diagnostics in source
| Directive | Effect |
|---|---|
// sight: ignore | On its own comment line, suppresses undefined-symbol and operator-style diagnostics on the next non-trivia statement. As a trailing // comment, suppresses those diagnostics on the same line. Does not silence lexer, parser / brace-style, or indentation diagnostics. |
// sight: ignore-next | Suppresses undefined-symbol and operator-style diagnostics on the next non-trivia statement. It does not suppress diagnostics on its own source line. |
When the next statement spans several physical lines — via ///
continuations, or under #delimit ; — a standalone sight: ignore /
sight: ignore-next covers every line the statement spans, wherever in
the statement the flagged construct sits. A statement that opens a block
({, mata, or python) is covered through its header line only, but
never the block body: to suppress a diagnostic inside a block, put the
directive on the offending statement inside the block. A trailing
// sight: ignore on any physical line an operator-style diagnostic
spans also suppresses it.
| // sight: local name [name ...] | Declares one or more local macros from the directive line forward. |
| // sight: global name [name ...] | Same, for globals. |
| // sight: variables var [var ...] | Declares variables (e.g., loaded from a .dta file). |
| // sight: scalar name, // sight: matrix name, // sight: program name | Declares scalars, matrices, and programs respectively. |
Directives other than sight: ignore and sight: ignore-next must occupy
their own // or line-leading * comment line. sight: ignore may also be
used as a trailing // comment for same-line suppression; sight: ignore-next
always targets the following statement. /* ... */ block comments are not
directive comments. @lsp- spellings are permanent aliases for all directive
forms above.
Declaration directives are forward-only: they suppress warnings at and after the directive line, not earlier ones. See Declaration Directives for the full syntax.
For cross-file linkage (the usual reason a global appears "undefined"
when it is defined in a sibling file), prefer the dependency-graph
mechanism — Sight auto-discovers do / run / include chains and
inherits the parent's symbols. Header directives (sight: done-by,
sight: included-by) handle cases auto-discovery cannot, such as paths
built from macros. See Cross-File Awareness.
How scope is decided
A symbol is considered in scope at a reference line when one of these holds:
- Same file, on or before the reference line. Subject to the forward-reference check below.
- Inherited from a parent file via the resolved scope chain:
done-by/run-by/do/runinherit non-local symbols (programs, globals, scalars, matrices, variables).included-by/includeinherit all symbols, including local macros.
- Declared by directive (
sight: local,sight: global, … ) on or before the reference line.
Workspace indexing alone does not suppress diagnostics. A global
defined in an unrelated file is still offered in completion (marked
out of scope) but a reference to it produces an undefined-symbol
warning. The warning is the cue to add a do / run / include
statement or a cross-file directive.
Forward references
Within a single file, the analyzer compares each reference's preorder position against the symbol's first definition:
- Reference appears before the first definition in the same file →
warning (under
UNDEFINED_MACRO). - Reference appears at or after the first definition → no warning.
- When a name is defined more than once, the first definition wins.
Forward-reference checking is intra-file only. Symbols inherited from parent files are subject to call-site filtering instead: only definitions on or before the call site of the current file in the parent are considered in scope.
Startup behavior
Sight defers diagnostics until the workspace scan finishes parsing
every .do, .ado, .doh, and .mata file and the dependency graph
is populated. Files opened before the scan completes are re-checked
once the graph is ready, so cross-file warnings reflect the full
project rather than just the open buffer.
What counts as a variable definition
Sight recognizes a variable as defined when it sees one of:
generate/gen,egen,input— explicit creation.rename/ren— the new name is registered.confirm variable/confirm var— treated as an assertion that the variable exists; useful for declaring columns loaded from a dataset without disabling the diagnostic.sight: variables name [name …]— manual declaration, forward-only.
Variables loaded from use, import, or merge are not
introspected — Sight does not read .dta files. If the diagnostic is
on and you load data, declare the columns with sight: variables (or a
confirm variable line if you want a runtime assertion as well).
Why undefined-variable is off by default
Stata variable existence depends on the dataset in memory at runtime,
which the LSP cannot observe statically. Even with the definitions
listed above, a name may legitimately come from a use, import, or
merge the analyzer cannot read — particularly when the dataset path
is built from macros. Defaulting the diagnostic to off avoids
drowning users in false positives. To opt in, set
sight.diagnostics.severity.undefinedVariable to any non-off value
and declare dynamically-loaded columns with sight: variables or
confirm variable.
Relationship to completion
Completion and diagnostics share the same scope resolver but apply
different rules. Completion may surface workspace symbols as
out-of-scope suggestions (see Completion);
diagnostics never treat workspace-only visibility as resolved scope.
Accepting an out-of-scope completion produces a diagnostic by design —
that is the signal to link the file via do / run / include or a
cross-file directive.