Skip to content

Fix TypeError on unclosed [, ( and trailing | - #330

Open
theRizwan wants to merge 1 commit into
postcss:mainfrom
theRizwan:fix/eof-typeerror-unclosed-delimiters
Open

Fix TypeError on unclosed [, ( and trailing |#330
theRizwan wants to merge 1 commit into
postcss:mainfrom
theRizwan:fix/eof-typeerror-unclosed-delimiters

Conversation

@theRizwan

Copy link
Copy Markdown

Fixes #329.

Selectors that ran out of tokens before a bracket closed threw
TypeError: Cannot read properties of undefined instead of the parser's own
Expected a closing … error. The error path already existed in each case — it just couldn't be
reached, because constructing the message dereferenced the token that was missing.

Input Before After
a[href TypeError: …reading '0' Expected a closing square bracket.
a[href=x TypeError: …reading '0' Expected a closing square bracket.
a( TypeError: …reading '5' Expected a closing parenthesis.
.foo| TypeError: …reading '0' Unexpected '|'.

Closing delimiters with no opener (a], a)) already produced clean errors, so this brings the
two directions into line.

Changes

Three guards in src/parser.js:

  • attribute() — the while loop exits on either a closing bracket or end of input, and the
    check after it assumed the former. Errors now point at the opening bracket, which is where the
    author needs to look.
  • namespace() — a trailing | with nothing after it now goes to the existing
    unexpectedPipe(), which reports against currToken (the pipe itself, always present).
  • parentheses() — the unbalanced branch falls back to the opening parenthesis when the
    stream ended.

No behaviour change for input that already parsed, and no change to any existing error message.

Tests

Four cases added to src/__tests__/exceptions.mjs.

They assert the message rather than the type, deliberately. src/__tests__/exceptions.mjs:8
already covers this input shape:

throws("unclosed attribute selector", '[name="james"][href');

That test passes today, because throws falls back to { instanceOf: Error } when no message is
given (util/helpers.mjs:33) and TypeError satisfies it. Asserting the message is what makes
the new tests able to fail.

Confirmed they do: reverting only src/parser.js and re-running gives

actual:   "Cannot read properties of undefined (reading '5')"
expected: "Expected a closing parenthesis."

Verification

  • npm test781/781 pass (777 before), oxlint clean, coverage thresholds met
  • npm run format:check clean
  • Verified against postcss-selector-parser@7.1.4 from npm and against main at 4a7e4e3

Scope

I've limited this to the three paths I could reproduce. There are other unguarded token reads in
the file that may be unreachable in practice — I'd rather not add speculative guards to a parser
this widely used without a failing case to justify each one.

Selectors that ran out of tokens before a bracket closed threw
`TypeError: Cannot read properties of undefined` instead of the parser's
own `Expected a closing ...` error. The error path already existed in each
case; it just could not be reached, because building the message
dereferenced the token that was missing.

    parser().astSync('a]')       // Expected an opening square bracket.  (ok)
    parser().astSync('a[href')   // TypeError: ...reading '0'

Closing delimiters with no opener were already handled properly, so this
brings the two directions into line.

- attribute(): the while loop exits on either a closing bracket or end of
  input, and the check after it assumed the former. Errors now point at the
  opening bracket.
- namespace(): a trailing `|` with nothing after it now reaches the
  existing unexpectedPipe(), which reports against currToken.
- parentheses(): the unbalanced branch falls back to the opening token.

No behaviour change for input that already parsed, and no existing error
message changes.

Tests assert the message rather than the type. exceptions.mjs already
covered this input shape via `throws("unclosed attribute selector", ...)`,
which passed throughout: `throws` falls back to `{instanceOf: Error}` when
no message is given, and TypeError satisfies that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unclosed [, ( or trailing | throws a raw TypeError instead of the parser's own error

1 participant