Skip to content

Comments

Use StatementRangeSyntaxError#919

Draft
DavisVaughan wants to merge 3 commits intoquarto-dev:mainfrom
DavisVaughan:feature/statement-range-rejection
Draft

Use StatementRangeSyntaxError#919
DavisVaughan wants to merge 3 commits intoquarto-dev:mainfrom
DavisVaughan:feature/statement-range-rejection

Conversation

@DavisVaughan
Copy link
Collaborator

@DavisVaughan DavisVaughan commented Feb 18, 2026

It feels to me like we should still use the "old" hooks.d.ts system for this, because StatementRange is already tied up in that hooks.d.ts file. Mixing hooks.d.ts and types from @posit-dev/positron doesn't feel like it would be a very good idea for this extension of an existing feature, IIUC.

IIUC, we can technically go ahead and merge this even without the next Positron release due to the version guard.

To resolve

```
quarto:dev: ✘ [ERROR] Could not resolve "positron"
quarto:dev:
quarto:dev:     src/host/hooks.ts:19:23:
quarto:dev:       19 │ import * as hooks from 'positron';
```
Comment on lines +218 to +226
// TODO: Remove this once `apps/vscode/package.json` bumps to `"positron": "^2026.03.0"` or higher.
// For now we avoid aggressive bumping due to https://github.com/posit-dev/positron/issues/11321.
if (semver.gte(hooks.version, "2026.03.0")) {
if (err instanceof hooks.StatementRangeSyntaxError) {
// Rethrow syntax error with unadjusted line number, so Positron's notification will
// jump to the correct line
throw new hooks.StatementRangeSyntaxError(err.line ? unadjustedLine(vdoc.language, err.line) : undefined);
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Version guarding on the expected next positron release of "2026.03.0"

DavisVaughan added a commit to posit-dev/positron that referenced this pull request Feb 24, 2026
Addresses #8350

- Requires posit-dev/ark#1040
- Blocks quarto-dev/quarto#919

Builds on:
- quarto-dev/quarto#914
- posit-dev/ark#1030

# Summary

This PR adds a new throwable `StatementRangeSyntaxError` to
`positron.d.ts`.

```ts
/**
 * An error thrown by a {@link StatementRangeProvider} to indicate that a statement range
 * cannot be provided due to a syntax error in the document.
 */
export class StatementRangeSyntaxError extends Error {
	/**
	 * Zero indexed line number where the syntax error occurred.
	 */
	readonly line?: number;

	/**
	 * Creates a new statement range syntax error.
	 *
	 * @param line Zero indexed line number where the syntax error occurred.
	 */
	constructor(line?: number);
}
```

This is the only public facing API change from this PR.

The idea is that from extension land (`positron-r`, `positron-python`,
and `quarto`) you should be able to throw a `StatementRangeSyntaxError`
from your `provideStatementRange()` provider, with an optional `line`
number of where the syntax error _roughly_ starts. This gets propagated
up through the main thread and into our `Cmd+Enter` handling in
`positronConsoleActions.ts`, where we now detect this special case and:
- Emit an info level notification telling the user that we can't execute
code due to a syntax error. If the `line` number is present, we give
them a button to jump to that line.
- Bail from `Cmd+Enter` handling altogether. We don't even do
one-line-at-a-time execution. #8350 has proved that this is just too
confusing when you are trying to execute code after a syntax error.

Here is what the end result looks like in an R script:


https://github.com/user-attachments/assets/23dca240-06af-40d5-8b1b-a8b24af99259

Note how you can execute R code _above_ the first syntax error, thanks
to posit-dev/ark#1030. It is only _below_ the
first syntax error that we emit this notification and refuse to execute.

It even works in roxygen2 comments where we have a little "subdocument":


https://github.com/user-attachments/assets/ec920753-400d-4493-b534-188dcf4e2a65

And lastly, it works in Quarto, where we have a "virtual document" for
R/Python and map the `line` number from that virtual document back into
the original Qmd, thanks to
quarto-dev/quarto#919 (note that if you try to
run that Quarto PR, you need to remove the version guard on Positron
2026.03.0 manually).

Additionally, quarto-dev/quarto#914 ensures that
you can use StatementRange in any syntax-error-free cell, even if some
other cell in the document has a syntax error.


https://github.com/user-attachments/assets/a01b9ed1-22fe-4169-b088-a8393800d9db

# Design

I considered many different designs along the way, but landed on a model
of:

- Extension host side _models errors as throwable errors_, i.e. with
`StatementRange` and `throw StatementRangeSyntaxError`

  ```ts
  export interface StatementRange {
    readonly range: vscode.Range;
    readonly code?: string;
  }

  export class StatementRangeSyntaxError extends Error {
    readonly line?: number;
    constructor(line?: number);
  }
  ```

- Main thread side _models errors as data_, i.e. a single
`IStatementRange` type made up as:

  ```ts
type IStatementRange = IStatementRangeSuccess | IStatementRangeRejection

  type IStatementRangeRejection = IStatementRangeSyntaxRejection

  export interface IStatementRangeSuccess {
    readonly kind: StatementRangeKind.Success;
    readonly range: IRange;
    readonly code?: string;
  }

  export interface IStatementRangeSyntaxRejection {
    readonly kind: StatementRangeKind.Rejection;
    readonly rejectionKind: StatementRangeRejectionKind.Syntax;
    readonly line?: number;
  }
  ```

Some rationale:

- Backwards compatible and public facing `StatementRange` doesn't change

- Can add new error/rejection variants as needed

- Can't easily model main thread side errors as, well, `Error`s, because
the `StatementRangeSyntaxError` doesn't serialize across the extension
-> main thread boundary well. You basically have to turn it into some
"data" type anyways to do this, and then it doesn't feel worth it to
convert back on the main thread side.

- Ark's custom `StatementRange` LSP Request also now uses the "success"
vs "rejection" model. From Ark's LSP-ish perspective, a "rejection" is
an allowed value that can be returned as an LSP Response. We reserve the
JSONRPC error path for true "holy crap something horrible happened and I
can't complete this request" cases.

The end result looks like this:

<img width="5127" height="4470" alt="test"
src="https://github.com/user-attachments/assets/b341c1dd-edac-45a2-9aed-03a4b05b3c01"
/>

# Prior art

There isn't much prior art for exactly what I'm trying to do here, but
there is a little bit:

- `FileSystemError` with its various flavors. Not quite the same as us
because none of the flavors have any metadata to pass through, like
`line`. That metadata is what steered me away from trying to have one
overarching `StatementRangeError` class that could contain something
like `static SyntaxError(line?: number): StatementRangeError`. I did try
this, but the boilerplate didn't feel worth it.


https://github.com/posit-dev/positron/blob/31c0111d42eaff76f06e1800836663e5765e1df1/src/vscode-dts/vscode.d.ts#L9474-L9486

- `Rejection`, which is used by the `RenameProvider`. Catches an error
from the LSP side of things and converts it into a `Rejection` "data"
type, kind of like we do. Strangely does `RenameProvider & Rejection` as
the return value type.
 
- Defined here
https://github.com/posit-dev/positron/blob/9754ca9e56524315fa6b2d1981fb6cb82ef5cd7b/src/vs/editor/common/languages.ts#L2127-L2138
- Catching the error here
https://github.com/posit-dev/positron/blob/9754ca9e56524315fa6b2d1981fb6cb82ef5cd7b/src/vs/workbench/api/common/extHostLanguageFeatures.ts#L882-L903

# QA

### Release Notes

#### New Features

- Stepping through code with `Cmd + Enter` now works more reliably when
there are syntax errors in the document:
    - For R code:
- [Code _above_ the first syntax
error](posit-dev/ark#1030) can now be executed
reliably with `Cmd + Enter`
- [Code _below_ the first syntax
error](#11907) causes a
notification to pop up that informs you that reliable execution is
impossible and provides you with a button to jump to the syntax error
- For Quarto documents, a syntax error in one chunk no longer affects
the ability to [run code in other
chunks](quarto-dev/quarto#914)

### QA Notes

QA: It would be nice to have two integration tests

```r
1 
  + 1 # <put cursor here and execute this, it should work now and send the whole statement>

2 + \ 2
```

```r
1 + \ 1

2 
  + 2 # <put cursor here and execute this, it should not send anything, and a notification should pop up>
```

`@:ark` `@:quarto` `@:console`

---------

Signed-off-by: Davis Vaughan <davis@posit.co>
Co-authored-by: Lionel Henry <lionel.hry@proton.me>
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.

1 participant