feat: new lint warning for unknown math vars (#2065)
Some checks failed
tinymist::auto_tag / auto-tag (push) Has been cancelled
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / announce (push) Has been cancelled
tinymist::ci / build (push) Has been cancelled

Add a lint warning for unknown math variables[^1], powered by Tinymist's
existing reference analysis. This allows users to see all such undefined
variables at once if they have the linter enabled, instead of just the
first error from the compiler. The autofix from #2062 is also applicable
to these warnings.

Example:
![example of new warning in VS
Code](https://github.com/user-attachments/assets/42672d50-e637-49b2-907b-aa413431e55e)
(The first error is pre-existing and emitted by the Typst compiler; the
second warning is new from this PR and emitted by us.)

Implementation notes:
- The generated diagnostic tries to closely match the corresponding
Typst compiler error, with one deliberate change: to differentiate the
Tinymist warning and the compiler error, we use the message `potentially
unknown variable: ...` instead of `unknown variable: ...`. I am not the
biggest fan of this choice, but I think it is very important that users
don't blame the Typst compiler for warnings that we generate; changing
the message so that it isn't an exact clone is the best way I thought of
for now.
- We avoid duplicating a warning if the compiler has already generated
an error for a given identifier by computing a set of `KnownLintIssues`
from the compiler diagnostics and threading this information through the
lint routines.

[^1]: If users like this warning, we can extend it later to apply to
undefined variables outside of math as well.

---------

Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>
This commit is contained in:
Joseph Liu 2025-09-23 13:09:10 -04:00 committed by GitHub
parent 3b6de80a2f
commit 6b52aa70ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 148 additions and 29 deletions

View file

@ -13,7 +13,7 @@ use tinymist_analysis::stats::AllocStats;
use tinymist_analysis::syntax::classify_def_loosely;
use tinymist_analysis::ty::term_value;
use tinymist_analysis::{analyze_expr_, analyze_import_};
use tinymist_lint::LintInfo;
use tinymist_lint::{KnownIssues, LintInfo};
use tinymist_project::{LspComputeGraph, LspWorld, TaskWhen};
use tinymist_std::hash::{FxDashMap, hash128};
use tinymist_std::typst::TypstDocument;
@ -475,8 +475,12 @@ impl LocalContext {
cache.get_or_init(|| self.shared.type_check(source)).clone()
}
pub(crate) fn lint(&mut self, source: &Source) -> EcoVec<SourceDiagnostic> {
self.shared.lint(source).diagnostics
pub(crate) fn lint(
&mut self,
source: &Source,
known_issues: &KnownIssues,
) -> EcoVec<SourceDiagnostic> {
self.shared.lint(source, known_issues).diagnostics
}
/// Get the type check information of a source file.
@ -794,13 +798,13 @@ impl SharedContext {
/// Get the lint result of a source file.
#[typst_macros::time(span = source.root().span())]
pub(crate) fn lint(self: &Arc<Self>, source: &Source) -> LintInfo {
pub(crate) fn lint(self: &Arc<Self>, source: &Source, issues: &KnownIssues) -> LintInfo {
let ei = self.expr_stage(source);
let ti = self.type_check(source);
let guard = self.query_stat(source.id(), "lint");
self.slot.lint.compute(hash128(&(&ei, &ti)), |_prev| {
self.slot.lint.compute(hash128(&(&ei, &ti, issues)), |_| {
guard.miss();
tinymist_lint::lint_file(&self.world, &ei, ti)
tinymist_lint::lint_file(&self.world, &ei, ti, issues.clone())
})
}