Skip to content

Commit 6b08a6c

Browse files
authored
error messages and parse updates (#109)
* error messages and parse updates
1 parent 98e4947 commit 6b08a6c

File tree

719 files changed

+118285
-96496
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

719 files changed

+118285
-96496
lines changed

crates/pico-quarto-render/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ fn process_qmd_file(
127127
qmd_path.to_str().unwrap_or("<unknown>"),
128128
&mut output_stream,
129129
true,
130+
None,
130131
)
131132
.map_err(|diagnostics| {
132133
// Format error messages using DiagnosticMessage API
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
use anyhow::{Context, Result};
2+
use std::fs;
3+
use std::path::Path;
4+
5+
use crate::rule::{CheckResult, ConvertResult, Rule, SourceLocation};
6+
use crate::utils::file_io::read_file;
7+
8+
pub struct ApostropheQuotesConverter {}
9+
10+
#[derive(Debug, Clone)]
11+
struct ApostropheViolation {
12+
offset: usize, // Offset of the apostrophe character
13+
error_location: Option<SourceLocation>, // For reporting
14+
}
15+
16+
impl ApostropheQuotesConverter {
17+
pub fn new() -> Result<Self> {
18+
Ok(Self {})
19+
}
20+
21+
/// Get parse errors and extract Q-2-10 apostrophe violations
22+
fn get_apostrophe_violations(&self, file_path: &Path) -> Result<Vec<ApostropheViolation>> {
23+
let content = fs::read_to_string(file_path)
24+
.with_context(|| format!("Failed to read file: {}", file_path.display()))?;
25+
26+
// Parse with quarto-markdown-pandoc to get diagnostics
27+
let mut sink = std::io::sink();
28+
let filename = file_path.to_string_lossy();
29+
30+
let result = quarto_markdown_pandoc::readers::qmd::read(
31+
content.as_bytes(),
32+
false, // not loose mode
33+
&filename,
34+
&mut sink,
35+
true,
36+
None,
37+
);
38+
39+
let diagnostics = match result {
40+
Ok(_) => return Ok(Vec::new()), // No errors
41+
Err(diagnostics) => diagnostics,
42+
};
43+
44+
let mut violations = Vec::new();
45+
46+
for diagnostic in diagnostics {
47+
// Check if this is a Q-2-10 error
48+
if diagnostic.code.as_deref() != Some("Q-2-10") {
49+
continue;
50+
}
51+
52+
// Extract location
53+
let location = diagnostic.location.as_ref();
54+
if location.is_none() {
55+
continue;
56+
}
57+
58+
let offset = location.as_ref().unwrap().start_offset();
59+
60+
violations.push(ApostropheViolation {
61+
offset,
62+
error_location: Some(SourceLocation {
63+
row: self.offset_to_row(&content, offset),
64+
column: self.offset_to_column(&content, offset),
65+
}),
66+
});
67+
}
68+
69+
Ok(violations)
70+
}
71+
72+
/// Apply fixes to the content by inserting backslashes before apostrophes
73+
fn apply_fixes(
74+
&self,
75+
content: &str,
76+
mut violations: Vec<ApostropheViolation>,
77+
) -> Result<String> {
78+
if violations.is_empty() {
79+
return Ok(content.to_string());
80+
}
81+
82+
// Sort violations in reverse order to avoid offset invalidation
83+
violations.sort_by_key(|v| std::cmp::Reverse(v.offset));
84+
85+
let mut result = content.to_string();
86+
87+
for violation in violations {
88+
// The offset points to the space after the apostrophe,
89+
// so we need to insert the backslash at offset-1 (before the apostrophe)
90+
result.insert(violation.offset - 1, '\\');
91+
}
92+
93+
Ok(result)
94+
}
95+
96+
/// Convert byte offset to row number (0-indexed)
97+
fn offset_to_row(&self, content: &str, offset: usize) -> usize {
98+
content[..offset].matches('\n').count()
99+
}
100+
101+
/// Convert byte offset to column number (0-indexed)
102+
fn offset_to_column(&self, content: &str, offset: usize) -> usize {
103+
let line_start = content[..offset]
104+
.rfind('\n')
105+
.map(|pos| pos + 1)
106+
.unwrap_or(0);
107+
offset - line_start
108+
}
109+
}
110+
111+
impl Rule for ApostropheQuotesConverter {
112+
fn name(&self) -> &str {
113+
"apostrophe-quotes"
114+
}
115+
116+
fn description(&self) -> &str {
117+
"Fix Q-2-10: Escape apostrophes misinterpreted as quote closes"
118+
}
119+
120+
fn check(&self, file_path: &Path, _verbose: bool) -> Result<Vec<CheckResult>> {
121+
let violations = self.get_apostrophe_violations(file_path)?;
122+
123+
let results: Vec<CheckResult> = violations
124+
.into_iter()
125+
.map(|v| CheckResult {
126+
rule_name: self.name().to_string(),
127+
file_path: file_path.to_string_lossy().to_string(),
128+
has_issue: true,
129+
issue_count: 1,
130+
message: Some(format!(
131+
"Q-2-10 apostrophe violation at offset {}",
132+
v.offset
133+
)),
134+
location: v.error_location,
135+
error_code: Some("Q-2-10".to_string()),
136+
error_codes: None,
137+
})
138+
.collect();
139+
140+
Ok(results)
141+
}
142+
143+
fn convert(
144+
&self,
145+
file_path: &Path,
146+
in_place: bool,
147+
check_mode: bool,
148+
_verbose: bool,
149+
) -> Result<ConvertResult> {
150+
let content = read_file(file_path)?;
151+
let violations = self.get_apostrophe_violations(file_path)?;
152+
153+
if violations.is_empty() {
154+
return Ok(ConvertResult {
155+
rule_name: self.name().to_string(),
156+
file_path: file_path.to_string_lossy().to_string(),
157+
fixes_applied: 0,
158+
message: Some("No Q-2-10 apostrophe issues found".to_string()),
159+
});
160+
}
161+
162+
let fixed_content = self.apply_fixes(&content, violations.clone())?;
163+
164+
if check_mode {
165+
// Just report what would be done
166+
return Ok(ConvertResult {
167+
rule_name: self.name().to_string(),
168+
file_path: file_path.to_string_lossy().to_string(),
169+
fixes_applied: violations.len(),
170+
message: Some(format!(
171+
"Would fix {} Q-2-10 apostrophe violation(s)",
172+
violations.len()
173+
)),
174+
});
175+
}
176+
177+
if in_place {
178+
// Write back to file
179+
crate::utils::file_io::write_file(file_path, &fixed_content)?;
180+
Ok(ConvertResult {
181+
rule_name: self.name().to_string(),
182+
file_path: file_path.to_string_lossy().to_string(),
183+
fixes_applied: violations.len(),
184+
message: Some(format!(
185+
"Fixed {} Q-2-10 apostrophe violation(s)",
186+
violations.len()
187+
)),
188+
})
189+
} else {
190+
// Return the converted content in message
191+
Ok(ConvertResult {
192+
rule_name: self.name().to_string(),
193+
file_path: file_path.to_string_lossy().to_string(),
194+
fixes_applied: violations.len(),
195+
message: Some(fixed_content),
196+
})
197+
}
198+
}
199+
}

crates/qmd-syntax-helper/src/conversions/attribute_ordering.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ impl AttributeOrderingConverter {
4949
&filename,
5050
&mut sink,
5151
true,
52+
None,
5253
);
5354

5455
let diagnostics = match result {

0 commit comments

Comments
 (0)