mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 17:58:17 +00:00
feat: simple lint types comparing with strings (#1643)
* feat: simple lint types comparing with strings * dev: edit message * fix: message
This commit is contained in:
parent
23f10a2648
commit
4265bc25bc
10 changed files with 170 additions and 13 deletions
|
@ -1,6 +1,11 @@
|
|||
//! A linter for Typst.
|
||||
|
||||
use tinymist_analysis::syntax::ExprInfo;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tinymist_analysis::{
|
||||
syntax::ExprInfo,
|
||||
ty::{Ty, TyCtx, TypeInfo},
|
||||
};
|
||||
use typst::{
|
||||
diag::{eco_format, EcoString, SourceDiagnostic, Tracepoint},
|
||||
ecow::EcoVec,
|
||||
|
@ -13,26 +18,32 @@ use typst::{
|
|||
/// A type alias for a vector of diagnostics.
|
||||
type DiagnosticVec = EcoVec<SourceDiagnostic>;
|
||||
|
||||
/// Performs linting check on syntax and returns a vector of diagnostics.
|
||||
pub fn lint_file(expr: &ExprInfo) -> DiagnosticVec {
|
||||
ExprLinter::new().lint(expr.source.root())
|
||||
/// 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())
|
||||
}
|
||||
|
||||
struct ExprLinter {
|
||||
struct Linter {
|
||||
ti: Arc<TypeInfo>,
|
||||
diag: DiagnosticVec,
|
||||
loop_info: Option<LoopInfo>,
|
||||
func_info: Option<FuncInfo>,
|
||||
}
|
||||
|
||||
impl ExprLinter {
|
||||
fn new() -> Self {
|
||||
impl Linter {
|
||||
fn new(ti: Arc<TypeInfo>) -> Self {
|
||||
Self {
|
||||
ti,
|
||||
diag: EcoVec::new(),
|
||||
loop_info: None,
|
||||
func_info: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn tctx(&self) -> &impl TyCtx {
|
||||
self.ti.as_ref()
|
||||
}
|
||||
|
||||
fn lint(mut self, node: &SyntaxNode) -> DiagnosticVec {
|
||||
if let Some(markup) = node.cast::<ast::Markup>() {
|
||||
self.exprs(markup.exprs());
|
||||
|
@ -151,9 +162,42 @@ impl ExprLinter {
|
|||
|
||||
has_set
|
||||
}
|
||||
|
||||
fn check_type_compare(&mut self, expr: ast::Binary<'_>) {
|
||||
let op = expr.op();
|
||||
if is_compare_op(op) {
|
||||
let lhs = expr.lhs();
|
||||
let rhs = expr.rhs();
|
||||
|
||||
let mut lhs = self.expr_ty(lhs);
|
||||
let mut rhs = self.expr_ty(rhs);
|
||||
|
||||
let other_is_str = lhs.is_str(self.tctx());
|
||||
if other_is_str {
|
||||
(lhs, rhs) = (rhs, lhs);
|
||||
}
|
||||
|
||||
if lhs.is_type(self.tctx()) && (other_is_str || rhs.is_str(self.tctx())) {
|
||||
let msg = "comparing strings with types is deprecated";
|
||||
let diag = SourceDiagnostic::warning(expr.span(), msg);
|
||||
let diag = diag.with_hints([
|
||||
"compare with the literal type instead".into(),
|
||||
"this comparison will always return `false` since typst v0.14".into(),
|
||||
]);
|
||||
self.diag.push(diag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_ty<'a>(&self, expr: ast::Expr<'a>) -> TypedExpr<'a> {
|
||||
TypedExpr {
|
||||
expr,
|
||||
ty: self.ti.type_of_span(expr.span()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DataFlowVisitor for ExprLinter {
|
||||
impl DataFlowVisitor for Linter {
|
||||
fn exprs<'a>(&mut self, exprs: impl DoubleEndedIterator<Item = ast::Expr<'a>>) -> Option<()> {
|
||||
for expr in exprs {
|
||||
self.expr(expr);
|
||||
|
@ -262,10 +306,15 @@ impl DataFlowVisitor for ExprLinter {
|
|||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn binary(&mut self, expr: ast::Binary<'_>) -> Option<()> {
|
||||
self.check_type_compare(expr);
|
||||
self.exprs([expr.lhs(), expr.rhs()].into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
struct LateFuncLinter<'a> {
|
||||
linter: &'a mut ExprLinter,
|
||||
linter: &'a mut Linter,
|
||||
func_info: FuncInfo,
|
||||
return_block_info: Option<ReturnBlockInfo>,
|
||||
}
|
||||
|
@ -753,6 +802,28 @@ impl<'a> Block<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TypedExpr<'a> {
|
||||
expr: ast::Expr<'a>,
|
||||
ty: Option<Ty>,
|
||||
}
|
||||
|
||||
impl TypedExpr<'_> {
|
||||
fn is_str(&self, ctx: &impl TyCtx) -> bool {
|
||||
self.ty
|
||||
.as_ref()
|
||||
.map(|ty| ty.is_str(ctx))
|
||||
.unwrap_or_else(|| matches!(self.expr, ast::Expr::Str(..)))
|
||||
}
|
||||
|
||||
fn is_type(&self, ctx: &impl TyCtx) -> bool {
|
||||
self.ty
|
||||
.as_ref()
|
||||
.map(|ty| ty.is_type(ctx))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
enum BuggyBlockLoc<'a> {
|
||||
Show(ast::ShowRule<'a>),
|
||||
IfTrue(ast::Conditional<'a>),
|
||||
|
@ -830,3 +901,8 @@ impl BuggyBlockLoc<'_> {
|
|||
fn is_show_set(it: ast::Expr) -> bool {
|
||||
matches!(it, ast::Expr::Set(..) | ast::Expr::Show(..))
|
||||
}
|
||||
|
||||
fn is_compare_op(op: ast::BinOp) -> bool {
|
||||
use ast::BinOp::*;
|
||||
matches!(op, Lt | Leq | Gt | Geq | Eq | Neq)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue