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: ignore or // sight: ignore-next on its own line above the offending statement, or add // sight: ignore as 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 via sight.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 / include relationships 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.enabled to false.

Lexical errors

Reported by the lexer. Most are hard errors; CONTINUATION_NO_SPACE is gated by the styleWarnings severity.

Rule IDDefaultTrigger
UNBALANCED_QUOTESerrorA string literal is opened but never closed on the same statement.
UNBALANCED_BLOCK_COMMENTerror/* is not matched by */.
UNTERMINATED_STATEMENTerrorA statement runs off the end of the file in #delimit ; mode without a terminating ;.
CONTINUATION_NO_SPACEinformation (styleWarnings)/// continuation marker not preceded by whitespace (foo/// instead of foo ///).
BLOCK_COMMENT_IN_STAR_COMMENTwarningA /* ... */ block appears inside a * line comment, which Stata parses unexpectedly.

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.

Rule IDDefaultTrigger
SYNTAX_ERRORerrorGeneric parser failure not covered by a more specific rule.
BRACE_ELSE_SAME_LINEerror} else { style: Stata requires the closing brace and else on separate lines.
BRACE_NOT_ALONEerrorA closing } shares a line with other code.
MISSING_PROGRAM_ENDerrorA program / mata / python block is not closed by end.
OPEN_BRACE_ALONEerrorAn opening { appears alone on a line where Stata expects it on the control statement.
UNCLOSED_BLOCKerrorAn if / foreach / forvalues / while / frame / mata / python block is not closed.
CODE_AFTER_OPEN_BRACEwarningTokens follow { on the same line as the opening brace. Stata runs the block but ignores trailing code on that line.
FORVALUES_SYNTAXerrorMalformed forvalues range (e.g., missing = or bad to// separators).
REDUNDANT_MACRO_SUFFIXwarningA macro reference includes redundant trailing characters that Stata ignores.
INVALID_MACRO_CHARerrorA macro name contains a character Stata does not allow in identifiers.
MISSING_EXPRESSION_AFTER_EQUALSerrorAn = is followed by no expression: e.g., gen x =, an empty if / while condition, or an empty qualifier.
UNBALANCED_PARENTHESESerrorMismatched ( / ) in an expression, if / while condition, or by / bysort qualifier.
ORPHAN_CLOSE_BRACEerrorA } appears with no matching opening block.
STRAY_TOKEN_IN_CONDITIONerrorUnexpected token inside an if / while / qualifier condition.
SPLIT_LITERAL_IN_CONDITIONerrorA string or compound literal is split across the condition boundary, leaving an unterminated literal in an if / while condition.

Embedded language context diagnostics

Reported by the context tracker for Mata and Python block structure.

Rule IDDefaultTrigger
UNCLOSED_MATA_BLOCKerrorA Mata block is opened but not closed.
UNCLOSED_PYTHON_BLOCKerrorA Python block is opened but not closed.
UNEXPECTED_ENDerrorAn end appears where no embedded language block is open.
UNEXPECTED_END_COMMANDerrorAn end command does not match the current embedded language context.
MISMATCHED_END_PYTHONerrorA Python block is closed with a mismatched delimiter.
NESTED_BLOCK_ERRORerrorEmbedded language block nesting is invalid.
INVALID_DELIMITER_POSITIONerrorA Mata or Python delimiter appears where it cannot start or end a valid block.

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 IDDefaultSeverity keyTrigger
UNDEFINED_MACROwarningundefinedMacro`name', $name, or ${name} references a macro that is not in scope at the reference line. Also covers undefined scalars, matrices, and programs.
UNDEFINED_VARIABLEoffundefinedVariableA varlist position references a name Sight has not seen defined. See What counts as a variable definition.
OUT_OF_SCOPE_SYMBOLinheritsmatches the underlying symbol's keyA 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_NAMEerrorn/aA 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.

Rule IDDefaultSeverity keyTrigger
MALFORMED_OPERATORwarningmalformedOperator= =, which Stata does not treat as == in all expression contexts.
INVALID_OPERATOR_SEQUENCEerrorinvalidOperatorSequenceToken sequences Stata cannot parse (e.g., < |, = ==).
CSTYLE_LOGICAL_IN_CONTROL_FLOWinformationcStyleLogicalInControlFlow&& / || used in if / else if. Legal in Stata, but the canonical style uses & / |.
MIXED_LOGICAL_OPERATORSwarningmixedLogicalOperators& and | mixed in one expression without parentheses; precedence is easy to misread.
SPACED_COMPOUND_OPERATORinformationspacedCompoundOperatorCompound operators split by whitespace that Stata accepts as the compact form: < =, > =, ! =, and ~ =.
CHAINED_COMPARISONwarningchainedComparisonTwo or more comparisons chained without a logical connector (e.g., a != b != c, a < b < c). Stata evaluates these left-to-right — a < b < c is (a < b) < c — so a chain is usually a missing & / | or missing parentheses.
LITERAL_MACRO_ADJACENCYhintliteralMacroAdjacencyA number or complete string literal placed directly against a following macro reference where the pair is an operand of a comparison/logical operator (e.g., a == 1\b', or 1`b' == a). Stata concatenates them during macro expansion — if `` b' `` is 0, 1\b'becomes10. Only flagged when a comparison/logical operator sits immediately before the literal or immediately after the macro, so intentional adjacency (gen x`i'`, function arguments, string interpolation) is left alone.

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: capcapture, quiquietly, and noinoisily. 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 IDTrigger
UNNECESSARY_INDENTATIONA line is indented past the AST-computed depth.
MISSING_INDENTATIONA 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.

Rule IDDefaultSeverity keyTrigger
PATH_CASE_MISMATCHautocrossFile.diagnostics.caseMismatchA do/run/include command or cross-file directive (sight: do, sight: run, sight: include, sight: done-by, sight: run-by, sight: included-by) references a path that differs from the on-disk file by letter case only. The file is resolved and symbols are inherited normally; only the spelling is wrong.
CROSS_FILE_MISSING_FILEwarningcrossFile.diagnostics.missingFileA do/run/include command or cross-file directive references a target file Sight cannot read.
CROSS_FILE_TRUNCATEDwarningcrossFile.diagnostics.maxDepthCross-file scope resolution hit a configured traversal depth cap. Results may be incomplete, but this is not treated as an undefined-symbol error by sight check.

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.

Rule IDTrigger
SIGHT_FILE_TOO_LARGEAn explicitly checked source file exceeds indexing.maxFileSizeBytes.
SIGHT_FILE_NOT_INDEXEDAn explicitly checked source file was skipped because crossFile.maxIndexedFiles was reached.
SIGHT_UNREADABLEA target file could not be read or decoded when sight check tried to analyze it.
SIGHT_INVALID_ENCODINGA target file is not valid UTF-8, so sight check could not decode its contents.

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:

  • undefinedMacro also governs undefined scalars, matrices, programs, and the OUT_OF_SCOPE_SYMBOL variants of those categories.
  • undefinedVariable is experimental and ships off — see Why undefined-variable is off by default.
  • styleWarnings currently gates CONTINUATION_NO_SPACE.
  • Parse and brace-style diagnostics have no per-category control and sight: ignore does 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 at information severity (there is no severity key), and sight: ignore does not silence individual sites.

Suppressing diagnostics in source

DirectiveEffect
// sight: ignoreOn 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-nextSuppresses 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:

  1. Same file, on or before the reference line. Subject to the forward-reference check below.
  2. Inherited from a parent file via the resolved scope chain:
    • done-by / run-by / do / run inherit non-local symbols (programs, globals, scalars, matrices, variables).
    • included-by / include inherit all symbols, including local macros.
  3. 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.