Skip to content

Commit 2c35216

Browse files
saeckielegaanz
authored andcommitted
Add check for readme links to file in default branch
1 parent d0fb136 commit 2c35216

1 file changed

Lines changed: 88 additions & 3 deletions

File tree

src/check/readme.rs

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ fn check_readme_link_url(
248248
if url.contains("://") {
249249
// TODO: Should we check the URL here like for the `homepage` and
250250
// `repository` manifest fields?
251+
252+
check_repo_file_url(diags, readme, sourcepos, url);
251253
} else if url.starts_with("#") {
252254
// TODO: Validate markdown anchor.
253255
} else {
@@ -257,9 +259,9 @@ fn check_readme_link_url(
257259
Diagnostic::error()
258260
.with_code("readme/link/file-not-found")
259261
.with_message(format_args!(
260-
"Linked file not found: `{url}`. Make sure to commit all linked files, \
261-
and possibly add them to the `exclude` list. \
262-
More details: https://github.com/typst/packages/blob/main/docs/tips.md#what-to-commit-what-to-exclude",
262+
"Linked file not found: `{url}`.\n\n\
263+
Make sure to commit all linked files and possibly add them to the `exclude` list.\n\n\
264+
More details: https://github.com/typst/packages/blob/main/docs/tips.md#what-to-commit-what-to-exclude",
263265
))
264266
.with_labels(vec![Label::primary(
265267
readme_fake_file_id(),
@@ -270,6 +272,89 @@ fn check_readme_link_url(
270272
}
271273
}
272274

275+
const DEFAULT_BRANCHES: [&str; 2] = ["main", "master"];
276+
277+
fn check_repo_file_url(
278+
diags: &mut Diagnostics,
279+
readme: &str,
280+
sourcepos: Sourcepos,
281+
url: &str,
282+
) -> Option<()> {
283+
static GITHUB_URL: LazyLock<Regex> = LazyLock::new(|| {
284+
Regex::new(r"https://github.com/([^/]+)/([^/]+)/(?:blob|tree)/([^/]+)/(.+)").unwrap()
285+
});
286+
static GITHUB_RAW_URL: LazyLock<Regex> = LazyLock::new(|| {
287+
Regex::new(
288+
r"^https://raw.githubusercontent.com/([^/]+)/([^/]+)/(?:refs/heads/)?([^/]+)/(.+)$",
289+
)
290+
.unwrap()
291+
});
292+
static GITLAB_URL: LazyLock<Regex> = LazyLock::new(|| {
293+
Regex::new(r"https://gitlab.com/([^/]+)/([^/]+)/-/(?:raw|blob|tree)/([^/]+)/(.+)").unwrap()
294+
});
295+
static CODEBERG_URL: LazyLock<Regex> = LazyLock::new(|| {
296+
Regex::new(r"https://codeberg.org/([^/]+)/([^/]+)/(?:raw|src)/branch/([^/]+)/(.+)").unwrap()
297+
});
298+
299+
enum Host {
300+
Github,
301+
Gitlab,
302+
Codeberg,
303+
}
304+
305+
let (host, captures) = if let Some(captures) =
306+
(GITHUB_URL.captures(url)).or_else(|| GITHUB_RAW_URL.captures(url))
307+
{
308+
(Host::Github, captures)
309+
} else if let Some(captures) = GITLAB_URL.captures(url) {
310+
(Host::Gitlab, captures)
311+
} else if let Some(captures) = CODEBERG_URL.captures(url) {
312+
(Host::Codeberg, captures)
313+
} else {
314+
return None;
315+
};
316+
317+
let user = captures.get(1).unwrap().as_str();
318+
let repo = captures.get(2).unwrap().as_str();
319+
let branch = captures.get(3).unwrap().as_str();
320+
let path = captures.get(4).unwrap().as_str();
321+
322+
if !DEFAULT_BRANCHES.contains(&branch) {
323+
return None;
324+
}
325+
326+
let name = match host {
327+
Host::Github => "GitHub",
328+
Host::Gitlab => "Gitlab",
329+
Host::Codeberg => "Codeberg",
330+
};
331+
332+
let non_raw_url = match host {
333+
Host::Github => format!("https://github.com/{user}/{repo}/blob/{branch}/{path}"),
334+
Host::Gitlab => format!("https://gitlab.com/{user}/{repo}/-/blob/{branch}/{path}"),
335+
Host::Codeberg => format!("https://codeberg.org/{user}/{repo}/src/branch/{branch}/{path}"),
336+
};
337+
338+
diags.emit(
339+
Diagnostic::warning()
340+
.with_code("readme/link/github-url-permalink")
341+
.with_message(format_args!(
342+
"{name} URL links to default branch: `{url}`.\n\n\
343+
Consider using a link to a specific tag/release or a permalink to a commit instead. \
344+
This will ensure that the linked resource always matches this version of the package.\n\n\
345+
You can create a permalink here: {non_raw_url}\n\n\
346+
Alternatively you can also link to a local file. This is preferred if the linked file \
347+
is already present in the submitted package."
348+
))
349+
.with_label(Label::primary(
350+
readme_fake_file_id(),
351+
sourcepos_to_range(readme, sourcepos),
352+
)),
353+
);
354+
355+
Some(())
356+
}
357+
273358
fn check_image_alternative_description(
274359
diags: &mut Diagnostics,
275360
readme: &str,

0 commit comments

Comments
 (0)