Reduce cognitive complexity in find, lists, PDF font/layer scans; fix wildcard import (#1846)

* test(pdf): replace wildcard super import in registration_tests

Resolves SonarCloud rust:S2208 (new code).

* test(engine): extract child-scope rejection assertions in find tests

Resolves SonarCloud rust:S3776 on find_scope_children_error_atomically_and_parent_requests_stay_top_level.

* refactor(engine): extract per-paragraph list update from apply_list

Resolves SonarCloud rust:S3776 on apply_list.

* refactor(pdf): extract form scanning from FontScan::content

Resolves SonarCloud rust:S3776 on FontScan::content (pdfx/fonts.rs).

* refactor(pdf): extract layer helpers from optional_content::prepare

Resolves SonarCloud rust:S3776 on prepare (optional_content.rs).
This commit is contained in:
Dylan M. Taylor
2026-10-25 21:25:48 -04:00
committed by GitHub
parent e91f830116
commit e413cdf59b
5 changed files with 131 additions and 106 deletions
+22 -16
View File
@@ -1082,6 +1082,27 @@ mod tests {
(s, sid, selected)
}
/// Child-story scopes are rejected by every find command, leaving the document untouched.
fn assert_child_scope_requests_rejected(s: &mut Session) {
let before = s.doc().unwrap().doc.clone();
let selection = s.doc().unwrap().selection.clone();
let history = s.doc().unwrap().history.undo.len();
let revision = s.doc().unwrap().revision;
let journal = s.journal.clone();
for scope in ["story", "selection"] {
for command in ["find.find", "find.next", "find.change"] {
let err = s.execute(command, &json!({"find": "red", "change": "blue", "scope": scope})).unwrap_err();
assert!(matches!(err, crate::EngineError::BadParams { .. }), "{err}");
assert!(err.to_string().contains("not supported"));
assert!(std::sync::Arc::ptr_eq(&before, &s.doc().unwrap().doc));
assert_eq!(s.doc().unwrap().selection, selection);
assert_eq!(s.doc().unwrap().history.undo.len(), history);
assert_eq!(s.doc().unwrap().revision, revision);
assert_eq!(s.journal, journal);
}
}
}
#[test]
fn find_scope_children_error_atomically_and_parent_requests_stay_top_level() {
for note in [false, true] {
@@ -1090,23 +1111,8 @@ mod tests {
selected.anchor = 0;
selected.focus = child.len();
s.doc_mut().unwrap().selection = designcraft_doc::Selection::text(selected);
let before = s.doc().unwrap().doc.clone();
assert_child_scope_requests_rejected(&mut s);
let selection = s.doc().unwrap().selection.clone();
let history = s.doc().unwrap().history.undo.len();
let revision = s.doc().unwrap().revision;
let journal = s.journal.clone();
for scope in ["story", "selection"] {
for command in ["find.find", "find.next", "find.change"] {
let err = s.execute(command, &json!({"find": "red", "change": "blue", "scope": scope})).unwrap_err();
assert!(matches!(err, crate::EngineError::BadParams { .. }), "{err}");
assert!(err.to_string().contains("not supported"));
assert!(std::sync::Arc::ptr_eq(&before, &s.doc().unwrap().doc));
assert_eq!(s.doc().unwrap().selection, selection);
assert_eq!(s.doc().unwrap().history.undo.len(), history);
assert_eq!(s.doc().unwrap().revision, revision);
assert_eq!(s.journal, journal);
}
}
for explicit in [false, true] {
let mut p = json!({"find": "red", "change": "blue"});
if explicit {
+32 -29
View File
@@ -92,40 +92,43 @@ fn apply_list(s: &mut Session, p: &Value, kind: ListType) -> Result<Value> {
for t in &targets {
let Some(st) = d.text_story_mut(t.story, t.cell) else { continue };
let span = para_span(&st.text, &t.range);
st.format_paras(span, |pf| {
let (now, _) = styles.resolve_para(pf);
// Off takes away this list only: a paragraph with the other list keeps it.
if !on && now.list_type != kind {
return;
}
let (style, _) = styles.resolve_para_style(&pf.style);
// The hanging indent this button set (and nothing has changed since).
let own_hang = pf.para.list_hang == Some(true) && now.left_indent == LIST_HANG && now.first_line_indent == -LIST_HANG;
// A value: left to the style when it gives the same, else set here.
let mut set = |key: &str, v: Value, same: bool| {
let _ = pf.para.set_json(key, if same { &Value::Null } else { &v });
};
let list = if on { kind } else { ListType::None };
set("listType", serde_json::to_value(list).unwrap_or(Value::Null), style.list_type == list);
if on && now.left_indent == 0.0 && now.first_line_indent == 0.0 {
set("leftIndent", json!(LIST_HANG), style.left_indent == LIST_HANG);
set("firstLineIndent", json!(-LIST_HANG), style.first_line_indent == -LIST_HANG);
set("listHang", json!(true), false);
} else if !on && now.list_type == kind {
// Only the hanging indent set here: one the paragraph had before, or its style
// gives, stays.
if own_hang {
set("leftIndent", json!(0.0), style.left_indent == 0.0);
set("firstLineIndent", json!(0.0), style.first_line_indent == 0.0);
}
set("listHang", Value::Null, true);
}
});
st.format_paras(span, |pf| set_para_list(pf, &styles, on, kind));
}
Ok(json!({"on": on}))
})
}
/// One paragraph's share of [`apply_list`]: turns the list on or off, setting only the values the
/// style does not already give.
fn set_para_list(pf: &mut designcraft_doc::ParaFormat, styles: &designcraft_doc::Styles, on: bool, kind: ListType) {
let (now, _) = styles.resolve_para(pf);
// Off takes away this list only: a paragraph with the other list keeps it.
if !on && now.list_type != kind {
return;
}
let (style, _) = styles.resolve_para_style(&pf.style);
// The hanging indent this button set (and nothing has changed since).
let own_hang = pf.para.list_hang == Some(true) && now.left_indent == LIST_HANG && now.first_line_indent == -LIST_HANG;
// A value: left to the style when it gives the same, else set here.
let mut set = |key: &str, v: Value, same: bool| {
let _ = pf.para.set_json(key, if same { &Value::Null } else { &v });
};
let list = if on { kind } else { ListType::None };
set("listType", serde_json::to_value(list).unwrap_or(Value::Null), style.list_type == list);
if on && now.left_indent == 0.0 && now.first_line_indent == 0.0 {
set("leftIndent", json!(LIST_HANG), style.left_indent == LIST_HANG);
set("firstLineIndent", json!(-LIST_HANG), style.first_line_indent == -LIST_HANG);
set("listHang", json!(true), false);
} else if !on && now.list_type == kind {
// Only the hanging indent set here: one the paragraph had before, or its style gives, stays.
if own_hang {
set("leftIndent", json!(0.0), style.left_indent == 0.0);
set("firstLineIndent", json!(0.0), style.first_line_indent == 0.0);
}
set("listHang", Value::Null, true);
}
}
/// The range whose paragraphs a list applies to: a selection ending where a paragraph starts (after
/// a return) doesn't reach into that paragraph. The story's last paragraph, empty after a final
/// return, stays in when the range runs to the end (a whole frame).