-
Notifications
You must be signed in to change notification settings - Fork 0
The relative-link check skips inline code spans, so a regular expression in backticks such as [2-9](\.\d+) is no longer reported as a missing file #295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1803,9 +1803,10 @@ fn cfg_test_lines(path: &Path) -> BTreeSet<u64> { | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /// `(line_number, target)` for every resolvable link in a Markdown document. | ||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||
| /// Skips fenced code blocks, external schemes, and pure fragments. A link inside | ||||||||||||||||||||||||||||||||||
| /// a fence is an illustration rather than a reference, and resolving one would | ||||||||||||||||||||||||||||||||||
| /// fail on every README that documents a link. | ||||||||||||||||||||||||||||||||||
| /// Skips fenced code blocks, inline code spans, external schemes, and pure | ||||||||||||||||||||||||||||||||||
| /// fragments. A link inside a fence or a code span is an illustration rather | ||||||||||||||||||||||||||||||||||
| /// than a reference, and resolving one would fail on every README that | ||||||||||||||||||||||||||||||||||
| /// documents a link. | ||||||||||||||||||||||||||||||||||
| fn link_targets(text: &str) -> Vec<(u64, String)> { | ||||||||||||||||||||||||||||||||||
| static INLINE: OnceLock<Regex> = OnceLock::new(); | ||||||||||||||||||||||||||||||||||
| static REFERENCE: OnceLock<Regex> = OnceLock::new(); | ||||||||||||||||||||||||||||||||||
|
|
@@ -1850,6 +1851,8 @@ fn link_targets(text: &str) -> Vec<(u64, String)> { | |||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let line = without_code_spans(line); | ||||||||||||||||||||||||||||||||||
| let line = line.as_str(); | ||||||||||||||||||||||||||||||||||
| let mut targets: Vec<String> = Vec::new(); | ||||||||||||||||||||||||||||||||||
| if let Some(captures) = reference.captures(line) { | ||||||||||||||||||||||||||||||||||
| targets.push(capture_target(&captures)); | ||||||||||||||||||||||||||||||||||
|
|
@@ -1870,6 +1873,39 @@ fn link_targets(text: &str) -> Vec<(u64, String)> { | |||||||||||||||||||||||||||||||||
| found | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /// `line` with its inline code spans removed, as `CommonMark` delimits them: a | ||||||||||||||||||||||||||||||||||
| /// run of N backticks opens a span that the next run of exactly N closes. A run | ||||||||||||||||||||||||||||||||||
| /// with no matching closer is literal text and stays. | ||||||||||||||||||||||||||||||||||
| fn without_code_spans(line: &str) -> String { | ||||||||||||||||||||||||||||||||||
| let run_at = |from: usize| line[from..].len() - line[from..].trim_start_matches('`').len(); | ||||||||||||||||||||||||||||||||||
| let mut kept = String::with_capacity(line.len()); | ||||||||||||||||||||||||||||||||||
| let mut rest = 0; | ||||||||||||||||||||||||||||||||||
| while let Some(offset) = line[rest..].find('`') { | ||||||||||||||||||||||||||||||||||
| let open = rest + offset; | ||||||||||||||||||||||||||||||||||
| let width = run_at(open); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+1883
to
+1885
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1810,1915p' src/scan.rs
sed -n '950,990p' src/scan.rsRepository: HackingGate/uphold Length of output: 5956 🏁 Script executed: git diff --unified=8 d3ae04d84de231cff56d768ef46855ee621ff8ac 1242255ea24b210876ae54e93bfb7ee847ab0a95 -- src/scan.rs | sed -n '1,240p'
printf '\n--- related tests ---\n'
rg -n -C 3 'without_code_spans|inline code|code span|missing\.md|link_targets' src/scan.rsRepository: HackingGate/uphold Length of output: 6489 Do not treat an escaped backtick as a code-span opener. When a backtick is preceded by an odd number of backslashes, preserve the escaped backtick and continue scanning. Otherwise, 🐛 Suggested fix while let Some(offset) = line[rest..].find('`') {
let open = rest + offset;
+ let escapes = line[..open]
+ .bytes()
+ .rev()
+ .take_while(|&byte| byte == b'\\')
+ .count();
+ if escapes % 2 == 1 {
+ kept.push_str(&line[rest..open + 1]);
+ rest = open + 1;
+ continue;
+ }
let width = run_at(open);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| let mut search = open + width; | ||||||||||||||||||||||||||||||||||
| let mut close = None; | ||||||||||||||||||||||||||||||||||
| while let Some(found) = line[search..].find('`') { | ||||||||||||||||||||||||||||||||||
| let start = search + found; | ||||||||||||||||||||||||||||||||||
| let run = run_at(start); | ||||||||||||||||||||||||||||||||||
| if run == width { | ||||||||||||||||||||||||||||||||||
| close = Some(start + run); | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| search = start + run; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if let Some(end) = close { | ||||||||||||||||||||||||||||||||||
| kept.push_str(&line[rest..open]); | ||||||||||||||||||||||||||||||||||
| rest = end; | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+1898
to
+1899
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1810,1915p' src/scan.rs
rg -n 'LINK|link_targets|link_re' src/scan.rs | tail -65Repository: HackingGate/uphold Length of output: 4501 🏁 Script executed: printf '%s\n' '--- link scanner caller ---'
sed -n '935,985p' src/scan.rs
printf '%s\n' '--- PR diff for src/scan.rs ---'
git diff --unified=5 d3ae04d84de231cff56d768ef46855ee621ff8ac 1242255ea24b210876ae54e93bfb7ee847ab0a95 -- src/scan.rsRepository: HackingGate/uphold Length of output: 5859 Keep a separator when removing an inline code span. For 🐛 Suggested fix if let Some(end) = close {
kept.push_str(&line[rest..open]);
+ kept.push(' ');
rest = end;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| kept.push_str(&line[rest..open + width]); | ||||||||||||||||||||||||||||||||||
| rest = open + width; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| kept.push_str(&line[rest..]); | ||||||||||||||||||||||||||||||||||
| kept | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fn capture_target(captures: ®ex::Captures<'_>) -> String { | ||||||||||||||||||||||||||||||||||
| captures | ||||||||||||||||||||||||||||||||||
| .name("angled") | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: HackingGate/uphold
Length of output: 7325
🏁 Script executed:
Repository: HackingGate/uphold
Length of output: 35887
🏁 Script executed:
Repository: HackingGate/uphold
Length of output: 10332
🏁 Script executed:
Repository: HackingGate/uphold
Length of output: 4541
Track code spans across non-fenced lines.
When a valid code span opens on one line and closes after
[a](missing.md)on another, the per-line filter leaves the link visible.links-resolvecan then report it as a missing target and fail the scan. Match exact-width backtick runs across non-fenced lines before extracting links, while preserving the original line numbers. This is a narrow false-positive case, not a major-impact issue.🤖 Prompt for AI Agents