pnpm/pnpm · error · UnexpectedTokenError

UNEXPECTED_TOKEN_IN_PROPERTY_PATH

UNEXPECTED_TOKEN_IN_PROPERTY_PATH

Error message

Unexpected token ${JSON.stringify(token.content)} in property path

What it means

Thrown by parsePropertyPath() in @pnpm/object.property-path when a '.' separator token arrives while another separator ('.' or '[') is still pending on the parse stack. Property paths alternate segments and separators (a.b[0]["c"]), so two consecutive separators are a syntax error. The offending token content is included in the message.

Source

Thrown at pnpm11/object/property-path/src/parse.ts:73

 *
 * @param propertyPath The string of property path to parse.
 * @returns The parsed path in the form of an array.
 */
export function * parsePropertyPath (propertyPath: string): Generator<string | number, void, void> {
  type Stack =
    | ExactToken<'.'>
    | ExactToken<'['>
    | [ExactToken<'['>, NumericLiteral | StringLiteral]
  let stack: Stack | undefined

  for (const token of tokenize(propertyPath)) {
    if (token.type === 'exact' && token.content === '.') {
      if (!stack) {
        stack = token
        continue
      }

      throw new UnexpectedTokenError(token)
    }

    if (token.type === 'exact' && token.content === '[') {
      if (!stack) {
        stack = token
        continue
      }

      throw new UnexpectedTokenError(token)
    }

    if (token.type === 'exact' && token.content === ']') {
      if (!Array.isArray(stack)) throw new UnexpectedTokenError(token)

      const [openBracket, literal] = stack
      assert.equal(openBracket.type, 'exact')
      assert.equal(openBracket.content, '[')
      assert(literal.type === 'numeric-literal' || literal.type === 'string-literal')

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Fix the path: remove the duplicated separator ('a..b' -> 'a.b', 'a[.foo]' -> 'a.foo').
  2. If the path is built by joining, filter out empty segments first: segments.filter(s => s !== '').join('.')
  3. If a segment may contain dots or special characters, quote it instead: 'a["b.c"]'.
  4. Validate paths before parsing with a small check such as !/\.\.|\[\.|^\./.test(path).

Example fix

// before
parsePropertyPath('a..b')

// after
parsePropertyPath('a.b')
// or, when joining dynamically
const path = ['a', '', 'b'].filter(s => s !== '').join('.')
Defensive patterns

Strategy: validation

Validate before calling

// Reject a doubled separator before parsing
function hasDoubledSeparator (path: string): boolean {
  return /\.\.|\.\[|\[\[/.test(path)
}
if (!hasDoubledSeparator(input)) {
  for (const seg of parsePropertyPath(input)) { /* ... */ }
}

Type guard

function isParseIssue (err: unknown): err is PnpmError {
  return util.types.isNativeError(err) && 'code' in err && (err as PnpmError).code === 'UNEXPECTED_TOKEN_IN_PROPERTY_PATH'
}

Try / catch

try {
  const path = Array.from(parsePropertyPath(userInput))
} catch (err) {
  if (util.types.isNativeError(err) && 'code' in err && err.code === 'UNEXPECTED_TOKEN_IN_PROPERTY_PATH') {
    // surface a field-level error to the user, not a crash
    throw new Error(`Invalid property path: ${userInput}`)
  }
  throw err
}

Prevention

When it happens

Trigger: parsePropertyPath() input with a doubled dot ('a..b', '..a'), or a dot directly after an unconsumed open bracket ('a[.foo]' — '[' sets the stack, then '.' sees it non-empty and throws at parse.ts:73).

Common situations: Programmatically joining path segments with '.' (['a','b'].join('.') where a segment is empty), typos in dotfiles keys, or hand-built paths from user input like key + '.' + subkey where subkey starts with '.'.

Understand the failure class

Related errors


AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17). Data as JSON: /api/errors/9e535826467bf9ab. Report an issue: GitHub.