feat: pass world to linter (#1650)

This commit is contained in:
Myriad-Dreamin 2025-04-14 21:37:29 +08:00 committed by GitHub
parent 9c98876dfb
commit 3d6c712565
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 77 additions and 53 deletions

View file

@ -14,14 +14,10 @@ rust-version.workspace = true
[dependencies]
typst-library.workspace = true
typst.workspace = true
tinymist-std.workspace = true
tinymist-analysis.workspace = true
tinymist-world = { workspace = true, features = ["system"] }
parking_lot.workspace = true
tinymist-project = { workspace = true, features = ["lsp"] }
tinymist-std.workspace = true
comemo.workspace = true
serde.workspace = true
serde_json.workspace = true
base64.workspace = true
rayon.workspace = true
[dev-dependencies]

View file

@ -6,33 +6,52 @@ use tinymist_analysis::{
syntax::ExprInfo,
ty::{Ty, TyCtx, TypeInfo},
};
use tinymist_project::LspWorld;
use typst::{
diag::{eco_format, EcoString, SourceDiagnostic, Tracepoint},
ecow::EcoVec,
syntax::{
ast::{self, AstNode},
Span, Spanned, SyntaxNode,
FileId, Span, Spanned, SyntaxNode,
},
};
/// A type alias for a vector of diagnostics.
type DiagnosticVec = EcoVec<SourceDiagnostic>;
/// Performs linting check on file and returns a vector of diagnostics.
pub fn lint_file(expr: &ExprInfo, ti: Arc<TypeInfo>) -> DiagnosticVec {
Linter::new(ti).lint(expr.source.root())
/// The lint information about a file.
#[derive(Debug, Clone)]
pub struct LintInfo {
/// The revision of expression information
pub revision: usize,
/// The belonging file id
pub fid: FileId,
/// The diagnostics
pub diagnostics: DiagnosticVec,
}
struct Linter {
/// Performs linting check on file and returns a vector of diagnostics.
pub fn lint_file(world: &LspWorld, expr: &ExprInfo, ti: Arc<TypeInfo>) -> LintInfo {
let diagnostics = Linter::new(world, ti).lint(expr.source.root());
LintInfo {
revision: expr.revision,
fid: expr.fid,
diagnostics,
}
}
struct Linter<'w> {
world: &'w LspWorld,
ti: Arc<TypeInfo>,
diag: DiagnosticVec,
loop_info: Option<LoopInfo>,
func_info: Option<FuncInfo>,
}
impl Linter {
fn new(ti: Arc<TypeInfo>) -> Self {
impl<'w> Linter<'w> {
fn new(world: &'w LspWorld, ti: Arc<TypeInfo>) -> Self {
Self {
world,
ti,
diag: EcoVec::new(),
loop_info: None,
@ -229,6 +248,8 @@ impl Linter {
return None;
}
let _ = self.world;
let diag =
SourceDiagnostic::warning(expr.span(), "variable font is not supported by typst yet");
let diag = diag.with_hint("consider using a static font instead. For more information, see https://github.com/typst/typst/issues/185");
@ -238,7 +259,7 @@ impl Linter {
}
}
impl DataFlowVisitor for Linter {
impl DataFlowVisitor for Linter<'_> {
fn exprs<'a>(&mut self, exprs: impl DoubleEndedIterator<Item = ast::Expr<'a>>) -> Option<()> {
for expr in exprs {
self.expr(expr);
@ -368,13 +389,13 @@ impl DataFlowVisitor for Linter {
}
}
struct LateFuncLinter<'a> {
linter: &'a mut Linter,
struct LateFuncLinter<'a, 'b> {
linter: &'a mut Linter<'b>,
func_info: FuncInfo,
return_block_info: Option<ReturnBlockInfo>,
}
impl LateFuncLinter<'_> {
impl LateFuncLinter<'_, '_> {
fn late_closure(&mut self, expr: ast::Closure<'_>) -> Option<()> {
if !self.func_info.has_return {
return Some(());
@ -408,7 +429,7 @@ impl LateFuncLinter<'_> {
}
}
impl DataFlowVisitor for LateFuncLinter<'_> {
impl DataFlowVisitor for LateFuncLinter<'_, '_> {
fn exprs<'a>(&mut self, exprs: impl DoubleEndedIterator<Item = ast::Expr<'a>>) -> Option<()> {
for expr in exprs.rev() {
self.expr(expr);

View file

@ -669,10 +669,8 @@ mod lint_tests {
fn test() {
snapshot_testing("lint", &|ctx, path| {
let source = ctx.source_by_path(&path).unwrap();
let expr = ctx.expr_stage(&source);
let ti = ctx.type_check(&source);
let result = tinymist_lint::lint_file(&expr, ti);
let result = ctx.lint(&source);
let result = crate::diagnostics::DiagWorker::new(ctx).convert_all(result.iter());
let result = result
.into_iter()

View file

@ -11,13 +11,16 @@ use rustc_hash::FxHashMap;
use tinymist_analysis::stats::AllocStats;
use tinymist_analysis::ty::term_value;
use tinymist_analysis::{analyze_expr_, analyze_import_};
use tinymist_lint::LintInfo;
use tinymist_project::{LspComputeGraph, LspWorld};
use tinymist_std::hash::{hash128, FxDashMap};
use tinymist_std::typst::TypstDocument;
use tinymist_world::debug_loc::DataSource;
use tinymist_world::vfs::{PathResolution, WorkspaceResolver};
use tinymist_world::{EntryReader, DETACHED_ENTRY};
use typst::diag::{eco_format, At, FileError, FileResult, SourceResult, StrResult};
use typst::diag::{
eco_format, At, FileError, FileResult, SourceDiagnostic, SourceResult, StrResult,
};
use typst::foundations::{Bytes, IntoValue, Module, StyleChain, Styles};
use typst::introspection::Introspector;
use typst::layout::Position;
@ -463,6 +466,10 @@ 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
}
/// Get the type check information of a source file.
pub(crate) fn type_check_by_id(&mut self, id: TypstFileId) -> Arc<TypeInfo> {
let cache = &self.caches.modules.entry(id).or_default().type_check;
@ -759,17 +766,9 @@ impl SharedContext {
let ei = self.expr_stage(source);
let guard = self.query_stat(source.id(), "type_check");
self.slot.type_check.compute(hash128(&ei), |prev| {
let cache_hit = prev.and_then(|prev| {
// todo: recursively check changed scheme type
if prev.revision != ei.revision {
return None;
}
Some(prev)
});
if let Some(prev) = cache_hit {
return prev.clone();
// todo: recursively check changed scheme type
if let Some(cache_hint) = prev.filter(|prev| prev.revision == ei.revision) {
return cache_hint;
}
guard.miss();
@ -777,6 +776,17 @@ impl SharedContext {
})
}
/// Get the lint result of a source file.
pub(crate) fn lint(self: &Arc<Self>, source: &Source) -> 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| {
guard.miss();
tinymist_lint::lint_file(&self.world, &ei, ti)
})
}
pub(crate) fn type_of_func(self: &Arc<Self>, func: Func) -> Signature {
crate::log_debug_ct!("convert runtime func {func:?}");
analyze_signature(self, SignatureTarget::Convert(func)).unwrap()
@ -1180,6 +1190,7 @@ impl RevisionManagerLike for AnalysisRevCache {
fn gc(&mut self, rev: usize) {
self.manager.gc(rev);
// todo: the following code are time consuming.
{
let mut max_ei = FxHashMap::default();
let es = self.default_slot.expr_stage.global.lock();
@ -1199,6 +1210,16 @@ impl RevisionManagerLike for AnalysisRevCache {
}
ts.retain(|_, r| r.1.revision == *max_ti.get(&r.1.fid).unwrap_or(&0));
}
{
let mut max_li = FxHashMap::default();
let ts = self.default_slot.lint.global.lock();
for r in ts.iter() {
let rev: &mut usize = max_li.entry(r.1.fid).or_default();
*rev = (*rev).max(r.1.revision);
}
ts.retain(|_, r| r.1.revision == *max_li.get(&r.1.fid).unwrap_or(&0));
}
}
}
@ -1222,6 +1243,7 @@ impl AnalysisRevCache {
revision: slot.revision,
expr_stage: slot.data.expr_stage.crawl(revision.get()),
type_check: slot.data.type_check.crawl(revision.get()),
lint: slot.data.lint.crawl(revision.get()),
})
.unwrap_or_else(|| self.default_slot.clone())
})
@ -1254,6 +1276,7 @@ struct AnalysisRevSlot {
revision: usize,
expr_stage: IncrCacheMap<u128, ExprInfo>,
type_check: IncrCacheMap<u128, Arc<TypeInfo>>,
lint: IncrCacheMap<u128, LintInfo>,
}
impl Drop for AnalysisRevSlot {

View file

@ -1,11 +1,10 @@
use std::borrow::Cow;
use tinymist_analysis::ty::TypeInfo;
use tinymist_project::LspWorld;
use tinymist_world::vfs::WorkspaceResolver;
use typst::{diag::SourceDiagnostic, syntax::Span};
use typst::syntax::Span;
use crate::{analysis::Analysis, prelude::*, syntax::ExprInfo, LspWorldExt};
use crate::{analysis::Analysis, prelude::*, LspWorldExt};
use regex::RegexSet;
@ -57,13 +56,9 @@ impl<'w> DiagWorker<'w> {
let Ok(source) = self.ctx.world.source(dep) else {
continue;
};
let expr = self.ctx.expr_stage(&source);
let ti = self.ctx.type_check(&source);
let res = lint_file(&expr, ti);
if !res.is_empty() {
for diag in res {
self.handle(&diag);
}
for diag in self.ctx.lint(&source) {
self.handle(&diag);
}
}
@ -246,8 +241,3 @@ impl DiagnosticRefiner for OutOfRootHintRefiner {
raw.with_hint("Cannot read file outside of project root.")
}
}
#[comemo::memoize]
fn lint_file(source: &ExprInfo, ti: Arc<TypeInfo>) -> EcoVec<SourceDiagnostic> {
tinymist_lint::lint_file(source, ti)
}