Skip to content

Commit 3be6762

Browse files
committed
Add check for unlinked manual files
1 parent a71b6d3 commit 3be6762

4 files changed

Lines changed: 79 additions & 32 deletions

File tree

src/check.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use typst::{
66
WorldExt,
77
};
88

9-
use crate::check::readme::check_readme;
109
use crate::world::SystemWorld;
1110

1211
pub mod authors;
@@ -30,8 +29,6 @@ pub async fn all_checks(
3029

3130
let (manifest, worlds) = manifest::check(&package_dir, &mut diags, package_spec).await?;
3231

33-
files::check(&mut diags, &package_dir, &manifest);
34-
3532
compile::check(&mut diags, &worlds.package);
3633
if let Some(template_world) = worlds.template {
3734
let mut template_diags = Diagnostics::default();
@@ -43,8 +40,10 @@ pub async fn all_checks(
4340
diags.extend(template_diags, template_dir);
4441
}
4542

46-
let res = check_readme(&worlds.package, &mut diags).await;
47-
diags.maybe_emit(res);
43+
let res = readme::check(&worlds.package, &mut diags).await;
44+
let readme = diags.maybe_emit(res);
45+
46+
files::check(&mut diags, &package_dir, &manifest, &readme);
4847

4948
kebab_case::check(&mut diags, &worlds.package);
5049

src/check/diagnostics.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ pub struct Diagnostics {
3636
}
3737

3838
impl Diagnostics {
39-
pub fn maybe_emit<T>(&mut self, maybe_err: Result<T>) {
40-
if let Err(e) = maybe_err {
41-
self.emit(e)
39+
pub fn maybe_emit<T>(&mut self, maybe_err: Result<T>) -> Option<T> {
40+
match maybe_err {
41+
Ok(v) => Some(v),
42+
Err(e) => {
43+
self.emit(e);
44+
None
45+
}
4246
}
4347
}
4448

src/check/files.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ use codespan_reporting::diagnostic::{Diagnostic, Label};
55

66
use crate::check::manifest::Manifest;
77
use crate::check::path::PackagePath;
8+
use crate::check::readme::Readme;
89
use crate::check::Diagnostics;
910

10-
pub fn check(diags: &mut Diagnostics, package_dir: &Path, manifest: &Manifest) {
11+
pub fn check(
12+
diags: &mut Diagnostics,
13+
package_dir: &Path,
14+
manifest: &Manifest,
15+
readme: &Option<Readme>,
16+
) {
1117
let exclude = &manifest.package.exclude;
1218
let thumbnail_path = manifest.thumbnail();
1319

@@ -43,6 +49,7 @@ pub fn check(diags: &mut Diagnostics, package_dir: &Path, manifest: &Manifest) {
4349
forbid_font_files(diags, file_path);
4450
exclude_large_files(diags, file_path, excluded, metadata.len());
4551
exclude_examples_and_tests(diags, file_path, excluded);
52+
link_manuals(diags, readme, file_path, excluded);
4653
}
4754
}
4855

@@ -110,10 +117,7 @@ fn exclude_large_files(
110117
}
111118

112119
fn check_wasm_file_size(diags: &mut Diagnostics, path: PackagePath<&Path>, original_size: u64) {
113-
let Some(file_name) = path.full().file_name() else {
114-
return;
115-
};
116-
let out = std::env::temp_dir().join(file_name);
120+
let out = std::env::temp_dir().join(path.file_name());
117121

118122
let wasm_opt_result = wasm_opt::OptimizationOptions::new_optimize_for_size()
119123
// Explicitely enable and disable features to best match what wasmi supports
@@ -194,3 +198,33 @@ fn forbid_font_files(diags: &mut Diagnostics, path: PackagePath<&Path>) {
194198
),
195199
);
196200
}
201+
202+
fn link_manuals(
203+
diags: &mut Diagnostics,
204+
readme: &Option<Readme>,
205+
path: PackagePath<&Path>,
206+
excluded: bool,
207+
) {
208+
let Some(readme) = readme.as_ref() else {
209+
return;
210+
};
211+
212+
let name = path.file_name().to_string_lossy().to_lowercase();
213+
214+
const MANUAL_FILES: [&str; 4] = ["manual.pdf", "doc.pdf", "docs.pdf", "documentation.pdf"];
215+
if MANUAL_FILES.contains(&name.as_str())
216+
&& readme.linked_files.iter().all(|l| l.full() != path.full())
217+
{
218+
let note =
219+
excluded.then(|| "It should also be added to `exclude` in your `typst.toml`.".into());
220+
221+
diags.emit(Diagnostic::warning()
222+
.with_label(Label::primary(path.file_id(), 0..0))
223+
.with_code("files/manual/unlinked")
224+
.with_message(
225+
"This file seems to be a manual/documentation, but isn't linked in the readme. \
226+
It will be inacessible on Typst Universe.",
227+
)
228+
.with_notes_iter(note));
229+
}
230+
}

src/check/readme.rs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@ use crate::{
1919
world::SystemWorld,
2020
};
2121

22-
pub async fn check_readme(
23-
world: &SystemWorld,
24-
diags: &mut Diagnostics,
25-
) -> crate::check::Result<()> {
22+
#[derive(Default)]
23+
pub struct Readme {
24+
pub text: String,
25+
pub linked_files: Vec<PackagePath>,
26+
}
27+
28+
pub async fn check(world: &SystemWorld, diags: &mut Diagnostics) -> crate::check::Result<Readme> {
2629
// check syntax, versions and kebab-case
2730
// warn on unsupported gfm features
28-
let readme = tokio::fs::read_to_string(world.root().join("README.md"))
31+
let text = tokio::fs::read_to_string(world.root().join("README.md"))
2932
.await
3033
.error("io/readme", "Failed to read README.md")?;
3134

3235
let arena = comrak::Arena::new();
3336
let md_ast = comrak::parse_document(
3437
&arena,
35-
&readme,
38+
&text,
3639
&comrak::Options {
3740
// Try to be faithful to the Universe parser
3841
extension: comrak::options::Extension {
@@ -52,6 +55,11 @@ pub async fn check_readme(
5255
},
5356
);
5457

58+
let mut readme = Readme {
59+
text,
60+
linked_files: Vec::new(),
61+
};
62+
5563
for node in md_ast.descendants() {
5664
let md_node = &*node.data.borrow();
5765
match &md_node.value {
@@ -87,7 +95,7 @@ pub async fn check_readme(
8795
}
8896
// Check if all links are valid.
8997
MdNode::Link(link) => {
90-
check_readme_link_url(world, diags, &readme, md_node.sourcepos, &link.url);
98+
check_readme_link_url(world, diags, &mut readme, md_node.sourcepos, &link.url);
9199
}
92100
// Check all image URLs are valid and alt text is specified.
93101
MdNode::Image(link) => {
@@ -100,19 +108,19 @@ pub async fn check_readme(
100108
}
101109
check_image_alternative_description(diags, &readme, md_node.sourcepos, &alt);
102110

103-
check_readme_link_url(world, diags, &readme, md_node.sourcepos, &link.url);
111+
check_readme_link_url(world, diags, &mut readme, md_node.sourcepos, &link.url);
104112
}
105113
MdNode::HtmlBlock(html) => {
106-
check_readme_html(world, diags, &readme, md_node.sourcepos, &html.literal);
114+
check_readme_html(world, diags, &mut readme, md_node.sourcepos, &html.literal);
107115
}
108116
MdNode::HtmlInline(html) => {
109-
check_readme_html(world, diags, &readme, md_node.sourcepos, html);
117+
check_readme_html(world, diags, &mut readme, md_node.sourcepos, html);
110118
}
111119
_ => (),
112120
}
113121
}
114122

115-
Ok(())
123+
Ok(readme)
116124
}
117125

118126
fn check_readme_code_block(
@@ -176,7 +184,7 @@ fn check_readme_code_block(
176184
fn check_readme_html(
177185
world: &SystemWorld,
178186
diags: &mut Diagnostics,
179-
readme: &str,
187+
readme: &mut Readme,
180188
sourcepos: Sourcepos,
181189
html: &str,
182190
) {
@@ -203,7 +211,7 @@ fn check_readme_html(
203211
fn check_html_elems(
204212
world: &SystemWorld,
205213
diags: &mut Diagnostics,
206-
readme: &str,
214+
readme: &mut Readme,
207215
sourcepos: Sourcepos,
208216
node: &markup5ever_rcdom::Node,
209217
) {
@@ -243,7 +251,7 @@ fn check_html_elems(
243251
fn check_readme_link_url(
244252
world: &SystemWorld,
245253
diags: &mut Diagnostics,
246-
readme: &str,
254+
readme: &mut Readme,
247255
sourcepos: Sourcepos,
248256
url_text: &str,
249257
) {
@@ -310,11 +318,13 @@ fn check_readme_link_url(
310318
)]),
311319
);
312320
}
321+
322+
readme.linked_files.push(path);
313323
}
314324

315325
const DEFAULT_BRANCHES: [&str; 2] = ["main", "master"];
316326

317-
fn check_repo_file_url(diags: &mut Diagnostics, readme: &str, sourcepos: Sourcepos, url: &str) {
327+
fn check_repo_file_url(diags: &mut Diagnostics, readme: &Readme, sourcepos: Sourcepos, url: &str) {
318328
static GITHUB_URL: LazyLock<Regex> = LazyLock::new(|| {
319329
Regex::new(r"https://github.com/([^/]+)/([^/]+)/(?:blob|tree)/([^/]+)/(.+)").unwrap()
320330
});
@@ -390,7 +400,7 @@ fn check_repo_file_url(diags: &mut Diagnostics, readme: &str, sourcepos: Sourcep
390400

391401
fn check_image_alternative_description(
392402
diags: &mut Diagnostics,
393-
readme: &str,
403+
readme: &Readme,
394404
sourcepos: Sourcepos,
395405
alt: &str,
396406
) {
@@ -430,7 +440,7 @@ fn check_image_alternative_description(
430440
}
431441
}
432442

433-
fn sourcepos_to_range(s: &str, pos: Sourcepos) -> Range<usize> {
443+
fn sourcepos_to_range(readme: &Readme, pos: Sourcepos) -> Range<usize> {
434444
fn byte_offset(s: &str, pos: LineColumn) -> usize {
435445
// `LineColumn` uses 1-indexed line numbers.
436446
let line_offset = s
@@ -443,9 +453,9 @@ fn sourcepos_to_range(s: &str, pos: Sourcepos) -> Range<usize> {
443453
}
444454

445455
// `LineColumn::column` is 1-indexed.
446-
let start = byte_offset(s, pos.start) - 1;
456+
let start = byte_offset(&readme.text, pos.start) - 1;
447457
// `Sourcepos::end` is end-inclusive (byte-wise), and thus `offset + 1 - 1`.
448-
let end = byte_offset(s, pos.end);
458+
let end = byte_offset(&readme.text, pos.end);
449459

450460
start..end
451461
}

0 commit comments

Comments
 (0)