Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/olive-cloths-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/form-core': patch
---

optimize makePathArray util function
46 changes: 10 additions & 36 deletions packages/form-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,6 @@ export function deleteBy(obj: any, _path: any) {
return doDelete(obj)
}

const reLineOfOnlyDigits = /^(\d+)$/gm
// the second dot must be in a lookahead or the engine
// will skip subsequent numbers (like foo.0.1.)
const reDigitsBetweenDots = /\.(\d+)(?=\.)/gm
const reStartWithDigitThenDot = /^(\d+)\./gm
const reDotWithDigitsToEnd = /\.(\d+$)/gm
const reMultipleDots = /\.{2,}/gm

const intPrefix = '__int__'
const intReplace = `${intPrefix}$1`

/**
* @private
*/
Expand All @@ -164,31 +153,16 @@ export function makePathArray(str: string | Array<string | number>) {
throw new Error('Path must be a string.')
}

return (
str
// Leading `[` may lead to wrong parsing down the line
// (Example: '[0][1]' should be '0.1', not '.0.1')
.replace(/(^\[)|]/gm, '')
.replace(/\[/g, '.')
.replace(reLineOfOnlyDigits, intReplace)
.replace(reDigitsBetweenDots, `.${intReplace}.`)
.replace(reStartWithDigitThenDot, `${intReplace}.`)
.replace(reDotWithDigitsToEnd, `.${intReplace}`)
.replace(reMultipleDots, '.')
.split('.')
.map((d) => {
if (d.startsWith(intPrefix)) {
const numStr = d.substring(intPrefix.length)
const num = parseInt(numStr, 10)

if (String(num) === numStr) {
return num
}
return numStr
}
return d
})
)
// Leading `[` may lead to wrong parsing (e.g. '[0][1]' → '0.1', not '.0.1')
return str
.replace(/(^\[)|]/g, '')
.replace(/\[/g, '.')
.replace(/\.{2,}/g, '.')
.split('.')
.map((segment) => {
const num = parseInt(segment, 10)
return String(num) === segment ? num : segment
})
Comment on lines +156 to +165
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Strip leading-dot paths before splitting.

concatenatePaths can legitimately produce .child when the parent path is empty, but this parser still turns that into ['', 'child']. Downstream, getBy/setBy/deleteBy will then read or write under an empty-string key instead of child.

Suggested fix
   return str
+    .replace(/^\./, '')
     .replace(/(^\[)|]/g, '')
     .replace(/\[/g, '.')
     .replace(/\.{2,}/g, '.')
     .split('.')
+    .filter(Boolean)
     .map((segment) => {
       const num = parseInt(segment, 10)
       return String(num) === segment ? num : segment
     })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Leading `[` may lead to wrong parsing (e.g. '[0][1]' → '0.1', not '.0.1')
return str
.replace(/(^\[)|]/g, '')
.replace(/\[/g, '.')
.replace(/\.{2,}/g, '.')
.split('.')
.map((segment) => {
const num = parseInt(segment, 10)
return String(num) === segment ? num : segment
})
// Leading `[` may lead to wrong parsing (e.g. '[0][1]' → '0.1', not '.0.1')
return str
.replace(/^\./, '')
.replace(/(^\[)|]/g, '')
.replace(/\[/g, '.')
.replace(/\.{2,}/g, '.')
.split('.')
.filter(Boolean)
.map((segment) => {
const num = parseInt(segment, 10)
return String(num) === segment ? num : segment
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/form-core/src/utils.ts` around lines 156 - 165, The path parser
currently leaves a leading empty segment for paths like ".child" (produced by
concatenatePaths) because it doesn't strip leading dots; update the replace
chain that processes the path (the .replace(/(^\[)|]/g, '')... sequence) to
remove any leading dots before splitting (e.g. add .replace(/^\.+/, '') or
equivalent) so that ".child" becomes "child" and downstream helpers (getBy,
setBy, deleteBy) no longer treat an empty-string key as the first segment.

}

/**
Expand Down
Loading