feat: fledge diagnostic hint patterns by updating 2 refiners (#1544)

* feat: support more diagnostic hint patterns about typst v0.13 deprecation

* feat: add out of root hint diagnostic refiner

* optimize multi-pattern search with `RegexSet`

* add "cannot spread content"

* fix test

* Revert "add "cannot spread content""

This reverts commit 7d6c981413.
This commit is contained in:
7mile 2025-03-20 14:30:35 +08:00 committed by GitHub
parent 766a41f4d5
commit 8d9a8f8bed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 103 additions and 8 deletions

View file

@ -5,6 +5,9 @@ use typst::syntax::Span;
use crate::{prelude::*, LspWorldExt};
use once_cell::sync::Lazy;
use regex::RegexSet;
/// Stores diagnostics for files.
pub type DiagnosticsMap = HashMap<Url, EcoVec<Diagnostic>>;
@ -64,7 +67,8 @@ fn convert_diagnostic(
let mut diag = Cow::Borrowed(typst_diagnostic);
// Extend more refiners here by adding their instances.
let refiners = &[&DeprecationRefiner::<13> {}];
let refiners: &[&dyn DiagnosticRefiner] =
&[&DeprecationRefiner::<13> {}, &OutOfRootHintRefiner {}];
// NOTE: It would be nice to have caching here.
for refiner in refiners {
@ -195,9 +199,19 @@ trait DiagnosticRefiner {
struct DeprecationRefiner<const MINOR: usize>();
static DEPRECATION_PATTERNS: Lazy<RegexSet> = Lazy::new(|| {
RegexSet::new([
r"unknown variable: style",
r"unexpected argument: fill",
r"type state has no method `display`",
r"only element functions can be used as selectors",
])
.expect("Invalid regular expressions")
});
impl DiagnosticRefiner for DeprecationRefiner<13> {
fn matches(&self, raw: &TypstDiagnostic) -> bool {
raw.message.contains("unknown variable: style")
DEPRECATION_PATTERNS.is_match(&raw.message)
}
fn refine(&self, raw: TypstDiagnostic) -> TypstDiagnostic {
@ -209,3 +223,20 @@ impl DiagnosticRefiner for DeprecationRefiner<13> {
))
}
}
struct OutOfRootHintRefiner();
impl DiagnosticRefiner for OutOfRootHintRefiner {
fn matches(&self, raw: &TypstDiagnostic) -> bool {
raw.message.contains("failed to load file (access denied)")
&& raw
.hints
.iter()
.any(|hint| hint.contains("cannot read file outside of project root"))
}
fn refine(&self, mut raw: TypstDiagnostic) -> TypstDiagnostic {
raw.hints.clear();
raw.with_hint("Cannot read file outside of project root.")
}
}

View file

@ -0,0 +1 @@
#import "../out-of-root/library.typ": item

View file

@ -1,2 +1,17 @@
#style(styles => {
})
// Test Typst v0.13 deprecated diagnostics.
--- style-function ---
#style(styles => {})
--- outline-fill-argument ---
#set outline(fill: white)
--- state-display-method ---
#state("ok").display()
--- locate-function ---
#locate((loc) => {})

View file

@ -0,0 +1 @@
#let item = 1

View file

@ -4,6 +4,23 @@ import * as vscode from "vscode";
import type { Context } from ".";
export async function getTests(ctx: Context) {
function parseTestFile(content: string): Record<string, string> {
// 初始化结果对象
const result: Record<string, string> = {};
const sectionRegex = /---\s*(\S+)\s*---\r?\n([\s\S]*?)(?=\r?\n---|$)/g;
let match;
while ((match = sectionRegex.exec(content)) !== null) {
const sectionName = match[1];
const sectionContent = match[2].trim();
result[sectionName] = sectionContent;
}
if (Object.keys(result).length === 0)
return { "default": content };
return result;
}
await ctx.suite("diagnostics", (suite) => {
vscode.window.showInformationMessage("Start all tests.");
const workspaceUri = ctx.getWorkspace("diag");
@ -52,14 +69,44 @@ export async function getTests(ctx: Context) {
suite.addTest("typst0.13 diag hints", async () => {
const mainUrl = vscode.Uri.joinPath(workspaceUri, "typst013.typ");
const [_1, _2, diags] = await ctx.diagnostics(1, async () => {
await ctx.openDocument(mainUrl);
ctx.timeout(400);
});
const editor = await ctx.openDocument(mainUrl);
const testCases = parseTestFile(editor.document.getText());
const checkTypstHint = (diags: vscode.Diagnostic[]) => {
ctx.expect(diags).to.have.lengthOf(1);
const diag = diags[0];
ctx.expect(diag.message).contains("Hint: Typst 0.13");
};
for (const [name, content] of Object.entries(testCases)) {
console.log(`Running test case ${name}`);
const stats = await ctx.diagnostics(1, async () => {
await editor.edit((edit) => {
edit.replace(new vscode.Range(0, 0, editor.document.lineCount, 0), content);
});
});
checkTypstHint(stats[2]);
await ctx.diagnostics(0, async () => {
await editor.edit((edit) => {
edit.delete(new vscode.Range(0, 0, editor.document.lineCount, 0));
});
});
}
});
suite.addTest("out of root diag hints", async () => {
const mainUrl = vscode.Uri.joinPath(workspaceUri, "out-of-root.typ");
const stats = await ctx.diagnostics(1, async () => {
await ctx.openDocument(mainUrl);
});
const diags = stats[2];
ctx.expect(diags).to.have.lengthOf(1);
const diag = diags[0];
ctx.expect(diag.message).contains("Hint: Cannot read file outside of project root");
ctx.expect(diag.message).not.contains("Hint: you can adjust the project root with the --root argument");
});
});
}