Refuse table names that clash with defined names or R1C1 references

Found while testing crates/engine/src/cmd/insert.rs:477

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-15 06:22:02 +02:00
co-authored by Claude Haiku 5.5
parent e6b6b34970
commit 1b4df8acc2
2 changed files with 25 additions and 2 deletions
+1 -1
View File
@@ -237,7 +237,7 @@ fn valid_name(n: &str) -> bool {
/// Looks like an R1C1 reference (`R`, `C`, `RC`, `R2`, `C3`, `R1C1`), which Excel doesn't
/// accept as a name.
fn is_r1c1(n: &str) -> bool {
pub(crate) fn is_r1c1(n: &str) -> bool {
let digits = |s: &str| s.bytes().take_while(u8::is_ascii_digit).count();
let mut rest = n;
if let Some(r) = rest.strip_prefix(['R', 'r']) {
+24 -1
View File
@@ -471,6 +471,7 @@ fn table_style(s: &mut Session, p: &Json) -> Result<Json> {
fn valid_table_name(name: &str) -> bool {
name.starts_with(|c: char| c.is_alphabetic() || c == '_' || c == '\\')
&& CellRef::parse(name).is_none()
&& !super::formulas::is_r1c1(name)
&& name.chars().enumerate().all(|(i, c)| c.is_alphanumeric() || c == '_' || c == '.' || (i == 0 && c == '\\'))
}
@@ -480,7 +481,8 @@ fn table_rename(s: &mut Session, p: &Json) -> Result<Json> {
if !valid_table_name(&name) {
return Err(EngineError::Other("The name that you entered is not valid.".into()));
}
if s.doc()?.wb.table(&name).is_some_and(|x| x != (si, ti)) {
let wb = &s.doc()?.wb;
if wb.table(&name).is_some_and(|x| x != (si, ti)) || wb.names.iter().any(|n| n.name.eq_ignore_ascii_case(&name)) {
return Err(EngineError::Other("The name entered already exists. Enter a unique name.".into()));
}
edit(s, |cx| {
@@ -1553,4 +1555,25 @@ mod tests {
let rules = s.execute("home.manageRules", json!({})).unwrap();
assert_eq!(rules["rules"].as_array().map(Vec::len), Some(0));
}
#[test]
fn table_names_refuse_defined_names_and_r1c1() {
let mut s = Session::new();
s.new_workbook();
s.execute("range.setValues", json!({"range": "A1:A2", "values": [["h"], ["1"]]})).unwrap();
s.execute("formulas.defineName", json!({"name": "Rate", "refersTo": "=0.2"})).unwrap();
s.execute("insert.table", json!({"range": "A1:A2"})).unwrap();
let e = s.execute("table.rename", json!({"table": "Table1", "name": "Rate"})).unwrap_err();
assert!(e.to_string().contains("already exists"), "{e}");
let e = s.execute("table.rename", json!({"table": "Table1", "name": "rate"})).unwrap_err();
assert!(e.to_string().contains("already exists"), "{e}");
for n in ["R2C3", "c", "r", "RC", "C5"] {
let e = s.execute("table.rename", json!({"table": "Table1", "name": n})).unwrap_err();
assert!(e.to_string().contains("not valid"), "{n}: {e}");
let e = s.execute("insert.table", json!({"range": "C1:C2", "name": n})).unwrap_err();
assert!(e.to_string().contains("not valid"), "{n}: {e}");
}
s.execute("table.rename", json!({"table": "Table1", "name": "Table1"})).unwrap();
s.execute("table.rename", json!({"table": "Table1", "name": "Rates"})).unwrap();
}
}