diff --git a/src/util/toPath.js b/src/util/toPath.js index 3271d4e9bd01..090b66cfdb8f 100644 --- a/src/util/toPath.js +++ b/src/util/toPath.js @@ -15,6 +15,95 @@ export function toPath(path) { if (Array.isArray(path)) return path + let parts = [] + let inBrackets = false + let partStart = 0 + let partEnd = 0 + + for (let i = 0, len = path.length; i <= len; i++) { + let prev = path[i - 1] + let curr = path[i] + + // Tokens: + // [ = lb + // ] = rb + // . = dot if not in brackets + // . = ident if in brackets + // everything else = ident + let prevIsIdent = inBrackets + ? prev !== undefined && prev !== '[' && prev !== ']' + : prev !== undefined && prev !== '.' && prev !== '[' && prev !== ']' + + let currIsIdent = inBrackets + ? curr !== undefined && curr !== '[' && curr !== ']' + : curr !== undefined && curr !== '.' && curr !== '[' && curr !== ']' + + // The only valid transitions are: + // start -> dot | lb | ident | end + // dot -> ident + // lb -> rb | ident + // rb -> dot | lb + // ident -> dot | rb | ident | end + // ident -> lb if not in brackets + if ( + (prev === undefined && curr === ']') || + (prev === '.' && !inBrackets && !currIsIdent) || + (prev === '[' && curr === undefined) || + (prev === '[' && curr === '[') || + (prev === ']' && curr === ']') || + (prev === ']' && currIsIdent) || + (prevIsIdent && curr === '[' && inBrackets) + ) { + throw new Error(`Invalid path: ${path}\n` + `${' '.repeat(14 + i)}^`) + } + + // The last thing we looked at was an ident so we ened to keep looking for more + // Later scans will identify the end of the ident + if (prevIsIdent) { + partEnd = i + } + + // Whenever we go from an ident to a non-ident we capture the new part + // We also capture: + // - start -> dot for initial empty strings + // - lb -> rb for ocurrences of [] also treated as empty strings + if ( + (prev === undefined && curr === '.') || + (prev === '[' && curr === ']') || + (prevIsIdent && curr === '.' && !inBrackets) || + (prevIsIdent && curr === '[' && !inBrackets) || + (prevIsIdent && curr === ']') || + (prevIsIdent && curr === undefined) + ) { + parts.push(path.slice(partStart, partEnd)) + } + + // Whenever we go from a non-ident to ident we start capturing a new part + // We also capture start -> lb for initial empty strings generated by `[]` + if ( + (prev === undefined && curr === '[') || + (prev === '.' && !inBrackets && currIsIdent) || + (prev === '[' && currIsIdent) || + (prev === ']' && curr === '.') + ) { + partStart = partEnd = i + } + + // Bracket bookkeeping + if (curr === '[') { + inBrackets = true + } else if (curr === ']') { + inBrackets = false + } + } + + return parts +} + +// Herre for reference in case one is preferred over the other +export function toPathFull(path) { + if (Array.isArray(path)) return path + // The general outline/properties of a path string: // Tokens: