diff --git a/.github/workflows/ci.generate.ts b/.github/workflows/ci.generate.ts index 0de49b2004..571e744471 100755 --- a/.github/workflows/ci.generate.ts +++ b/.github/workflows/ci.generate.ts @@ -5,7 +5,7 @@ import { stringify } from "jsr:@std/yaml@^0.221/stringify"; // Bump this number when you want to purge the cache. // Note: the tools/release/01_bump_crate_versions.ts script will update this version // automatically via regex, so ensure that this line maintains this format. -const cacheVersion = 76; +const cacheVersion = 77; const ubuntuX86Runner = "ubuntu-24.04"; const ubuntuX86XlRunner = "ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04"; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14815edef3..5c5ebd2cf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,8 +189,8 @@ jobs: ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db - key: '76-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles(''Cargo.lock'') }}' - restore-keys: '76-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-' + key: '77-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-${{ hashFiles(''Cargo.lock'') }}' + restore-keys: '77-cargo-home-${{ matrix.os }}-${{ matrix.arch }}-' if: '!(matrix.skip)' - uses: dsherret/rust-toolchain-file@v1 if: '!(matrix.skip)' @@ -392,7 +392,7 @@ jobs: !./target/*/*.zip !./target/*/*.tar.gz key: never_saved - restore-keys: '76-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-' + restore-keys: '77-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-' - name: Apply and update mtime cache if: '!(matrix.skip) && (!startsWith(github.ref, ''refs/tags/''))' uses: ./.github/mtime_cache @@ -773,7 +773,7 @@ jobs: !./target/*/gn_root !./target/*/*.zip !./target/*/*.tar.gz - key: '76-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}' + key: '77-cargo-target-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}' libs: name: build libs needs: diff --git a/Cargo.lock b/Cargo.lock index 5c4467214d..690a097471 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1647,6 +1647,7 @@ dependencies = [ "deno_telemetry", "deno_terminal 0.2.2", "deno_tower_lsp", + "deno_typescript_go_client_rust", "dhat", "dissimilar", "dotenvy", @@ -3073,6 +3074,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "deno_typescript_go_client_rust" +version = "0.1.0" +dependencies = [ + "indexmap 2.9.0", + "log", + "rmp", + "serde", + "serde_json", + "serde_repr", + "thiserror 2.0.12", +] + [[package]] name = "deno_unsync" version = "0.4.4" @@ -5903,9 +5917,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" dependencies = [ "serde", ] @@ -7714,6 +7728,17 @@ dependencies = [ "digest", ] +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + [[package]] name = "ron" version = "0.8.1" @@ -9369,6 +9394,7 @@ dependencies = [ "url", "win32job", "winapi", + "zip", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 21c2af74f7..45302e0661 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "libs/npm_installer", "libs/package_json", "libs/resolver", + "libs/typescript_go_client", "runtime", "runtime/features", "runtime/permissions", @@ -131,6 +132,7 @@ deno_resolver = { version = "0.50.0", path = "./libs/resolver" } deno_runtime = { version = "0.227.0", path = "./runtime" } deno_snapshots = { version = "0.34.0", path = "./cli/snapshot" } deno_subprocess_windows = { path = "./runtime/subprocess_windows", version = "0.14.0" } +deno_typescript_go_client_rust = { version = "0.1.0", path = "./libs/typescript_go_client" } napi_sym = { version = "0.149.0", path = "./ext/napi/sym" } node_resolver = { version = "0.57.0", path = "./libs/node_resolver" } test_util = { package = "test_server", path = "./tests/util/server" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 297b32b176..e0255ce863 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -92,6 +92,7 @@ deno_snapshots.workspace = true deno_task_shell.workspace = true deno_telemetry.workspace = true deno_terminal.workspace = true +deno_typescript_go_client_rust.workspace = true eszip.workspace = true libsui.workspace = true node_resolver = { workspace = true, features = ["graph", "sync"] } diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 5d9ff43317..5ecd25d4cc 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -6628,6 +6628,7 @@ fn unstable_args_parse( matches.get_flag("unstable-sloppy-imports"); flags.unstable_config.npm_lazy_caching = matches.get_flag("unstable-npm-lazy-caching"); + flags.unstable_config.tsgo = matches.get_flag("unstable-tsgo"); if matches!(cfg, UnstableArgsConfig::ResolutionAndRuntime) { for feature in deno_runtime::UNSTABLE_FEATURES { diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 68c473ed9f..e73b2e5467 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1210,6 +1210,10 @@ impl CliOptions { self.flags.type_check_mode } + pub fn unstable_tsgo(&self) -> bool { + self.flags.unstable_config.tsgo || self.workspace().has_unstable("tsgo") + } + pub fn unsafely_ignore_certificate_errors(&self) -> &Option> { &self.flags.unsafely_ignore_certificate_errors } diff --git a/cli/factory.rs b/cli/factory.rs index 7512ae2bd1..7f59452843 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -524,6 +524,20 @@ impl CliFactory { self.resolver_factory()?.in_npm_package_checker() } + pub async fn tsgo_path(&self) -> Result, AnyError> { + if self.cli_options()?.unstable_tsgo() { + Ok(Some( + crate::tsc::ensure_tsgo( + self.deno_dir()?, + self.http_client_provider().clone(), + ) + .await?, + )) + } else { + Ok(None) + } + } + pub fn jsr_version_resolver( &self, ) -> Result<&Arc, AnyError> { @@ -757,6 +771,7 @@ impl CliFactory { self.module_graph_builder().await?.clone(), self.node_resolver().await?.clone(), self.npm_resolver().await?.clone(), + self.resolver_factory()?.pkg_json_resolver().clone(), self.sys(), self.compiler_options_resolver()?.clone(), if cli_options.code_cache_enabled() { @@ -764,6 +779,7 @@ impl CliFactory { } else { None }, + self.tsgo_path().await?.cloned(), ))) } .boxed_local(), diff --git a/cli/lib/args.rs b/cli/lib/args.rs index 71b51e98f7..86895a6fa8 100644 --- a/cli/lib/args.rs +++ b/cli/lib/args.rs @@ -211,6 +211,7 @@ pub struct UnstableConfig { pub raw_imports: bool, pub sloppy_imports: bool, pub npm_lazy_caching: bool, + pub tsgo: bool, pub features: Vec, // --unstabe-kv --unstable-cron } @@ -234,6 +235,7 @@ impl UnstableConfig { &mut self.npm_lazy_caching, UNSTABLE_ENV_VAR_NAMES.npm_lazy_caching, ); + maybe_set(&mut self.tsgo, UNSTABLE_ENV_VAR_NAMES.tsgo); maybe_set(&mut self.raw_imports, UNSTABLE_ENV_VAR_NAMES.raw_imports); maybe_set( &mut self.sloppy_imports, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 19f1d236d3..7e56eb400f 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -5269,6 +5269,12 @@ fn op_project_version(state: &mut OpState) -> usize { r } +#[op2] +#[serde] +fn op_tsc_constants() -> crate::tsc::TscConstants { + crate::tsc::TscConstants::new() +} + struct TscRuntime { js_runtime: JsRuntime, server_main_loop_fn_global: v8::Global, @@ -5393,6 +5399,7 @@ deno_core::extension!(deno_tsc, op_is_cancelled, op_is_node_file, op_load, + op_tsc_constants, op_release, op_resolve, op_respond, diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index 904b801af0..c5ebef84f5 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -788,6 +788,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { npm_lazy_caching: self.cli_options.unstable_npm_lazy_caching(), raw_imports: self.cli_options.unstable_raw_imports(), sloppy_imports: self.cli_options.unstable_sloppy_imports(), + tsgo: self.cli_options.unstable_tsgo(), }, otel_config: self.cli_options.otel_config(), vfs_case_sensitivity: vfs.case_sensitivity, diff --git a/cli/tsc/97_ts_host.js b/cli/tsc/97_ts_host.js index d04ebfe139..9ec0c2eca7 100644 --- a/cli/tsc/97_ts_host.js +++ b/cli/tsc/97_ts_host.js @@ -303,51 +303,12 @@ const CACHE_URL_PREFIX = "cache:///"; /** Diagnostics that are intentionally ignored when compiling TypeScript in * Deno, as they provide misleading or incorrect information. */ -const IGNORED_DIAGNOSTICS = [ - // TS1452: 'resolution-mode' assertions are only supported when `moduleResolution` is `node16` or `nodenext`. - // We specify the resolution mode to be CommonJS for some npm files and this - // diagnostic gets generated even though we're using custom module resolution. - 1452, - // Module '...' cannot be imported using this construct. The specifier only resolves to an - // ES module, which cannot be imported with 'require'. - 1471, - // TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; - // however, the referenced file is an ECMAScript module and cannot be imported with 'require'. - 1479, - // TS1543: Importing a JSON file into an ECMAScript module requires a 'type: \"json\"' import - // attribute when 'module' is set to 'NodeNext'. - 1543, - // TS2306: File '.../index.d.ts' is not a module. - // We get this for `x-typescript-types` declaration files which don't export - // anything. We prefer to treat these as modules with no exports. - 2306, - // TS2688: Cannot find type definition file for '...'. - // We ignore because type definition files can end with '.ts'. - 2688, - // TS2792: Cannot find module. Did you mean to set the 'moduleResolution' - // option to 'node', or to add aliases to the 'paths' option? - 2792, - // TS2307: Cannot find module '{0}' or its corresponding type declarations. - 2307, - // Relative import errors to add an extension - 2834, - 2835, - // TS5009: Cannot find the common subdirectory path for the input files. - 5009, - // TS5055: Cannot write file - // 'http://localhost:4545/subdir/mt_application_x_javascript.j4.js' - // because it would overwrite input file. - 5055, - // TypeScript is overly opinionated that only CommonJS modules kinds can - // support JSON imports. Allegedly this was fixed in - // Microsoft/TypeScript#26825 but that doesn't seem to be working here, - // so we will ignore complaints about this compiler setting. - 5070, - // TS7016: Could not find a declaration file for module '...'. '...' - // implicitly has an 'any' type. This is due to `allowJs` being off by - // default but importing of a JavaScript module. - 7016, -]; +const TSC_CONSTANTS = ops.op_tsc_constants(); +const IGNORED_DIAGNOSTICS = TSC_CONSTANTS.ignoredDiagnosticCodes; +const TYPES_NODE_IGNORABLE_NAMES = new Set( + TSC_CONSTANTS.typesNodeIgnorableNames, +); +const NODE_ONLY_GLOBALS = new Set(TSC_CONSTANTS.nodeOnlyGlobals); // todo(dsherret): can we remove this and just use ts.OperationCanceledException? /** Error thrown on cancellation. */ @@ -851,94 +812,14 @@ export function filterMapDiagnostic(diagnostic) { // list of globals that should be kept in Node's globalThis ts.deno.setNodeOnlyGlobalNames( - new Set([ - "__dirname", - "__filename", - '"buffer"', - "Buffer", - "BufferConstructor", - "BufferEncoding", - "clearImmediate", - "clearInterval", - "clearTimeout", - "console", - "Console", - "crypto", - "ErrorConstructor", - "gc", - "Global", - "localStorage", - "queueMicrotask", - "RequestInit", - "ResponseInit", - "sessionStorage", - "setImmediate", - "setInterval", - "setTimeout", - ]), + NODE_ONLY_GLOBALS, ); // List of globals in @types/node that collide with Deno's types. // When the `@types/node` package attempts to assign to these types // if the type is already in the global symbol table, then assignment // will be a no-op, but if the global type does not exist then the package can // create the global. -const setTypesNodeIgnorableNames = new Set([ - "AbortController", - "AbortSignal", - "AsyncIteratorObject", - "atob", - "Blob", - "BroadcastChannel", - "btoa", - "ByteLengthQueuingStrategy", - "CloseEvent", - "CompressionStream", - "CountQueuingStrategy", - "CustomEvent", - "DecompressionStream", - "Disposable", - "DOMException", - "Event", - "EventSource", - "EventTarget", - "fetch", - "File", - "Float32Array", - "Float64Array", - "FormData", - "Headers", - "ImportMeta", - "MessageChannel", - "MessageEvent", - "MessagePort", - "performance", - "PerformanceEntry", - "PerformanceMark", - "PerformanceMeasure", - "ReadableByteStreamController", - "ReadableStream", - "ReadableStreamBYOBReader", - "ReadableStreamBYOBRequest", - "ReadableStreamDefaultController", - "ReadableStreamDefaultReader", - "ReadonlyArray", - "Request", - "Response", - "Storage", - "TextDecoder", - "TextDecoderStream", - "TextEncoder", - "TextEncoderStream", - "TransformStream", - "TransformStreamDefaultController", - "URL", - "URLPattern", - "URLSearchParams", - "WebSocket", - "WritableStream", - "WritableStreamDefaultController", - "WritableStreamDefaultWriter", -]); +const setTypesNodeIgnorableNames = TYPES_NODE_IGNORABLE_NAMES; ts.deno.setTypesNodeIgnorableNames(setTypesNodeIgnorableNames); /** diff --git a/cli/tsc/diagnostics.rs b/cli/tsc/diagnostics.rs index 0c3ff37a8d..4c6251cce3 100644 --- a/cli/tsc/diagnostics.rs +++ b/cli/tsc/diagnostics.rs @@ -81,10 +81,10 @@ impl From for DiagnosticCategory { #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DiagnosticMessageChain { - message_text: String, - category: DiagnosticCategory, - code: i64, - next: Option>, + pub message_text: String, + pub category: DiagnosticCategory, + pub code: i64, + pub next: Option>, } impl DiagnosticMessageChain { @@ -358,6 +358,12 @@ impl fmt::Display for Diagnostic { #[class(generic)] pub struct Diagnostics(Vec); +impl From> for Diagnostics { + fn from(diagnostics: Vec) -> Self { + Diagnostics(diagnostics) + } +} + impl Diagnostics { #[cfg(test)] pub fn new(diagnostics: Vec) -> Self { diff --git a/cli/tsc/go.rs b/cli/tsc/go.rs new file mode 100644 index 0000000000..f26e31428b --- /dev/null +++ b/cli/tsc/go.rs @@ -0,0 +1,671 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +mod setup; +mod tsgo_version; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; + +use deno_ast::MediaType; +use deno_ast::ModuleSpecifier; +use deno_config::deno_json::CompilerOptions; +use deno_core::serde_json; +use deno_core::serde_json::json; +use deno_graph::ModuleGraph; +use deno_typescript_go_client_rust::CallbackHandler; +use deno_typescript_go_client_rust::SyncRpcChannel; +use deno_typescript_go_client_rust::types::GetImpliedNodeFormatForFilePayload; +use deno_typescript_go_client_rust::types::Project; +use deno_typescript_go_client_rust::types::ResolveModuleNamePayload; +use deno_typescript_go_client_rust::types::ResolveTypeReferenceDirectivePayload; +pub use setup::DownloadError; +pub use setup::ensure_tsgo; + +use super::Request; +use super::Response; +use crate::args::TypeCheckMode; + +macro_rules! jsons { + ($($arg:tt)*) => { + serde_json::to_string(&json!($($arg)*)) + }; +} + +fn deser( + payload: impl AsRef, +) -> Result { + serde_json::from_str::(payload.as_ref()) +} + +// the way tsgo currently works, it really wants an actual tsconfig.json file. +// it also doesn't let you just pass in root file names. instead of making more changes in tsgo, +// work around both by making a fake tsconfig.json file with the `"files"` field set to the root file names. +// it's "synthetic" because it's not actually on disk, we pass a fake path to load it from memory. +fn synthetic_config( + config: &CompilerOptions, + root_names: &[String], + type_check_mode: TypeCheckMode, +) -> Result { + let mut config = serde_json::to_value(config)?; + let obj = config.as_object_mut().unwrap(); + obj.insert("allowImportingTsExtensions".to_string(), json!(true)); + if type_check_mode != TypeCheckMode::All { + obj.insert("skipDefaultLibCheck".to_string(), json!(true)); + } + if let Some(jsx) = obj.get("jsx") + && jsx.as_str() == Some("precompile") + { + obj.insert("jsx".to_string(), json!("react-jsx")); + } + let config = serde_json::to_string(&json!({ + "compilerOptions": config, + "files": root_names, + }))?; + log::debug!("synthetic config: {}", config); + Ok(config) +} + +pub fn exec_request( + request: Request, + root_names: Vec, + root_map: HashMap, + remapped_specifiers: HashMap, + tsgo_path: &Path, +) -> Result { + exec_request_inner( + request, + root_names, + root_map, + remapped_specifiers, + tsgo_path, + ) + .map_err(super::ExecError::Go) +} + +fn exec_request_inner( + request: Request, + root_names: Vec, + root_map: HashMap, + remapped_specifiers: HashMap, + tsgo_path: &Path, +) -> Result { + let handler = Handler::new( + "/virtual/tsconfig.json".to_string(), + synthetic_config(request.config.as_ref(), &root_names, request.check_mode)?, + remapped_specifiers, + root_map, + request.initial_cwd, + request.graph.clone(), + request.maybe_npm, + ); + + let callbacks = handler.supported_callbacks(); + let bin_path = tsgo_path; + let mut channel = SyncRpcChannel::new(bin_path, vec!["--api"], handler)?; + + channel.request_sync( + "configure", + jsons!({ + "callbacks": callbacks.iter().collect::>(), + "logFile": "", + "forkContextInfo": { + "typesNodeIgnorableNames": super::TYPES_NODE_IGNORABLE_NAMES, + "nodeOnlyGlobalNames": super::NODE_ONLY_GLOBALS, + }, + })?, + )?; + + let project = channel.request_sync( + "loadProject", + jsons!({ + "configFileName": "/virtual/tsconfig.json", + })?, + )?; + let project = deser::(project)?; + + let file_names = if request.check_mode != TypeCheckMode::All { + root_names + } else { + Vec::new() + }; + let diagnostics = channel.request_sync( + "getDiagnostics", + jsons!({ + "project": &project.id, + "fileNames": file_names, + })?, + )?; + let diagnostics = deser::< + Vec, + >(diagnostics)?; + + Ok(Response { + diagnostics: convert_diagnostics(diagnostics), + maybe_tsbuildinfo: None, + ambient_modules: vec![], + stats: super::Stats::default(), + }) +} + +fn diagnostic_category(category: &str) -> super::DiagnosticCategory { + match category { + "error" => super::DiagnosticCategory::Error, + "warning" => super::DiagnosticCategory::Warning, + "message" => super::DiagnosticCategory::Message, + "suggestion" => super::DiagnosticCategory::Suggestion, + _ => unreachable!("unexpected diagnostic category: {category}"), + } +} + +fn maybe_rewrite_message(message: String, code: u64) -> String { + if code == 2304 && message.starts_with("Cannot find name 'Deno'") { + r#"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 the top of your entrypoint (main file): /// "#.to_string() + } else if code == 2581 { + r#"Cannot find name '$'. Did you mean to import jQuery? Try adding `import $ from "npm:jquery";`."#.to_string() + } else if code == 2580 { + let regex = lazy_regex::regex!(r#"Cannot find name '([^']+)'"#); + let captures = regex.captures(&message).unwrap(); + let name = captures.get(1).unwrap().as_str(); + format!("Cannot find name '{}'.", name) + } else if code == 1203 { + "Export assignment cannot be used when targeting ECMAScript modules. Consider using 'export default' or another module format instead. This will start erroring in a future version of Deno 2 in order to align with TypeScript.".to_string() + } else if code == 2339 && message.contains("on type 'typeof Deno'") { + let regex = lazy_regex::regex!( + r#"Property '([^']+)' does not exist on type 'typeof Deno'"# + ); + let captures = regex.captures(&message).unwrap(); + let name = captures.get(1).unwrap().as_str(); + format!( + "Property '{name}' does not exist on type 'typeof Deno'. 'Deno.{name}' is an unstable API. If not, try changing the 'lib' compiler option to include 'deno.unstable' or add a triple-slash directive to the top of your entrypoint (main file): /// ", + ) + } else { + message + } +} + +fn maybe_remap_category( + code: u64, + category: super::DiagnosticCategory, +) -> super::DiagnosticCategory { + if code == 1203 { + super::DiagnosticCategory::Warning + } else { + category + } +} + +fn convert_diagnostic( + diagnostic: deno_typescript_go_client_rust::types::Diagnostic, + _diagnostics: &[deno_typescript_go_client_rust::types::Diagnostic], +) -> super::Diagnostic { + let (start, end) = if diagnostic.start.line == 0 + && diagnostic.start.character == 0 + && diagnostic.end.line == 0 + && diagnostic.end.character == 0 + { + (None, None) + } else { + (Some(diagnostic.start), Some(diagnostic.end)) + }; + + super::Diagnostic { + category: maybe_remap_category( + diagnostic.code as u64, + diagnostic_category(diagnostic.category.as_str()), + ), + code: diagnostic.code as u64, + start: start.map(|s| super::Position { + line: s.line, + character: s.character, + }), + end: end.map(|e| super::Position { + line: e.line, + character: e.character, + }), + original_source_start: None, + message_chain: None, + message_text: Some(maybe_rewrite_message( + diagnostic.message, + diagnostic.code as u64, + )), + file_name: Some(diagnostic.file_name), + missing_specifier: None, + other: Default::default(), + related_information: if diagnostic.related_information.is_empty() { + None + } else { + Some( + diagnostic + .related_information + .into_iter() + .map(|d| convert_diagnostic(d, _diagnostics)) + .collect::>(), + ) + }, + reports_deprecated: Some(diagnostic.reports_deprecated), + reports_unnecessary: Some(diagnostic.reports_unnecessary), + source: None, + source_line: Some(diagnostic.source_line), + } +} + +fn should_ignore_diagnostic(diagnostic: &super::Diagnostic) -> bool { + super::IGNORED_DIAGNOSTIC_CODES.contains(&diagnostic.code) +} + +fn convert_diagnostics( + diagnostics: Vec, +) -> super::Diagnostics { + super::diagnostics::Diagnostics::from( + diagnostics + .iter() + .map(|diagnostic| convert_diagnostic(diagnostic.clone(), &diagnostics)) + .filter(|diagnostic| !should_ignore_diagnostic(diagnostic)) + .collect::>(), + ) +} + +struct Handler { + state: RefCell, +} + +impl Handler { + fn new( + config_path: String, + synthetic_config: String, + remapped_specifiers: HashMap, + root_map: HashMap, + current_dir: PathBuf, + graph: Arc, + maybe_npm: Option, + ) -> Self { + Self { + state: RefCell::new(HandlerState { + config_path, + synthetic_config, + remapped_specifiers, + root_map, + current_dir, + graph, + maybe_npm, + module_kind_map: HashMap::new(), + load_result_pending: HashMap::new(), + }), + } + } +} + +fn get_package_json_scope_if_applicable( + state: &mut HandlerState, + payload: String, +) -> Result { + log::debug!("get_package_json_scope_if_applicable: {}", payload); + if let Some(maybe_npm) = state.maybe_npm.as_ref() { + let file_path = deser::(&payload)?; + let file_path = if let Ok(specifier) = ModuleSpecifier::parse(&file_path) { + deno_path_util::url_to_file_path(&specifier).ok() + } else { + Some(PathBuf::from(file_path)) + }; + let Some(file_path) = file_path else { + return Ok(jsons!(None::)?); + }; + if let Some(package_json) = maybe_npm + .package_json_resolver + .get_closest_package_jsons(&file_path) + .next() + .and_then(|r| r.ok()) + { + let package_directory = package_json.path.parent(); + let contents = serde_json::to_string(&package_json).ok(); + if let Some(contents) = contents { + return Ok(jsons!({ + "packageDirectory": package_directory, + "directoryExists": true, + "contents": contents, + })?); + } + } + } + + Ok(jsons!(None::)?) +} + +struct HandlerState { + config_path: String, + synthetic_config: String, + remapped_specifiers: HashMap, + root_map: HashMap, + current_dir: PathBuf, + graph: Arc, + maybe_npm: Option, + + module_kind_map: + HashMap, + + load_result_pending: HashMap, +} + +impl deno_typescript_go_client_rust::CallbackHandler for Handler { + fn supported_callbacks(&self) -> &'static [&'static str] { + &[ + "readFile", + "resolveModuleName", + "getPackageJsonScopeIfApplicable", + "getPackageScopeForPath", + "resolveTypeReferenceDirective", + "getImpliedNodeFormatForFile", + "isNodeSourceFile", + ] + } + + fn handle_callback( + &self, + name: &str, + payload: String, + ) -> Result { + let mut state = self.state.borrow_mut(); + match name { + "readFile" => { + log::debug!("readFile: {}", payload); + let payload = deser::(payload)?; + if payload == state.config_path { + Ok(jsons!(&state.synthetic_config)?) + } else { + if let Some(load_result) = state.load_result_pending.remove(&payload) + { + return Ok(jsons!(load_result.contents)?); + } + let result = load_inner(&mut state, &payload).map_err(adhoc)?; + + if let Some(result) = result { + let contents = result.contents; + Ok(jsons!(&contents)?) + } else { + let path = Path::new(&payload); + + if let Ok(contents) = std::fs::read_to_string(path) { + Ok(jsons!(&contents)?) + } else { + Ok(jsons!(None::)?) + } + } + } + } + "loadSourceFile" => { + let payload = deser::(payload)?; + log::debug!("loadSourceFile: {}", payload); + if let Some(load_result) = state.load_result_pending.remove(&payload) { + Ok(jsons!(&load_result)?) + } else { + let result = load_inner(&mut state, &payload).map_err(adhoc)?; + Ok(jsons!(&result)?) + } + } + "resolveModuleName" => { + let payload = deser::(payload)?; + let (out_name, extension) = resolve_name(&mut state, payload)?; + + Ok(jsons!({ + "resolvedFileName": out_name, + "extension": extension, + })?) + } + "getPackageJsonScopeIfApplicable" => { + log::debug!("getPackageJsonScopeIfApplicable: {}", payload); + get_package_json_scope_if_applicable(&mut state, payload).inspect( + |res| log::debug!("getPackageJsonScopeIfApplicable -> {}", res), + ) + } + "getPackageScopeForPath" => { + log::debug!("getPackageScopeForPath: {}", payload); + get_package_json_scope_if_applicable(&mut state, payload) + .inspect(|res| log::debug!("getPackageScopeForPath -> {}", res)) + } + "resolveTypeReferenceDirective" => { + log::debug!("resolveTypeReferenceDirective: {}", payload); + let payload = deser::(payload)?; + let payload = ResolveModuleNamePayload { + module_name: payload.type_reference_directive_name, + containing_file: payload.containing_file, + resolution_mode: payload.resolution_mode, + }; + let (out_name, extension) = resolve_name(&mut state, payload)?; + log::debug!( + "resolveTypeReferenceDirective: {:?}", + (&out_name, &extension) + ); + Ok(jsons!({ + "resolvedFileName": out_name, + "extension": extension, + "primary": true, + })?) + } + "getImpliedNodeFormatForFile" => { + let payload = deser::(payload)?; + log::debug!("getImpliedNodeFormatForFile: {:?}", payload); + // check if we already determined the module kind from a previous load + if let Some(module_kind) = state.module_kind_map.get(&payload.file_name) + { + log::debug!("getImpliedNodeFormatForFile -> {:?}", module_kind); + Ok(jsons!(&module_kind)?) + } else { + // if not, load the file and determine the module kind + let load_result = + load_inner(&mut state, &payload.file_name).map_err(adhoc)?; + if let Some(load_result) = load_result { + // store the load result in the pending map to avoid loading the file again + state + .load_result_pending + .insert(payload.file_name.clone(), load_result); + let module_kind = state + .module_kind_map + .get(&payload.file_name) + .copied() + .unwrap_or( + deno_typescript_go_client_rust::types::ResolutionMode::ESM, + ); + Ok(jsons!(&module_kind)?) + } else { + Ok(jsons!( + &deno_typescript_go_client_rust::types::ResolutionMode::ESM + )?) + } + } + } + "isNodeSourceFile" => { + let path = deser::(payload)?; + let state = &*state; + let result = ModuleSpecifier::parse(&path) + .ok() + .or_else(|| { + deno_path_util::resolve_url_or_path(&path, &state.current_dir).ok() + }) + .and_then(|specifier| { + state + .maybe_npm + .as_ref() + .map(|n| n.node_resolver.in_npm_package(&specifier)) + }) + .unwrap_or(false); + Ok(jsons!(result)?) + } + _ => unreachable!("unknown callback: {name}"), + } + } +} + +fn adhoc(err: impl std::error::Error) -> deno_typescript_go_client_rust::Error { + deno_typescript_go_client_rust::Error::AdHoc(err.to_string()) +} + +fn resolve_name( + handler: &mut HandlerState, + payload: ResolveModuleNamePayload, +) -> Result<(String, Option<&'static str>), deno_typescript_go_client_rust::Error> +{ + let graph = &handler.graph; + let maybe_npm = handler.maybe_npm.as_ref(); + let referrer = if let Some(remapped_specifier) = + handler.maybe_remapped_specifier(&payload.containing_file) + { + remapped_specifier.clone() + } else { + deno_path_util::resolve_url_or_path( + &payload.containing_file, + &handler.current_dir, + ) + .map_err(adhoc)? + }; + let referrer_module = graph.get(&referrer); + let specifier = payload.module_name; + let result = super::resolve_specifier_for_tsc( + specifier, + &referrer, + graph, + match payload.resolution_mode { + deno_typescript_go_client_rust::types::ResolutionMode::None => { + super::ResolutionMode::Import + } + deno_typescript_go_client_rust::types::ResolutionMode::CommonJS => { + super::ResolutionMode::Require + } + deno_typescript_go_client_rust::types::ResolutionMode::ESM => { + super::ResolutionMode::Import + } + }, + maybe_npm, + referrer_module, + &mut handler.remapped_specifiers, + ) + .map_err(adhoc)?; + + Ok(result) +} + +impl HandlerState { + pub fn maybe_remapped_specifier( + &self, + specifier: &str, + ) -> Option<&ModuleSpecifier> { + self + .remapped_specifiers + .get(specifier) + .or_else(|| self.root_map.get(specifier)) + } +} + +#[derive(Debug, thiserror::Error, deno_error::JsError)] +pub enum ExecError { + #[class(generic)] + #[error(transparent)] + SerdeJson(#[from] serde_json::Error), + #[class(generic)] + #[error(transparent)] + TsgoClient(#[from] deno_typescript_go_client_rust::Error), + + #[class(generic)] + #[error(transparent)] + PackageJsonLoad(#[from] deno_package_json::PackageJsonLoadError), + + #[class(generic)] + #[error(transparent)] + PackageJsonLoadError(#[from] node_resolver::errors::PackageJsonLoadError), + + #[class(generic)] + #[error(transparent)] + DownloadError(#[from] DownloadError), + + #[class(generic)] + #[error(transparent)] + LoadError(#[from] super::LoadError), +} + +#[derive(Debug, Clone, serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct LoadResult { + contents: String, + script_kind: i32, +} + +impl super::LoadContent for String { + fn from_static(source: &'static str) -> Self { + source.to_string() + } + fn from_string(source: String) -> Self { + source + } + fn from_arc_str(source: Arc) -> Self { + source.to_string() + } +} + +impl super::Mapper for HandlerState { + fn maybe_remapped_specifier( + &self, + specifier: &str, + ) -> Option<&ModuleSpecifier> { + self.maybe_remapped_specifier(specifier) + } +} + +fn load_inner( + state: &mut HandlerState, + load_specifier: &str, +) -> Result, ExecError> { + log::debug!("load_inner: {}", load_specifier); + let result = super::load_for_tsc( + load_specifier, + state.maybe_npm.as_ref(), + &state.current_dir, + &state.graph, + None, + 0, + state, + )?; + let Some(result) = result else { + return Ok(None); + }; + let is_cjs = result.is_cjs; + let media_type = result.media_type; + + let module_kind = get_resolution_mode(is_cjs, media_type); + let script_kind = super::as_ts_script_kind(media_type); + log::debug!("load_inner {load_specifier} -> {:?}", module_kind); + state + .module_kind_map + .insert(load_specifier.to_string(), module_kind); + Ok(Some(LoadResult { + contents: result.data, + script_kind, + })) +} + +fn get_resolution_mode( + is_cjs: bool, + media_type: MediaType, +) -> deno_typescript_go_client_rust::types::ResolutionMode { + if is_cjs { + deno_typescript_go_client_rust::types::ResolutionMode::CommonJS + } else { + match media_type { + MediaType::Cjs | MediaType::Dcts | MediaType::Cts => { + deno_typescript_go_client_rust::types::ResolutionMode::CommonJS + } + + MediaType::Css + | MediaType::Json + | MediaType::Html + | MediaType::Sql + | MediaType::Wasm + | MediaType::SourceMap + | MediaType::Unknown => { + deno_typescript_go_client_rust::types::ResolutionMode::None + } + _ => deno_typescript_go_client_rust::types::ResolutionMode::ESM, + } + } +} diff --git a/cli/tsc/go/setup.rs b/cli/tsc/go/setup.rs new file mode 100644 index 0000000000..bd9cd6c642 --- /dev/null +++ b/cli/tsc/go/setup.rs @@ -0,0 +1,169 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::OnceLock; + +use deno_core::error::AnyError; +use deno_error::JsErrorBox; +use sha2::Digest; + +use super::tsgo_version; +use crate::cache::DenoDir; +use crate::http_util::HttpClientProvider; + +fn get_download_url(platform: &str) -> String { + format!( + "{}/typescript-go-{}-{}.zip", + tsgo_version::DOWNLOAD_BASE_URL, + tsgo_version::VERSION, + platform + ) +} + +#[derive(Debug, thiserror::Error, deno_error::JsError)] +#[class(generic)] +pub enum DownloadError { + #[error("unsupported platform for typescript-go: {0}")] + UnsupportedPlatform(String), + #[error("invalid download url: {0}")] + InvalidDownloadUrl(String, #[source] deno_core::url::ParseError), + #[error("failed to unpack typescript-go: {0}")] + UnpackFailed(#[source] AnyError), + #[error("failed to rename typescript-go: {0}")] + RenameFailed(#[source] std::io::Error), + #[error("failed to write zip file to {0}: {1}")] + WriteZipFailed(String, #[source] std::io::Error), + #[error("failed to download typescript-go: {0}")] + DownloadFailed(#[source] crate::http_util::DownloadError), + #[error("{0}")] + HttpClient(#[source] JsErrorBox), + #[error("failed to create temp directory: {0}")] + CreateTempDirFailed(#[source] std::io::Error), + #[error("hash mismatch: expected {0}, got {1}")] + HashMismatch(String, String), + #[error("binary not found: {0}")] + BinaryNotFound(String), +} + +fn verify_hash(platform: &str, data: &[u8]) -> Result<(), DownloadError> { + let expected_hash = match platform { + "windows-x64" => tsgo_version::HASHES.windows_x64, + "macos-x64" => tsgo_version::HASHES.macos_x64, + "macos-arm64" => tsgo_version::HASHES.macos_arm64, + "linux-x64" => tsgo_version::HASHES.linux_x64, + "linux-arm64" => tsgo_version::HASHES.linux_arm64, + _ => unreachable!(), + }; + let (algorithm, expected_hash) = expected_hash.split_once(':').unwrap(); + if algorithm != "sha256" { + panic!("Hash algorithm is not sha256"); + } + + let mut hash = sha2::Sha256::new(); + hash.update(data); + let hash = hash.finalize(); + + let hash = faster_hex::hex_string(&hash); + if hash != expected_hash { + return Err(DownloadError::HashMismatch(expected_hash.to_string(), hash)); + } + + Ok(()) +} + +pub async fn ensure_tsgo( + deno_dir: &DenoDir, + http_client_provider: Arc, +) -> Result<&'static PathBuf, DownloadError> { + static TSGO_PATH: OnceLock = OnceLock::new(); + + if let Some(bin_path) = TSGO_PATH.get() { + return Ok(bin_path); + } + + if let Ok(tsgo_path) = std::env::var("DENO_TSGO_PATH") { + let tsgo_path = Path::new(&tsgo_path); + if tsgo_path.exists() { + return Ok(TSGO_PATH.get_or_init(|| PathBuf::from(tsgo_path))); + } else { + return Err(DownloadError::BinaryNotFound( + tsgo_path.to_string_lossy().into_owned(), + )); + } + } + + let platform = match (std::env::consts::OS, std::env::consts::ARCH) { + ("windows", "x86_64") => "windows-x64", + ("macos", "x86_64") => "macos-x64", + ("macos", "aarch64") => "macos-arm64", + ("linux", "x86_64") => "linux-x64", + ("linux", "aarch64") => "linux-arm64", + _ => { + return Err(DownloadError::UnsupportedPlatform(format!( + "{} {}", + std::env::consts::OS, + std::env::consts::ARCH + ))); + } + }; + + let folder_path = deno_dir + .dl_folder_path() + .join(format!("tsgo-{}", tsgo_version::VERSION)); + + let bin_path = folder_path.join(format!( + "tsgo-{}{}", + platform, + if cfg!(windows) { ".exe" } else { "" } + )); + + if bin_path.exists() { + return Ok(TSGO_PATH.get_or_init(|| bin_path)); + } + + std::fs::create_dir_all(&folder_path) + .map_err(DownloadError::CreateTempDirFailed)?; + + let client = http_client_provider + .get_or_create() + .map_err(DownloadError::HttpClient)?; + let download_url = get_download_url(platform); + log::debug!("Downloading tsgo from {}", download_url); + let temp = tempfile::tempdir().map_err(DownloadError::CreateTempDirFailed)?; + let path = temp.path().join("tsgo.zip"); + log::debug!("Downloading tsgo to {}", path.display()); + let data = client + .download( + deno_core::url::Url::parse(&download_url) + .map_err(|e| DownloadError::InvalidDownloadUrl(download_url, e))?, + ) + .await + .map_err(DownloadError::DownloadFailed)?; + + verify_hash(platform, &data)?; + + std::fs::write(&path, &data).map_err(|e| { + DownloadError::WriteZipFailed(path.display().to_string(), e) + })?; + + log::debug!( + "Unpacking tsgo from {} to {}", + path.display(), + temp.path().display() + ); + let unpacked_path = + crate::util::archive::unpack_into_dir(crate::util::archive::UnpackArgs { + exe_name: "tsgo", + archive_name: "tsgo.zip", + archive_data: &data, + is_windows: cfg!(windows), + dest_path: temp.path(), + }) + .map_err(DownloadError::UnpackFailed)?; + std::fs::rename(unpacked_path, &bin_path) + .map_err(DownloadError::RenameFailed)?; + + Ok(TSGO_PATH.get_or_init(|| bin_path)) +} diff --git a/cli/tsc/go/tsgo_version.rs b/cli/tsc/go/tsgo_version.rs new file mode 100644 index 0000000000..2fe4b7dcde --- /dev/null +++ b/cli/tsc/go/tsgo_version.rs @@ -0,0 +1,55 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +// This file is auto-generated by tools/update_tsgo.ts +// DO NOT EDIT THIS FILE MANUALLY + +pub struct Hashes { + pub windows_x64: &'static str, + pub macos_x64: &'static str, + pub macos_arm64: &'static str, + pub linux_x64: &'static str, + pub linux_arm64: &'static str, +} + +impl Hashes { + pub const fn all(&self) -> [&'static str; 5] { + [ + self.windows_x64, + self.macos_x64, + self.macos_arm64, + self.linux_x64, + self.linux_arm64, + ] + } +} + +pub const VERSION: &str = "0.1.6"; +pub const DOWNLOAD_BASE_URL: &str = + "https://github.com/denoland/typescript-go/releases/download/v0.1.6"; +pub const HASHES: Hashes = Hashes { + windows_x64: "sha256:04f85a9a64807437471cd45ed569b1ee9910dbe9751c1a5085028ae5eb09db56", + macos_x64: "sha256:84619ab4a6ac3dc1c78a62c209ea853cf077871fe086657bc861e268b9c0412c", + macos_arm64: "sha256:cef3d3f60abe9f2947f2e30f8075d860f9bf176a5545c651eabfcaa9e791e0f9", + linux_x64: "sha256:ea0ae7f3782a8372a03ec2c1f1bbb415e0c10a43ce917b0564084f896e0df127", + linux_arm64: "sha256:67b4f4d9982ff5c5c14105b37c48b582e3dc806c8a504f1a1b5416e29de68198", +}; + +const _: () = { + let sha256 = "sha256".as_bytes(); + + let mut i = 0; + let hashes = HASHES.all(); + + while i < hashes.len() { + let hash = hashes[i].as_bytes(); + let mut j = 0; + + while j < 6 { + if hash[j] != sha256[j] { + panic!("Hash algorithm is not sha256"); + } + j += 1; + } + i += 1; + } +}; diff --git a/cli/tsc/js.rs b/cli/tsc/js.rs new file mode 100644 index 0000000000..46893bb325 --- /dev/null +++ b/cli/tsc/js.rs @@ -0,0 +1,985 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use std::borrow::Cow; +use std::collections::HashMap; +use std::path::PathBuf; +use std::rc::Rc; +use std::sync::Arc; + +use deno_core::FastString; +use deno_core::JsRuntime; +use deno_core::ModuleSpecifier; +use deno_core::OpState; +use deno_core::RuntimeOptions; +use deno_core::anyhow::Context; +use deno_core::located_script_name; +use deno_core::op2; +use deno_core::serde::Deserialize; +use deno_core::serde::Serialize; +use deno_core::serde_json::json; +use deno_core::url::Url; +use deno_graph::GraphKind; +use deno_graph::ModuleGraph; +use deno_lib::util::hash::FastInsecureHasher; +use deno_lib::worker::create_isolate_create_params; +use deno_path_util::resolve_url_or_path; +use node_resolver::ResolutionMode; + +use super::LAZILY_LOADED_STATIC_ASSETS; +use super::ResolveArgs; +use super::ResolveError; +use crate::args::TypeCheckMode; +use crate::tsc::Diagnostics; +use crate::tsc::ExecError; +use crate::tsc::LoadError; +use crate::tsc::Request; +use crate::tsc::RequestNpmState; +use crate::tsc::Response; +use crate::tsc::Stats; +use crate::tsc::get_hash; + +#[op2] +#[string] +fn op_remap_specifier( + state: &mut OpState, + #[string] specifier: &str, +) -> Option { + let state = state.borrow::(); + state + .maybe_remapped_specifier(specifier) + .map(|url| url.to_string()) +} + +#[op2] +#[serde] +fn op_libs() -> Vec { + let mut out = Vec::with_capacity(LAZILY_LOADED_STATIC_ASSETS.len()); + for (key, value) in LAZILY_LOADED_STATIC_ASSETS.iter() { + if !value.is_lib { + continue; + } + let lib = key + .replace("lib.", "") + .replace(".d.ts", "") + .replace("deno_", "deno."); + out.push(lib); + } + out +} + +#[op2] +#[serde] +fn op_resolve( + state: &mut OpState, + #[string] base: String, + #[serde] specifiers: Vec<(bool, String)>, +) -> Result)>, ResolveError> { + op_resolve_inner(state, ResolveArgs { base, specifiers }) +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TscConstants { + types_node_ignorable_names: Vec<&'static str>, + node_only_globals: Vec<&'static str>, + ignored_diagnostic_codes: Vec, +} + +impl TscConstants { + pub fn new() -> Self { + Self { + types_node_ignorable_names: super::TYPES_NODE_IGNORABLE_NAMES.to_vec(), + node_only_globals: super::NODE_ONLY_GLOBALS.to_vec(), + ignored_diagnostic_codes: super::IGNORED_DIAGNOSTIC_CODES + .iter() + .copied() + .collect(), + } + } +} + +#[op2] +#[serde] +fn op_tsc_constants() -> TscConstants { + TscConstants::new() +} + +#[inline] +fn op_resolve_inner( + state: &mut OpState, + args: ResolveArgs, +) -> Result)>, ResolveError> { + let state = state.borrow_mut::(); + let mut resolved: Vec<(String, Option<&'static str>)> = + Vec::with_capacity(args.specifiers.len()); + let referrer = if let Some(remapped_specifier) = + state.maybe_remapped_specifier(&args.base) + { + remapped_specifier.clone() + } else { + resolve_url_or_path(&args.base, &state.current_dir)? + }; + let referrer_module = state.graph.get(&referrer); + for (is_cjs, specifier) in args.specifiers { + let result = super::resolve_specifier_for_tsc( + specifier, + &referrer, + &state.graph, + if is_cjs { + ResolutionMode::Require + } else { + ResolutionMode::Import + }, + state.maybe_npm.as_ref(), + referrer_module, + &mut state.remapped_specifiers, + )?; + resolved.push(result); + } + + Ok(resolved) +} + +deno_core::extension!(deno_cli_tsc, + ops = [ + op_create_hash, + op_emit, + op_is_node_file, + op_load, + op_remap_specifier, + op_resolve, + op_tsc_constants, + op_respond, + op_libs, + ], + options = { + request: Request, + root_map: HashMap, + remapped_specifiers: HashMap, + }, + state = |state, options| { + state.put(State::new( + options.request.graph, + options.request.hash_data, + options.request.maybe_npm, + options.request.maybe_tsbuildinfo, + options.root_map, + options.remapped_specifiers, + std::env::current_dir() + .context("Unable to get CWD") + .unwrap(), + )); + }, + customizer = |ext: &mut deno_core::Extension| { + use deno_core::ExtensionFileSource; + ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/99_main_compiler.js", crate::tsc::MAIN_COMPILER_SOURCE.as_str().into())); + ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/97_ts_host.js", crate::tsc::TS_HOST_SOURCE.as_str().into())); + ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/98_lsp.js", crate::tsc::LSP_SOURCE.as_str().into())); + ext.js_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/00_typescript.js", crate::tsc::TYPESCRIPT_SOURCE.as_str().into())); + ext.esm_entry_point = Some("ext:deno_cli_tsc/99_main_compiler.js"); + } +); +// TODO(bartlomieju): this mechanism is questionable. +// Can't we use something more efficient here? +#[op2] +fn op_respond(state: &mut OpState, #[serde] args: RespondArgs) { + op_respond_inner(state, args) +} + +#[inline] +fn op_respond_inner(state: &mut OpState, args: RespondArgs) { + let state = state.borrow_mut::(); + state.maybe_response = Some(args); +} + +#[op2] +#[string] +fn op_create_hash(s: &mut OpState, #[string] text: &str) -> String { + op_create_hash_inner(s, text) +} + +#[inline] +fn op_create_hash_inner(s: &mut OpState, text: &str) -> String { + let state = s.borrow_mut::(); + get_hash(text, state.hash_data) +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct EmitArgs { + /// The text data/contents of the file. + data: String, + /// The _internal_ filename for the file. This will be used to determine how + /// the file is cached and stored. + file_name: String, +} + +#[op2(fast)] +fn op_emit( + state: &mut OpState, + #[string] data: String, + #[string] file_name: String, +) -> bool { + op_emit_inner(state, EmitArgs { data, file_name }) +} + +#[inline] +fn op_emit_inner(state: &mut OpState, args: EmitArgs) -> bool { + let state = state.borrow_mut::(); + match args.file_name.as_ref() { + "internal:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(args.data), + _ => { + if cfg!(debug_assertions) { + panic!("Unhandled emit write: {}", args.file_name); + } + } + } + + true +} + +#[op2(fast)] +fn op_is_node_file(state: &mut OpState, #[string] path: &str) -> bool { + let state = state.borrow::(); + ModuleSpecifier::parse(path) + .ok() + .and_then(|specifier| { + state + .maybe_npm + .as_ref() + .map(|n| n.node_resolver.in_npm_package(&specifier)) + }) + .unwrap_or(false) +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +struct RespondArgs { + pub diagnostics: Diagnostics, + pub ambient_modules: Vec, + pub stats: Stats, +} + +impl super::LoadContent for FastString { + fn from_static(source: &'static str) -> Self { + FastString::from_static(source) + } + fn from_string(source: String) -> Self { + FastString::from(source) + } + fn from_arc_str(source: Arc) -> Self { + FastString::from(source) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct LoadResponse { + data: FastString, + version: Option, + script_kind: i32, + is_cjs: bool, +} + +#[op2] +#[serde] +fn op_load( + state: &mut OpState, + #[string] load_specifier: &str, +) -> Result, LoadError> { + op_load_inner(state, load_specifier) +} + +impl super::Mapper for State { + fn maybe_remapped_specifier( + &self, + specifier: &str, + ) -> Option<&ModuleSpecifier> { + self.maybe_remapped_specifier(specifier) + } +} + +fn op_load_inner( + state: &mut OpState, + load_specifier: &str, +) -> Result, LoadError> { + let state = state.borrow::(); + Ok( + super::load_for_tsc::( + load_specifier, + state.maybe_npm.as_ref(), + &state.current_dir, + &state.graph, + state.maybe_tsbuildinfo.as_deref(), + state.hash_data, + state, + )? + .map(|res| LoadResponse { + data: res.data, + version: res.version, + is_cjs: res.is_cjs, + script_kind: super::as_ts_script_kind(res.media_type), + }), + ) +} + +pub fn exec_request( + request: Request, + root_names: Vec, + root_map: HashMap, + remapped_specifiers: HashMap, + code_cache: Option>, +) -> Result { + let request_value = json!({ + "config": request.config, + "debug": request.debug, + "rootNames": root_names, + "localOnly": request.check_mode == TypeCheckMode::Local, + }); + let exec_source = format!("globalThis.exec({request_value})"); + + let mut extensions = + deno_runtime::snapshot_info::get_extensions_in_snapshot(); + extensions.push(deno_cli_tsc::init(request, root_map, remapped_specifiers)); + let extension_code_cache = code_cache.map(|cache| { + Rc::new(TscExtCodeCache::new(cache)) as Rc + }); + let mut runtime = JsRuntime::new(RuntimeOptions { + extensions, + create_params: create_isolate_create_params(&crate::sys::CliSys::default()), + startup_snapshot: deno_snapshots::CLI_SNAPSHOT, + extension_code_cache, + ..Default::default() + }); + + runtime + .execute_script(located_script_name!(), exec_source) + .map_err(ExecError::Js)?; + + let op_state = runtime.op_state(); + let mut op_state = op_state.borrow_mut(); + let state = op_state.take::(); + + if let Some(response) = state.maybe_response { + let diagnostics = response.diagnostics; + let ambient_modules = response.ambient_modules; + let maybe_tsbuildinfo = state.maybe_tsbuildinfo; + let stats = response.stats; + + Ok(Response { + diagnostics, + ambient_modules, + maybe_tsbuildinfo, + stats, + }) + } else { + Err(ExecError::ResponseNotSet) + } +} + +pub struct TscExtCodeCache { + cache: Arc, +} + +impl TscExtCodeCache { + pub fn new(cache: Arc) -> Self { + Self { cache } + } +} + +impl deno_core::ExtCodeCache for TscExtCodeCache { + fn get_code_cache_info( + &self, + specifier: &ModuleSpecifier, + code: &deno_core::ModuleSourceCode, + esm: bool, + ) -> deno_core::SourceCodeCacheInfo { + use deno_runtime::code_cache::CodeCacheType; + let code_hash = FastInsecureHasher::new_deno_versioned() + .write_hashable(code) + .finish(); + let data = self + .cache + .get_sync( + specifier, + if esm { + CodeCacheType::EsModule + } else { + CodeCacheType::Script + }, + code_hash, + ) + .map(Cow::from) + .inspect(|_| { + log::debug!( + "V8 code cache hit for Extension module: {specifier}, [{code_hash:?}]" + ); + }); + deno_core::SourceCodeCacheInfo { + hash: code_hash, + data, + } + } + + fn code_cache_ready( + &self, + specifier: ModuleSpecifier, + source_hash: u64, + code_cache: &[u8], + esm: bool, + ) { + use deno_runtime::code_cache::CodeCacheType; + + log::debug!( + "Updating V8 code cache for Extension module: {specifier}, [{source_hash:?}]" + ); + self.cache.set_sync( + specifier, + if esm { + CodeCacheType::EsModule + } else { + CodeCacheType::Script + }, + source_hash, + code_cache, + ); + } +} + +// TODO(bartlomieju): we have similar struct in `tsc.rs` - maybe at least change +// the name of the struct to avoid confusion? +#[derive(Debug)] +struct State { + hash_data: u64, + graph: Arc, + maybe_tsbuildinfo: Option, + maybe_response: Option, + maybe_npm: Option, + // todo(dsherret): it looks like the remapped_specifiers and + // root_map could be combined... what is the point of the separation? + remapped_specifiers: HashMap, + root_map: HashMap, + current_dir: PathBuf, +} + +impl Default for State { + fn default() -> Self { + Self { + hash_data: Default::default(), + graph: Arc::new(ModuleGraph::new(GraphKind::All)), + maybe_tsbuildinfo: Default::default(), + maybe_response: Default::default(), + maybe_npm: Default::default(), + remapped_specifiers: Default::default(), + root_map: Default::default(), + current_dir: Default::default(), + } + } +} + +impl State { + pub fn new( + graph: Arc, + hash_data: u64, + maybe_npm: Option, + maybe_tsbuildinfo: Option, + root_map: HashMap, + remapped_specifiers: HashMap, + current_dir: PathBuf, + ) -> Self { + State { + hash_data, + graph, + maybe_npm, + maybe_tsbuildinfo, + maybe_response: None, + remapped_specifiers, + root_map, + current_dir, + } + } + + pub fn maybe_remapped_specifier( + &self, + specifier: &str, + ) -> Option<&ModuleSpecifier> { + self + .remapped_specifiers + .get(specifier) + .or_else(|| self.root_map.get(specifier)) + } +} + +#[cfg(test)] +mod tests { + use deno_ast::MediaType; + use deno_core::OpState; + use deno_core::futures::future; + use deno_core::parking_lot::Mutex; + use deno_core::serde_json; + use deno_error::JsErrorBox; + use deno_graph::GraphKind; + use deno_graph::ModuleGraph; + use deno_runtime::code_cache::CodeCacheType; + use test_util::PathRef; + + use super::super::Diagnostic; + use super::super::DiagnosticCategory; + use super::*; + use crate::args::CompilerOptions; + use crate::tsc::MISSING_DEPENDENCY_SPECIFIER; + use crate::tsc::get_lazily_loaded_asset; + + #[derive(Debug, Default)] + pub struct MockLoader { + pub fixtures: PathRef, + } + + impl deno_graph::source::Loader for MockLoader { + fn load( + &self, + specifier: &ModuleSpecifier, + _options: deno_graph::source::LoadOptions, + ) -> deno_graph::source::LoadFuture { + let specifier_text = specifier + .to_string() + .replace(":///", "_") + .replace("://", "_") + .replace('/', "-"); + let source_path = self.fixtures.join(specifier_text); + let response = source_path + .read_to_bytes_if_exists() + .map(|c| { + Some(deno_graph::source::LoadResponse::Module { + specifier: specifier.clone(), + mtime: None, + maybe_headers: None, + content: c.into(), + }) + }) + .map_err(|e| { + deno_graph::source::LoadError::Other(Arc::new(JsErrorBox::generic( + e.to_string(), + ))) + }); + Box::pin(future::ready(response)) + } + } + + async fn setup( + maybe_specifier: Option, + maybe_hash_data: Option, + maybe_tsbuildinfo: Option, + ) -> OpState { + let specifier = maybe_specifier + .unwrap_or_else(|| ModuleSpecifier::parse("file:///main.ts").unwrap()); + let hash_data = maybe_hash_data.unwrap_or(0); + let fixtures = test_util::testdata_path().join("tsc2"); + let loader = MockLoader { fixtures }; + let mut graph = ModuleGraph::new(GraphKind::TypesOnly); + graph + .build(vec![specifier], Vec::new(), &loader, Default::default()) + .await; + let state = State::new( + Arc::new(graph), + hash_data, + None, + maybe_tsbuildinfo, + HashMap::new(), + HashMap::new(), + std::env::current_dir() + .context("Unable to get CWD") + .unwrap(), + ); + let mut op_state = OpState::new(None); + op_state.put(state); + op_state + } + + async fn test_exec( + specifier: &ModuleSpecifier, + ) -> Result { + test_exec_with_cache(specifier, None).await + } + async fn test_exec_with_cache( + specifier: &ModuleSpecifier, + code_cache: Option>, + ) -> Result { + let hash_data = 123; // something random + let fixtures = test_util::testdata_path().join("tsc2"); + let loader = MockLoader { fixtures }; + let mut graph = ModuleGraph::new(GraphKind::TypesOnly); + graph + .build( + vec![specifier.clone()], + Vec::new(), + &loader, + Default::default(), + ) + .await; + let config = Arc::new(CompilerOptions::new(json!({ + "allowJs": true, + "checkJs": false, + "esModuleInterop": true, + "emitDecoratorMetadata": false, + "incremental": true, + "jsx": "react", + "jsxFactory": "React.createElement", + "jsxFragmentFactory": "React.Fragment", + "lib": ["deno.window"], + "noEmit": true, + "outDir": "internal:///", + "strict": true, + "target": "esnext", + "tsBuildInfoFile": "internal:///.tsbuildinfo", + }))); + let request = Request { + config, + debug: false, + graph: Arc::new(graph), + hash_data, + maybe_npm: None, + maybe_tsbuildinfo: None, + root_names: vec![(specifier.clone(), MediaType::TypeScript)], + check_mode: TypeCheckMode::All, + initial_cwd: std::env::current_dir().unwrap(), + }; + crate::tsc::exec(request, code_cache, None) + } + + #[tokio::test] + async fn test_create_hash() { + let mut state = setup(None, Some(123), None).await; + let actual = op_create_hash_inner(&mut state, "some sort of content"); + assert_eq!(actual, "11905938177474799758"); + } + + #[tokio::test] + async fn test_hash_url() { + let specifier = deno_core::resolve_url( + "data:application/javascript,console.log(\"Hello%20Deno\");", + ) + .unwrap(); + assert_eq!( + crate::tsc::hash_url(&specifier, MediaType::JavaScript), + "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js" + ); + } + + #[tokio::test] + async fn test_emit_tsbuildinfo() { + let mut state = setup(None, None, None).await; + let actual = op_emit_inner( + &mut state, + EmitArgs { + data: "some file content".to_string(), + file_name: "internal:///.tsbuildinfo".to_string(), + }, + ); + assert!(actual); + let state = state.borrow::(); + assert_eq!( + state.maybe_tsbuildinfo, + Some("some file content".to_string()) + ); + } + + #[tokio::test] + async fn test_load() { + let mut state = setup( + Some(ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap()), + None, + Some("some content".to_string()), + ) + .await; + let actual = + op_load_inner(&mut state, "https://deno.land/x/mod.ts").unwrap(); + assert_eq!( + serde_json::to_value(actual).unwrap(), + json!({ + "data": "console.log(\"hello deno\");\n", + "version": "7821807483407828376", + "scriptKind": 3, + "isCjs": false, + }) + ); + } + + #[tokio::test] + async fn test_load_asset() { + let mut state = setup( + Some(ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap()), + None, + Some("some content".to_string()), + ) + .await; + let actual = op_load_inner(&mut state, "asset:///lib.dom.d.ts") + .expect("should have invoked op") + .expect("load should have succeeded"); + let expected = get_lazily_loaded_asset("lib.dom.d.ts").unwrap(); + assert_eq!(actual.data.to_string(), expected.to_string()); + assert!(actual.version.is_some()); + assert_eq!(actual.script_kind, 3); + } + + #[tokio::test] + async fn test_load_tsbuildinfo() { + let mut state = setup( + Some(ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap()), + None, + Some("some content".to_string()), + ) + .await; + let actual = op_load_inner(&mut state, "internal:///.tsbuildinfo") + .expect("should have invoked op") + .expect("load should have succeeded"); + assert_eq!( + serde_json::to_value(actual).unwrap(), + json!({ + "data": "some content", + "version": null, + "scriptKind": 0, + "isCjs": false, + }) + ); + } + + #[tokio::test] + async fn test_load_missing_specifier() { + let mut state = setup(None, None, None).await; + let actual = op_load_inner(&mut state, "https://deno.land/x/mod.ts") + .expect("should have invoked op"); + assert_eq!(serde_json::to_value(actual).unwrap(), json!(null)); + } + + #[tokio::test] + async fn test_resolve() { + let mut state = setup( + Some(ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap()), + None, + None, + ) + .await; + let actual = op_resolve_inner( + &mut state, + ResolveArgs { + base: "https://deno.land/x/a.ts".to_string(), + specifiers: vec![(false, "./b.ts".to_string())], + }, + ) + .expect("should have invoked op"); + assert_eq!( + actual, + vec![("https://deno.land/x/b.ts".into(), Some(".ts"))] + ); + } + + #[tokio::test] + async fn test_resolve_empty() { + let mut state = setup( + Some(ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap()), + None, + None, + ) + .await; + let actual = op_resolve_inner( + &mut state, + ResolveArgs { + base: "https://deno.land/x/a.ts".to_string(), + specifiers: vec![(false, "./bad.ts".to_string())], + }, + ) + .expect("should have not errored"); + assert_eq!( + actual, + vec![(MISSING_DEPENDENCY_SPECIFIER.into(), Some(".d.ts"))] + ); + } + + #[tokio::test] + async fn test_respond() { + let mut state = setup(None, None, None).await; + let args = serde_json::from_value(json!({ + "diagnostics": [ + { + "messageText": "Unknown compiler option 'invalid'.", + "category": 1, + "code": 5023 + } + ], + "stats": [["a", 12]], + "ambientModules": [] + })) + .unwrap(); + op_respond_inner(&mut state, args); + let state = state.borrow::(); + assert_eq!( + state.maybe_response, + Some(RespondArgs { + diagnostics: Diagnostics::new(vec![Diagnostic { + category: DiagnosticCategory::Error, + code: 5023, + start: None, + end: None, + original_source_start: None, + message_text: Some( + "Unknown compiler option \'invalid\'.".to_string() + ), + message_chain: None, + source: None, + source_line: None, + file_name: None, + related_information: None, + reports_deprecated: None, + reports_unnecessary: None, + other: Default::default(), + missing_specifier: None, + }]), + ambient_modules: vec![], + stats: Stats(vec![("a".to_string(), 12)]) + }) + ); + } + + #[tokio::test] + async fn test_exec_basic() { + let specifier = ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap(); + let actual = test_exec(&specifier) + .await + .expect("exec should not have errored"); + assert!(!actual.diagnostics.has_diagnostic()); + assert!(actual.maybe_tsbuildinfo.is_some()); + assert_eq!(actual.stats.0.len(), 12); + } + + #[tokio::test] + async fn test_exec_reexport_dts() { + let specifier = ModuleSpecifier::parse("file:///reexports.ts").unwrap(); + let actual = test_exec(&specifier) + .await + .expect("exec should not have errored"); + assert!(!actual.diagnostics.has_diagnostic()); + assert!(actual.maybe_tsbuildinfo.is_some()); + assert_eq!(actual.stats.0.len(), 12); + } + + #[tokio::test] + async fn fix_lib_ref() { + let specifier = ModuleSpecifier::parse("file:///libref.ts").unwrap(); + let actual = test_exec(&specifier) + .await + .expect("exec should not have errored"); + assert!(!actual.diagnostics.has_diagnostic()); + } + + pub type SpecifierWithType = (ModuleSpecifier, CodeCacheType); + + #[derive(Default)] + struct TestExtCodeCache { + cache: Mutex>>, + + hits: Mutex>, + misses: Mutex>, + } + + impl deno_runtime::code_cache::CodeCache for TestExtCodeCache { + fn get_sync( + &self, + specifier: &ModuleSpecifier, + code_cache_type: CodeCacheType, + source_hash: u64, + ) -> Option> { + let result = self + .cache + .lock() + .get(&((specifier.clone(), code_cache_type), source_hash)) + .cloned(); + if result.is_some() { + *self + .hits + .lock() + .entry((specifier.clone(), code_cache_type)) + .or_default() += 1; + } else { + *self + .misses + .lock() + .entry((specifier.clone(), code_cache_type)) + .or_default() += 1; + } + result + } + + fn set_sync( + &self, + specifier: ModuleSpecifier, + code_cache_type: CodeCacheType, + source_hash: u64, + data: &[u8], + ) { + self + .cache + .lock() + .insert(((specifier, code_cache_type), source_hash), data.to_vec()); + } + } + + #[tokio::test] + async fn test_exec_code_cache() { + let code_cache = Arc::new(TestExtCodeCache::default()); + let specifier = ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap(); + let actual = test_exec_with_cache(&specifier, Some(code_cache.clone())) + .await + .expect("exec should not have errored"); + assert!(!actual.diagnostics.has_diagnostic()); + + let expect = [ + ( + "ext:deno_cli_tsc/99_main_compiler.js", + CodeCacheType::EsModule, + ), + ("ext:deno_cli_tsc/98_lsp.js", CodeCacheType::EsModule), + ("ext:deno_cli_tsc/97_ts_host.js", CodeCacheType::EsModule), + ("ext:deno_cli_tsc/00_typescript.js", CodeCacheType::Script), + ]; + + { + let mut files = HashMap::new(); + + for (((specifier, ty), _), _) in code_cache.cache.lock().iter() { + let specifier = specifier.to_string(); + if files.contains_key(&specifier) { + panic!("should have only 1 entry per specifier"); + } + files.insert(specifier, *ty); + } + + // 99_main_compiler, 98_lsp, 97_ts_host, 00_typescript + assert_eq!(files.len(), 4); + assert_eq!(code_cache.hits.lock().len(), 0); + assert_eq!(code_cache.misses.lock().len(), 4); + + for (specifier, ty) in &expect { + assert_eq!(files.get(*specifier), Some(ty)); + } + + code_cache.hits.lock().clear(); + code_cache.misses.lock().clear(); + } + + { + let _ = test_exec_with_cache(&specifier, Some(code_cache.clone())) + .await + .expect("exec should not have errored"); + + // 99_main_compiler, 98_lsp, 97_ts_host, 00_typescript + assert_eq!(code_cache.hits.lock().len(), 4); + assert_eq!(code_cache.misses.lock().len(), 0); + + for (specifier, ty) in expect { + let url = ModuleSpecifier::parse(specifier).unwrap(); + assert_eq!(code_cache.hits.lock().get(&(url, ty)), Some(&1)); + } + } + } +} diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index b3b23dfa77..6c7555570a 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -1,37 +1,29 @@ // Copyright 2018-2025 the Deno authors. MIT license. +// +mod go; +mod js; -use std::borrow::Cow; use std::collections::HashMap; +use std::collections::HashSet; use std::fmt; use std::io::ErrorKind; +use std::path::Path; use std::path::PathBuf; -use std::rc::Rc; use std::sync::Arc; +use std::sync::LazyLock; use std::sync::OnceLock; use deno_ast::MediaType; -use deno_core::FastString; -use deno_core::JsRuntime; use deno_core::ModuleSpecifier; -use deno_core::OpState; -use deno_core::RuntimeOptions; -use deno_core::anyhow::Context; -use deno_core::located_script_name; -use deno_core::op2; use deno_core::serde::Deserialize; use deno_core::serde::Deserializer; use deno_core::serde::Serialize; use deno_core::serde::Serializer; -use deno_core::serde_json::json; use deno_core::url::Url; -use deno_graph::GraphKind; use deno_graph::Module; use deno_graph::ModuleGraph; -use deno_graph::ResolutionResolved; use deno_lib::util::checksum; use deno_lib::util::hash::FastInsecureHasher; -use deno_lib::worker::create_isolate_create_params; -use deno_path_util::resolve_url_or_path; use deno_resolver::npm::ResolvePkgFolderFromDenoReqError; use deno_resolver::npm::managed::ResolvePkgFolderFromDenoModuleError; use deno_semver::npm::NpmPackageReqReference; @@ -49,6 +41,7 @@ use crate::args::CompilerOptions; use crate::args::TypeCheckMode; use crate::cache::ModuleInfoCache; use crate::node::CliNodeResolver; +use crate::node::CliPackageJsonResolver; use crate::npm::CliNpmResolver; use crate::resolver::CliCjsTracker; use crate::sys::CliSys; @@ -60,6 +53,8 @@ pub use self::diagnostics::Diagnostic; pub use self::diagnostics::DiagnosticCategory; pub use self::diagnostics::Diagnostics; pub use self::diagnostics::Position; +pub use self::go::ensure_tsgo; +pub use self::js::TscConstants; pub fn get_types_declaration_file_text() -> String { let lib_names = vec![ @@ -468,6 +463,7 @@ pub struct RequestNpmState { pub cjs_tracker: Arc, pub node_resolver: Arc, pub npm_resolver: CliNpmResolver, + pub package_json_resolver: Arc, } /// A structure representing a request to be sent to the tsc runtime. @@ -486,6 +482,8 @@ pub struct Request { /// program. pub root_names: Vec<(ModuleSpecifier, MediaType)>, pub check_mode: TypeCheckMode, + + pub initial_cwd: PathBuf, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -499,116 +497,6 @@ pub struct Response { pub stats: Stats, } -// TODO(bartlomieju): we have similar struct in `tsc.rs` - maybe at least change -// the name of the struct to avoid confusion? -#[derive(Debug)] -struct State { - hash_data: u64, - graph: Arc, - maybe_tsbuildinfo: Option, - maybe_response: Option, - maybe_npm: Option, - // todo(dsherret): it looks like the remapped_specifiers and - // root_map could be combined... what is the point of the separation? - remapped_specifiers: HashMap, - root_map: HashMap, - current_dir: PathBuf, -} - -impl Default for State { - fn default() -> Self { - Self { - hash_data: Default::default(), - graph: Arc::new(ModuleGraph::new(GraphKind::All)), - maybe_tsbuildinfo: Default::default(), - maybe_response: Default::default(), - maybe_npm: Default::default(), - remapped_specifiers: Default::default(), - root_map: Default::default(), - current_dir: Default::default(), - } - } -} - -impl State { - pub fn new( - graph: Arc, - hash_data: u64, - maybe_npm: Option, - maybe_tsbuildinfo: Option, - root_map: HashMap, - remapped_specifiers: HashMap, - current_dir: PathBuf, - ) -> Self { - State { - hash_data, - graph, - maybe_npm, - maybe_tsbuildinfo, - maybe_response: None, - remapped_specifiers, - root_map, - current_dir, - } - } - - pub fn maybe_remapped_specifier( - &self, - specifier: &str, - ) -> Option<&ModuleSpecifier> { - self - .remapped_specifiers - .get(specifier) - .or_else(|| self.root_map.get(specifier)) - } -} - -#[op2] -#[string] -fn op_create_hash(s: &mut OpState, #[string] text: &str) -> String { - op_create_hash_inner(s, text) -} - -#[inline] -fn op_create_hash_inner(s: &mut OpState, text: &str) -> String { - let state = s.borrow_mut::(); - get_hash(text, state.hash_data) -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct EmitArgs { - /// The text data/contents of the file. - data: String, - /// The _internal_ filename for the file. This will be used to determine how - /// the file is cached and stored. - file_name: String, -} - -#[op2(fast)] -fn op_emit( - state: &mut OpState, - #[string] data: String, - #[string] file_name: String, -) -> bool { - op_emit_inner(state, EmitArgs { data, file_name }) -} - -#[inline] -fn op_emit_inner(state: &mut OpState, args: EmitArgs) -> bool { - let state = state.borrow_mut::(); - match args.file_name.as_ref() { - "internal:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(args.data), - _ => { - if cfg!(debug_assertions) { - panic!("Unhandled emit write: {}", args.file_name); - } - } - } - - true -} - pub fn as_ts_script_kind(media_type: MediaType) -> i32 { match media_type { MediaType::JavaScript => 1, @@ -650,170 +538,6 @@ pub enum LoadError { #[error("{0}")] ClosestPkgJson(#[from] node_resolver::errors::PackageJsonLoadError), } - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct LoadResponse { - data: FastString, - version: Option, - script_kind: i32, - is_cjs: bool, -} - -#[op2] -#[serde] -fn op_load( - state: &mut OpState, - #[string] load_specifier: &str, -) -> Result, LoadError> { - op_load_inner(state, load_specifier) -} - -fn op_load_inner( - state: &mut OpState, - load_specifier: &str, -) -> Result, LoadError> { - fn load_from_node_modules( - specifier: &ModuleSpecifier, - npm_state: Option<&RequestNpmState>, - media_type: &mut MediaType, - is_cjs: &mut bool, - ) -> Result, LoadError> { - *media_type = MediaType::from_specifier(specifier); - let file_path = specifier.to_file_path().unwrap(); - let code = match std::fs::read_to_string(&file_path) { - Ok(code) => code, - Err(err) if err.kind() == ErrorKind::NotFound => { - return Ok(None); - } - Err(err) => { - return Err(LoadError::LoadFromNodeModule { - path: file_path.display().to_string(), - error: err, - }); - } - }; - let code: Arc = code.into(); - *is_cjs = npm_state - .map(|npm_state| { - npm_state.cjs_tracker.is_cjs(specifier, *media_type, &code) - }) - .unwrap_or(false); - Ok(Some(code.into())) - } - - let state = state.borrow_mut::(); - - let specifier = resolve_url_or_path(load_specifier, &state.current_dir)?; - - let mut hash: Option = None; - let mut media_type = MediaType::Unknown; - let graph = &state.graph; - let mut is_cjs = false; - - let data = if load_specifier == "internal:///.tsbuildinfo" { - state - .maybe_tsbuildinfo - .as_deref() - .map(|s| s.to_string().into()) - // in certain situations we return a "blank" module to tsc and we need to - // handle the request for that module here. - } else if load_specifier == MISSING_DEPENDENCY_SPECIFIER { - None - } else if let Some(name) = load_specifier.strip_prefix("asset:///") { - let maybe_source = get_lazily_loaded_asset(name); - hash = get_maybe_hash(maybe_source, state.hash_data); - media_type = MediaType::from_str(load_specifier); - maybe_source.map(FastString::from_static) - } else if let Some(source) = load_raw_import_source(&specifier) { - return Ok(Some(LoadResponse { - data: FastString::from_static(source), - version: Some("1".to_string()), - script_kind: as_ts_script_kind(MediaType::TypeScript), - is_cjs: false, - })); - } else { - let specifier = if let Some(remapped_specifier) = - state.maybe_remapped_specifier(load_specifier) - { - remapped_specifier - } else { - &specifier - }; - let maybe_module = graph.try_get(specifier).ok().flatten(); - let maybe_source = if let Some(module) = maybe_module { - match module { - Module::Js(module) => { - media_type = module.media_type; - if let Some(npm_state) = &state.maybe_npm { - is_cjs = npm_state.cjs_tracker.is_cjs_with_known_is_script( - specifier, - module.media_type, - module.is_script, - )?; - } - Some( - module - .fast_check_module() - .map(|m| FastString::from(m.source.clone())) - .unwrap_or(module.source.text.clone().into()), - ) - } - Module::Json(module) => { - media_type = MediaType::Json; - Some(FastString::from(module.source.text.clone())) - } - Module::Wasm(module) => { - media_type = MediaType::Dts; - Some(FastString::from(module.source_dts.clone())) - } - Module::Npm(_) | Module::Node(_) => None, - Module::External(module) => { - if module.specifier.scheme() != "file" { - None - } else { - // means it's Deno code importing an npm module - let specifier = resolve_specifier_into_node_modules( - &CliSys::default(), - &module.specifier, - ); - load_from_node_modules( - &specifier, - state.maybe_npm.as_ref(), - &mut media_type, - &mut is_cjs, - )? - } - } - } - } else if let Some(npm) = state - .maybe_npm - .as_ref() - .filter(|npm| npm.node_resolver.in_npm_package(specifier)) - { - load_from_node_modules( - specifier, - Some(npm), - &mut media_type, - &mut is_cjs, - )? - } else { - None - }; - hash = get_maybe_hash(maybe_source.as_deref(), state.hash_data); - maybe_source - }; - let Some(data) = data else { - return Ok(None); - }; - Ok(Some(LoadResponse { - data, - version: hash, - script_kind: as_ts_script_kind(media_type), - is_cjs, - })) -} - pub fn load_raw_import_source(specifier: &Url) -> Option<&'static str> { let raw_import = get_specifier_raw_import(specifier)?; let source = match raw_import { @@ -884,177 +608,13 @@ pub struct ResolveArgs { pub specifiers: Vec<(bool, String)>, } -#[op2] -#[string] -fn op_remap_specifier( - state: &mut OpState, - #[string] specifier: &str, -) -> Option { - let state = state.borrow::(); - state - .maybe_remapped_specifier(specifier) - .map(|url| url.to_string()) -} - -#[op2] -#[serde] -fn op_libs() -> Vec { - let mut out = Vec::with_capacity(LAZILY_LOADED_STATIC_ASSETS.len()); - for (key, value) in LAZILY_LOADED_STATIC_ASSETS.iter() { - if !value.is_lib { - continue; - } - let lib = key - .replace("lib.", "") - .replace(".d.ts", "") - .replace("deno_", "deno."); - out.push(lib); - } - out -} - -#[op2] -#[serde] -fn op_resolve( - state: &mut OpState, - #[string] base: String, - #[serde] specifiers: Vec<(bool, String)>, -) -> Result)>, ResolveError> { - op_resolve_inner(state, ResolveArgs { base, specifiers }) -} - -#[inline] -fn op_resolve_inner( - state: &mut OpState, - args: ResolveArgs, -) -> Result)>, ResolveError> { - let state = state.borrow_mut::(); - let mut resolved: Vec<(String, Option<&'static str>)> = - Vec::with_capacity(args.specifiers.len()); - let referrer = if let Some(remapped_specifier) = - state.maybe_remapped_specifier(&args.base) - { - remapped_specifier.clone() - } else { - resolve_url_or_path(&args.base, &state.current_dir)? - }; - let referrer_module = state.graph.get(&referrer); - for (is_cjs, specifier) in args.specifiers { - if specifier.starts_with("node:") { - resolved.push(( - MISSING_DEPENDENCY_SPECIFIER.to_string(), - Some(MediaType::Dts.as_ts_extension()), - )); - continue; - } - - if specifier.starts_with("asset:///") { - let ext = MediaType::from_str(&specifier).as_ts_extension(); - resolved.push((specifier, Some(ext))); - continue; - } - - let resolved_dep = referrer_module - .and_then(|m| match m { - Module::Js(m) => m.dependencies_prefer_fast_check().get(&specifier), - Module::Json(_) => None, - Module::Wasm(m) => m.dependencies.get(&specifier), - Module::Npm(_) | Module::Node(_) | Module::External(_) => None, - }) - .and_then(|d| d.maybe_type.ok().or_else(|| d.maybe_code.ok())); - let resolution_mode = if is_cjs { - ResolutionMode::Require - } else { - ResolutionMode::Import - }; - - let maybe_result = match resolved_dep { - Some(ResolutionResolved { specifier, .. }) => { - resolve_graph_specifier_types( - specifier, - &referrer, - // we could get this from the resolved dep, but for now assume - // the value resolved in TypeScript is better - resolution_mode, - state, - )? - } - _ => { - match resolve_non_graph_specifier_types( - &specifier, - &referrer, - resolution_mode, - state, - ) { - Ok(maybe_result) => maybe_result, - Err( - err @ ResolveNonGraphSpecifierTypesError::ResolvePkgFolderFromDenoReq( - ResolvePkgFolderFromDenoReqError::Managed(_), - ), - ) => { - // it's most likely requesting the jsxImportSource, which isn't loaded - // into the graph when not using jsx, so just ignore this error - if specifier.ends_with("/jsx-runtime") { - None - } else { - return Err(err.into()); - } - } - Err(err) => return Err(err.into()), - } - } - }; - let result = match maybe_result { - Some((specifier, media_type)) => { - let specifier_str = match specifier.scheme() { - "data" | "blob" => { - let specifier_str = hash_url(&specifier, media_type); - state - .remapped_specifiers - .insert(specifier_str.clone(), specifier); - specifier_str - } - _ => { - if let Some(specifier_str) = - mapped_specifier_for_tsc(&specifier, media_type) - { - state - .remapped_specifiers - .insert(specifier_str.clone(), specifier); - specifier_str - } else { - specifier.to_string() - } - } - }; - ( - specifier_str, - match media_type { - MediaType::Css => Some(".js"), // surface these as .js for typescript - MediaType::Unknown => None, - media_type => Some(media_type.as_ts_extension()), - }, - ) - } - None => ( - MISSING_DEPENDENCY_SPECIFIER.to_string(), - Some(MediaType::Dts.as_ts_extension()), - ), - }; - log::debug!("Resolved {} from {} to {:?}", specifier, referrer, result); - resolved.push(result); - } - - Ok(resolved) -} - fn resolve_graph_specifier_types( specifier: &ModuleSpecifier, referrer: &ModuleSpecifier, resolution_mode: ResolutionMode, - state: &State, + graph: &ModuleGraph, + maybe_npm: Option<&RequestNpmState>, ) -> Result, ResolveError> { - let graph = &state.graph; let maybe_module = match graph.try_get(specifier) { Ok(Some(module)) => Some(module), Ok(None) => None, @@ -1096,7 +656,7 @@ fn resolve_graph_specifier_types( Ok(Some((module.specifier.clone(), MediaType::Dmts))) } Some(Module::Npm(module)) => { - if let Some(npm) = &state.maybe_npm.as_ref() { + if let Some(npm) = maybe_npm { let package_folder = npm .npm_resolver .as_managed() @@ -1159,7 +719,7 @@ fn resolve_graph_specifier_types( } Some(Module::External(module)) => { // we currently only use "External" for when the module is in an npm package - Ok(state.maybe_npm.as_ref().map(|_| { + Ok(maybe_npm.map(|_| { let specifier = resolve_specifier_into_node_modules( &CliSys::default(), &module.specifier, @@ -1188,12 +748,12 @@ fn resolve_non_graph_specifier_types( raw_specifier: &str, referrer: &ModuleSpecifier, resolution_mode: ResolutionMode, - state: &State, + maybe_npm: Option<&RequestNpmState>, ) -> Result< Option<(ModuleSpecifier, MediaType)>, ResolveNonGraphSpecifierTypesError, > { - let npm = match state.maybe_npm.as_ref() { + let npm = match maybe_npm { Some(npm) => npm, None => return Ok(None), // we only support non-graph types for npm packages }; @@ -1247,41 +807,6 @@ fn resolve_non_graph_specifier_types( } } -#[op2(fast)] -fn op_is_node_file(state: &mut OpState, #[string] path: &str) -> bool { - let state = state.borrow::(); - ModuleSpecifier::parse(path) - .ok() - .and_then(|specifier| { - state - .maybe_npm - .as_ref() - .map(|n| n.node_resolver.in_npm_package(&specifier)) - }) - .unwrap_or(false) -} - -#[derive(Debug, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -struct RespondArgs { - pub diagnostics: Diagnostics, - pub ambient_modules: Vec, - pub stats: Stats, -} - -// TODO(bartlomieju): this mechanism is questionable. -// Can't we use something more efficient here? -#[op2] -fn op_respond(state: &mut OpState, #[serde] args: RespondArgs) { - op_respond_inner(state, args) -} - -#[inline] -fn op_respond_inner(state: &mut OpState, args: RespondArgs) { - let state = state.borrow_mut::(); - state.maybe_response = Some(args); -} - #[derive(Debug, Error, deno_error::JsError)] pub enum ExecError { #[class(generic)] @@ -1290,6 +815,10 @@ pub enum ExecError { #[class(inherit)] #[error(transparent)] Js(Box), + + #[class(inherit)] + #[error(transparent)] + Go(#[from] go::ExecError), } #[derive(Clone)] @@ -1331,114 +860,6 @@ pub(crate) fn decompress_source(contents: &[u8]) -> Arc { String::from_utf8(uncompressed).unwrap().into() } -deno_core::extension!(deno_cli_tsc, - ops = [ - op_create_hash, - op_emit, - op_is_node_file, - op_load, - op_remap_specifier, - op_resolve, - op_respond, - op_libs, - ], - options = { - request: Request, - root_map: HashMap, - remapped_specifiers: HashMap, - }, - state = |state, options| { - state.put(State::new( - options.request.graph, - options.request.hash_data, - options.request.maybe_npm, - options.request.maybe_tsbuildinfo, - options.root_map, - options.remapped_specifiers, - std::env::current_dir() - .context("Unable to get CWD") - .unwrap(), - )); - }, - customizer = |ext: &mut deno_core::Extension| { - use deno_core::ExtensionFileSource; - ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/99_main_compiler.js", crate::tsc::MAIN_COMPILER_SOURCE.as_str().into())); - ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/97_ts_host.js", crate::tsc::TS_HOST_SOURCE.as_str().into())); - ext.esm_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/98_lsp.js", crate::tsc::LSP_SOURCE.as_str().into())); - ext.js_files.to_mut().push(ExtensionFileSource::new_computed("ext:deno_cli_tsc/00_typescript.js", crate::tsc::TYPESCRIPT_SOURCE.as_str().into())); - ext.esm_entry_point = Some("ext:deno_cli_tsc/99_main_compiler.js"); - } -); - -pub struct TscExtCodeCache { - cache: Arc, -} - -impl TscExtCodeCache { - pub fn new(cache: Arc) -> Self { - Self { cache } - } -} - -impl deno_core::ExtCodeCache for TscExtCodeCache { - fn get_code_cache_info( - &self, - specifier: &ModuleSpecifier, - code: &deno_core::ModuleSourceCode, - esm: bool, - ) -> deno_core::SourceCodeCacheInfo { - use deno_runtime::code_cache::CodeCacheType; - let code_hash = FastInsecureHasher::new_deno_versioned() - .write_hashable(code) - .finish(); - let data = self - .cache - .get_sync( - specifier, - if esm { - CodeCacheType::EsModule - } else { - CodeCacheType::Script - }, - code_hash, - ) - .map(Cow::from) - .inspect(|_| { - log::debug!( - "V8 code cache hit for Extension module: {specifier}, [{code_hash:?}]" - ); - }); - deno_core::SourceCodeCacheInfo { - hash: code_hash, - data, - } - } - - fn code_cache_ready( - &self, - specifier: ModuleSpecifier, - source_hash: u64, - code_cache: &[u8], - esm: bool, - ) { - use deno_runtime::code_cache::CodeCacheType; - - log::debug!( - "Updating V8 code cache for Extension module: {specifier}, [{source_hash:?}]" - ); - self.cache.set_sync( - specifier, - if esm { - CodeCacheType::EsModule - } else { - CodeCacheType::Script - }, - source_hash, - code_cache, - ); - } -} - /// Execute a request on the supplied snapshot, returning a response which /// contains information, like any emitted files, diagnostics, statistics and /// optionally an updated TypeScript build info. @@ -1446,6 +867,7 @@ impl deno_core::ExtCodeCache for TscExtCodeCache { pub fn exec( request: Request, code_cache: Option>, + maybe_tsgo_path: Option<&Path>, ) -> Result { // tsc cannot handle root specifiers that don't have one of the "acceptable" // extensions. Therefore, we have to check the root modules against their @@ -1453,6 +875,7 @@ pub fn exec( // op state so when requested, we can remap to the original specifier. let mut root_map = HashMap::new(); let mut remapped_specifiers = HashMap::new(); + log::debug!("exec request, root_names: {:?}", request.root_names); let root_names: Vec = request .root_names .iter() @@ -1462,6 +885,12 @@ pub fn exec( remapped_specifiers.insert(specifier_str.clone(), s.clone()); specifier_str } + // "file" if tsgo => { + // let specifier_str = s.to_string(); + // let out = specifier_str.strip_prefix("file://").unwrap().to_string(); + // remapped_specifiers.insert(out.to_string(), s.clone()); + // out + // } _ => { if let Some(new_specifier) = mapped_specifier_for_tsc(s, *mt) { root_map.insert(new_specifier.clone(), s.clone()); @@ -1473,519 +902,428 @@ pub fn exec( }) .collect(); - let request_value = json!({ - "config": request.config, - "debug": request.debug, - "rootNames": root_names, - "localOnly": request.check_mode == TypeCheckMode::Local, - }); - let exec_source = format!("globalThis.exec({request_value})"); - - let mut extensions = - deno_runtime::snapshot_info::get_extensions_in_snapshot(); - extensions.push(deno_cli_tsc::init(request, root_map, remapped_specifiers)); - let extension_code_cache = code_cache.map(|cache| { - Rc::new(TscExtCodeCache::new(cache)) as Rc - }); - let mut runtime = JsRuntime::new(RuntimeOptions { - extensions, - create_params: create_isolate_create_params(&crate::sys::CliSys::default()), - startup_snapshot: deno_snapshots::CLI_SNAPSHOT, - extension_code_cache, - ..Default::default() - }); - - runtime - .execute_script(located_script_name!(), exec_source) - .map_err(ExecError::Js)?; - - let op_state = runtime.op_state(); - let mut op_state = op_state.borrow_mut(); - let state = op_state.take::(); - - if let Some(response) = state.maybe_response { - let diagnostics = response.diagnostics; - let ambient_modules = response.ambient_modules; - let maybe_tsbuildinfo = state.maybe_tsbuildinfo; - let stats = response.stats; - - Ok(Response { - diagnostics, - ambient_modules, - maybe_tsbuildinfo, - stats, - }) + if let Some(tsgo_path) = maybe_tsgo_path { + go::exec_request( + request, + root_names, + root_map, + remapped_specifiers, + tsgo_path, + ) } else { - Err(ExecError::ResponseNotSet) + js::exec_request( + request, + root_names, + root_map, + remapped_specifiers, + code_cache, + ) } } -#[cfg(test)] -mod tests { - use deno_core::OpState; - use deno_core::futures::future; - use deno_core::parking_lot::Mutex; - use deno_core::serde_json; - use deno_error::JsErrorBox; - use deno_graph::GraphKind; - use deno_graph::ModuleGraph; - use deno_runtime::code_cache::CodeCacheType; - use test_util::PathRef; - - use super::Diagnostic; - use super::DiagnosticCategory; - use super::*; - use crate::args::CompilerOptions; - - #[derive(Debug, Default)] - pub struct MockLoader { - pub fixtures: PathRef, +pub fn resolve_specifier_for_tsc( + specifier: String, + referrer: &ModuleSpecifier, + graph: &ModuleGraph, + resolution_mode: ResolutionMode, + maybe_npm: Option<&RequestNpmState>, + referrer_module: Option<&Module>, + remapped_specifiers: &mut HashMap, +) -> Result<(String, Option<&'static str>), ResolveError> { + if specifier.starts_with("node:") { + return Ok(( + MISSING_DEPENDENCY_SPECIFIER.to_string(), + Some(MediaType::Dts.as_ts_extension()), + )); } - impl deno_graph::source::Loader for MockLoader { - fn load( - &self, - specifier: &ModuleSpecifier, - _options: deno_graph::source::LoadOptions, - ) -> deno_graph::source::LoadFuture { - let specifier_text = specifier - .to_string() - .replace(":///", "_") - .replace("://", "_") - .replace('/', "-"); - let source_path = self.fixtures.join(specifier_text); - let response = source_path - .read_to_bytes_if_exists() - .map(|c| { - Some(deno_graph::source::LoadResponse::Module { - specifier: specifier.clone(), - mtime: None, - maybe_headers: None, - content: c.into(), - }) - }) - .map_err(|e| { - deno_graph::source::LoadError::Other(Arc::new(JsErrorBox::generic( - e.to_string(), - ))) - }); - Box::pin(future::ready(response)) + if specifier.starts_with("asset:///") { + let ext = MediaType::from_str(&specifier).as_ts_extension(); + return Ok((specifier, Some(ext))); + } + + let resolved_dep = referrer_module + .and_then(|m| match m { + Module::Js(m) => m.dependencies_prefer_fast_check().get(&specifier), + Module::Json(_) => None, + Module::Wasm(m) => m.dependencies.get(&specifier), + Module::Npm(_) | Module::Node(_) | Module::External(_) => None, + }) + .and_then(|d| d.maybe_type.ok().or_else(|| d.maybe_code.ok())); + + let maybe_result = match resolved_dep { + Some(deno_graph::ResolutionResolved { specifier, .. }) => { + resolve_graph_specifier_types( + specifier, + referrer, + // we could get this from the resolved dep, but for now assume + // the value resolved in TypeScript is better + resolution_mode, + graph, + maybe_npm, + )? } - } - - async fn setup( - maybe_specifier: Option, - maybe_hash_data: Option, - maybe_tsbuildinfo: Option, - ) -> OpState { - let specifier = maybe_specifier - .unwrap_or_else(|| ModuleSpecifier::parse("file:///main.ts").unwrap()); - let hash_data = maybe_hash_data.unwrap_or(0); - let fixtures = test_util::testdata_path().join("tsc2"); - let loader = MockLoader { fixtures }; - let mut graph = ModuleGraph::new(GraphKind::TypesOnly); - graph - .build(vec![specifier], Vec::new(), &loader, Default::default()) - .await; - let state = State::new( - Arc::new(graph), - hash_data, - None, - maybe_tsbuildinfo, - HashMap::new(), - HashMap::new(), - std::env::current_dir() - .context("Unable to get CWD") - .unwrap(), - ); - let mut op_state = OpState::new(None); - op_state.put(state); - op_state - } - - async fn test_exec( - specifier: &ModuleSpecifier, - ) -> Result { - test_exec_with_cache(specifier, None).await - } - async fn test_exec_with_cache( - specifier: &ModuleSpecifier, - code_cache: Option>, - ) -> Result { - let hash_data = 123; // something random - let fixtures = test_util::testdata_path().join("tsc2"); - let loader = MockLoader { fixtures }; - let mut graph = ModuleGraph::new(GraphKind::TypesOnly); - graph - .build( - vec![specifier.clone()], - Vec::new(), - &loader, - Default::default(), - ) - .await; - let config = Arc::new(CompilerOptions::new(json!({ - "allowJs": true, - "checkJs": false, - "esModuleInterop": true, - "emitDecoratorMetadata": false, - "incremental": true, - "jsx": "react", - "jsxFactory": "React.createElement", - "jsxFragmentFactory": "React.Fragment", - "lib": ["deno.window"], - "noEmit": true, - "outDir": "internal:///", - "strict": true, - "target": "esnext", - "tsBuildInfoFile": "internal:///.tsbuildinfo", - }))); - let request = Request { - config, - debug: false, - graph: Arc::new(graph), - hash_data, - maybe_npm: None, - maybe_tsbuildinfo: None, - root_names: vec![(specifier.clone(), MediaType::TypeScript)], - check_mode: TypeCheckMode::All, - }; - exec(request, code_cache) - } - - #[tokio::test] - async fn test_create_hash() { - let mut state = setup(None, Some(123), None).await; - let actual = op_create_hash_inner(&mut state, "some sort of content"); - assert_eq!(actual, "11905938177474799758"); - } - - #[tokio::test] - async fn test_hash_url() { - let specifier = deno_core::resolve_url( - "data:application/javascript,console.log(\"Hello%20Deno\");", - ) - .unwrap(); - assert_eq!( - hash_url(&specifier, MediaType::JavaScript), - "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js" - ); - } - - #[tokio::test] - async fn test_emit_tsbuildinfo() { - let mut state = setup(None, None, None).await; - let actual = op_emit_inner( - &mut state, - EmitArgs { - data: "some file content".to_string(), - file_name: "internal:///.tsbuildinfo".to_string(), - }, - ); - assert!(actual); - let state = state.borrow::(); - assert_eq!( - state.maybe_tsbuildinfo, - Some("some file content".to_string()) - ); - } - - #[tokio::test] - async fn test_load() { - let mut state = setup( - Some(ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap()), - None, - Some("some content".to_string()), - ) - .await; - let actual = - op_load_inner(&mut state, "https://deno.land/x/mod.ts").unwrap(); - assert_eq!( - serde_json::to_value(actual).unwrap(), - json!({ - "data": "console.log(\"hello deno\");\n", - "version": "7821807483407828376", - "scriptKind": 3, - "isCjs": false, - }) - ); - } - - #[tokio::test] - async fn test_load_asset() { - let mut state = setup( - Some(ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap()), - None, - Some("some content".to_string()), - ) - .await; - let actual = op_load_inner(&mut state, "asset:///lib.dom.d.ts") - .expect("should have invoked op") - .expect("load should have succeeded"); - let expected = get_lazily_loaded_asset("lib.dom.d.ts").unwrap(); - assert_eq!(actual.data.to_string(), expected.to_string()); - assert!(actual.version.is_some()); - assert_eq!(actual.script_kind, 3); - } - - #[tokio::test] - async fn test_load_tsbuildinfo() { - let mut state = setup( - Some(ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap()), - None, - Some("some content".to_string()), - ) - .await; - let actual = op_load_inner(&mut state, "internal:///.tsbuildinfo") - .expect("should have invoked op") - .expect("load should have succeeded"); - assert_eq!( - serde_json::to_value(actual).unwrap(), - json!({ - "data": "some content", - "version": null, - "scriptKind": 0, - "isCjs": false, - }) - ); - } - - #[tokio::test] - async fn test_load_missing_specifier() { - let mut state = setup(None, None, None).await; - let actual = op_load_inner(&mut state, "https://deno.land/x/mod.ts") - .expect("should have invoked op"); - assert_eq!(serde_json::to_value(actual).unwrap(), json!(null)); - } - - #[tokio::test] - async fn test_resolve() { - let mut state = setup( - Some(ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap()), - None, - None, - ) - .await; - let actual = op_resolve_inner( - &mut state, - ResolveArgs { - base: "https://deno.land/x/a.ts".to_string(), - specifiers: vec![(false, "./b.ts".to_string())], - }, - ) - .expect("should have invoked op"); - assert_eq!( - actual, - vec![("https://deno.land/x/b.ts".into(), Some(".ts"))] - ); - } - - #[tokio::test] - async fn test_resolve_empty() { - let mut state = setup( - Some(ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap()), - None, - None, - ) - .await; - let actual = op_resolve_inner( - &mut state, - ResolveArgs { - base: "https://deno.land/x/a.ts".to_string(), - specifiers: vec![(false, "./bad.ts".to_string())], - }, - ) - .expect("should have not errored"); - assert_eq!( - actual, - vec![(MISSING_DEPENDENCY_SPECIFIER.into(), Some(".d.ts"))] - ); - } - - #[tokio::test] - async fn test_respond() { - let mut state = setup(None, None, None).await; - let args = serde_json::from_value(json!({ - "diagnostics": [ - { - "messageText": "Unknown compiler option 'invalid'.", - "category": 1, - "code": 5023 - } - ], - "stats": [["a", 12]], - "ambientModules": [] - })) - .unwrap(); - op_respond_inner(&mut state, args); - let state = state.borrow::(); - assert_eq!( - state.maybe_response, - Some(RespondArgs { - diagnostics: Diagnostics::new(vec![Diagnostic { - category: DiagnosticCategory::Error, - code: 5023, - start: None, - end: None, - original_source_start: None, - message_text: Some( - "Unknown compiler option \'invalid\'.".to_string() + _ => { + match resolve_non_graph_specifier_types( + &specifier, + referrer, + resolution_mode, + maybe_npm, + ) { + Ok(maybe_result) => maybe_result, + Err( + err + @ ResolveNonGraphSpecifierTypesError::ResolvePkgFolderFromDenoReq( + ResolvePkgFolderFromDenoReqError::Managed(_), ), - message_chain: None, - source: None, - source_line: None, - file_name: None, - related_information: None, - reports_deprecated: None, - reports_unnecessary: None, - other: Default::default(), - missing_specifier: None, - }]), - ambient_modules: vec![], - stats: Stats(vec![("a".to_string(), 12)]) - }) - ); - } - - #[tokio::test] - async fn test_exec_basic() { - let specifier = ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap(); - let actual = test_exec(&specifier) - .await - .expect("exec should not have errored"); - assert!(!actual.diagnostics.has_diagnostic()); - assert!(actual.maybe_tsbuildinfo.is_some()); - assert_eq!(actual.stats.0.len(), 12); - } - - #[tokio::test] - async fn test_exec_reexport_dts() { - let specifier = ModuleSpecifier::parse("file:///reexports.ts").unwrap(); - let actual = test_exec(&specifier) - .await - .expect("exec should not have errored"); - assert!(!actual.diagnostics.has_diagnostic()); - assert!(actual.maybe_tsbuildinfo.is_some()); - assert_eq!(actual.stats.0.len(), 12); - } - - #[tokio::test] - async fn fix_lib_ref() { - let specifier = ModuleSpecifier::parse("file:///libref.ts").unwrap(); - let actual = test_exec(&specifier) - .await - .expect("exec should not have errored"); - assert!(!actual.diagnostics.has_diagnostic()); - } - - pub type SpecifierWithType = (ModuleSpecifier, CodeCacheType); - - #[derive(Default)] - struct TestExtCodeCache { - cache: Mutex>>, - - hits: Mutex>, - misses: Mutex>, - } - - impl deno_runtime::code_cache::CodeCache for TestExtCodeCache { - fn get_sync( - &self, - specifier: &ModuleSpecifier, - code_cache_type: CodeCacheType, - source_hash: u64, - ) -> Option> { - let result = self - .cache - .lock() - .get(&((specifier.clone(), code_cache_type), source_hash)) - .cloned(); - if result.is_some() { - *self - .hits - .lock() - .entry((specifier.clone(), code_cache_type)) - .or_default() += 1; - } else { - *self - .misses - .lock() - .entry((specifier.clone(), code_cache_type)) - .or_default() += 1; - } - result - } - - fn set_sync( - &self, - specifier: ModuleSpecifier, - code_cache_type: CodeCacheType, - source_hash: u64, - data: &[u8], - ) { - self - .cache - .lock() - .insert(((specifier, code_cache_type), source_hash), data.to_vec()); - } - } - - #[tokio::test] - async fn test_exec_code_cache() { - let code_cache = Arc::new(TestExtCodeCache::default()); - let specifier = ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap(); - let actual = test_exec_with_cache(&specifier, Some(code_cache.clone())) - .await - .expect("exec should not have errored"); - assert!(!actual.diagnostics.has_diagnostic()); - - let expect = [ - ( - "ext:deno_cli_tsc/99_main_compiler.js", - CodeCacheType::EsModule, - ), - ("ext:deno_cli_tsc/98_lsp.js", CodeCacheType::EsModule), - ("ext:deno_cli_tsc/97_ts_host.js", CodeCacheType::EsModule), - ("ext:deno_cli_tsc/00_typescript.js", CodeCacheType::Script), - ]; - - { - let mut files = HashMap::new(); - - for (((specifier, ty), _), _) in code_cache.cache.lock().iter() { - let specifier = specifier.to_string(); - if files.contains_key(&specifier) { - panic!("should have only 1 entry per specifier"); + ) => { + // it's most likely requesting the jsxImportSource, which isn't loaded + // into the graph when not using jsx, so just ignore this error + if specifier.ends_with("/jsx-runtime") { + None + } else { + return Err(err.into()); + } } - files.insert(specifier, *ty); - } - - // 99_main_compiler, 98_lsp, 97_ts_host, 00_typescript - assert_eq!(files.len(), 4); - assert_eq!(code_cache.hits.lock().len(), 0); - assert_eq!(code_cache.misses.lock().len(), 4); - - for (specifier, ty) in &expect { - assert_eq!(files.get(*specifier), Some(ty)); - } - - code_cache.hits.lock().clear(); - code_cache.misses.lock().clear(); - } - - { - let _ = test_exec_with_cache(&specifier, Some(code_cache.clone())) - .await - .expect("exec should not have errored"); - - // 99_main_compiler, 98_lsp, 97_ts_host, 00_typescript - assert_eq!(code_cache.hits.lock().len(), 4); - assert_eq!(code_cache.misses.lock().len(), 0); - - for (specifier, ty) in expect { - let url = ModuleSpecifier::parse(specifier).unwrap(); - assert_eq!(code_cache.hits.lock().get(&(url, ty)), Some(&1)); + Err(err) => return Err(err.into()), } } - } + }; + let result = match maybe_result { + Some((specifier, media_type)) => { + let specifier_str = match specifier.scheme() { + "data" | "blob" => { + let specifier_str = hash_url(&specifier, media_type); + + remapped_specifiers.insert(specifier_str.clone(), specifier); + specifier_str + } + _ => { + if let Some(specifier_str) = + mapped_specifier_for_tsc(&specifier, media_type) + { + remapped_specifiers.insert(specifier_str.clone(), specifier); + specifier_str + } else { + specifier.to_string() + } + } + }; + ( + specifier_str, + match media_type { + MediaType::Css => Some(".js"), // surface these as .js for typescript + MediaType::Unknown => None, + media_type => Some(media_type.as_ts_extension()), + }, + ) + } + None => ( + MISSING_DEPENDENCY_SPECIFIER.to_string(), + Some(MediaType::Dts.as_ts_extension()), + ), + }; + log::debug!("Resolved {} from {} to {:?}", specifier, referrer, result); + Ok(result) } + +pub trait LoadContent: AsRef { + fn from_static(source: &'static str) -> Self; + fn from_string(source: String) -> Self; + fn from_arc_str(source: Arc) -> Self; +} + +#[derive(Debug)] +pub struct LoadResponse { + data: T, + version: Option, + is_cjs: bool, + media_type: MediaType, +} + +pub trait Mapper { + fn maybe_remapped_specifier( + &self, + specifier: &str, + ) -> Option<&ModuleSpecifier>; +} + +pub fn load_for_tsc( + load_specifier: &str, + maybe_npm: Option<&RequestNpmState>, + current_dir: &Path, + graph: &ModuleGraph, + maybe_tsbuildinfo: Option<&str>, + hash_data: u64, + remapper: &M, +) -> Result>, LoadError> { + fn load_from_node_modules( + specifier: &ModuleSpecifier, + npm_state: Option<&RequestNpmState>, + media_type: &mut MediaType, + is_cjs: &mut bool, + ) -> Result, LoadError> { + *media_type = MediaType::from_specifier(specifier); + let file_path = specifier.to_file_path().unwrap(); + let code = match std::fs::read_to_string(&file_path) { + Ok(code) => code, + Err(err) if err.kind() == ErrorKind::NotFound => { + return Ok(None); + } + Err(err) => { + return Err(LoadError::LoadFromNodeModule { + path: file_path.display().to_string(), + error: err, + }); + } + }; + let code: Arc = code.into(); + *is_cjs = npm_state + .map(|npm_state| { + npm_state.cjs_tracker.is_cjs(specifier, *media_type, &code) + }) + .unwrap_or(false); + Ok(Some(T::from_arc_str(code))) + } + + let specifier = + deno_path_util::resolve_url_or_path(load_specifier, current_dir)?; + + let mut hash: Option = None; + let mut media_type = MediaType::Unknown; + let mut is_cjs = false; + + let data = if load_specifier == "internal:///.tsbuildinfo" { + maybe_tsbuildinfo.map(|s| T::from_string(s.to_string())) + // in certain situations we return a "blank" module to tsc and we need to + // handle the request for that module here. + } else if load_specifier == MISSING_DEPENDENCY_SPECIFIER { + None + } else if let Some(name) = load_specifier.strip_prefix("asset:///") { + let maybe_source = get_lazily_loaded_asset(name); + hash = get_maybe_hash(maybe_source, hash_data); + media_type = MediaType::from_str(load_specifier); + maybe_source.map(T::from_static) + } else if let Some(source) = load_raw_import_source(&specifier) { + return Ok(Some(LoadResponse { + data: T::from_static(source), + version: Some("1".to_string()), + is_cjs: false, + media_type: MediaType::TypeScript, + })); + } else { + let specifier = if let Some(remapped_specifier) = + remapper.maybe_remapped_specifier(load_specifier) + { + remapped_specifier + } else { + &specifier + }; + let maybe_module = graph.try_get(specifier).ok().flatten(); + let maybe_source = if let Some(module) = maybe_module { + match module { + Module::Js(module) => { + media_type = module.media_type; + if let Some(npm_state) = &maybe_npm { + is_cjs = npm_state.cjs_tracker.is_cjs_with_known_is_script( + specifier, + module.media_type, + module.is_script, + )?; + } + Some( + module + .fast_check_module() + .map(|m| T::from_arc_str(m.source.clone())) + .unwrap_or(T::from_arc_str(module.source.text.clone())), + ) + } + Module::Json(module) => { + media_type = MediaType::Json; + Some(T::from_arc_str(module.source.text.clone())) + } + Module::Wasm(module) => { + media_type = MediaType::Dts; + Some(T::from_arc_str(module.source_dts.clone())) + } + Module::Npm(_) | Module::Node(_) => None, + Module::External(module) => { + if module.specifier.scheme() != "file" { + None + } else { + // means it's Deno code importing an npm module + let specifier = resolve_specifier_into_node_modules( + &CliSys::default(), + &module.specifier, + ); + load_from_node_modules( + &specifier, + maybe_npm, + &mut media_type, + &mut is_cjs, + )? + } + } + } + } else if let Some(npm) = maybe_npm + .as_ref() + .filter(|npm| npm.node_resolver.in_npm_package(specifier)) + { + load_from_node_modules( + specifier, + Some(npm), + &mut media_type, + &mut is_cjs, + )? + } else { + None + }; + hash = get_maybe_hash(maybe_source.as_ref().map(|s| s.as_ref()), hash_data); + maybe_source + }; + let Some(data) = data else { + return Ok(None); + }; + Ok(Some(LoadResponse { + data, + version: hash, + is_cjs, + media_type, + })) +} + +pub static IGNORED_DIAGNOSTIC_CODES: LazyLock> = + LazyLock::new(|| { + [ + // TS1452: 'resolution-mode' assertions are only supported when `moduleResolution` is `node16` or `nodenext`. + // We specify the resolution mode to be CommonJS for some npm files and this + // diagnostic gets generated even though we're using custom module resolution. + 1452, + // Module '...' cannot be imported using this construct. The specifier only resolves to an + // ES module, which cannot be imported with 'require'. + 1471, + // TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; + // however, the referenced file is an ECMAScript module and cannot be imported with 'require'. + 1479, + // TS1543: Importing a JSON file into an ECMAScript module requires a 'type: \"json\"' import + // attribute when 'module' is set to 'NodeNext'. + 1543, + // TS2306: File '.../index.d.ts' is not a module. + // We get this for `x-typescript-types` declaration files which don't export + // anything. We prefer to treat these as modules with no exports. + 2306, + // TS2688: Cannot find type definition file for '...'. + // We ignore because type definition files can end with '.ts'. + 2688, + // TS2792: Cannot find module. Did you mean to set the 'moduleResolution' + // option to 'node', or to add aliases to the 'paths' option? + 2792, + // TS2307: Cannot find module '{0}' or its corresponding type declarations. + 2307, // Relative import errors to add an extension + 2834, 2835, + // TS5009: Cannot find the common subdirectory path for the input files. + 5009, + // TS5055: Cannot write file + // 'http://localhost:4545/subdir/mt_application_x_javascript.j4.js' + // because it would overwrite input file. + 5055, + // TypeScript is overly opinionated that only CommonJS modules kinds can + // support JSON imports. Allegedly this was fixed in + // Microsoft/TypeScript#26825 but that doesn't seem to be working here, + // so we will ignore complaints about this compiler setting. + 5070, + // TS7016: Could not find a declaration file for module '...'. '...' + // implicitly has an 'any' type. This is due to `allowJs` being off by + // default but importing of a JavaScript module. + 7016, + ] + .into_iter() + .collect() + }); + +pub static TYPES_NODE_IGNORABLE_NAMES: &[&str] = &[ + "AbortController", + "AbortSignal", + "AsyncIteratorObject", + "atob", + "Blob", + "BroadcastChannel", + "btoa", + "ByteLengthQueuingStrategy", + "CloseEvent", + "CompressionStream", + "CountQueuingStrategy", + "CustomEvent", + "DecompressionStream", + "Disposable", + "DOMException", + "Event", + "EventSource", + "EventTarget", + "fetch", + "File", + "Float32Array", + "Float64Array", + "FormData", + "Headers", + "ImportMeta", + "MessageChannel", + "MessageEvent", + "MessagePort", + "Navigator", + "performance", + "PerformanceEntry", + "PerformanceMark", + "PerformanceMeasure", + "QueuingStrategy", + "QueuingStrategySize", + "ReadableByteStreamController", + "ReadableStream", + "ReadableStreamBYOBReader", + "ReadableStreamBYOBRequest", + "ReadableStreamDefaultController", + "ReadableStreamDefaultReader", + "ReadonlyArray", + "Request", + "Response", + "Storage", + "TextDecoder", + "TextDecoderStream", + "TextEncoder", + "TextEncoderStream", + "TransformStream", + "TransformStreamDefaultController", + "URL", + "URLPattern", + "URLSearchParams", + "WebSocket", + "WritableStream", + "WritableStreamDefaultController", + "WritableStreamDefaultWriter", +]; + +pub static NODE_ONLY_GLOBALS: &[&str] = &[ + "__dirname", + "__filename", + "\"buffer\"", + "Buffer", + "BufferConstructor", + "BufferEncoding", + "clearImmediate", + "clearInterval", + "clearTimeout", + "console", + "Console", + "crypto", + "ErrorConstructor", + "gc", + "Global", + "localStorage", + "queueMicrotask", + "RequestInit", + "ResponseInit", + "sessionStorage", + "setImmediate", + "setInterval", + "setTimeout", +]; diff --git a/cli/type_checker.rs b/cli/type_checker.rs index 7fce098515..1d4079d0ac 100644 --- a/cli/type_checker.rs +++ b/cli/type_checker.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; +use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; @@ -37,6 +38,7 @@ use crate::graph_util::BuildFastCheckGraphOptions; use crate::graph_util::ModuleGraphBuilder; use crate::graph_util::module_error_for_tsc_diagnostic; use crate::node::CliNodeResolver; +use crate::node::CliPackageJsonResolver; use crate::npm::CliNpmResolver; use crate::sys::CliSys; use crate::tsc; @@ -103,9 +105,11 @@ pub struct TypeChecker { module_graph_builder: Arc, node_resolver: Arc, npm_resolver: CliNpmResolver, + package_json_resolver: Arc, sys: CliSys, compiler_options_resolver: Arc, code_cache: Option>, + tsgo_path: Option, } impl TypeChecker { @@ -117,9 +121,11 @@ impl TypeChecker { module_graph_builder: Arc, node_resolver: Arc, npm_resolver: CliNpmResolver, + package_json_resolver: Arc, sys: CliSys, compiler_options_resolver: Arc, code_cache: Option>, + tsgo_path: Option, ) -> Self { Self { caches, @@ -128,9 +134,11 @@ impl TypeChecker { module_graph_builder, node_resolver, npm_resolver, + package_json_resolver, sys, compiler_options_resolver, code_cache, + tsgo_path, } } @@ -233,6 +241,7 @@ impl TypeChecker { cjs_tracker: &self.cjs_tracker, node_resolver: &self.node_resolver, npm_resolver: &self.npm_resolver, + package_json_resolver: &self.package_json_resolver, compiler_options_resolver: &self.compiler_options_resolver, log_level: self.cli_options.log_level(), npm_check_state_hash: check_state_hash(&self.npm_resolver), @@ -244,6 +253,8 @@ impl TypeChecker { options, seen_diagnotics: Default::default(), code_cache: self.code_cache.clone(), + tsgo_path: self.tsgo_path.clone(), + initial_cwd: self.cli_options.initial_cwd().to_path_buf(), }), )) } @@ -362,6 +373,7 @@ struct DiagnosticsByFolderRealIterator<'a> { cjs_tracker: &'a Arc, node_resolver: &'a Arc, npm_resolver: &'a CliNpmResolver, + package_json_resolver: &'a Arc, compiler_options_resolver: &'a CompilerOptionsResolver, type_check_cache: TypeCheckCache, groups: Vec>, @@ -371,6 +383,8 @@ struct DiagnosticsByFolderRealIterator<'a> { seen_diagnotics: HashSet, options: CheckOptions, code_cache: Option>, + tsgo_path: Option, + initial_cwd: PathBuf, } impl Iterator for DiagnosticsByFolderRealIterator<'_> { @@ -522,12 +536,15 @@ impl DiagnosticsByFolderRealIterator<'_> { cjs_tracker: self.cjs_tracker.clone(), node_resolver: self.node_resolver.clone(), npm_resolver: self.npm_resolver.clone(), + package_json_resolver: self.package_json_resolver.clone(), }), maybe_tsbuildinfo, root_names, check_mode: self.options.type_check_mode, + initial_cwd: self.initial_cwd.clone(), }, code_cache, + self.tsgo_path.as_deref(), )?; let ambient_modules = response.ambient_modules; diff --git a/docs/tsgo.md b/docs/tsgo.md new file mode 100644 index 0000000000..c1e081a699 --- /dev/null +++ b/docs/tsgo.md @@ -0,0 +1,39 @@ +# Typescript-Go Integration + +Currently only integrated with deno check, though in the future it will also be +integrated with our LSP implementation. + +In the CLI, we have a small abstraction over the tsc backend in +[cli/tsc/mod.rs](../cli/tsc/mod.rs). Along with some shared types and +functionality, the main piece is the `exec` function, which takes a "request" to +be served by the typescript compiler and returns the result. This now has two +different "backend" which can serve the request – the current tsc, which runs in +an isolate and communicates via ops, and typescript-go which runs in a +subprocess and uses IPC. + +From a high level, the way the tsgo backend works is that we download a +typescript-go binary from +[github releases](https://github.com/denoland/typescript-go/releases) into the +deno cache dir. To actually interface with tsgo, we spawn it in a subprocess and +write messages over stdin/stdout (similar to the Language Server Protocol). The +format is a mixture of binary data (for the header and other protocol level +details) followed by json encoded values for RPC calls. The rust implementation +of the IPC protocol is in the +[deno_typescript_go_client_rust crate](../libs/typescript_go_client/src/lib.rs). + +We currently maintain a +[fork of typescript-go](https://github.com/denoland/typescript-go) with the +following changes: + +- Special handling of the global symbol tables to account for the fact that we + have two slightly different sets of globals: one for node contexts (in npm + packages), and one for deno contexts. At this point, the main difference is + the type returned by `setTimeout`. With node globals `setTimeout` returns an + object, and with deno globals it returns a number (just like the web + standard). +- Symbol table logic to prevent @types/node from creating type errors by + introducing incompatible definitions for globals +- Additional hooks to allow us to provide our own resolution, determine whether + a file is esm/cjs, etc. +- Additional APIs exposed from the IPC server +- Support for deno's custom libs (`deno.window`, `deno.worker`, etc) diff --git a/libs/node_resolver/package_json.rs b/libs/node_resolver/package_json.rs index 84020d4027..0cae0ce10a 100644 --- a/libs/node_resolver/package_json.rs +++ b/libs/node_resolver/package_json.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; use deno_package_json::PackageJson; use deno_package_json::PackageJsonCacheResult; use deno_package_json::PackageJsonRc; +use sys_traits::FsMetadata; use sys_traits::FsRead; use crate::errors::PackageJsonLoadError; @@ -76,12 +77,12 @@ pub type PackageJsonResolverRc = deno_maybe_sync::MaybeArc>; #[derive(Debug)] -pub struct PackageJsonResolver { +pub struct PackageJsonResolver { sys: TSys, loader_cache: Option, } -impl PackageJsonResolver { +impl PackageJsonResolver { pub fn new(sys: TSys, loader_cache: Option) -> Self { Self { sys, loader_cache } } @@ -124,12 +125,14 @@ impl PackageJsonResolver { } } -pub struct ClosestPackageJsonsIterator<'a, TSys: FsRead> { +pub struct ClosestPackageJsonsIterator<'a, TSys: FsRead + FsMetadata> { current_path: &'a Path, resolver: &'a PackageJsonResolver, } -impl<'a, TSys: FsRead> Iterator for ClosestPackageJsonsIterator<'a, TSys> { +impl<'a, TSys: FsRead + FsMetadata> Iterator + for ClosestPackageJsonsIterator<'a, TSys> +{ type Item = Result; fn next(&mut self) -> Option { diff --git a/libs/resolver/cjs/analyzer/mod.rs b/libs/resolver/cjs/analyzer/mod.rs index c564f9ef6f..e156ee40cb 100644 --- a/libs/resolver/cjs/analyzer/mod.rs +++ b/libs/resolver/cjs/analyzer/mod.rs @@ -87,7 +87,7 @@ impl NodeAnalysisCache for NullNodeAnalysisCache { #[sys_traits::auto_impl] pub trait DenoCjsCodeAnalyzerSys: - sys_traits::FsRead + MaybeSend + MaybeSync + 'static + sys_traits::FsRead + sys_traits::FsMetadata + MaybeSend + MaybeSync + 'static { } diff --git a/libs/resolver/cjs/mod.rs b/libs/resolver/cjs/mod.rs index 7bd163c498..aa6198957a 100644 --- a/libs/resolver/cjs/mod.rs +++ b/libs/resolver/cjs/mod.rs @@ -6,6 +6,7 @@ use node_resolver::InNpmPackageChecker; use node_resolver::PackageJsonResolverRc; use node_resolver::ResolutionMode; use node_resolver::errors::PackageJsonLoadError; +use sys_traits::FsMetadata; use sys_traits::FsRead; use url::Url; @@ -21,12 +22,15 @@ pub type CjsTrackerRc = /// be CJS or ESM after they're loaded based on their contents. So these /// files will be "maybe CJS" until they're loaded. #[derive(Debug)] -pub struct CjsTracker { +pub struct CjsTracker< + TInNpmPackageChecker: InNpmPackageChecker, + TSys: FsRead + FsMetadata, +> { is_cjs_resolver: IsCjsResolver, known: MaybeDashMap, } -impl +impl CjsTracker { pub fn new( @@ -155,14 +159,14 @@ pub enum IsCjsResolutionMode { #[derive(Debug)] pub struct IsCjsResolver< TInNpmPackageChecker: InNpmPackageChecker, - TSys: FsRead, + TSys: FsRead + FsMetadata, > { in_npm_pkg_checker: TInNpmPackageChecker, pkg_json_resolver: PackageJsonResolverRc, mode: IsCjsResolutionMode, } -impl +impl IsCjsResolver { pub fn new( diff --git a/libs/resolver/npm/byonm.rs b/libs/resolver/npm/byonm.rs index ae4d48394e..8000a642a0 100644 --- a/libs/resolver/npm/byonm.rs +++ b/libs/resolver/npm/byonm.rs @@ -49,7 +49,7 @@ pub enum ByonmResolvePkgFolderFromDenoReqError { JsrReqUnsupported { req: PackageReq }, } -pub struct ByonmNpmResolverCreateOptions { +pub struct ByonmNpmResolverCreateOptions { // todo(dsherret): investigate removing this pub root_node_modules_dir: Option, pub sys: NodeResolutionSys, diff --git a/libs/typescript_go_client/Cargo.toml b/libs/typescript_go_client/Cargo.toml new file mode 100644 index 0000000000..e5573d348f --- /dev/null +++ b/libs/typescript_go_client/Cargo.toml @@ -0,0 +1,17 @@ +# Copyright 2018-2025 the Deno authors. MIT license. + +[package] +name = "deno_typescript_go_client_rust" +version = "0.1.0" +edition = "2024" +license = "MIT" +description = "Rust library to communicate with TSGO binary over IPC" + +[dependencies] +indexmap = { version = "2", features = ["serde"] } +log = "0.4.28" +rmp = "0.8.14" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_repr = "0.1" +thiserror = "2" diff --git a/libs/typescript_go_client/src/connection.rs b/libs/typescript_go_client/src/connection.rs new file mode 100644 index 0000000000..8424b6305a --- /dev/null +++ b/libs/typescript_go_client/src/connection.rs @@ -0,0 +1,77 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +// Partially extracted / adapted from https://github.com/microsoft/libsyncrpc +// Copyright 2024 Microsoft Corporation. MIT license. + +use std::io::BufRead; +use std::io::Result; +use std::io::Write; +use std::io::{self}; + +/// Lower-level wrapper around RPC-related messaging and process management. +pub struct RpcConnection { + reader: R, + writer: W, +} + +impl RpcConnection { + pub fn new(reader: R, writer: W) -> Result { + Ok(Self { reader, writer }) + } + + pub fn write(&mut self, ty: u8, name: &[u8], payload: &[u8]) -> Result<()> { + let w = &mut self.writer; + rmp::encode::write_array_len(w, 3)?; + rmp::encode::write_u8(w, ty)?; + rmp::encode::write_bin(w, name)?; + rmp::encode::write_bin(w, payload)?; + w.flush()?; + Ok(()) + } + + pub fn read(&mut self) -> Result<(u8, Vec, Vec)> { + let r = &mut self.reader; + assert_eq!( + rmp::decode::read_array_len(r).map_err(to_io)?, + 3, + "Message components must be a valid 3-part messagepack array." + ); + Ok(( + rmp::decode::read_int(r).map_err(to_io)?, + self.read_bin()?, + self.read_bin()?, + )) + } + + fn read_bin(&mut self) -> Result> { + let r = &mut self.reader; + let payload_len = rmp::decode::read_bin_len(r).map_err(to_io)?; + let mut payload = vec![0u8; payload_len as usize]; + r.read_exact(&mut payload)?; + Ok(payload) + } + + // Helper method to create an error + pub fn create_error( + &self, + name: &str, + payload: Vec, + expected_method: &str, + ) -> io::Error { + if name == expected_method { + let payload = match String::from_utf8(payload) { + Ok(payload) => payload, + Err(err) => return io::Error::other(format!("{err}")), + }; + io::Error::other(payload) + } else { + io::Error::other(format!( + "name mismatch for response: expected `{expected_method}`, got `{name}`" + )) + } + } +} + +fn to_io(err: T) -> io::Error { + io::Error::other(format!("{err}")) +} diff --git a/libs/typescript_go_client/src/lib.rs b/libs/typescript_go_client/src/lib.rs new file mode 100644 index 0000000000..c584ebb893 --- /dev/null +++ b/libs/typescript_go_client/src/lib.rs @@ -0,0 +1,320 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +// Partially extracted / adapted from https://github.com/microsoft/libsyncrpc +// Copyright 2024 Microsoft Corporation. MIT license. + +pub mod connection; +pub mod types; + +use std::collections::HashSet; +use std::ffi::OsStr; +use std::io::BufReader; +use std::io::BufWriter; +use std::process::Child; +use std::process::ChildStdin; +use std::process::ChildStdout; + +use connection::RpcConnection; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Failed to spawn process: {0}")] + ProcessSpawn(#[source] std::io::Error), + + #[error("Failed to kill process: {0}")] + ProcessKill(#[source] std::io::Error), + + #[error("Error in RPC connection: {0}")] + RpcConnection(#[source] std::io::Error), + + #[error("Error encoding {obj} as {ty}: {source}")] + Encoding { + obj: &'static str, + ty: &'static str, + source: Box, + }, + + #[error("Error decoding UTF-8: {0}")] + Utf8(#[source] std::string::FromUtf8Error), + + #[error("Invalid message type: {0}")] + InvalidMessageType(MessageType), + + #[error("{0}")] + AdHoc(String), + + #[error("serde json error: {0}")] + Json(#[from] serde_json::Error), +} + +impl Error { + pub fn from_reason>(reason: S) -> Self { + Self::AdHoc(reason.into()) + } +} + +pub trait CallbackHandler { + fn supported_callbacks(&self) -> &'static [&'static str]; + + fn handle_callback( + &self, + name: &str, + payload: String, + ) -> Result; +} + +/// A synchronous RPC channel that allows JavaScript to synchronously call out +/// to a child process and get a response over a line-based protocol, +/// including handling of JavaScript-side callbacks before the call completes. +/// +/// #### Protocol +/// +/// Requests follow a MessagePack-based "tuple"/array protocol with 3 items: +/// `(, , )`. All items are binary arrays of 8-bit +/// integers, including the `` and ``, to avoid unnecessary +/// encoding/decoding at the protocol level. +/// +/// For specific message types and their corresponding protocol behavior, please +/// see `MessageType` below. +pub struct SyncRpcChannel { + child: Child, + conn: RpcConnection, BufWriter>, + callback_handler: T, + supported_callbacks: HashSet<&'static str>, +} + +impl SyncRpcChannel { + /// Constructs a new `SyncRpcChannel` by spawning a child process with the + /// given `exe` executable, and a given set of `args`. + pub fn new( + exe: impl AsRef, + args: I, + callback_handler: T, + ) -> Result + where + I: IntoIterator, + S: AsRef, + { + let mut child = std::process::Command::new(exe) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::inherit()) + .args(args) + .spawn() + .map_err(Error::ProcessSpawn)?; + let supported_callbacks = callback_handler.supported_callbacks(); + Ok(Self { + conn: RpcConnection::new( + BufReader::new(child.stdout.take().expect("Where did ChildStdout go?")), + BufWriter::new(child.stdin.take().expect("Where did ChildStdin go?")), + ) + .map_err(Error::RpcConnection)?, + supported_callbacks: supported_callbacks.iter().copied().collect(), + callback_handler, + child, + }) + } + + /// Send a request to the child process and wait for a response. The method + /// will not return, synchronously, until a response is received or an error + /// occurs. + /// + /// This method will take care of encoding and decoding the binary payload to + /// and from a JS string automatically and suitable for smaller payloads. + pub fn request_sync( + &mut self, + method: &str, + payload: String, + ) -> Result { + self + .request_bytes_sync(method, payload.as_bytes()) + .and_then(|arr| { + String::from_utf8((&arr[..]).into()).map_err(|e| Error::Encoding { + obj: "response", + ty: "string", + source: Box::new(Error::Utf8(e)), + }) + }) + } + + /// Send a request to the child process and wait for a response. The method + /// will not return, synchronously, until a response is received or an error + /// occurs. + /// + /// Unlike `requestSync`, this method will not do any of its own encoding or + /// decoding of payload data. Everything will be as sent/received through the + /// underlying protocol. + pub fn request_bytes_sync( + &mut self, + method: &str, + payload: &[u8], + ) -> Result, Error> { + log::trace!("request_bytes_sync: {method}"); + let method_bytes = method.as_bytes(); + self + .conn + .write(MessageType::Request as u8, method_bytes, payload) + .map_err(Error::RpcConnection)?; + loop { + let (ty, name, payload) = + self.conn.read().map_err(Error::RpcConnection)?; + match ty.try_into().map_err(Error::from_reason)? { + MessageType::Response => { + if name == method_bytes { + return Ok(payload); + } else { + let name = String::from_utf8_lossy(&name); + return Err(Error::from_reason(format!( + "name mismatch for response: expected `{method}`, got `{name}`" + ))); + } + } + MessageType::Error => { + return Err(Error::RpcConnection(self.conn.create_error( + &String::from_utf8_lossy(&name), + payload, + method, + ))); + } + MessageType::Call => { + self.handle_call(&String::from_utf8_lossy(&name), payload)?; + } + _ => { + return Err(Error::from_reason(format!( + "Invalid message type from child: {ty:?}" + ))); + } + } + } + } + + // Closes the channel, terminating its underlying process. + pub fn close(&mut self) -> Result<(), Error> { + self.child.kill().map_err(Error::ProcessKill)?; + Ok(()) + } + + // Helper method to handle callback calls + fn handle_call(&mut self, name: &str, payload: Vec) -> Result<(), Error> { + if !self.supported_callbacks.contains(name) { + self.conn.write(MessageType::CallError as u8, name.as_bytes(), format!("unknown callback: `{name}`. Please make sure to register it on the JavaScript side before invoking it.").as_bytes()) + .map_err(Error::RpcConnection)?; + return Err(Error::from_reason(format!( + "no callback named `{name}` found" + ))); + } + let res = self + .callback_handler + .handle_callback(name, String::from_utf8(payload).map_err(Error::Utf8)?); + match res { + Ok(res) => { + self + .conn + .write( + MessageType::CallResponse as u8, + name.as_bytes(), + res.as_bytes(), + ) + .map_err(Error::RpcConnection)?; + } + Err(e) => { + self + .conn + .write( + MessageType::CallError as u8, + name.as_bytes(), + format!("{e}").trim().as_bytes(), + ) + .map_err(Error::RpcConnection)?; + return Err(Error::from_reason(format!( + "Error calling callback `{name}`: {}", + e + ))); + } + } + + Ok(()) + } +} + +/// Messages types exchanged between the channel and its child. All messages +/// have an associated `` and ``, which will both be arrays of +/// 8-bit integers (`Uint8Array`s). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum MessageType { + // --- Sent by channel--- + /// A request to the child with the given raw byte ``, with + /// `` as the method name. The child may send back any number of + /// `MessageType.Call` messages and must then close the request with either a + /// `MessageType.Response`, or a `MessageType.Error`. message. + Request = 1, + /// A response to a `MessageType.Call` message that the child previously sent. + /// The `` is the return value from invoking the JavaScript callback + /// associated with it. If the callback errors, `MessageType.CallError` will + /// be sent to the child. + CallResponse, + /// Informs the child that an error occurred. The `` will be the + /// binary representation of the stringified error, as UTF-8 bytes, not + /// necessarily in JSON format. The method linked to this message will also + /// throw an error after sending this message to its child and terminate the + /// request call. + CallError, + + // --- Sent by child --- + /// A response to a request that the call was for. `` MUST match the + /// `MessageType.Request` message's `` argument. + Response, + /// A response that denotes some error occurred while processing the request + /// on the child side. The `` will simply be the binary + /// representation of the stringified error, as UTF-8 bytes, not necessarily + /// in JSON format. The method associated with this call will also throw an + /// error after receiving this message from the child. + Error, + /// A request to invoke a pre-registered JavaScript callback (see + /// `SyncRpcChannel#registerCallback`). `` is the name of the callback, + /// and `` is an encoded UTF-8 string that the callback will be + /// called with. The child should then listen for `MessageType.CallResponse` + /// and `MessageType.CallError` messages. + Call, + // NOTE: Do NOT put any variants below this one, always add them _before_ it. + // See comment in TryFrom impl, and remove this when `variant_count` stabilizes. + _UnusedPlaceholderVariant, + // NOTHING SHOULD GO BELOW HERE +} + +impl std::fmt::Display for MessageType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MessageType::Request => write!(f, "MessageType::Request"), + MessageType::CallResponse => write!(f, "MessageType::CallResponse"), + MessageType::CallError => write!(f, "MessageType::CallError"), + MessageType::Response => write!(f, "MessageType::Response"), + MessageType::Error => write!(f, "MessageType::Error"), + MessageType::Call => write!(f, "MessageType::Call"), + MessageType::_UnusedPlaceholderVariant => { + write!(f, "MessageType::_UnusedPlaceholderVariant") + } + } + } +} + +impl TryFrom for MessageType { + type Error = String; + + fn try_from( + value: u8, + ) -> std::result::Result>::Error> { + // TODO: change to the following line when `variant_count` stabilizes + // (https://github.com/rust-lang/rust/issues/73662) and remove `_UnusedPlaceholderVariant` + // + // if (1..=std::mem::variant_count::()) { + if (1..(MessageType::_UnusedPlaceholderVariant as u8)).contains(&value) { + // SAFETY: This is safe as long as the above holds true. It'll be fully + // safe once `variant_count` stabilizes. + Ok(unsafe { std::mem::transmute::(value) }) + } else { + Err(format!("Invalid message type: {value}")) + } + } +} diff --git a/libs/typescript_go_client/src/types.rs b/libs/typescript_go_client/src/types.rs new file mode 100644 index 0000000000..2b6e714062 --- /dev/null +++ b/libs/typescript_go_client/src/types.rs @@ -0,0 +1,134 @@ +// Copyright 2018-2025 the Deno authors. MIT license. + +use std::marker::PhantomData; + +use indexmap::IndexMap; + +#[derive(serde::Deserialize, Debug, Clone, Copy)] +#[serde(rename_all = "camelCase")] +pub struct Position { + pub line: u64, + pub character: u64, +} + +#[derive(serde::Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Diagnostic { + pub file_name: String, + pub start: Position, + pub end: Position, + pub start_pos: u32, + pub end_pos: u32, + pub code: u32, + pub category: String, + pub message: String, + pub message_chain: Vec, + pub related_information: Vec, + pub reports_unnecessary: bool, + pub reports_deprecated: bool, + pub skipped_on_no_emit: bool, + pub source_line: String, +} + +pub type DiagnosticId = u32; + +#[derive( + serde_repr::Deserialize_repr, serde_repr::Serialize_repr, Debug, Clone, Copy, +)] +#[repr(u32)] +pub enum ResolutionMode { + None = 0, + CommonJS = 1, + ESM = 99, +} + +#[derive(serde::Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ResolveModuleNamePayload { + pub module_name: String, + pub containing_file: String, + pub resolution_mode: ResolutionMode, + // redirected_reference: Handle, +} + +#[derive(serde::Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ResolveTypeReferenceDirectivePayload { + pub type_reference_directive_name: String, + pub containing_file: String, + pub resolution_mode: ResolutionMode, +} + +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(from = "String", into = "String")] +pub struct Handle { + pub id: String, + #[serde(skip)] + _phantom: PhantomData, +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Handle").field(&self.id).finish() + } +} + +impl From> for String { + fn from(value: Handle) -> Self { + value.id + } +} + +impl Clone for Handle { + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + _phantom: PhantomData, + } + } +} + +impl From for Handle { + fn from(id: String) -> Self { + Self { + id, + _phantom: PhantomData, + } + } +} + +#[derive(serde::Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Project { + pub id: Handle, + pub config_file_name: String, + pub root_files: Vec, + pub compiler_options: IndexMap, +} + +#[derive( + Debug, Clone, serde_repr::Deserialize_repr, serde_repr::Serialize_repr, +)] +#[repr(u32)] +pub enum ModuleKind { + None = 0, + CommonJS = 1, + AMD = 2, + UMD = 3, + System = 4, + ES2015 = 5, + ES2020 = 6, + ES2022 = 7, + ESNext = 99, + Node16 = 100, + Node18 = 101, + NodeNext = 199, + Preserve = 200, +} + +#[derive(serde::Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetImpliedNodeFormatForFilePayload { + pub file_name: String, + pub package_json_type: String, +} diff --git a/runtime/features/data.rs b/runtime/features/data.rs index 9afabae9bf..e40ab4d27f 100644 --- a/runtime/features/data.rs +++ b/runtime/features/data.rs @@ -232,4 +232,12 @@ pub static FEATURE_DESCRIPTIONS: &[UnstableFeatureDescription] = &[ config_option: ConfigFileOption::SameAsFlagName, env_var: None, }, + UnstableFeatureDescription { + name: "tsgo", + help_text: "Enable unstable TypeScript Go integration", + show_in_help: true, + kind: UnstableFeatureKind::Cli, + config_option: ConfigFileOption::SameAsFlagName, + env_var: Some("DENO_UNSTABLE_TSGO"), + }, ]; diff --git a/runtime/features/gen.js b/runtime/features/gen.js index 77b58abe4d..6f4d320e65 100644 --- a/runtime/features/gen.js +++ b/runtime/features/gen.js @@ -21,8 +21,8 @@ export const unstableIds = { process: 17, rawImports: 18, temporal: 21, - unsafeProto: 22, - vsock: 23, - webgpu: 24, - workerOptions: 25, + unsafeProto: 23, + vsock: 24, + webgpu: 25, + workerOptions: 26, }; diff --git a/runtime/features/gen.rs b/runtime/features/gen.rs index 7c9251ad59..c389d10f74 100644 --- a/runtime/features/gen.rs +++ b/runtime/features/gen.rs @@ -205,12 +205,21 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[ kind: UnstableFeatureKind::Runtime, config_file_option: "temporal", }, + UnstableFeatureDefinition { + name: "tsgo", + flag_name: "unstable-tsgo", + help_text: "Enable unstable TypeScript Go integration", + show_in_help: true, + id: 22, + kind: UnstableFeatureKind::Cli, + config_file_option: "tsgo", + }, UnstableFeatureDefinition { name: "unsafe-proto", flag_name: "unstable-unsafe-proto", help_text: "Enable unsafe __proto__ support. This is a security risk.", show_in_help: true, - id: 22, + id: 23, kind: UnstableFeatureKind::Runtime, config_file_option: "unsafe-proto", }, @@ -219,7 +228,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[ flag_name: "unstable-vsock", help_text: "Enable unstable VSOCK APIs", show_in_help: false, - id: 23, + id: 24, kind: UnstableFeatureKind::Runtime, config_file_option: "vsock", }, @@ -228,7 +237,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[ flag_name: "unstable-webgpu", help_text: "Enable unstable WebGPU APIs", show_in_help: true, - id: 24, + id: 25, kind: UnstableFeatureKind::Runtime, config_file_option: "webgpu", }, @@ -237,7 +246,7 @@ pub static UNSTABLE_FEATURES: &[UnstableFeatureDefinition] = &[ flag_name: "unstable-worker-options", help_text: "Enable unstable Web Worker APIs", show_in_help: true, - id: 25, + id: 26, kind: UnstableFeatureKind::Runtime, config_file_option: "worker-options", }, @@ -250,6 +259,7 @@ pub struct UnstableEnvVarNames { pub raw_imports: &'static str, pub sloppy_imports: &'static str, pub subdomain_wildcards: &'static str, + pub tsgo: &'static str, } pub static UNSTABLE_ENV_VAR_NAMES: UnstableEnvVarNames = UnstableEnvVarNames { bare_node_builtins: "DENO_UNSTABLE_BARE_NODE_BUILTINS", @@ -259,4 +269,5 @@ pub static UNSTABLE_ENV_VAR_NAMES: UnstableEnvVarNames = UnstableEnvVarNames { raw_imports: "DENO_UNSTABLE_RAW_IMPORTS", sloppy_imports: "DENO_UNSTABLE_SLOPPY_IMPORTS", subdomain_wildcards: "DENO_UNSTABLE_SUBDOMAIN_WILDCARDS", + tsgo: "DENO_UNSTABLE_TSGO", }; diff --git a/tests/integration/compile_tests.rs b/tests/integration/compile_tests.rs index 56e1f209ed..b58c279651 100644 --- a/tests/integration/compile_tests.rs +++ b/tests/integration/compile_tests.rs @@ -6,6 +6,10 @@ use util::TestContextBuilder; use util::assert_not_contains; use util::testdata_path; +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn compile_basic() { let context = TestContextBuilder::new().build(); @@ -57,6 +61,10 @@ fn compile_basic() { output.assert_matches_text("Welcome to Deno!\n"); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn standalone_args() { let context = TestContextBuilder::new().build(); @@ -88,6 +96,10 @@ fn standalone_args() { .assert_matches_text("a\nb\nfoo\n--bar\n--unstable\n"); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn standalone_load_datauri() { let context = TestContextBuilder::new().build(); @@ -117,6 +129,10 @@ fn standalone_load_datauri() { } // https://github.com/denoland/deno/issues/13704 +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn standalone_follow_redirects() { let context = TestContextBuilder::new().build(); @@ -262,6 +278,10 @@ fn compile_and_overwrite_file() { } } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn standalone_runtime_flags() { let context = TestContextBuilder::new().build(); @@ -298,6 +318,10 @@ fn standalone_runtime_flags() { ); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn standalone_ext_flag_ts() { let context = TestContextBuilder::new().build(); @@ -329,6 +353,10 @@ fn standalone_ext_flag_ts() { .assert_matches_text("executing typescript with no extension\n"); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn standalone_ext_flag_js() { let context = TestContextBuilder::new().build(); @@ -359,6 +387,10 @@ fn standalone_ext_flag_js() { .assert_matches_text("executing javascript with no extension\n"); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn standalone_import_map() { let context = TestContextBuilder::new().build(); @@ -390,6 +422,10 @@ fn standalone_import_map() { .assert_exit_code(0); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn standalone_import_map_config_file() { let context = TestContextBuilder::new().build(); @@ -421,6 +457,10 @@ fn standalone_import_map_config_file() { .assert_exit_code(0); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] // https://github.com/denoland/deno/issues/12670 fn skip_rebundle() { @@ -500,6 +540,10 @@ fn check_local_by_default2() { ); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn workers_basic() { let context = TestContext::with_http_server(); @@ -530,6 +574,10 @@ fn workers_basic() { .assert_matches_file("./compile/workers/basic.out"); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn workers_not_in_module_map() { let context = TestContext::with_http_server(); @@ -559,6 +607,10 @@ fn workers_not_in_module_map() { )); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn workers_with_include_flag() { let context = TestContext::with_http_server(); @@ -590,6 +642,10 @@ fn workers_with_include_flag() { .assert_matches_text("Hello from worker!\nReceived 42\nClosing\n"); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn dynamic_import() { let context = TestContext::with_http_server(); @@ -620,6 +676,10 @@ fn dynamic_import() { .assert_exit_code(0); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn dynamic_import_unanalyzable() { let context = TestContext::with_http_server(); @@ -772,6 +832,10 @@ testing[WILDCARD]this .assert_matches_text("2\n"); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn compile_npm_bin_esm() { run_npm_bin_compile_test(RunNpmBinCompileOptions { @@ -788,6 +852,10 @@ fn compile_npm_bin_esm() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn compile_npm_bin_cjs() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:@denotest/bin/cli-cjs", @@ -803,6 +871,10 @@ fn compile_npm_bin_cjs() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn compile_npm_cowsay_main() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:cowsay@1.5.0", @@ -818,6 +890,10 @@ fn compile_npm_cowsay_main() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn compile_npm_no_permissions() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:@denotest/cli-with-permissions@1.0.0", @@ -833,6 +909,10 @@ fn compile_npm_no_permissions() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn compile_npm_cowsay_explicit() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:cowsay@1.5.0/cowsay", @@ -848,6 +928,10 @@ fn compile_npm_cowsay_explicit() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn compile_npm_cowthink() { run_npm_bin_compile_test(RunNpmBinCompileOptions { input_specifier: "npm:cowsay@1.5.0/cowthink", @@ -921,6 +1005,10 @@ fn run_npm_bin_compile_test(opts: RunNpmBinCompileOptions) { output.assert_matches_file(opts.output_file); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn compile_node_modules_symlink_outside() { // this code is using a canonicalized temp dir because otherwise @@ -989,6 +1077,10 @@ fn compile_node_modules_symlink_outside() { output.assert_matches_file("compile/node_modules_symlink_outside/main.out"); } +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] #[test] fn compile_node_modules_symlink_non_existent() { let context = TestContextBuilder::for_npm().use_temp_cwd().build(); @@ -1033,6 +1125,10 @@ Embedded Files } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn dynamic_imports_tmp_lit() { let context = TestContextBuilder::new().build(); let dir = context.temp_dir(); @@ -1057,6 +1153,10 @@ fn dynamic_imports_tmp_lit() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn granular_unstable_features() { let context = TestContextBuilder::new().build(); let dir = context.temp_dir(); @@ -1084,6 +1184,10 @@ fn granular_unstable_features() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn granular_unstable_features_config_file() { let context = TestContextBuilder::new().use_temp_cwd().build(); let dir = context.temp_dir(); @@ -1121,6 +1225,10 @@ fn granular_unstable_features_config_file() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn dynamic_import_bad_data_uri() { let context = TestContextBuilder::new().build(); let dir = context.temp_dir(); @@ -1150,6 +1258,10 @@ fn dynamic_import_bad_data_uri() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn standalone_config_file_respects_compiler_options() { let context = TestContextBuilder::new().build(); let dir = context.temp_dir(); @@ -1179,6 +1291,10 @@ fn standalone_config_file_respects_compiler_options() { } #[test] +#[cfg_attr( + all(target_os = "macos", target_arch = "x86_64", debug_assertions), + ignore // TODO: fix this, see https://github.com/denoland/sui/issues/43 +)] fn standalone_jsr_dynamic_import() { let context = TestContextBuilder::for_jsr().build(); let dir = context.temp_dir(); diff --git a/tests/specs/check/ambient_modules/__test__.jsonc b/tests/specs/check/ambient_modules/__test__.jsonc index 8fe741849b..e833bec39b 100644 --- a/tests/specs/check/ambient_modules/__test__.jsonc +++ b/tests/specs/check/ambient_modules/__test__.jsonc @@ -1,18 +1,36 @@ { - "steps": [{ - "args": "check foo.ts", - "output": "foo.out" - }, { - "args": "check bar.ts", - "output": "bar.out", - "exitCode": 1 - }, { - "args": "run foo.ts", - "output": "run.out", - "exitCode": 1 - }, { - "args": "run bar.ts", - "output": "run.out", - "exitCode": 1 - }] + "steps": [ + { + "args": "check foo.ts", + "output": "foo.out" + }, + { + "args": "check bar.ts", + "output": "bar.out", + "exitCode": 1 + }, + { + "args": "run foo.ts", + "output": "run.out", + "exitCode": 1 + }, + { + "args": "run bar.ts", + "output": "run.out", + "exitCode": 1 + } + ], + "variants": { + // TODO(nathanwhit): not implemented yet, needs extra api to get ambient modules on + // the tsgo side + // "tsgo": { + // "use_tsgo": "1" + // }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/bare_specifier_not_found/__test__.jsonc b/tests/specs/check/bare_specifier_not_found/__test__.jsonc index 2a7878fc74..66be993513 100644 --- a/tests/specs/check/bare_specifier_not_found/__test__.jsonc +++ b/tests/specs/check/bare_specifier_not_found/__test__.jsonc @@ -1,4 +1,3 @@ -// https://github.com/denoland/deno/issues/30116 { "tests": { "import_map": { @@ -11,5 +10,16 @@ "output": "check_no_import_map.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/byonm_import_missing_types/__test__.jsonc b/tests/specs/check/byonm_import_missing_types/__test__.jsonc index 708675b86d..3a5be61fd6 100644 --- a/tests/specs/check/byonm_import_missing_types/__test__.jsonc +++ b/tests/specs/check/byonm_import_missing_types/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check main.ts", - "output": "check.out" + "output": "check.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/bytes_and_text_imports/__test__.jsonc b/tests/specs/check/bytes_and_text_imports/__test__.jsonc index 66fa4a27ae..42c00284ce 100644 --- a/tests/specs/check/bytes_and_text_imports/__test__.jsonc +++ b/tests/specs/check/bytes_and_text_imports/__test__.jsonc @@ -1,5 +1,21 @@ { "args": "check --unstable-raw-imports main.ts", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + // TODO(nathanwhit): not implemented yet, needs tsgo changes + // see appendRawImportFragment in cli/tsc/97_ts_host.js and the code that calls it. + // it will need tsgo changes because the import kind is not easily available on the tsgo side + // during resolution. + // + // "tsgo": { + // "use_tsgo": "1" + // }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_all/__test__.jsonc b/tests/specs/check/check_all/__test__.jsonc index 101e5aab9e..d20535b04c 100644 --- a/tests/specs/check/check_all/__test__.jsonc +++ b/tests/specs/check/check_all/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --allow-import --quiet --all check_all.ts", "output": "check_all.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_all_local/__test__.jsonc b/tests/specs/check/check_all_local/__test__.jsonc index 0cd1c16acb..406965c100 100644 --- a/tests/specs/check/check_all_local/__test__.jsonc +++ b/tests/specs/check/check_all_local/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --allow-import --quiet check_all_local.ts", "output": "", - "exitCode": 0 + "exitCode": 0, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_broadcast_channel/__test__.jsonc b/tests/specs/check/check_broadcast_channel/__test__.jsonc index 6190781a5c..249aaaa5b4 100644 --- a/tests/specs/check/check_broadcast_channel/__test__.jsonc +++ b/tests/specs/check/check_broadcast_channel/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet broadcast_channel.ts", "output": "", - "exitCode": 0 + "exitCode": 0, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_deno_not_found/__test__.jsonc b/tests/specs/check/check_deno_not_found/__test__.jsonc index cbc6552b35..dd8f066696 100644 --- a/tests/specs/check/check_deno_not_found/__test__.jsonc +++ b/tests/specs/check/check_deno_not_found/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet deno_not_found/main.ts", "output": "deno_not_found/main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_deno_not_found/deno.json b/tests/specs/check/check_deno_not_found/deno.json new file mode 100644 index 0000000000..bd4df05530 --- /dev/null +++ b/tests/specs/check/check_deno_not_found/deno.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": [] + } +} diff --git a/tests/specs/check/check_dts/__test__.jsonc b/tests/specs/check/check_dts/__test__.jsonc index d684e20634..0a9edb9332 100644 --- a/tests/specs/check/check_dts/__test__.jsonc +++ b/tests/specs/check/check_dts/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet dts/check_dts.d.ts", "output": "dts/check_dts.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_exclude_option/__test__.jsonc b/tests/specs/check/check_exclude_option/__test__.jsonc index d8e41403c2..add104e08a 100644 --- a/tests/specs/check/check_exclude_option/__test__.jsonc +++ b/tests/specs/check/check_exclude_option/__test__.jsonc @@ -15,5 +15,16 @@ "output": "exclude_option.ts.error.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/check_imported_files_listed_in_exclude_option/__test__.jsonc b/tests/specs/check/check_imported_files_listed_in_exclude_option/__test__.jsonc index 1fd2d87602..ce4efbc337 100644 --- a/tests/specs/check/check_imported_files_listed_in_exclude_option/__test__.jsonc +++ b/tests/specs/check/check_imported_files_listed_in_exclude_option/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet --config exclude_option/deno.exclude_dir.json exclude_option/index.ts", "output": "exclude_option/exclude_option.ts.error.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_jsximportsource_importmap_config/__test__.jsonc b/tests/specs/check/check_jsximportsource_importmap_config/__test__.jsonc index bbe32ca9e7..d9dc9dde64 100644 --- a/tests/specs/check/check_jsximportsource_importmap_config/__test__.jsonc +++ b/tests/specs/check/check_jsximportsource_importmap_config/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --quiet --config jsximportsource_importmap_config/deno.json jsximportsource_importmap_config/main.tsx", - "output": "" + "output": "", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_no_error_truncation/__test__.jsonc b/tests/specs/check/check_no_error_truncation/__test__.jsonc index 15884059c6..fa852fbfec 100644 --- a/tests/specs/check/check_no_error_truncation/__test__.jsonc +++ b/tests/specs/check/check_no_error_truncation/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet no_error_truncation/main.ts --config no_error_truncation/deno.json", "output": "no_error_truncation/main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_node_builtin_modules/__test__.jsonc b/tests/specs/check/check_node_builtin_modules/__test__.jsonc index eb56845a19..9c4bc3f48d 100644 --- a/tests/specs/check/check_node_builtin_modules/__test__.jsonc +++ b/tests/specs/check/check_node_builtin_modules/__test__.jsonc @@ -2,13 +2,26 @@ "tests": { "js": { "args": "check --quiet mod.js", - "output": "mod.js.out", + "output": "mod.js${out_suffix}", "exitCode": 1 }, "ts": { "args": "check --quiet mod.ts", - "output": "mod.ts.out", + "output": "mod.ts${out_suffix}", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1", + "out_suffix": ".tsgo.out" + }, + "tsc": { + "use_tsgo": "", + "out_suffix": ".out" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/check_node_builtin_modules/mod.js.tsgo.out b/tests/specs/check/check_node_builtin_modules/mod.js.tsgo.out new file mode 100644 index 0000000000..2bd34f128e --- /dev/null +++ b/tests/specs/check/check_node_builtin_modules/mod.js.tsgo.out @@ -0,0 +1,11 @@ +TS2769 [ERROR]: No overload matches this call. +const _data = fs.readFileSync("./node_builtin.js", 123); + ~~~ + at [WILDLINE]/mod.js:3:52 + +TS2771 [ERROR]: The last overload is declared here. + export function readFileSync( + ~~~~~~~~~~~~ + at [WILDLINE]/fs.d.ts:2925:21 + +error: Type checking failed. diff --git a/tests/specs/check/check_node_builtin_modules/mod.ts.tsgo.out b/tests/specs/check/check_node_builtin_modules/mod.ts.tsgo.out new file mode 100644 index 0000000000..3d91081083 --- /dev/null +++ b/tests/specs/check/check_node_builtin_modules/mod.ts.tsgo.out @@ -0,0 +1,18 @@ +TS2769 [ERROR]: No overload matches this call. +const _data = fs.readFileSync("./node_builtin.js", 123); + ~~~ + at [WILDLINE]/mod.ts:2:52 + +TS2771 [ERROR]: The last overload is declared here. + export function readFileSync( + ~~~~~~~~~~~~ + at [WILDLINE]/fs.d.ts:2925:21 + +TS4104 [ERROR]: The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'. +const _testString: number[] = builtinModules; + ~~~~~~~~~~~ + at [WILDLINE]/mod.ts:9:7 + +Found 2 errors. + +error: Type checking failed. diff --git a/tests/specs/check/check_non_normalized_specifier/__test__.jsonc b/tests/specs/check/check_non_normalized_specifier/__test__.jsonc index 9f10aa2201..cad3d4e330 100644 --- a/tests/specs/check/check_non_normalized_specifier/__test__.jsonc +++ b/tests/specs/check/check_non_normalized_specifier/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "", - "exitCode": 0 + "exitCode": 0, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_npm_install_diagnostics/__test__.jsonc b/tests/specs/check/check_npm_install_diagnostics/__test__.jsonc index 18260d362f..74184b002b 100644 --- a/tests/specs/check/check_npm_install_diagnostics/__test__.jsonc +++ b/tests/specs/check/check_npm_install_diagnostics/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet npm_install_diagnostics/main.ts", "output": "npm_install_diagnostics/main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_static_response_json/__test__.jsonc b/tests/specs/check/check_static_response_json/__test__.jsonc index d286d3aa80..76f6355e4f 100644 --- a/tests/specs/check/check_static_response_json/__test__.jsonc +++ b/tests/specs/check/check_static_response_json/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet response_json.ts", "output": "", - "exitCode": 0 + "exitCode": 0, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_types_dts/__test__.jsonc b/tests/specs/check/check_types_dts/__test__.jsonc index 7f620daa27..b858bf6cad 100644 --- a/tests/specs/check/check_types_dts/__test__.jsonc +++ b/tests/specs/check/check_types_dts/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check main.ts", "output": "main.out", - "exitCode": 0 + "exitCode": 0, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_with_excluded_file_specified/__test__.jsonc b/tests/specs/check/check_with_excluded_file_specified/__test__.jsonc index 212ab8fb3a..9237ef8b2d 100644 --- a/tests/specs/check/check_with_excluded_file_specified/__test__.jsonc +++ b/tests/specs/check/check_with_excluded_file_specified/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check lib/types.d.ts", - "output": "check.out" + "output": "check.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/check_workspace/__test__.jsonc b/tests/specs/check/check_workspace/__test__.jsonc index 19677cbb7f..2ae775b9ef 100644 --- a/tests/specs/check/check_workspace/__test__.jsonc +++ b/tests/specs/check/check_workspace/__test__.jsonc @@ -10,5 +10,16 @@ "output": "check_config_flag.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/cjs_default_export/__test__.jsonc b/tests/specs/check/cjs_default_export/__test__.jsonc index a991c6eed4..2f8a9caec1 100644 --- a/tests/specs/check/cjs_default_export/__test__.jsonc +++ b/tests/specs/check/cjs_default_export/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check main.ts", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/compiler_options_paths_and_sloppy_imports/__test__.jsonc b/tests/specs/check/compiler_options_paths_and_sloppy_imports/__test__.jsonc index 68fbcefca2..17c91cf2d2 100644 --- a/tests/specs/check/compiler_options_paths_and_sloppy_imports/__test__.jsonc +++ b/tests/specs/check/compiler_options_paths_and_sloppy_imports/__test__.jsonc @@ -9,5 +9,16 @@ "output": "unmapped.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/__test__.jsonc b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/__test__.jsonc index bf7e7e1c94..3d8a8bb08e 100644 --- a/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/__test__.jsonc +++ b/tests/specs/check/compiler_options_root_dirs_and_sloppy_imports/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --quiet subdir/mod.ts", - "output": "" + "output": "", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/compiler_options_types/__test__.jsonc b/tests/specs/check/compiler_options_types/__test__.jsonc index f23081fef4..a3212dc462 100644 --- a/tests/specs/check/compiler_options_types/__test__.jsonc +++ b/tests/specs/check/compiler_options_types/__test__.jsonc @@ -49,5 +49,16 @@ } ] } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/css_import/__test__.jsonc b/tests/specs/check/css_import/__test__.jsonc index 7f4bed0e28..dd242ceb1d 100644 --- a/tests/specs/check/css_import/__test__.jsonc +++ b/tests/specs/check/css_import/__test__.jsonc @@ -1,27 +1,46 @@ { - "steps": [{ - "args": "check exists.ts", - "output": "exists.out" - }, { - "args": "run --check exists.ts", - "output": "exists_run_with_check.out", - "exitCode": 1 - }, { - "args": "check not_exists.ts", - "output": "not_exists.out", - "exitCode": 1 - }, { - "args": "run --check not_exists.ts", - "output": "not_exists_run.out", - "exitCode": 1 - }, { - "args": "check exists_and_try_uses.ts", - "output": "exists_and_try_uses.out" - }, { - "args": "check exists_dynamic_import.ts", - "output": "Check file:///[WILDCARD]exists_dynamic_import.ts\n" - }, { - "args": "run --check --reload exists_dynamic_import.ts", - "output": "Check file:///[WILDCARD]exists_dynamic_import.ts\n" - }] + "steps": [ + { + "args": "check exists.ts", + "output": "exists.out" + }, + { + "args": "run --check exists.ts", + "output": "exists_run_with_check.out", + "exitCode": 1 + }, + { + "args": "check not_exists.ts", + "output": "not_exists.out", + "exitCode": 1 + }, + { + "args": "run --check not_exists.ts", + "output": "not_exists_run.out", + "exitCode": 1 + }, + { + "args": "check exists_and_try_uses.ts", + "output": "exists_and_try_uses.out" + }, + { + "args": "check exists_dynamic_import.ts", + "output": "Check file:///[WILDCARD]exists_dynamic_import.ts\n" + }, + { + "args": "run --check --reload exists_dynamic_import.ts", + "output": "Check file:///[WILDCARD]exists_dynamic_import.ts\n" + } + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/declaration_header_file_with_no_exports/__test__.jsonc b/tests/specs/check/declaration_header_file_with_no_exports/__test__.jsonc index 08fc0a2d6e..151c20b486 100644 --- a/tests/specs/check/declaration_header_file_with_no_exports/__test__.jsonc +++ b/tests/specs/check/declaration_header_file_with_no_exports/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --quiet declaration_header_file_with_no_exports.ts", - "output": "" + "output": "", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/definitely_typed/__test__.jsonc b/tests/specs/check/definitely_typed/__test__.jsonc index 941ad51f0c..faf46a1a69 100644 --- a/tests/specs/check/definitely_typed/__test__.jsonc +++ b/tests/specs/check/definitely_typed/__test__.jsonc @@ -1,7 +1,8 @@ { "tempDir": true, "envs": { - "RUST_BACKTRACE": "0" + "RUST_BACKTRACE": "0", + "DENO_UNSTABLE_TSGO": "${use_tsgo}" }, "tests": { "node_modules_dir_auto": { @@ -72,5 +73,13 @@ } ] } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } } } diff --git a/tests/specs/check/dts_importing_non_existent/__test__.jsonc b/tests/specs/check/dts_importing_non_existent/__test__.jsonc index 3775b7fb4c..61f1c5e715 100644 --- a/tests/specs/check/dts_importing_non_existent/__test__.jsonc +++ b/tests/specs/check/dts_importing_non_existent/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check index.js", "output": "check.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/erasable_syntax_only/__test__.jsonc b/tests/specs/check/erasable_syntax_only/__test__.jsonc index aca25e1ef0..b34e34861a 100644 --- a/tests/specs/check/erasable_syntax_only/__test__.jsonc +++ b/tests/specs/check/erasable_syntax_only/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check main.ts", "output": "error.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/export_equals_declaration_file/__test__.jsonc b/tests/specs/check/export_equals_declaration_file/__test__.jsonc index d69543a478..d352eeb4a8 100644 --- a/tests/specs/check/export_equals_declaration_file/__test__.jsonc +++ b/tests/specs/check/export_equals_declaration_file/__test__.jsonc @@ -1,10 +1,23 @@ { - "steps": [{ - "args": "check main.ts", - "output": "main.out" - }, { - // ensure diagnostic is not cached - "args": "check main.ts", - "output": "main.out" - }] + "steps": [ + { + "args": "check main.ts", + "output": "main.out" + }, + { + "args": "check main.ts", + "output": "main.out" + } + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/extensionless/__test__.jsonc b/tests/specs/check/extensionless/__test__.jsonc index 670680b7d9..99e59fcdb0 100644 --- a/tests/specs/check/extensionless/__test__.jsonc +++ b/tests/specs/check/extensionless/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --reload --all http://localhost:4545/subdir/no_js_ext", - "output": "check.out" + "output": "check.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/globbing/__test__.jsonc b/tests/specs/check/globbing/__test__.jsonc index 73df1de4d1..cd40b55531 100644 --- a/tests/specs/check/globbing/__test__.jsonc +++ b/tests/specs/check/globbing/__test__.jsonc @@ -20,5 +20,16 @@ "output": "Check [WILDLINE]sub_dir/main.ts\nTS2322[WILDCARD]", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/import_meta_no_errors/__test__.jsonc b/tests/specs/check/import_meta_no_errors/__test__.jsonc index e03edb297f..b67780acee 100644 --- a/tests/specs/check/import_meta_no_errors/__test__.jsonc +++ b/tests/specs/check/import_meta_no_errors/__test__.jsonc @@ -49,5 +49,16 @@ } ] } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/import_non_existent_in_remote/__test__.jsonc b/tests/specs/check/import_non_existent_in_remote/__test__.jsonc index 39cd37ffc0..3d8048c564 100644 --- a/tests/specs/check/import_non_existent_in_remote/__test__.jsonc +++ b/tests/specs/check/import_non_existent_in_remote/__test__.jsonc @@ -10,5 +10,16 @@ "output": "check_all.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/instability_fetch/__test__.jsonc b/tests/specs/check/instability_fetch/__test__.jsonc index 58c84ac573..89e6daa50f 100644 --- a/tests/specs/check/instability_fetch/__test__.jsonc +++ b/tests/specs/check/instability_fetch/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check main.ts", "output": "[WILDCARD]", - "exitCode": 0 // should be successful + "exitCode": 0, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/jsdoc_import_decl/__test__.jsonc b/tests/specs/check/jsdoc_import_decl/__test__.jsonc index fdbb52ab35..b2d799ded3 100644 --- a/tests/specs/check/jsdoc_import_decl/__test__.jsonc +++ b/tests/specs/check/jsdoc_import_decl/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --allow-import main.js", "output": "check.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/jsx_automatic_no_explicit_import_source/__test__.jsonc b/tests/specs/check/jsx_automatic_no_explicit_import_source/__test__.jsonc index 6cc57b8af8..93f04aa1bd 100644 --- a/tests/specs/check/jsx_automatic_no_explicit_import_source/__test__.jsonc +++ b/tests/specs/check/jsx_automatic_no_explicit_import_source/__test__.jsonc @@ -1,10 +1,24 @@ { "tempDir": true, - "steps": [{ - "args": "install", - "output": "[WILDCARD]" - }, { - "args": "check main.tsx", - "output": "check.out" - }] + "steps": [ + { + "args": "install", + "output": "[WILDCARD]" + }, + { + "args": "check main.tsx", + "output": "check.out" + } + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/jsx_import_source_not_in_graph/__test__.jsonc b/tests/specs/check/jsx_import_source_not_in_graph/__test__.jsonc index de0339cfb4..8d00ed0410 100644 --- a/tests/specs/check/jsx_import_source_not_in_graph/__test__.jsonc +++ b/tests/specs/check/jsx_import_source_not_in_graph/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check main.ts", - "output": "main.out" + "output": "main.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/jsx_import_source_types/__test__.jsonc b/tests/specs/check/jsx_import_source_types/__test__.jsonc index 18ff8cd079..d589ac0b06 100644 --- a/tests/specs/check/jsx_import_source_types/__test__.jsonc +++ b/tests/specs/check/jsx_import_source_types/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --allow-import --all main.tsx", - "output": "main.out" + "output": "main.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/jsx_import_source_types_config/__test__.jsonc b/tests/specs/check/jsx_import_source_types_config/__test__.jsonc index 18ff8cd079..d589ac0b06 100644 --- a/tests/specs/check/jsx_import_source_types_config/__test__.jsonc +++ b/tests/specs/check/jsx_import_source_types_config/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --allow-import --all main.tsx", - "output": "main.out" + "output": "main.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/jsx_not_checked/__test__.jsonc b/tests/specs/check/jsx_not_checked/__test__.jsonc index dbaa9b2eaf..1c784361ad 100644 --- a/tests/specs/check/jsx_not_checked/__test__.jsonc +++ b/tests/specs/check/jsx_not_checked/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check jsx_not_checked/main.jsx", "output": "jsx_not_checked/main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/lockfile_types_node/__test__.jsonc b/tests/specs/check/lockfile_types_node/__test__.jsonc index 3681246039..6952ec3b86 100644 --- a/tests/specs/check/lockfile_types_node/__test__.jsonc +++ b/tests/specs/check/lockfile_types_node/__test__.jsonc @@ -2,30 +2,48 @@ "tempDir": true, "tests": { "check": { - "steps": [{ - "args": "check --frozen=true", - "output": "[WILDCARD]The lockfile is out of date.[WILDCARD]", - "exitCode": 1 - }, { - "args": "check --frozen=false", - "output": "[WILDCARD]", - "exitCode": 0 - }, { - "args": "check --frozen=true", - "output": "[WILDCARD]", - "exitCode": 0 - }] + "steps": [ + { + "args": "check --frozen=true", + "output": "[WILDCARD]The lockfile is out of date.[WILDCARD]", + "exitCode": 1 + }, + { + "args": "check --frozen=false", + "output": "[WILDCARD]", + "exitCode": 0 + }, + { + "args": "check --frozen=true", + "output": "[WILDCARD]", + "exitCode": 0 + } + ] }, "install": { - "steps": [{ - "args": "install --frozen=false --entrypoint main.ts", - "output": "[WILDCARD]", - "exitCode": 0 - }, { - "args": "check --frozen=true", - "output": "[WILDCARD]", - "exitCode": 0 - }] + "steps": [ + { + "args": "install --frozen=false --entrypoint main.ts", + "output": "[WILDCARD]", + "exitCode": 0 + }, + { + "args": "check --frozen=true", + "output": "[WILDCARD]", + "exitCode": 0 + } + ] } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/lockfile_types_node_existing/__test__.jsonc b/tests/specs/check/lockfile_types_node_existing/__test__.jsonc index 95b82c0a2d..a639e3ac60 100644 --- a/tests/specs/check/lockfile_types_node_existing/__test__.jsonc +++ b/tests/specs/check/lockfile_types_node_existing/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --frozen=true", "output": "[WILDCARD]", - "exitCode": 0 + "exitCode": 0, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/message_chain_formatting/__test__.jsonc b/tests/specs/check/message_chain_formatting/__test__.jsonc index 1b5b49d8a9..af18f1b35f 100644 --- a/tests/specs/check/message_chain_formatting/__test__.jsonc +++ b/tests/specs/check/message_chain_formatting/__test__.jsonc @@ -1,6 +1,18 @@ -// Regression test for https://github.com/denoland/deno/issues/27411. { "args": "check --quiet message_chain_formatting.ts", - "output": "message_chain_formatting.out", - "exitCode": 1 + "output": "${out}", + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1", + "out": "message_chain_formatting_tsgo.out" + }, + "tsc": { + "use_tsgo": "", + "out": "message_chain_formatting.out" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/message_chain_formatting/message_chain_formatting_tsgo.out b/tests/specs/check/message_chain_formatting/message_chain_formatting_tsgo.out new file mode 100644 index 0000000000..f322c6b2bb --- /dev/null +++ b/tests/specs/check/message_chain_formatting/message_chain_formatting_tsgo.out @@ -0,0 +1,11 @@ +TS2769 [ERROR]: No overload matches this call. +foo("hello", 42); + ~~~~~~~ + at [WILDLINE]/message_chain_formatting.ts:8:5 + +TS2771 [ERROR]: The last overload is declared here. + function foo(ss: string[], b: Date): void; + ~~~ + at [WILDLINE]/message_chain_formatting.ts:3:10 + +error: Type checking failed. diff --git a/tests/specs/check/module_detection_force/__test__.jsonc b/tests/specs/check/module_detection_force/__test__.jsonc index 9c892bd723..e7bf7611a0 100644 --- a/tests/specs/check/module_detection_force/__test__.jsonc +++ b/tests/specs/check/module_detection_force/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --quiet module_detection_force/main.ts", - "output": "" + "output": "", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/module_not_found/__test__.jsonc b/tests/specs/check/module_not_found/__test__.jsonc index 16783275cc..38e40176b0 100644 --- a/tests/specs/check/module_not_found/__test__.jsonc +++ b/tests/specs/check/module_not_found/__test__.jsonc @@ -20,5 +20,16 @@ "output": "missing_remote_root.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/module_not_found_npm_pkg_entrypoint/__test__.jsonc b/tests/specs/check/module_not_found_npm_pkg_entrypoint/__test__.jsonc index 25b1a7c324..6b98b717db 100644 --- a/tests/specs/check/module_not_found_npm_pkg_entrypoint/__test__.jsonc +++ b/tests/specs/check/module_not_found_npm_pkg_entrypoint/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check main.ts", "output": "check.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/module_not_found_npm_pkg_internal/__test__.jsonc b/tests/specs/check/module_not_found_npm_pkg_internal/__test__.jsonc index 7a5a4fb74a..f005af8cb3 100644 --- a/tests/specs/check/module_not_found_npm_pkg_internal/__test__.jsonc +++ b/tests/specs/check/module_not_found_npm_pkg_internal/__test__.jsonc @@ -1,5 +1,17 @@ { "args": "check --all main.ts", "output": "check.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + // currently failing + // "tsgo": { + // "use_tsgo": "1" + // }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/module_resolution_bundler_sloppy_imports/__test__.jsonc b/tests/specs/check/module_resolution_bundler_sloppy_imports/__test__.jsonc index 250dcc74b8..5813bcee0b 100644 --- a/tests/specs/check/module_resolution_bundler_sloppy_imports/__test__.jsonc +++ b/tests/specs/check/module_resolution_bundler_sloppy_imports/__test__.jsonc @@ -15,5 +15,16 @@ "output": "main.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/no_implicit_override/__test__.jsonc b/tests/specs/check/no_implicit_override/__test__.jsonc index a991c6eed4..2f8a9caec1 100644 --- a/tests/specs/check/no_implicit_override/__test__.jsonc +++ b/tests/specs/check/no_implicit_override/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check main.ts", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/npm_pkg_empty_main_entry/__test__.jsonc b/tests/specs/check/npm_pkg_empty_main_entry/__test__.jsonc index a61769ed61..e204b98456 100644 --- a/tests/specs/check/npm_pkg_empty_main_entry/__test__.jsonc +++ b/tests/specs/check/npm_pkg_empty_main_entry/__test__.jsonc @@ -1,10 +1,24 @@ { - "steps": [{ - "args": "check index.ts", - "output": "check.out", - "exitCode": 1 - }, { - "args": "run index.ts", - "output": "run.out" - }] + "steps": [ + { + "args": "check index.ts", + "output": "check.out", + "exitCode": 1 + }, + { + "args": "run index.ts", + "output": "run.out" + } + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/package_json/__test__.jsonc b/tests/specs/check/package_json/__test__.jsonc index 29a964b9f0..fca3bd3a15 100644 --- a/tests/specs/check/package_json/__test__.jsonc +++ b/tests/specs/check/package_json/__test__.jsonc @@ -9,5 +9,16 @@ "args": "check main.ts", "output": "check.out" } - ] + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/package_json_auto_install/__test__.jsonc b/tests/specs/check/package_json_auto_install/__test__.jsonc index 3fa33cdb79..d0b0c61842 100644 --- a/tests/specs/check/package_json_auto_install/__test__.jsonc +++ b/tests/specs/check/package_json_auto_install/__test__.jsonc @@ -5,5 +5,16 @@ "args": "check main.ts", "output": "check.out" } - ] + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/package_json_fail_check/__test__.jsonc b/tests/specs/check/package_json_fail_check/__test__.jsonc index e5b4a4db0a..cf0eed97ee 100644 --- a/tests/specs/check/package_json_fail_check/__test__.jsonc +++ b/tests/specs/check/package_json_fail_check/__test__.jsonc @@ -10,5 +10,16 @@ "output": "fail_check.out", "exitCode": 1 } - ] + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/package_json_with_deno_json/__test__.jsonc b/tests/specs/check/package_json_with_deno_json/__test__.jsonc index 13752ebeee..dc91df6d5c 100644 --- a/tests/specs/check/package_json_with_deno_json/__test__.jsonc +++ b/tests/specs/check/package_json_with_deno_json/__test__.jsonc @@ -10,5 +10,16 @@ "output": "check.out", "exitCode": 1 } - ] + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/random_extension/__test__.jsonc b/tests/specs/check/random_extension/__test__.jsonc index f504c1d2b4..00911508ef 100644 --- a/tests/specs/check/random_extension/__test__.jsonc +++ b/tests/specs/check/random_extension/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --reload --all http://localhost:4545/subdir/no_js_ext@1.0.0", - "output": "output.out" + "output": "output.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/reject_string_in_readable_stream_from/__test__.jsonc b/tests/specs/check/reject_string_in_readable_stream_from/__test__.jsonc index 634dbac850..12afa7ccc1 100644 --- a/tests/specs/check/reject_string_in_readable_stream_from/__test__.jsonc +++ b/tests/specs/check/reject_string_in_readable_stream_from/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check ./main.ts", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/remote_missing_override/__test__.jsonc b/tests/specs/check/remote_missing_override/__test__.jsonc index 9eb58dcc5d..8e05e9adb1 100644 --- a/tests/specs/check/remote_missing_override/__test__.jsonc +++ b/tests/specs/check/remote_missing_override/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --allow-import --all main.ts", - "output": "Download [WILDLINE]\nCheck [WILDLINE]\n" + "output": "Download [WILDLINE]\nCheck [WILDLINE]\n", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/skip_lib_check/__test__.jsonc b/tests/specs/check/skip_lib_check/__test__.jsonc index 90c2660c41..22b172e380 100644 --- a/tests/specs/check/skip_lib_check/__test__.jsonc +++ b/tests/specs/check/skip_lib_check/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --quiet types.d.ts", - "output": "" + "output": "", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/special_specifiers/__test__.jsonc b/tests/specs/check/special_specifiers/__test__.jsonc index 82900d2b83..faa9fc5d1e 100644 --- a/tests/specs/check/special_specifiers/__test__.jsonc +++ b/tests/specs/check/special_specifiers/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check", - "output": "check.out" + "output": "check.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/ts_in_npm_pkg/__test__.jsonc b/tests/specs/check/ts_in_npm_pkg/__test__.jsonc index 7a5a4fb74a..7dae2a4c59 100644 --- a/tests/specs/check/ts_in_npm_pkg/__test__.jsonc +++ b/tests/specs/check/ts_in_npm_pkg/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --all main.ts", "output": "check.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_default_libs/__test__.jsonc b/tests/specs/check/tsconfig_default_libs/__test__.jsonc index 97cd3de543..29f06b0435 100644 --- a/tests/specs/check/tsconfig_default_libs/__test__.jsonc +++ b/tests/specs/check/tsconfig_default_libs/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --quiet", - "output": "" + "output": "", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_deno_json_priority/__test__.jsonc b/tests/specs/check/tsconfig_deno_json_priority/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_deno_json_priority/__test__.jsonc +++ b/tests/specs/check/tsconfig_deno_json_priority/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_exclude/__test__.jsonc b/tests/specs/check/tsconfig_exclude/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_exclude/__test__.jsonc +++ b/tests/specs/check/tsconfig_exclude/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_extends/__test__.jsonc b/tests/specs/check/tsconfig_extends/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_extends/__test__.jsonc +++ b/tests/specs/check/tsconfig_extends/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_extends_array/__test__.jsonc b/tests/specs/check/tsconfig_extends_array/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_extends_array/__test__.jsonc +++ b/tests/specs/check/tsconfig_extends_array/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_extends_node_resolution/__test__.jsonc b/tests/specs/check/tsconfig_extends_node_resolution/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_extends_node_resolution/__test__.jsonc +++ b/tests/specs/check/tsconfig_extends_node_resolution/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_files/__test__.jsonc b/tests/specs/check/tsconfig_files/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_files/__test__.jsonc +++ b/tests/specs/check/tsconfig_files/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_files_for_globals/__test__.jsonc b/tests/specs/check/tsconfig_files_for_globals/__test__.jsonc index 4342487f5a..36b30c932a 100644 --- a/tests/specs/check/tsconfig_files_for_globals/__test__.jsonc +++ b/tests/specs/check/tsconfig_files_for_globals/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check main.deno.ts main.dom.ts --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_files_for_globals/tsconfig.dom.json b/tests/specs/check/tsconfig_files_for_globals/tsconfig.dom.json index c0b63f63d0..7c3150f5d9 100644 --- a/tests/specs/check/tsconfig_files_for_globals/tsconfig.dom.json +++ b/tests/specs/check/tsconfig_files_for_globals/tsconfig.dom.json @@ -1,6 +1,7 @@ { "files": ["main.dom.ts", "globals.d.ts"], "compilerOptions": { - "composite": true + "composite": true, + "lib": [] } } diff --git a/tests/specs/check/tsconfig_include/__test__.jsonc b/tests/specs/check/tsconfig_include/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_include/__test__.jsonc +++ b/tests/specs/check/tsconfig_include/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_no_config/__test__.jsonc b/tests/specs/check/tsconfig_no_config/__test__.jsonc index 4d7671412d..2be544ffc8 100644 --- a/tests/specs/check/tsconfig_no_config/__test__.jsonc +++ b/tests/specs/check/tsconfig_no_config/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet --no-config", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_no_deno_json/__test__.jsonc b/tests/specs/check/tsconfig_no_deno_json/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_no_deno_json/__test__.jsonc +++ b/tests/specs/check/tsconfig_no_deno_json/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_non_strict_defaults/__test__.jsonc b/tests/specs/check/tsconfig_non_strict_defaults/__test__.jsonc index 97cd3de543..29f06b0435 100644 --- a/tests/specs/check/tsconfig_non_strict_defaults/__test__.jsonc +++ b/tests/specs/check/tsconfig_non_strict_defaults/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --quiet", - "output": "" + "output": "", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_references_dir/__test__.jsonc b/tests/specs/check/tsconfig_references_dir/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_references_dir/__test__.jsonc +++ b/tests/specs/check/tsconfig_references_dir/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/tsconfig_root_dirs/__test__.jsonc b/tests/specs/check/tsconfig_root_dirs/__test__.jsonc index 61e11a01ad..0063d02227 100644 --- a/tests/specs/check/tsconfig_root_dirs/__test__.jsonc +++ b/tests/specs/check/tsconfig_root_dirs/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --quiet", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/type_reference_import_meta/__test__.jsonc b/tests/specs/check/type_reference_import_meta/__test__.jsonc index f23081fef4..a3212dc462 100644 --- a/tests/specs/check/type_reference_import_meta/__test__.jsonc +++ b/tests/specs/check/type_reference_import_meta/__test__.jsonc @@ -49,5 +49,16 @@ } ] } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/typecheck_doc_duplicate_identifiers/__test__.jsonc b/tests/specs/check/typecheck_doc_duplicate_identifiers/__test__.jsonc index 8596142dde..130faafd78 100644 --- a/tests/specs/check/typecheck_doc_duplicate_identifiers/__test__.jsonc +++ b/tests/specs/check/typecheck_doc_duplicate_identifiers/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --doc --config ../../../config/deno.json mod.ts", "exitCode": 0, - "output": "mod.out" + "output": "mod.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/typecheck_doc_failure/__test__.jsonc b/tests/specs/check/typecheck_doc_failure/__test__.jsonc index 5d95f26668..449b9eb5fa 100644 --- a/tests/specs/check/typecheck_doc_failure/__test__.jsonc +++ b/tests/specs/check/typecheck_doc_failure/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --doc mod.ts", "exitCode": 1, - "output": "mod.out" + "output": "mod.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc b/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc index 00f98c4d0e..99115f55e6 100644 --- a/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc +++ b/tests/specs/check/typecheck_doc_in_markdown/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --doc-only markdown.md", "exitCode": 1, - "output": "markdown.out" + "output": "markdown.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/typecheck_doc_success/__test__.jsonc b/tests/specs/check/typecheck_doc_success/__test__.jsonc index 24fee3f2c9..9345bdee00 100644 --- a/tests/specs/check/typecheck_doc_success/__test__.jsonc +++ b/tests/specs/check/typecheck_doc_success/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --doc mod.ts", "exitCode": 0, - "output": "mod.out" + "output": "mod.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/types_resolved_relative_config/__test__.jsonc b/tests/specs/check/types_resolved_relative_config/__test__.jsonc index 6f4937209f..7a1e0983dd 100644 --- a/tests/specs/check/types_resolved_relative_config/__test__.jsonc +++ b/tests/specs/check/types_resolved_relative_config/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --config sub_dir/deno.json main.ts", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/unstable_suggestion/__test__.jsonc b/tests/specs/check/unstable_suggestion/__test__.jsonc index 28fe022be2..3166409214 100644 --- a/tests/specs/check/unstable_suggestion/__test__.jsonc +++ b/tests/specs/check/unstable_suggestion/__test__.jsonc @@ -1,11 +1,25 @@ { - "steps": [{ - "args": "check main.ts", - "output": "main.out", - "exitCode": 1 - }, { - "args": "check no_default_lib.ts", - "output": "no_default_lib.out", - "exitCode": 1 - }] + "steps": [ + { + "args": "check main.ts", + "output": "main.out", + "exitCode": 1 + }, + { + "args": "check no_default_lib.ts", + "output": "no_default_lib.out", + "exitCode": 1 + } + ], + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/unstable_suggestion/deno.json b/tests/specs/check/unstable_suggestion/deno.json new file mode 100644 index 0000000000..bd4df05530 --- /dev/null +++ b/tests/specs/check/unstable_suggestion/deno.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "lib": [] + } +} diff --git a/tests/specs/check/use_unknown_in_catch_variables/__test__.jsonc b/tests/specs/check/use_unknown_in_catch_variables/__test__.jsonc index a991c6eed4..2f8a9caec1 100644 --- a/tests/specs/check/use_unknown_in_catch_variables/__test__.jsonc +++ b/tests/specs/check/use_unknown_in_catch_variables/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check main.ts", "output": "main.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/wasm/__test__.jsonc b/tests/specs/check/wasm/__test__.jsonc index cb11ce33bd..52348f492b 100644 --- a/tests/specs/check/wasm/__test__.jsonc +++ b/tests/specs/check/wasm/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check --allow-import main.ts", "output": "check.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/with_bare_import/__test__.jsonc b/tests/specs/check/with_bare_import/__test__.jsonc index c32a66ad2c..641196daaf 100644 --- a/tests/specs/check/with_bare_import/__test__.jsonc +++ b/tests/specs/check/with_bare_import/__test__.jsonc @@ -1,5 +1,16 @@ { "args": "check 095_cache_with_bare_import.ts", "output": "095_cache_with_bare_import.ts.out", - "exitCode": 1 + "exitCode": 1, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/with_tsconfig_json/__test__.jsonc b/tests/specs/check/with_tsconfig_json/__test__.jsonc index 509aeeff10..e9382f61f4 100644 --- a/tests/specs/check/with_tsconfig_json/__test__.jsonc +++ b/tests/specs/check/with_tsconfig_json/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check", - "output": "main.out" + "output": "main.out", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/check/workspace/__test__.jsonc b/tests/specs/check/workspace/__test__.jsonc index 762149c13a..cd9ac74921 100644 --- a/tests/specs/check/workspace/__test__.jsonc +++ b/tests/specs/check/workspace/__test__.jsonc @@ -1,7 +1,6 @@ { "tests": { "root": { - // todo(dsherret): should be possible to not provide args here "args": "check package-a/mod.ts package-b/mod.ts package-c/mod.ts package-d/mod.ts", "output": "root.out", "exitCode": 1 @@ -18,5 +17,16 @@ "output": "package_b.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/workspace_compiler_option_types/__test__.jsonc b/tests/specs/check/workspace_compiler_option_types/__test__.jsonc index b2455f3175..fb2ed63cf5 100644 --- a/tests/specs/check/workspace_compiler_option_types/__test__.jsonc +++ b/tests/specs/check/workspace_compiler_option_types/__test__.jsonc @@ -5,5 +5,16 @@ "output": "root.out", "exitCode": 1 } + }, + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" } } diff --git a/tests/specs/check/workspace_compiler_options_root_dirs/__test__.jsonc b/tests/specs/check/workspace_compiler_options_root_dirs/__test__.jsonc index 8e273fb512..60adda66a6 100644 --- a/tests/specs/check/workspace_compiler_options_root_dirs/__test__.jsonc +++ b/tests/specs/check/workspace_compiler_options_root_dirs/__test__.jsonc @@ -1,4 +1,15 @@ { "args": "check --quiet member/subdir/mod.ts non_member/mod.ts", - "output": "" + "output": "", + "variants": { + "tsgo": { + "use_tsgo": "1" + }, + "tsc": { + "use_tsgo": "" + } + }, + "envs": { + "DENO_UNSTABLE_TSGO": "${use_tsgo}" + } } diff --git a/tests/specs/mod.rs b/tests/specs/mod.rs index 2b942da142..438107ce24 100644 --- a/tests/specs/mod.rs +++ b/tests/specs/mod.rs @@ -216,7 +216,7 @@ impl SingleTestMetaData { envs: Default::default(), steps: vec![self.step], ignore: self.ignore, - variants: Default::default(), + variants: self.variants, } } } @@ -541,30 +541,19 @@ fn substitute_variants_into_envs( pairs: &Vec<(String, String)>, envs: &mut HashMap, ) { - enum Update { - Remove(String), - Replace(String, String), - } - let mut updates = Vec::new(); + let mut to_remove = Vec::new(); for (key, value) in pairs { - for (k, v) in envs.iter() { + for (k, v) in envs.iter_mut() { let replaced = v.replace(key.as_str(), value); if replaced.is_empty() && &replaced != v { - updates.push(Update::Remove(k.clone())); + to_remove.push(k.clone()); continue; } - updates.push(Update::Replace(k.clone(), replaced)); + *v = replaced; } } - for update in updates { - match update { - Update::Remove(key) => { - envs.remove(&key); - } - Update::Replace(key, value) => { - envs.insert(key, value); - } - } + for key in to_remove { + envs.remove(&key); } } diff --git a/tests/util/server/Cargo.toml b/tests/util/server/Cargo.toml index 58f03137ab..a6b219e880 100644 --- a/tests/util/server/Cargo.toml +++ b/tests/util/server/Cargo.toml @@ -62,6 +62,7 @@ tempfile.workspace = true termcolor.workspace = true tokio.workspace = true url.workspace = true +zip.workspace = true [target.'cfg(windows)'.dependencies] winapi = { workspace = true, features = ["consoleapi", "synchapi", "handleapi", "namedpipeapi", "winbase", "winerror"] } diff --git a/tests/util/server/src/builders.rs b/tests/util/server/src/builders.rs index 65905e736e..f55d977428 100644 --- a/tests/util/server/src/builders.rs +++ b/tests/util/server/src/builders.rs @@ -33,6 +33,7 @@ use crate::lsp::LspClientBuilder; use crate::nodejs_org_mirror_unset_url; use crate::npm_registry_unset_url; use crate::pty::Pty; +use crate::servers::tsgo_prebuilt_path; use crate::strip_ansi_codes; use crate::testdata_path; use crate::tests_path; @@ -867,6 +868,12 @@ impl TestCommandBuilder { nodejs_org_mirror_unset_url(), ); } + if !envs.contains_key("DENO_TSGO_PATH") { + envs.insert( + "DENO_TSGO_PATH".to_string(), + tsgo_prebuilt_path().to_string(), + ); + } for key in &self.envs_remove { envs.remove(key); } diff --git a/tests/util/server/src/servers/mod.rs b/tests/util/server/src/servers/mod.rs index 7bc768dc2a..a7054c425e 100644 --- a/tests/util/server/src/servers/mod.rs +++ b/tests/util/server/src/servers/mod.rs @@ -53,7 +53,9 @@ use hyper_utils::run_server_with_acceptor; use super::https::SupportedHttpVersions; use super::https::get_tls_listener_stream; use super::testdata_path; +use crate::PathRef; use crate::TEST_SERVERS_COUNT; +use crate::prebuilt_path; pub(crate) const PORT: u16 = 4545; const TEST_AUTH_TOKEN: &str = "abcdef123456789"; @@ -155,6 +157,13 @@ pub async fn run_all_servers() { let node_js_mirror_server_fut = nodejs_org_mirror::nodejs_org_mirror(NODEJS_ORG_MIRROR_SERVER_PORT); + if let Err(e) = ensure_tsgo_prebuilt().await { + #[allow(clippy::print_stderr)] + { + eprintln!("failed to ensure tsgo prebuilt: {e}"); + } + } + let mut futures = vec![ redirect_server_fut.boxed_local(), ws_server_fut.boxed_local(), @@ -1475,3 +1484,73 @@ pub fn custom_headers( response } + +#[allow(unused)] +mod tsgo { + include!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../cli/tsc/go/tsgo_version.rs" + )); +} + +const TSGO_PLATFORM: &str = tsgo_platform(); +const fn tsgo_platform() -> &'static str { + match ( + std::env::consts::OS.as_bytes(), + std::env::consts::ARCH.as_bytes(), + ) { + (b"windows", b"x86_64") => "windows-x64", + (b"macos", b"x86_64") => "macos-x64", + (b"macos", b"aarch64") => "macos-arm64", + (b"linux", b"x86_64") => "linux-x64", + (b"linux", b"aarch64") => "linux-arm64", + _ => { + panic!("unsupported platform"); + } + } +} +pub fn tsgo_prebuilt_path() -> PathRef { + if let Ok(path) = std::env::var("DENO_TSGO_PATH") { + return PathRef::new(path); + } + let folder = match std::env::consts::OS { + "linux" => "linux64", + "windows" => "win", + "macos" | "apple" => "mac", + _ => panic!("unsupported platform"), + }; + prebuilt_path().join(folder).join(format!( + "tsgo-{}-{}", + tsgo::VERSION, + TSGO_PLATFORM + )) +} + +pub async fn ensure_tsgo_prebuilt() -> Result<(), anyhow::Error> { + let tsgo_path = tsgo_prebuilt_path(); + if tsgo_path.exists() { + return Ok(()); + } + + let archive_name = + format!("typescript-go-{}-{}.zip", tsgo::VERSION, TSGO_PLATFORM); + + let url = format!("{}/{archive_name}", tsgo::DOWNLOAD_BASE_URL); + + let response = reqwest::get(url).await?; + let bytes = response.bytes().await?; + + let mut archive = zip::ZipArchive::new(std::io::Cursor::new(bytes))?; + if !tsgo_path.parent().exists() { + tsgo_path.parent().create_dir_all(); + } + archive.extract(tsgo_path.parent().as_path())?; + + if cfg!(windows) { + std::fs::rename(tsgo_path.parent().join("tsgo.exe"), tsgo_path)?; + } else { + std::fs::rename(tsgo_path.parent().join("tsgo"), tsgo_path)?; + } + + Ok(()) +} diff --git a/tools/lint.js b/tools/lint.js index fae2d416c5..f30085749b 100755 --- a/tools/lint.js +++ b/tools/lint.js @@ -322,11 +322,37 @@ async function ensureNoUnusedOutFiles() { return entry.path.endsWith("__test__.jsonc"); }); - function checkObject(baseDirPath, obj) { + function checkObject(baseDirPath, obj, substsInit = {}) { + const substs = { ...substsInit }; + + if ("variants" in obj) { + for (const variantValue of Object.values(obj.variants)) { + for (const [substKey, substValue] of Object.entries(variantValue)) { + const subst = `\$\{${substKey}\}`; + if (subst in substs) { + substs[subst].push(substValue); + } else { + substs[subst] = [substValue]; + } + } + } + } for (const [key, value] of Object.entries(obj)) { if (typeof value === "object") { - checkObject(baseDirPath, value); + checkObject(baseDirPath, value, substs); } else if (key === "output" && typeof value === "string") { + for (const [subst, substValues] of Object.entries(substs)) { + if (value.includes(subst)) { + for (const substValue of substValues) { + const substitutedValue = value.replaceAll(subst, substValue); + const substitutedOutFilePath = join( + baseDirPath, + substitutedValue, + ); + outFilePaths.delete(substitutedOutFilePath); + } + } + } const outFilePath = join(baseDirPath, value); outFilePaths.delete(outFilePath); } diff --git a/tools/update_tsgo.ts b/tools/update_tsgo.ts new file mode 100755 index 0000000000..a1ace864eb --- /dev/null +++ b/tools/update_tsgo.ts @@ -0,0 +1,195 @@ +#!/usr/bin/env -S deno run -RWN --allow-run=deno +// Copyright 2018-2025 the Deno authors. MIT license. + +// deno-lint-ignore-file no-console + +import { fileURLToPath } from "node:url"; + +/** + * Trims the minimum indent from each line of a multiline string, + * removing leading and trailing blank lines. + * @param text + * @returns the trimmed string + */ +export function trimIndent(text: string): string { + if (text.startsWith("\n")) { + text = text.slice(1); + } + const lines = text.split("\n"); + const nonEmptyLines = lines.filter((line) => line.trim().length > 0); + const indent = nonEmptyLines.length > 0 + ? Math.min( + ...nonEmptyLines.map((line) => line.length - line.trimStart().length), + ) + : 0; + + return lines + .map((line) => { + if (line.length <= indent) { + return line.replace(/^ +/, ""); + } else { + return line.slice(indent); + } + }) + .join("\n"); +} + +export function unindent(strings: TemplateStringsArray, ...values: unknown[]) { + // normal template substitution + const raw = String.raw({ raw: strings }, ...values); + return trimIndent(raw); +} + +const repo = "denoland/typescript-go"; + +interface GitHubRelease { + "tag_name": string; + assets: GitHubAsset[]; +} + +interface GitHubAsset { + name: string; + digest: string; // sha256:... +} + +const latestResponse = await fetch( + `https://api.github.com/repos/${repo}/releases/latest`, +); +if (!latestResponse.ok) { + throw new Error( + `Failed to fetch latest release: ${latestResponse.statusText}`, + ); +} +const latest = await latestResponse.json() as GitHubRelease; + +const version = latest.tag_name; + +const versionNoV = version.replace(/^v/, ""); + +const file = fileURLToPath(import.meta.resolve( + "../cli/tsc/go/tsgo_version.rs", +)); + +const content = await Deno.readTextFile(file); +const match = content.match(/const VERSION: &str = "([^"]+)"/); +let currentVersion = ""; +if (!match) { + currentVersion = ""; +} else { + currentVersion = match[1]; +} +console.log("Current version: ", currentVersion); +if (currentVersion === versionNoV) { + console.log("Version is up to date, updating generated code"); +} else { + console.log("Updating version to: ", versionNoV); +} + +function findHashes( + release: GitHubRelease, +): { + windowsX64: string; + macosX64: string; + macosArm64: string; + linuxX64: string; + linuxArm64: string; +} { + const hashes = { + windowsX64: "", + macosX64: "", + macosArm64: "", + linuxX64: "", + linuxArm64: "", + }; + for (const asset of release.assets) { + const parts = asset.name.split("-"); + const os = parts[parts.length - 2]; + const archAndExtension = parts[parts.length - 1]; + const arch = archAndExtension.split(".")[0]; + if (os === "windows" && arch === "x64") { + hashes.windowsX64 = asset.digest; + } else if (os === "macos" && arch === "x64") { + hashes.macosX64 = asset.digest; + } else if (os === "macos" && arch === "arm64") { + hashes.macosArm64 = asset.digest; + } else if (os === "linux" && arch === "x64") { + hashes.linuxX64 = asset.digest; + } else if (os === "linux" && arch === "arm64") { + hashes.linuxArm64 = asset.digest; + } + } + return hashes; +} + +const hashes = findHashes(latest); +for (const [platform, hash] of Object.entries(hashes)) { + if (!hash) { + console.error(`No hashes found for ${platform}`); + Deno.exit(1); + } +} +const newContent = unindent` + // Copyright 2018-2025 the Deno authors. MIT license. + + // This file is auto-generated by tools/update_tsgo.ts + // DO NOT EDIT THIS FILE MANUALLY + + pub struct Hashes { + pub windows_x64: &'static str, + pub macos_x64: &'static str, + pub macos_arm64: &'static str, + pub linux_x64: &'static str, + pub linux_arm64: &'static str, + } + + impl Hashes { + pub const fn all(&self) -> [&'static str; 5] { + [ + self.windows_x64, + self.macos_x64, + self.macos_arm64, + self.linux_x64, + self.linux_arm64, + ] + } + } + + pub const VERSION: &str = "${versionNoV}"; + pub const DOWNLOAD_BASE_URL: &str = + "https://github.com/${repo}/releases/download/${version}"; + pub const HASHES: Hashes = Hashes { + windows_x64: "${hashes.windowsX64}", + macos_x64: "${hashes.macosX64}", + macos_arm64: "${hashes.macosArm64}", + linux_x64: "${hashes.linuxX64}", + linux_arm64: "${hashes.linuxArm64}", + }; + + const _: () = { + let sha256 = "sha256".as_bytes(); + + let mut i = 0; + let hashes = HASHES.all(); + + while i < hashes.len() { + let hash = hashes[i].as_bytes(); + let mut j = 0; + + while j < 6 { + if hash[j] != sha256[j] { + panic!("Hash algorithm is not sha256"); + } + j += 1; + } + i += 1; + } + }; +`; + +await Deno.writeTextFile(file, newContent); + +if (currentVersion !== versionNoV) { + console.log("Version updated to ", versionNoV); +} + +await import("./format.js");