From 533ee83fde92e9e06e5ba4ffad1e30ba0ec5ad13 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 13 Jul 2023 19:29:51 -0400 Subject: [PATCH] fix(tsc): more informative diagnostic when `Deno` does not exist (#19825) Also improved the diagnostic when using something like `Deno.openKv` and it doesn't exist. --- cli/tests/integration/check_tests.rs | 12 +++ .../testdata/check/deno_not_found/main.out | 4 + .../testdata/check/deno_not_found/main.ts | 4 + .../check/deno_unstable_not_found/main.out | 16 ++++ .../check/deno_unstable_not_found/main.ts | 2 + cli/tests/testdata/run/unstable_disabled.out | 2 +- cli/tsc/99_main_compiler.js | 82 ++++++++++++++++++- cli/tsc/diagnostics.rs | 62 +------------- runtime/js/90_deno_ns.js | 1 + 9 files changed, 119 insertions(+), 66 deletions(-) create mode 100644 cli/tests/testdata/check/deno_not_found/main.out create mode 100644 cli/tests/testdata/check/deno_not_found/main.ts create mode 100644 cli/tests/testdata/check/deno_unstable_not_found/main.out create mode 100644 cli/tests/testdata/check/deno_unstable_not_found/main.ts diff --git a/cli/tests/integration/check_tests.rs b/cli/tests/integration/check_tests.rs index 0f1c8cb590..1b00cadbee 100644 --- a/cli/tests/integration/check_tests.rs +++ b/cli/tests/integration/check_tests.rs @@ -105,6 +105,18 @@ itest!(check_broadcast_channel_unstable { exit_code: 0, }); +itest!(check_deno_not_found { + args: "check --quiet check/deno_not_found/main.ts", + output: "check/deno_not_found/main.out", + exit_code: 1, +}); + +itest!(check_deno_unstable_not_found { + args: "check --quiet check/deno_unstable_not_found/main.ts", + output: "check/deno_unstable_not_found/main.out", + exit_code: 1, +}); + #[test] fn cache_switching_config_then_no_config() { let context = TestContext::default(); diff --git a/cli/tests/testdata/check/deno_not_found/main.out b/cli/tests/testdata/check/deno_not_found/main.out new file mode 100644 index 0000000000..39852ab952 --- /dev/null +++ b/cli/tests/testdata/check/deno_not_found/main.out @@ -0,0 +1,4 @@ +error: TS2304 [ERROR]: Cannot find name 'Deno'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'deno.ns' or add a triple-slash directive to your entrypoint: /// +Deno; +~~~~ + at file:///[WILDCARD]/check/deno_not_found/main.ts:4:1 diff --git a/cli/tests/testdata/check/deno_not_found/main.ts b/cli/tests/testdata/check/deno_not_found/main.ts new file mode 100644 index 0000000000..3269f047a7 --- /dev/null +++ b/cli/tests/testdata/check/deno_not_found/main.ts @@ -0,0 +1,4 @@ +/// +/// + +Deno; diff --git a/cli/tests/testdata/check/deno_unstable_not_found/main.out b/cli/tests/testdata/check/deno_unstable_not_found/main.out new file mode 100644 index 0000000000..dfe3cf3174 --- /dev/null +++ b/cli/tests/testdata/check/deno_unstable_not_found/main.out @@ -0,0 +1,16 @@ +error: TS2551 [ERROR]: Property 'openKv' does not exist on type 'typeof Deno'. Did you mean 'open'? 'Deno.openKv' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean 'open'? If not, try changing the 'lib' compiler option to include 'deno.unstable' or add a triple-slash directive to your entrypoint: /// +Deno.openKv; + ~~~~~~ + at file:///[WILDCARD]/deno_unstable_not_found/main.ts:1:6 + + 'open' is declared here. + export function open( + ~~~~ + at asset:///lib.deno.ns.d.ts:1667:19 + +TS2339 [ERROR]: Property 'createHttpClient' does not exist on type 'typeof Deno'. 'Deno.createHttpClient' is an unstable API. Did you forget to run with the '--unstable' flag? If not, try changing the 'lib' compiler option to include 'deno.unstable' or add a triple-slash directive to your entrypoint: /// +Deno.createHttpClient; + ~~~~~~~~~~~~~~~~ + at file:///[WILDCARD]/deno_unstable_not_found/main.ts:2:6 + +Found 2 errors. diff --git a/cli/tests/testdata/check/deno_unstable_not_found/main.ts b/cli/tests/testdata/check/deno_unstable_not_found/main.ts new file mode 100644 index 0000000000..6661bd205c --- /dev/null +++ b/cli/tests/testdata/check/deno_unstable_not_found/main.ts @@ -0,0 +1,2 @@ +Deno.openKv; +Deno.createHttpClient; diff --git a/cli/tests/testdata/run/unstable_disabled.out b/cli/tests/testdata/run/unstable_disabled.out index f3de913e62..fb8d3ee05c 100644 --- a/cli/tests/testdata/run/unstable_disabled.out +++ b/cli/tests/testdata/run/unstable_disabled.out @@ -1,5 +1,5 @@ [WILDCARD] -error: TS2339 [ERROR]: Property 'umask' does not exist on type 'typeof Deno'. 'Deno.umask' is an unstable API. Did you forget to run with the '--unstable' flag? +error: TS2339 [ERROR]: Property 'umask' does not exist on type 'typeof Deno'. 'Deno.umask' is an unstable API. Did you forget to run with the '--unstable' flag? If not, try changing the 'lib' compiler option to include 'deno.unstable' or add a triple-slash directive to your entrypoint: /// console.log(Deno.umask); ~~~~~ at [WILDCARD]/unstable.ts:1:18 diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index 56ac5522b0..2190dda994 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -29,6 +29,37 @@ delete Object.prototype.__proto__; /** @type {Map} */ const normalizedToOriginalMap = new Map(); + /** @type {ReadonlySet} */ + const unstableDenoProps = new Set([ + "AtomicOperation", + "CreateHttpClientOptions", + "DatagramConn", + "HttpClient", + "Kv", + "KvListIterator", + "KvU64", + "UnsafeCallback", + "UnsafePointer", + "UnsafePointerView", + "UnsafeFnPointer", + "UnixConnectOptions", + "UnixListenOptions", + "createHttpClient", + "dlopen", + "flock", + "flockSync", + "funlock", + "funlockSync", + "listen", + "listenDatagram", + "openKv", + "upgradeHttp", + "umask", + ]); + const unstableMsgSuggestion = + "If not, try changing the 'lib' compiler option to include 'deno.unstable' " + + 'or add a triple-slash directive to your entrypoint: /// '; + /** * @param {unknown} value * @returns {value is ts.CreateSourceFileOptions} @@ -303,6 +334,49 @@ delete Object.prototype.__proto__; return isNodeSourceFile; }); + /** + * @param msg {string} + * @param code {number} + */ + function formatMessage(msg, code) { + switch (code) { + case 2304: { + if (msg === "Cannot find name 'Deno'.") { + msg += " Do you need to change your target library? " + + "Try changing the 'lib' compiler option to include 'deno.ns' " + + 'or add a triple-slash directive to your entrypoint: /// '; + } + return msg; + } + case 2339: { + const property = getProperty(); + if (property && unstableDenoProps.has(property)) { + return `${msg} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag? ${unstableMsgSuggestion}`; + } + return msg; + } + default: { + const property = getProperty(); + if (property && unstableDenoProps.has(property)) { + const suggestion = getMsgSuggestion(); + if (suggestion) { + return `${msg} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean '${suggestion}'? ${unstableMsgSuggestion}`; + } + } + return msg; + } + } + + function getProperty() { + return /Property '([^']+)' does not exist on type 'typeof Deno'/ + .exec(msg)?.[1]; + } + + function getMsgSuggestion() { + return / Did you mean '([^']+)'\?/.exec(msg)?.[1]; + } + } + /** @param {ts.DiagnosticRelatedInformation} diagnostic */ function fromRelatedInformation({ start, @@ -316,7 +390,7 @@ delete Object.prototype.__proto__; if (typeof msgText === "object") { messageChain = msgText; } else { - messageText = msgText; + messageText = formatMessage(msgText, ri.code); } if (start !== undefined && length !== undefined && file) { const startPos = file.getLineAndCharacterOfPosition(start); @@ -341,7 +415,7 @@ delete Object.prototype.__proto__; } /** @param {readonly ts.Diagnostic[]} diagnostics */ - function fromTypeScriptDiagnostic(diagnostics) { + function fromTypeScriptDiagnostics(diagnostics) { return diagnostics.map(({ relatedInformation: ri, source, ...diag }) => { /** @type {any} */ const value = fromRelatedInformation(diag); @@ -864,7 +938,7 @@ delete Object.prototype.__proto__; performanceProgram({ program }); ops.op_respond({ - diagnostics: fromTypeScriptDiagnostic(diagnostics), + diagnostics: fromTypeScriptDiagnostics(diagnostics), stats: performanceEnd(), }); debug("<<< exec stop"); @@ -1055,7 +1129,7 @@ delete Object.prototype.__proto__; /** @type {Record} */ const diagnosticMap = {}; for (const specifier of request.specifiers) { - diagnosticMap[specifier] = fromTypeScriptDiagnostic([ + diagnosticMap[specifier] = fromTypeScriptDiagnostics([ ...languageService.getSemanticDiagnostics(specifier), ...languageService.getSuggestionDiagnostics(specifier), ...languageService.getSyntacticDiagnostics(specifier), diff --git a/cli/tsc/diagnostics.rs b/cli/tsc/diagnostics.rs index edddb0f6f8..08fa3e8dab 100644 --- a/cli/tsc/diagnostics.rs +++ b/cli/tsc/diagnostics.rs @@ -6,71 +6,11 @@ use deno_core::serde::Deserialize; use deno_core::serde::Deserializer; use deno_core::serde::Serialize; use deno_core::serde::Serializer; -use lazy_regex::lazy_regex; -use once_cell::sync::Lazy; -use regex::Regex; use std::error::Error; use std::fmt; const MAX_SOURCE_LINE_LENGTH: usize = 150; -const UNSTABLE_DENO_PROPS: &[&str] = &[ - "CreateHttpClientOptions", - "DatagramConn", - "HttpClient", - "UnixConnectOptions", - "UnixListenOptions", - "connect", - "createHttpClient", - "listen", - "listenDatagram", - "dlopen", - "removeSignalListener", - "shutdown", - "umask", -]; - -static MSG_MISSING_PROPERTY_DENO: Lazy = - lazy_regex!(r#"Property '([^']+)' does not exist on type 'typeof Deno'"#); - -static MSG_SUGGESTION: Lazy = - lazy_regex!(r#" Did you mean '([^']+)'\?"#); - -/// Potentially convert a "raw" diagnostic message from TSC to something that -/// provides a more sensible error message given a Deno runtime context. -fn format_message(msg: &str, code: &u64) -> String { - match code { - 2339 => { - if let Some(captures) = MSG_MISSING_PROPERTY_DENO.captures(msg) { - if let Some(property) = captures.get(1) { - if UNSTABLE_DENO_PROPS.contains(&property.as_str()) { - return format!("{} 'Deno.{}' is an unstable API. Did you forget to run with the '--unstable' flag?", msg, property.as_str()); - } - } - } - - msg.to_string() - } - 2551 => { - if let (Some(caps_property), Some(caps_suggestion)) = ( - MSG_MISSING_PROPERTY_DENO.captures(msg), - MSG_SUGGESTION.captures(msg), - ) { - if let (Some(property), Some(suggestion)) = - (caps_property.get(1), caps_suggestion.get(1)) - { - if UNSTABLE_DENO_PROPS.contains(&property.as_str()) { - return format!("{} 'Deno.{}' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean '{}'?", MSG_SUGGESTION.replace(msg, ""), property.as_str(), suggestion.as_str()); - } - } - } - - msg.to_string() - } - _ => msg.to_string(), - } -} - #[derive(Clone, Debug, Eq, PartialEq)] pub enum DiagnosticCategory { Warning, @@ -227,7 +167,7 @@ impl Diagnostic { f, "{:indent$}{}", "", - format_message(&self.message_text.clone().unwrap(), &self.code), + self.message_text.as_deref().unwrap_or_default(), indent = level, ) } diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index ffa4bf5296..00c6d6b475 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -152,6 +152,7 @@ const denoNs = { ChildProcess: process.ChildProcess, }; +// when editing this list, also update unstableDenoProps in cli/tsc/99_main_compiler.js const denoNsUnstable = { listenDatagram: net.createListenDatagram( ops.op_net_listen_udp,