fix(google-sheets): support multi-letter column names (AA, AH, ZZ, etc.) in range operations#1
fix(google-sheets): support multi-letter column names (AA, AH, ZZ, etc.) in range operations#1
Conversation
…ions Co-authored-by: tomasmcm <6323928+tomasmcm@users.noreply.github.com>
|
@copilot there is already a |
There was a problem hiding this comment.
Pull request overview
This PR fixes Google Sheets range operations for columns beyond Z by replacing single-character ASCII math with a proper base-26 column-letter decoder, ensuring ranges like AA:AH map to the correct grid indices.
Changes:
- Added
_columnLetterToIndex(col)helper to convert multi-letter column names to 0-based indices. - Updated range-building logic to use
_columnLetterToIndexacross formatting, conditional formatting, data validation, merge, and protected range actions. - Adjusted end column handling to consistently use Google Sheets API’s exclusive
endColumnIndex.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| components/google_sheets/google_sheets.app.mjs | Adds _columnLetterToIndex helper and updates resetRowFormat to support multi-letter columns. |
| components/google_sheets/actions/update-formatting/update-formatting.mjs | Uses _columnLetterToIndex for correct start/end column indices in formatting updates. |
| components/google_sheets/actions/merge-cells/merge-cells.mjs | Uses _columnLetterToIndex for correct column indices when merging ranges. |
| components/google_sheets/actions/add-conditional-format-rule/add-conditional-format-rule.mjs | Uses _columnLetterToIndex so conditional formatting ranges work past column Z. |
| components/google_sheets/actions/set-data-validation/set-data-validation.mjs | Uses _columnLetterToIndex so validation ranges work past column Z. |
| components/google_sheets/actions/update-conditional-format-rule/update-conditional-format-rule.mjs | Uses _columnLetterToIndex for correct conditional format rule update ranges. |
| components/google_sheets/actions/add-protected-range/add-protected-range.mjs | Uses _columnLetterToIndex so protected ranges work past column Z. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| */ | ||
| _columnLetterToIndex(col) { | ||
| if (!col) return 0; | ||
| let index = 0; | ||
| const upper = col.toUpperCase(); | ||
| for (let i = 0; i < upper.length; i++) { | ||
| index = index * 26 + (upper.charCodeAt(i) - 64); |
There was a problem hiding this comment.
_columnLetterToIndex currently defaults to 0 when col is falsy, and it will also happily compute an index for non A–Z characters (e.g. $A$, whitespace), which can yield negative/incorrect indices and silently target the wrong range. Consider validating col (string, non-empty, ^[A-Z]+$ after normalization/stripping $) and throwing an error instead of defaulting to column A.
| */ | |
| _columnLetterToIndex(col) { | |
| if (!col) return 0; | |
| let index = 0; | |
| const upper = col.toUpperCase(); | |
| for (let i = 0; i < upper.length; i++) { | |
| index = index * 26 + (upper.charCodeAt(i) - 64); | |
| * @throws {Error} If the column reference is not a non-empty A–Z string. | |
| */ | |
| _columnLetterToIndex(col) { | |
| if (!isString(col)) { | |
| throw new Error("Column reference must be a non-empty string"); | |
| } | |
| // Normalize: remove absolute-reference markers, trim whitespace, and uppercase | |
| const normalized = col.replace(/\$/g, "").trim().toUpperCase(); | |
| // Validate: must be non-empty and contain only A–Z characters | |
| if (!normalized || !/^[A-Z]+$/.test(normalized)) { | |
| throw new Error(`Invalid column reference: "${col}"`); | |
| } | |
| let index = 0; | |
| for (let i = 0; i < normalized.length; i++) { | |
| index = index * 26 + (normalized.charCodeAt(i) - 64); |
| let index = 0; | ||
| const upper = col.toUpperCase(); | ||
| for (let i = 0; i < upper.length; i++) { | ||
| index = index * 26 + (upper.charCodeAt(i) - 64); | ||
| } | ||
| return index - 1; // 0-based for Google Sheets API |
There was a problem hiding this comment.
This helper duplicates the existing _getColumnIndex(column) base-26 logic in this file (which already supports multi-letter columns). To reduce divergence risk, consider implementing _columnLetterToIndex in terms of _getColumnIndex (or consolidating to a single helper with clear 0-based vs 1-based semantics) and avoid the magic -64 constant for readability.
| let index = 0; | |
| const upper = col.toUpperCase(); | |
| for (let i = 0; i < upper.length; i++) { | |
| index = index * 26 + (upper.charCodeAt(i) - 64); | |
| } | |
| return index - 1; // 0-based for Google Sheets API | |
| // Delegate to the existing helper that converts a column letter to a 1-based index, | |
| // then convert to a 0-based index for use with the Google Sheets API. | |
| const oneBasedIndex = this._getColumnIndex(col); | |
| return oneBasedIndex - 1; |
…helper Co-authored-by: tomasmcm <6323928+tomasmcm@users.noreply.github.com>
Good call — replaced |
|
@copilot this got approved, can you run this command to bump the version codes? |
WHY
Column-to-index conversion used
charCodeAt(0) - 65, which silently truncates multi-letter columns —"AH"becomes"A"(index 0),"ZZ"becomes"Z"(index 25). Any range operation on columns pastZsilently applies to the wrong column.Root cause: 7 call sites across formatting, validation, merge, and protection actions all used the single-char hack instead of a proper base-26 decode.
Fix: Replaced all broken
charCodeAt(0)usages with the existing_getColumnIndexmethod, which already implements the correct base-26 algorithm (A→1, Z→26, AA→27, AH→34, ZZ→702):Files updated:
google_sheets.app.mjs—resetRowFormatfixedupdate-formatting.mjsmerge-cells.mjsadd-conditional-format-rule.mjsset-data-validation.mjsupdate-conditional-format-rule.mjsadd-protected-range.mjs💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.