deno
deno copied to clipboard
Restore the ability to type-check stable code
Ref:
- https://github.com/denoland/deno/pull/21825
Here's the message accompanying the linked PR for context here:
This commit removes conditional type-checking of unstable APIs.
Before this commit
deno check
(or any other type-checking command and the LSP) would error out if there was an unstable API in the code, but not--unstable
flag provided.This situation hinders DX and makes it harder to configure Deno. Failing during runtime unless
--unstable
flag is provided is enough in this case.
I think the change might have been introduced prematurely, as there is no longer a simple way to type-check stable code.
The change completely mitigates one of the useful safety features of type-checking: preventing use of references that won't exist at runtime for stable code (code that does not use the --unstable
runtime argument). By including all of the unstable type declarations during type-checking for all programs unconditionally, an entire class of preventable runtime ReferenceError
s are no longer caught. Although I don't have usage data to support it, I expect that the overwhelming majority of Deno users are not running code using the --unstable
flag, especially in production.
Here's a contrived example for demonstration which uses the WebSocketStream
class, which is a global that's only available when using the --unstable
flag since Deno v1.13.0:
example.ts
:
const wss = new WebSocketStream("ws://localhost:8000/live");
const { readable } = await wss.opened;
for await (const _data of readable) {
// code that handles the socket stream…
}
Type-checking passes (no diagnostics):
% deno check example.ts
Check file:///Users/deno/example.ts
Running the program crashes with an uncaught ReferenceError
:
% deno run example.ts
error: Uncaught (in promise) ReferenceError: WebSocketStream is not defined
const wss = new WebSocketStream("ws://localhost:8000/live");
^
at file:///Users/deno/example.ts:1:13
The problem is exaggerated when type-checking is run prior to execution using the --check
argument (deno run --check …
). The expectation is that a type-checking failure will occur for these kinds of reference errors and prevent execution of the code entirely. From the TypeScript section of the manual (📌):
When using
deno run
with the--check
argument, type-related diagnostics will prevent the program from running: it will halt on these warnings, and exit the process before executing the code.
This was true in previous releases…
% deno --version
deno 1.39.4 (release, aarch64-apple-darwin)
v8 12.0.267.8
typescript 5.3.3
% deno run --check example.ts
Check file:///Users/deno/example.ts
error: TS2304 [ERROR]: Cannot find name 'WebSocketStream'.
const wss = new WebSocketStream("ws://localhost:8000/live");
~~~~~~~~~~~~~~~
at file:///Users/deno/example.ts:1:17
…but after the change, these unavailable reference errors are not caught prior to runtime, and instead they create exceptions:
% deno --version
deno 1.40.1 (release, aarch64-apple-darwin)
v8 12.1.285.6
typescript 5.3.3
% deno run --check example.ts
Check file:///Users/deno/example.ts
error: Uncaught (in promise) ReferenceError: WebSocketStream is not defined
const wss = new WebSocketStream("ws://localhost:8000/live");
^
at file:///Users/deno/example.ts:1:13
The error occurs almost instantly in the simple, contrived example — but in other typically-complex code, a reference error might not be encountered until a deep, nested condition is met somewhere long after execution starts.
Workarounds are available at present, but tedious — they require modifying every program which doesn't use unstable features in order to emulate the previous default compiler behavior. Options include:
-
adding triple-slash directives to every entrypoint module:
example.ts
:/// <reference no-default-lib="true" /> /// <reference lib="deno.window" /> const wss = new WebSocketStream("ws://localhost:8000/live"); const { readable } = await wss.opened; for await (const _data of readable) { // code that handles the socket stream… }
-
explicitly configuring compiler options in the program's associated config file:
deno.json
:{ "compilerOptions": { "lib": ["deno.window"] } }
I'm sympathetic to users who want an LSP-enabled experience with less friction while using unstable features… but this change seems like a clear regression, and I hope we can find a better solution.