├── .gitignore ├── tests ├── json_module_project │ ├── data.json │ ├── mod.ts │ └── mod.test.ts ├── import_map_project │ ├── add │ │ ├── mod.ts │ │ └── other.ts │ ├── mod.ts │ ├── import_map.json │ └── mod_testfile.ts ├── workspace_project │ ├── add │ │ ├── deno.json │ │ └── mod.ts │ └── deno.json ├── bin_shebang_project │ └── main.ts ├── polyfill_import_meta_project │ ├── mod.ts │ └── mod.test.ts ├── declaration_import_project │ ├── types.d.ts │ └── mod.ts ├── module_mappings_project │ ├── node_file.ts │ ├── output.deno.ts │ ├── mod.ts │ ├── output.node.ts │ └── mod.test.ts ├── test_project │ ├── mod.ts │ └── mod.test.ts ├── polyfill_array_from_async_project │ ├── mod.ts │ └── mod_test.ts ├── shim_project │ ├── ArrayBuffer.ts │ ├── mod.ts │ └── mod.test.ts ├── tla_project │ ├── mod.ts │ └── mod.test.ts ├── using_decl_project │ ├── mod.ts │ └── mod.test.ts ├── package_mappings_project │ ├── mod.test.ts │ └── mod.ts ├── jsr_project │ ├── mod.test.ts │ └── mod.ts ├── undici_project │ └── mod.ts ├── polyfill_project │ ├── mod.ts │ └── mod.test.ts ├── node_types_project │ └── main.ts ├── web_socket_server.ts ├── polyfill_array_find_last_project │ ├── mod.ts │ └── mod_test.ts └── web_socket_project │ ├── mod.test.ts │ └── mod.ts ├── .rustfmt.toml ├── wasm ├── .gitignore ├── src │ └── utils.rs ├── helpers.js └── Cargo.toml ├── rust-toolchain.toml ├── .gitattributes ├── rs-lib ├── src │ ├── polyfills │ │ ├── scripts │ │ │ ├── esnext.error-cause.ts │ │ │ ├── esnext.object-has-own.ts │ │ │ ├── es2021.promise-withResolvers.ts │ │ │ ├── es2021.string-replaceAll.ts │ │ │ ├── esnext.array-fromAsync.ts │ │ │ └── esnext.array-findLast.ts │ │ ├── import_meta.rs │ │ ├── error_cause.rs │ │ ├── string_replace_all.rs │ │ ├── array_find_last.rs │ │ ├── object_has_own.rs │ │ ├── array_from_async.rs │ │ ├── promise_with_resolvers.rs │ │ └── mod.rs │ ├── analyze │ │ ├── mod.rs │ │ ├── get_ignore_line_indexes.rs │ │ ├── get_top_level_decls.rs │ │ └── helpers.rs │ ├── visitors │ │ ├── mod.rs │ │ ├── polyfill.rs │ │ ├── deno_comment_directives.rs │ │ └── imports_exports.rs │ ├── parser.rs │ ├── scripts │ │ ├── createMergeProxy.ts │ │ └── createMergeProxy.test.ts │ ├── loader │ │ └── mod.rs │ ├── declaration_file_resolution.rs │ ├── specifiers.rs │ └── graph.rs ├── Cargo.toml └── tests │ └── integration │ ├── mod.rs │ ├── in_memory_loader.rs │ └── test_builder.rs ├── CONTRIBUTING.md ├── .vscode └── settings.json ├── .clippy.toml ├── Cargo.toml ├── LICENSE ├── lib ├── test_utils.ts ├── utils.test.ts ├── types.ts ├── npm_ignore.ts ├── compiler_transforms.ts ├── shims.test.ts ├── compiler_transforms.test.ts ├── utils.ts ├── compiler.test.ts ├── test_runner │ ├── test_runner.test.ts │ ├── get_test_runner_code.ts │ ├── get_test_runner_code.test.ts │ └── test_runner.ts ├── npm_ignore.test.ts ├── compiler.ts ├── package_json.ts └── shims.ts ├── deno.jsonc ├── .github └── workflows │ └── ci.yml ├── transform.ts └── deno.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .vscode 3 | tests/*/npm 4 | lib/pkg 5 | -------------------------------------------------------------------------------- /tests/json_module_project/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "prop": 5 3 | } 4 | -------------------------------------------------------------------------------- /tests/import_map_project/add/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./other.ts"; 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | edition = "2021" 4 | -------------------------------------------------------------------------------- /tests/import_map_project/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "https://localhost/mod.ts"; 2 | -------------------------------------------------------------------------------- /tests/workspace_project/add/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "exports": "./mod.ts" 3 | } 4 | -------------------------------------------------------------------------------- /tests/bin_shebang_project/main.ts: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/node 2 | console.log("Hello!"); 3 | -------------------------------------------------------------------------------- /tests/polyfill_import_meta_project/mod.ts: -------------------------------------------------------------------------------- 1 | export const isMain = import.meta.main; 2 | -------------------------------------------------------------------------------- /wasm/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | bin/ 5 | wasm-pack.log 6 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.86.0" 3 | components = ["clippy", "rustfmt"] 4 | -------------------------------------------------------------------------------- /tests/declaration_import_project/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface MyType { 2 | prop: string; 3 | } 4 | -------------------------------------------------------------------------------- /tests/module_mappings_project/node_file.ts: -------------------------------------------------------------------------------- 1 | export function getValue() { 2 | return "node"; 3 | } 4 | -------------------------------------------------------------------------------- /tests/module_mappings_project/output.deno.ts: -------------------------------------------------------------------------------- 1 | export function output() { 2 | return "deno"; 3 | } 4 | -------------------------------------------------------------------------------- /tests/import_map_project/add/other.ts: -------------------------------------------------------------------------------- 1 | export function add(a: number, b: number) { 2 | return a + b; 3 | } 4 | -------------------------------------------------------------------------------- /tests/import_map_project/import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "https://localhost/": "./add/" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/module_mappings_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | export { output } from "./output.deno.ts"; 4 | -------------------------------------------------------------------------------- /tests/workspace_project/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspace": ["./add"], 3 | "imports": { 4 | "@std/assert": "jsr:@std/assert@1.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # exclude these from the language stats 2 | lib/pkg/dnt_wasm_bg.ts linguist-vendored 3 | lib/pkg/dnt_wasm.generated.js linguist-vendored 4 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/scripts/esnext.error-cause.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Error { 3 | cause?: unknown; 4 | } 5 | } 6 | 7 | export {}; 8 | -------------------------------------------------------------------------------- /tests/module_mappings_project/output.node.ts: -------------------------------------------------------------------------------- 1 | import { getValue } from "./node_file.ts"; 2 | 3 | export function output() { 4 | return getValue(); 5 | } 6 | -------------------------------------------------------------------------------- /tests/test_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | export function add(a: number, b: number) { 4 | return a + b; 5 | } 6 | -------------------------------------------------------------------------------- /tests/polyfill_array_from_async_project/mod.ts: -------------------------------------------------------------------------------- 1 | export function fromAsync(generator: AsyncIterable): Promise { 2 | return Array.fromAsync(generator); 3 | } 4 | -------------------------------------------------------------------------------- /tests/shim_project/ArrayBuffer.ts: -------------------------------------------------------------------------------- 1 | export class ArrayBuffer { 2 | constructor(_length: number) { 3 | } 4 | 5 | byteLength = 100; // pick some random number to test for in the tests 6 | } 7 | -------------------------------------------------------------------------------- /tests/tla_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | await new Promise((resolve) => resolve()); 4 | 5 | export function add(a: number, b: number) { 6 | return a + b; 7 | } 8 | -------------------------------------------------------------------------------- /tests/workspace_project/add/mod.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "@std/assert"; 2 | 3 | export function add(a: number, b: number) { 4 | const value = a + b; 5 | assertEquals(value, a + b); 6 | return value; 7 | } 8 | -------------------------------------------------------------------------------- /tests/using_decl_project/mod.ts: -------------------------------------------------------------------------------- 1 | (Symbol as any).dispose ??= Symbol("dispose"); 2 | 3 | export class DisposableClass { 4 | wasDisposed = false; 5 | 6 | [Symbol.dispose]() { 7 | this.wasDisposed = true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/tla_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { add } from "./mod.ts"; 4 | 5 | Deno.test("should add in test project", () => { 6 | if (add(1, 2) !== 3) { 7 | throw new Error("FAIL"); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /rs-lib/src/analyze/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | mod get_ignore_line_indexes; 4 | mod get_top_level_decls; 5 | mod helpers; 6 | 7 | pub use get_ignore_line_indexes::*; 8 | pub use get_top_level_decls::*; 9 | pub use helpers::*; 10 | -------------------------------------------------------------------------------- /tests/package_mappings_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { getResult } from "./mod.ts"; 4 | 5 | Deno.test("should get the result", () => { 6 | if (getResult() !== "test") { 7 | throw new Error("fail"); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Install [Deno](https://deno.com/). 4 | 1. Install [Rust](https://www.rust-lang.org/learn/get-started). 5 | 1. Run `deno task build` to build the Wasm file from the Rust code. 6 | 1. Run `deno task test` to run JS tests or `cargo test` to run Rust tests. 7 | -------------------------------------------------------------------------------- /tests/jsr_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { add } from "./mod.ts"; 4 | import { assertEquals } from "jsr:@std/assert@0.221/assert-equals"; 5 | 6 | Deno.test("should add in test project", () => { 7 | assertEquals(add(1, 2), 3); 8 | }); 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": false, 4 | "deno.suggest.imports.hosts": { 5 | "https://deno.land": false 6 | }, 7 | "[rust]": { 8 | "editor.defaultFormatter": "rust-lang.rust-analyzer", 9 | "editor.formatOnSave": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/import_map_project/mod_testfile.ts: -------------------------------------------------------------------------------- 1 | // this file is not named .test so it won't be picked up by regular `deno test` in root dir of dnt 2 | import { add } from "./mod.ts"; 3 | 4 | Deno.test("should add", () => { 5 | if (add(1, 2) !== 3) { 6 | throw new Error("Didn't equal."); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /tests/undici_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | export async function getJson(url: string) { 4 | const response = await fetch(url); 5 | if (!response.ok) { 6 | throw new Error(response.statusText); 7 | } 8 | return response.json(); 9 | } 10 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | # Prefer using `SourcePos` from deno_ast because it abstracts 2 | # away swc's non-zero-indexed based positioning 3 | disallowed-methods = [ 4 | "swc_common::Spanned::span", 5 | ] 6 | disallowed-types = [ 7 | "swc_common::BytePos", 8 | "swc_common::Span", 9 | "swc_common::Spanned", 10 | ] 11 | -------------------------------------------------------------------------------- /rs-lib/src/visitors/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | mod deno_comment_directives; 4 | mod globals; 5 | mod imports_exports; 6 | mod polyfill; 7 | 8 | pub use deno_comment_directives::*; 9 | pub use globals::*; 10 | pub use imports_exports::*; 11 | pub use polyfill::*; 12 | -------------------------------------------------------------------------------- /tests/json_module_project/mod.ts: -------------------------------------------------------------------------------- 1 | import jsonData from "./data.json" with { type: "json" }; 2 | 3 | export function getOutput() { 4 | return jsonData.prop; 5 | } 6 | 7 | export async function getDynamicOutput() { 8 | const module = await import("./data.json", { with: { type: "json" } }); 9 | return module.default.prop; 10 | } 11 | -------------------------------------------------------------------------------- /tests/package_mappings_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | import CodeBlockWriter from "https://deno.land/x/code_block_writer@11.0.0/mod.ts"; 3 | import { using } from "npm:using-statement@^0.4"; 4 | 5 | export function getResult() { 6 | console.log(using); 7 | return new CodeBlockWriter().write("test").toString(); 8 | } 9 | -------------------------------------------------------------------------------- /tests/polyfill_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | export function hasOwn(a: { prop?: number }) { 4 | try { 5 | return Object.hasOwn(a, "prop"); 6 | } catch (err) { 7 | (err as any).cause = new Error("test"); 8 | } 9 | } 10 | 11 | export function withResolvers() { 12 | return Promise.withResolvers(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/using_decl_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | import { DisposableClass } from "./mod.ts"; 2 | 3 | Deno.test("disposable", () => { 4 | const disposable = new DisposableClass(); 5 | { 6 | using inner = disposable; 7 | if (inner.wasDisposed) { 8 | throw new Error("Failed."); 9 | } 10 | } 11 | if (!disposable.wasDisposed) { 12 | throw new Error("Failed."); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /tests/node_types_project/main.ts: -------------------------------------------------------------------------------- 1 | import { join } from "https://deno.land/std@0.142.0/node/path.ts"; 2 | import fs from "https://deno.land/std@0.142.0/node/fs.ts"; 3 | 4 | console.log(join("test", "other")); 5 | fs.writeFileSync("test.txt", "test"); 6 | 7 | const data = new TextDecoder().decode(new TextEncoder().encode("test")); 8 | if (data !== "test") { 9 | throw new Error("ERROR"); 10 | } 11 | -------------------------------------------------------------------------------- /tests/jsr_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { parse } from "jsr:@std/csv/parse"; 4 | import { assertEquals } from "jsr:@std/assert@0.221/assert-equals"; 5 | import * as fs from "node:fs"; 6 | 7 | export function add(a: number, b: number) { 8 | console.log(fs.readFileSync); 9 | const result = parse("a,b,c\n1,2,3\n4,5,6"); 10 | assertEquals(result[0], ["a", "b", "c"]); 11 | return a + b; 12 | } 13 | -------------------------------------------------------------------------------- /wasm/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn set_panic_hook() { 2 | // When the `console_error_panic_hook` feature is enabled, we can call the 3 | // `set_panic_hook` function at least once during initialization, and then 4 | // we will get better error messages if our code ever panics. 5 | // 6 | // For more details see 7 | // https://github.com/rustwasm/console_error_panic_hook#readme 8 | #[cfg(feature = "console_error_panic_hook")] 9 | console_error_panic_hook::set_once(); 10 | } 11 | -------------------------------------------------------------------------------- /tests/declaration_import_project/mod.ts: -------------------------------------------------------------------------------- 1 | import type { MyType } from "./types.d.ts"; 2 | import type { 3 | RawSourceMap, 4 | SourceMapUrl, 5 | } from "https://esm.sh/source-map@0.7.3/source-map.d.ts"; 6 | 7 | export function main(): MyType { 8 | return { prop: "" }; 9 | } 10 | 11 | export function other(): RawSourceMap { 12 | const _test: SourceMapUrl = ""; 13 | return { 14 | file: "", 15 | mappings: "", 16 | names: [], 17 | sources: [], 18 | version: 2, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /tests/module_mappings_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { isDeno } from "https://deno.land/x/which_runtime@0.2.0/mod.ts"; 4 | import { output } from "./mod.ts"; 5 | 6 | Deno.test("should add in test project", () => { 7 | if (isDeno) { 8 | if (output() !== "deno") { 9 | throw new Error("Invalid output."); 10 | } 11 | } else { 12 | if (output() !== "node") { 13 | throw new Error("Invalid output."); 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /wasm/helpers.js: -------------------------------------------------------------------------------- 1 | export async function fetch_specifier(specifier, headers) { 2 | try { 3 | console.error("Downloading", specifier); 4 | const response = await fetch(specifier, { 5 | headers, 6 | redirect: "manual", 7 | }); 8 | const status = response.status; 9 | const body = await response.bytes(); 10 | return { 11 | status, 12 | body, 13 | headers: response.headers, 14 | }; 15 | } catch (err) { 16 | return { 17 | error: err.toString(), 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/polyfill_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { assertEquals } from "https://deno.land/std@0.181.0/testing/asserts.ts"; 4 | import { hasOwn, withResolvers } from "./mod.ts"; 5 | 6 | Deno.test("should test the polyfill", () => { 7 | assertEquals(hasOwn({}), false); 8 | assertEquals(hasOwn({ prop: 5 }), true); 9 | }); 10 | 11 | Deno.test("with resolvers", async () => { 12 | const { promise, resolve } = withResolvers(); 13 | setTimeout(() => resolve(5), 10); 14 | const value = await promise; 15 | assertEquals(value, 5); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/json_module_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | import { getDynamicOutput, getOutput } from "./mod.ts"; 2 | 3 | Deno.test("should get output", () => { 4 | // @ts-expect-error: not assignable to string 5 | const invalidValue: string = getOutput(); 6 | console.log(invalidValue); 7 | // is assignable to number 8 | const value: number = getOutput(); 9 | 10 | if (value !== 5) { 11 | throw new Error("Was not expected output."); 12 | } 13 | }); 14 | 15 | Deno.test("should get dynamic output", async () => { 16 | if ((await getDynamicOutput()) !== 5) { 17 | throw new Error("Was not expected output."); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /tests/web_socket_server.ts: -------------------------------------------------------------------------------- 1 | const port = 8089; 2 | Deno.serve({ port }, handleReq); 3 | console.log("Ready"); 4 | 5 | function handleReq(req: Request): Response { 6 | const upgrade = req.headers.get("upgrade") || ""; 7 | if (upgrade.toLowerCase() !== "websocket") { 8 | return new Response("request isn't trying to upgrade to websocket."); 9 | } 10 | const { socket, response } = Deno.upgradeWebSocket(req); 11 | let value = 0; 12 | socket.onmessage = (e) => { 13 | console.error("socket message:", e.data); 14 | value++; 15 | socket.send(value.toString()); 16 | }; 17 | socket.onerror = (e) => { 18 | console.error("Had error:", (e as any).message); 19 | }; 20 | return response; 21 | } 22 | -------------------------------------------------------------------------------- /tests/polyfill_array_from_async_project/mod_test.ts: -------------------------------------------------------------------------------- 1 | import { fromAsync } from "./mod.ts"; 2 | 3 | function assertEquals(a: unknown, b: unknown) { 4 | if (a !== b) { 5 | throw new Error(`${a} did not equal ${b}`); 6 | } 7 | } 8 | 9 | Deno.test("should get array from async generator", async () => { 10 | // example from https://www.npmjs.com/package/array-from-async 11 | async function* generator() { 12 | for (let i = 0; i < 4; i++) { 13 | yield i * 2; 14 | } 15 | } 16 | 17 | const result = await fromAsync(generator()); 18 | assertEquals(result.length, 4); 19 | assertEquals(result[0], 0); 20 | assertEquals(result[1], 2); 21 | assertEquals(result[2], 4); 22 | assertEquals(result[3], 6); 23 | }); 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [profile.release] 2 | codegen-units = 1 3 | incremental = true 4 | lto = true 5 | opt-level = "z" 6 | 7 | [workspace] 8 | resolver = "2" 9 | members = [ 10 | "rs-lib", 11 | "wasm", 12 | ] 13 | 14 | [workspace.dependencies] 15 | async-trait = "0.1.88" 16 | deno_cache_dir = "0.23.0" 17 | deno_config = "0.61.0" 18 | deno_error = { version = "0.6.1", features = ["serde", "serde_json", "url"] } 19 | deno_graph = { version = "0.96.2", features = ["swc"], default-features = false } 20 | deno_path_util = "0.4.0" 21 | deno_resolver = { version = "0.42.0", features = ["graph"] } 22 | serde_json = { version = "1.0.140", features = ["preserve_order"] } 23 | sys_traits = { version = "0.1.17", features = ["real"] } 24 | url = { version = "2.5.4", features =["serde"] } 25 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/import_meta.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::view::Expr; 4 | use deno_ast::view::Node; 5 | 6 | use super::Polyfill; 7 | use super::PolyfillVisitContext; 8 | use crate::ScriptTarget; 9 | 10 | pub struct ImportMetaPolyfill; 11 | 12 | impl Polyfill for ImportMetaPolyfill { 13 | fn use_for_target(&self, _target: ScriptTarget) -> bool { 14 | true 15 | } 16 | 17 | fn visit_node(&self, node: Node, _context: &PolyfillVisitContext) -> bool { 18 | if let Node::MemberExpr(expr) = node { 19 | if let Expr::MetaProp(_meta) = expr.obj { 20 | return true; 21 | } 22 | } 23 | false 24 | } 25 | 26 | fn get_file_text(&self) -> &'static str { 27 | include_str!("./scripts/deno.import-meta.ts") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rs-lib/src/parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use anyhow::Result; 4 | use deno_ast::parse_program; 5 | use deno_ast::ParseDiagnostic; 6 | use deno_ast::ParseParams; 7 | use deno_ast::ParsedSource; 8 | use deno_graph::ast::EsParser; 9 | use deno_graph::ast::ParseOptions; 10 | 11 | #[derive(Default, Copy, Clone)] 12 | pub struct ScopeAnalysisParser; 13 | 14 | impl EsParser for ScopeAnalysisParser { 15 | fn parse_program( 16 | &self, 17 | options: ParseOptions, 18 | ) -> Result { 19 | parse_program(ParseParams { 20 | specifier: options.specifier.clone(), 21 | text: options.source, 22 | media_type: options.media_type, 23 | capture_tokens: true, 24 | scope_analysis: true, 25 | maybe_syntax: None, 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/error_cause.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::view::Node; 4 | use deno_ast::SourceRanged; 5 | 6 | use super::Polyfill; 7 | use super::PolyfillVisitContext; 8 | use crate::ScriptTarget; 9 | 10 | pub struct ErrorCausePolyfill; 11 | 12 | impl Polyfill for ErrorCausePolyfill { 13 | fn use_for_target(&self, _target: ScriptTarget) -> bool { 14 | true 15 | } 16 | 17 | fn visit_node(&self, node: Node, context: &PolyfillVisitContext) -> bool { 18 | if let Node::MemberExpr(expr) = node { 19 | // very simple detection as we don't have type checking 20 | if expr.prop.text_fast(context.program) == "cause" { 21 | return true; 22 | } 23 | } 24 | false 25 | } 26 | 27 | fn get_file_text(&self) -> &'static str { 28 | include_str!("./scripts/esnext.error-cause.ts") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/scripts/esnext.object-has-own.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/tc39/proposal-accessible-object-hasownproperty/blob/main/polyfill.js 2 | if (!Object.hasOwn) { 3 | Object.defineProperty(Object, "hasOwn", { 4 | value: function (object: any, property: any) { 5 | if (object == null) { 6 | throw new TypeError("Cannot convert undefined or null to object"); 7 | } 8 | return Object.prototype.hasOwnProperty.call(Object(object), property); 9 | }, 10 | configurable: true, 11 | enumerable: false, 12 | writable: true, 13 | }); 14 | } 15 | 16 | declare global { 17 | interface Object { 18 | /** 19 | * Determines whether an object has a property with the specified name. 20 | * @param o An object. 21 | * @param v A property name. 22 | */ 23 | hasOwn(o: object, v: PropertyKey): boolean; 24 | } 25 | } 26 | 27 | export {}; 28 | -------------------------------------------------------------------------------- /tests/polyfill_array_find_last_project/mod.ts: -------------------------------------------------------------------------------- 1 | export function findLast( 2 | items: T[], 3 | predicate: (value: T, index: number, obj: T[]) => unknown, 4 | thisArg?: any, 5 | ): T | undefined; 6 | export function findLast( 7 | items: T[], 8 | predicate: (this: void, value: T, index: number, obj: T[]) => value is S, 9 | thisArg?: any, 10 | ): T | undefined; 11 | export function findLast( 12 | items: T[], 13 | predicate: (value: T, index: number, obj: T[]) => unknown, 14 | thisArg?: any, 15 | ): T | undefined { 16 | const index = items.findLastIndex(predicate, thisArg); 17 | const value = items.findLast(predicate, thisArg); 18 | if (items[index] !== value) { 19 | throw new Error( 20 | `The returned value ${value} did not equal the element at index ${index} (${ 21 | items[index] 22 | }).`, 23 | ); 24 | } 25 | return value; 26 | } 27 | -------------------------------------------------------------------------------- /tests/polyfill_import_meta_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | const assert = (ok: boolean) => { 2 | if (!ok) { 3 | throw new Error("no ok"); 4 | } 5 | }; 6 | 7 | Deno.test("import.meta expression", () => { 8 | assert( 9 | eval("typeof Deno") === "object" ? true : function () { 10 | return import.meta.main; 11 | }.toString().includes("import-meta-ponyfill"), 12 | ); 13 | }); 14 | 15 | Deno.test("import.meta.main", () => { 16 | assert(typeof import.meta.main === "boolean"); 17 | }); 18 | 19 | Deno.test("import.meta.url", () => { 20 | assert(typeof import.meta.url === "string"); 21 | }); 22 | 23 | Deno.test("import.meta.resolve", () => { 24 | assert(typeof import.meta.resolve === "function"); 25 | }); 26 | 27 | Deno.test("import.meta.filename", () => { 28 | assert(typeof import.meta.filename === "string"); 29 | }); 30 | 31 | Deno.test("import.meta.dirname", () => { 32 | assert(typeof import.meta.dirname === "string"); 33 | }); 34 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/scripts/es2021.promise-withResolvers.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | // https://github.com/denoland/deno/blob/0bfa0cc0276e94f1a308aaad5f925eaacb6e3db2/cli/tsc/dts/lib.es2021.promise.d.ts#L53 3 | interface PromiseConstructor { 4 | /** 5 | * Creates a Promise that can be resolved or rejected using provided functions. 6 | * @returns An object containing `promise` promise object, `resolve` and `reject` functions. 7 | */ 8 | withResolvers(): { promise: Promise, resolve: (value: T | PromiseLike) => void, reject: (reason?: any) => void }; 9 | } 10 | } 11 | 12 | // https://github.com/tc39/proposal-promise-with-resolvers/blob/3a78801e073e99217dbeb2c43ba7212f3bdc8b83/polyfills.js#L1C1-L9C2 13 | if (Promise.withResolvers === undefined) { 14 | Promise.withResolvers = () => { 15 | const out: any = {}; 16 | out.promise = new Promise((resolve_, reject_) => { 17 | out.resolve = resolve_; 18 | out.reject = reject_; 19 | }); 20 | return out; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/string_replace_all.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::view::Callee; 4 | use deno_ast::view::Expr; 5 | use deno_ast::view::Node; 6 | use deno_ast::SourceRanged; 7 | 8 | use super::Polyfill; 9 | use super::PolyfillVisitContext; 10 | use crate::ScriptTarget; 11 | 12 | pub struct StringReplaceAllPolyfill; 13 | 14 | impl Polyfill for StringReplaceAllPolyfill { 15 | fn use_for_target(&self, target: ScriptTarget) -> bool { 16 | (target as u32) < (ScriptTarget::ES2021 as u32) 17 | } 18 | 19 | fn visit_node(&self, node: Node, context: &PolyfillVisitContext) -> bool { 20 | if let Node::CallExpr(expr) = node { 21 | if let Callee::Expr(Expr::Member(callee)) = expr.callee { 22 | if expr.args.len() == 2 23 | && callee.prop.text_fast(context.program) == "replaceAll" 24 | { 25 | return true; 26 | } 27 | } 28 | } 29 | false 30 | } 31 | 32 | fn get_file_text(&self) -> &'static str { 33 | include_str!("./scripts/es2021.string-replaceAll.ts") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/array_find_last.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::view::Callee; 4 | use deno_ast::view::Expr; 5 | use deno_ast::view::Node; 6 | use deno_ast::SourceRanged; 7 | 8 | use super::Polyfill; 9 | use super::PolyfillVisitContext; 10 | use crate::ScriptTarget; 11 | 12 | pub struct ArrayFindLastPolyfill; 13 | 14 | impl Polyfill for ArrayFindLastPolyfill { 15 | fn use_for_target(&self, _target: ScriptTarget) -> bool { 16 | true 17 | } 18 | 19 | fn visit_node(&self, node: Node, context: &PolyfillVisitContext) -> bool { 20 | if let Node::CallExpr(expr) = node { 21 | if let Callee::Expr(Expr::Member(callee)) = expr.callee { 22 | if matches!(expr.args.len(), 1 | 2) 23 | && matches!( 24 | callee.prop.text_fast(context.program), 25 | "findLast" | "findLastIndex" 26 | ) 27 | { 28 | return true; 29 | } 30 | } 31 | } 32 | false 33 | } 34 | 35 | fn get_file_text(&self) -> &'static str { 36 | include_str!("./scripts/esnext.array-findLast.ts") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2018-2025 the Deno authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/test_utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | import { assertEquals } from "@std/assert"; 3 | 4 | export function wildcardAssertEquals(actual: string, expected: string) { 5 | const parts = expected.split("[WILDCARD]"); 6 | let index = 0; 7 | for (const part of parts) { 8 | if (part.length === 0) { 9 | continue; 10 | } 11 | 12 | if (index === 0) { 13 | assertEquals(actual.substring(0, part.length), part); 14 | index = part.length; 15 | } else { 16 | let foundIndex = undefined; 17 | while (true) { 18 | const nextIndex = actual.indexOf(part, (foundIndex ?? index) + 1); 19 | if (nextIndex === -1) { 20 | break; 21 | } else { 22 | foundIndex = nextIndex; 23 | } 24 | } 25 | if (foundIndex == null) { 26 | throw new Error(`Could not find part: ${part}`); 27 | } 28 | index = foundIndex + part.length; 29 | } 30 | } 31 | if (index !== actual.length && parts[parts.length - 1].length > 0) { 32 | throw new Error( 33 | `Text was missing end of text. ${index} -- ${actual.length}`, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rs-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "deno_node_transform" 3 | version = "0.6.0" 4 | authors = ["the Deno authors"] 5 | edition = "2021" 6 | license = "MIT" 7 | repository = "https://github.com/denoland/dnt" 8 | description = "Deno to Node/canonical TypeScript transform." 9 | 10 | [features] 11 | default = ["serialization"] 12 | serialization = ["serde"] 13 | 14 | [dependencies] 15 | anyhow = "1.0.70" 16 | async-trait.workspace = true 17 | base64 = "0.13.1" 18 | deno_ast = { version = "0.48.1", features = ["transforms", "view", "visit", "utils"] } 19 | deno_cache_dir.workspace = true 20 | deno_config.workspace = true 21 | deno_error.workspace = true 22 | deno_graph.workspace = true 23 | deno_path_util.workspace = true 24 | deno_resolver.workspace = true 25 | deno_semver = "0.7.1" 26 | node_resolver = "0.49.0" 27 | futures = "0.3.25" 28 | import_map = { version = "0.22.0", features = ["ext"] } 29 | jsonc-parser = { version = "0.26.2", features = ["serde"] } 30 | once_cell = "1.17.1" 31 | pathdiff = "0.2.1" 32 | regex = "1.7" 33 | serde = { version = "1.0.159", features = ["derive"], optional = true } 34 | serde_json.workspace = true 35 | sys_traits.workspace = true 36 | url.workspace = true 37 | 38 | [dev-dependencies] 39 | pretty_assertions = "1.3.0" 40 | sys_traits = { workspace = true, features = ["memory"] } 41 | tokio = { version = "1", features = ["full"] } 42 | -------------------------------------------------------------------------------- /rs-lib/src/analyze/get_ignore_line_indexes.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::HashSet; 4 | 5 | use deno_ast::view::*; 6 | use deno_ast::RootNode; 7 | use deno_ast::SourceRangedForSpanned; 8 | 9 | pub struct IgnoredLineIndexes { 10 | pub warnings: Vec, 11 | pub line_indexes: HashSet, 12 | } 13 | 14 | pub fn get_ignore_line_indexes( 15 | specifier: &str, 16 | program: Program, 17 | ) -> IgnoredLineIndexes { 18 | let mut warnings = Vec::new(); 19 | let mut line_indexes = HashSet::new(); 20 | for comment in program.comment_container().all_comments() { 21 | let lowercase_text = comment.text.trim().to_lowercase(); 22 | let starts_with_deno_shim_ignore = 23 | lowercase_text.starts_with("deno-shim-ignore"); 24 | if starts_with_deno_shim_ignore 25 | || lowercase_text.starts_with("dnt-shim-ignore") 26 | { 27 | if let Some(next_token) = comment.next_token_fast(program) { 28 | line_indexes.insert(next_token.span.lo.start_line_fast(program)); 29 | } 30 | } 31 | if starts_with_deno_shim_ignore { 32 | warnings.push( 33 | format!("deno-shim-ignore has been renamed to dnt-shim-ignore. Please rename it in {}", specifier) 34 | ); 35 | } 36 | } 37 | IgnoredLineIndexes { 38 | warnings, 39 | line_indexes, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/test_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { add } from "./mod.ts"; 4 | import { assertEquals } from "https://deno.land/std@0.181.0/testing/asserts.ts"; 5 | 6 | Deno.test("should add in test project", () => { 7 | assertEquals(add(1, 2), 3); 8 | }); 9 | 10 | Deno.test("should get properties on test context", async (t) => { 11 | if (t.name !== "should get properties on test context") { 12 | console.error("Name", t.name); 13 | throw new Error("Test definition name was unexpected."); 14 | } 15 | const url = import.meta.url; 16 | if (t.origin !== url) { 17 | console.log(`Context origin: ${t.origin}`); 18 | console.log(`Import meta url: ${url}`); 19 | throw new Error("Origin was not correct."); 20 | } 21 | if (t.parent !== undefined) { 22 | throw new Error("Parent should have been undefined."); 23 | } 24 | await t.step("inner", (tInner) => { 25 | if (tInner.name !== "inner") { 26 | console.error("Name", tInner.name); 27 | throw new Error("Test step definition name was unexpected."); 28 | } 29 | if (tInner.parent !== t) { 30 | throw new Error("The parent was not correct."); 31 | } 32 | }); 33 | }); 34 | 35 | Deno.test({ 36 | name: "should ignore", 37 | ignore: true, 38 | fn() { 39 | throw new Error("did not ignore"); 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /tests/web_socket_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { Client } from "./mod.ts"; 4 | import { assertEquals } from "https://deno.land/std@0.181.0/testing/asserts.ts"; 5 | import * as path from "https://deno.land/std@0.181.0/path/mod.ts"; 6 | import { isDeno } from "https://deno.land/x/which_runtime@0.2.0/mod.ts"; 7 | 8 | Deno.test("should get data from web socket server", async (t) => { 9 | const server = new Deno.Command("deno", { 10 | args: [ 11 | "run", 12 | "-A", 13 | isDeno 14 | ? path.dirname(path.fromFileUrl(import.meta.url)) + 15 | "/../web_socket_server.ts" 16 | : "../../../web_socket_server.ts", 17 | ], 18 | stdout: "piped", 19 | }); 20 | const child = server.spawn(); 21 | 22 | // wait for some output from the server 23 | const stdout = child.stdout.getReader({ mode: "byob" }); 24 | await stdout.read(new Uint8Array(1)); 25 | stdout.releaseLock(); 26 | 27 | for (let i = 0; i < 2; i++) { 28 | await t.step(`attempt ${i + 1}`, async (t) => { 29 | const server = await Client.create(); 30 | 31 | await t.step("should get values", async () => { 32 | assertEquals(await server.getValue(), "1"); 33 | assertEquals(await server.getValue(), "2"); 34 | }); 35 | 36 | await server.close(); 37 | }); 38 | } 39 | 40 | child.kill("SIGTERM"); 41 | await child.output(); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/web_socket_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | // This code isn't bullet proof and is lazily designed 4 | // for the purpose of the test. Don't use it elsewhere. 5 | export class Client { 6 | #ws: WebSocket; 7 | 8 | constructor(ws: WebSocket) { 9 | this.#ws = ws; 10 | } 11 | 12 | static create() { 13 | return new Promise((resolve, reject) => { 14 | const ws = new WebSocket("ws://localhost:8089"); 15 | ws.onerror = (e) => { 16 | reject((e as any).message); 17 | }; 18 | ws.onopen = () => { 19 | resolve(new Client(ws)); 20 | ws.onerror = null; 21 | ws.onopen = null; 22 | }; 23 | }); 24 | } 25 | 26 | close() { 27 | // Attempt to prevent left over ops. 28 | return new Promise((resolve, reject) => { 29 | this.#ws.onerror = (e) => { 30 | reject((e as any).message); 31 | }; 32 | this.#ws.onclose = () => resolve(); 33 | this.#ws.close(); 34 | }); 35 | } 36 | 37 | getValue() { 38 | return new Promise((resolve, reject) => { 39 | this.#ws.onerror = (ev) => { 40 | reject(ev); 41 | }; 42 | this.#ws.onmessage = (ev) => { 43 | resolve(ev.data); 44 | // @ts-ignore: waiting on https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59237 45 | this.#ws.onmessage = null; 46 | }; 47 | this.#ws.send("value"); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/shim_project/mod.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | export function addAsync(a: number, b: number) { 4 | return new Promise((resolve, reject) => { 5 | // The shim should be injected here because setTimeout and setInterval 6 | // return `Timeout` in node.js, but we want them to return `number` 7 | const timeoutResult: number = setTimeout( 8 | () => reject(new Error("fail")), 9 | 50, 10 | ); 11 | const intervalResult: number = setInterval( 12 | () => reject(new Error("fail")), 13 | 50, 14 | ); 15 | clearTimeout(timeoutResult); 16 | clearInterval(intervalResult); 17 | 18 | setTimeout(() => { 19 | resolve(a + b); 20 | }, 100); 21 | }); 22 | } 23 | 24 | export function other() { 25 | type test1 = typeof globalThis.fetch; 26 | type test2 = typeof globalThis; 27 | type test3 = test2["fetch"]; 28 | return fetch; 29 | } 30 | 31 | export async function getCryptoKeyPair( 32 | keyUsages: KeyUsage[], 33 | ): Promise { 34 | const keyPair = await crypto.subtle.generateKey( 35 | { 36 | name: "RSA-OAEP", 37 | modulusLength: 4096, 38 | publicExponent: new Uint8Array([1, 0, 1]), 39 | hash: "SHA-256", 40 | }, 41 | true, 42 | keyUsages, 43 | ); 44 | return keyPair; 45 | } 46 | 47 | export function throwDomException() { 48 | throw new DOMException("My message", "Something"); 49 | } 50 | 51 | export function localShimValue() { 52 | return new ArrayBuffer(5); 53 | } 54 | -------------------------------------------------------------------------------- /lib/utils.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "@std/path"; 2 | import { assertEquals, assertRejects } from "@std/assert"; 3 | import { getDntVersion, runCommand, valueToUrl } from "./utils.ts"; 4 | 5 | Deno.test({ 6 | name: "should error when command doesn't exist", 7 | ignore: Deno.build.os === "windows", 8 | async fn() { 9 | const commandName = "somenonexistentcommandforsure"; 10 | await assertRejects( 11 | () => 12 | runCommand({ 13 | cmd: [commandName], 14 | cwd: Deno.cwd(), 15 | }), 16 | Error, 17 | `Could not find command '${commandName}'. Ensure it is available on the path.`, 18 | ); 19 | }, 20 | }); 21 | 22 | Deno.test("valueToUrl", () => { 23 | assertEquals(valueToUrl("npm:test"), "npm:test"); 24 | assertEquals(valueToUrl("node:path"), "node:path"); 25 | assertEquals(valueToUrl("jsr:@scope/package"), "jsr:@scope/package"); 26 | assertEquals(valueToUrl("https://deno.land"), "https://deno.land"); 27 | assertEquals(valueToUrl("http://deno.land"), "http://deno.land"); 28 | assertEquals( 29 | valueToUrl("test"), 30 | path.toFileUrl(path.resolve("test")).toString(), 31 | ); 32 | assertEquals(valueToUrl("file:///test"), "file:///test"); 33 | }); 34 | 35 | Deno.test("getDntVersion", () => { 36 | assertEquals(getDntVersion("https://deno.land/x/dnt@0.1.0/mod.ts"), "0.1.0"); 37 | assertEquals( 38 | getDntVersion("https://deno.land/x/dnt@20.21.22/mod.ts"), 39 | "20.21.22", 40 | ); 41 | assertEquals(getDntVersion("file:///test/mod.ts"), "dev"); 42 | }); 43 | -------------------------------------------------------------------------------- /wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dnt-wasm" 3 | version = "0.0.0" 4 | authors = ["the Deno authors"] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | anyhow = "1.0.51" 15 | async-trait.workspace = true 16 | deno_cache_dir.workspace = true 17 | deno_config.workspace = true 18 | deno_graph.workspace = true 19 | deno_error.workspace = true 20 | deno_path_util.workspace = true 21 | dnt = { path = "../rs-lib", default-features = false, features = ["serialization"], package="deno_node_transform" } 22 | getrandom = { version = "*", features = ["js"] } 23 | js-sys = "=0.3.77" 24 | wasm-bindgen = { version = "=0.2.100", features = ["serde-serialize"] } 25 | wasm-bindgen-futures = "=0.4.50" 26 | url.workspace = true 27 | 28 | # The `console_error_panic_hook` crate provides better debugging of panics by 29 | # logging them with `console.error`. This is great for development, but requires 30 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 31 | # code size when deploying. 32 | console_error_panic_hook = { version = "0.1.6", optional = true } 33 | 34 | serde = { version = "1.0", features = ["derive"] } 35 | serde-wasm-bindgen = "=0.6.5" 36 | 37 | [target.'cfg(target_arch = "wasm32")'.dependencies] 38 | sys_traits = { workspace = true, features = ["real", "wasm"] } 39 | 40 | # get this compiling when using `cargo check/test` 41 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 42 | sys_traits = { workspace = true, features = ["real", "getrandom", "libc", "winapi"] } 43 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/scripts/es2021.string-replaceAll.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | // https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2021.string.d.ts 3 | interface String { 4 | /** 5 | * Replace all instances of a substring in a string, using a regular expression or search string. 6 | * @param searchValue A string to search for. 7 | * @param replaceValue A string containing the text to replace for every successful match of searchValue in this string. 8 | */ 9 | replaceAll(searchValue: string | RegExp, replaceValue: string): string; 10 | 11 | /** 12 | * Replace all instances of a substring in a string, using a regular expression or search string. 13 | * @param searchValue A string to search for. 14 | * @param replacer A function that returns the replacement text. 15 | */ 16 | replaceAll( 17 | searchValue: string | RegExp, 18 | replacer: (substring: string, ...args: any[]) => string, 19 | ): string; 20 | } 21 | } 22 | 23 | /** 24 | * String.prototype.replaceAll() polyfill 25 | * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ 26 | * @author Chris Ferdinandi 27 | * @license MIT 28 | */ 29 | if (!String.prototype.replaceAll) { 30 | String.prototype.replaceAll = function (str: any, newStr: any) { 31 | // If a regex pattern 32 | if ( 33 | Object.prototype.toString.call(str).toLowerCase() === "[object regexp]" 34 | ) { 35 | return this.replace(str, newStr); 36 | } 37 | 38 | // If a string 39 | return this.replace(new RegExp(str, "g"), newStr); 40 | }; 41 | } 42 | 43 | export {}; 44 | -------------------------------------------------------------------------------- /tests/polyfill_array_find_last_project/mod_test.ts: -------------------------------------------------------------------------------- 1 | import { findLast } from "./mod.ts"; 2 | 3 | function assertEquals(a: unknown, b: unknown) { 4 | if (a !== b) { 5 | throw new Error(`${a} did not equal ${b}`); 6 | } 7 | } 8 | 9 | Deno.test("should find last in array", () => { 10 | assertEquals(findLast([1, 2, 3], () => false), undefined); 11 | assertEquals(findLast([1, 2, 3], () => true), 3); 12 | assertEquals(findLast([1, 2, 3], (value) => value === 1), 1); 13 | assertEquals(findLast([1, 2, 3], (_, index) => index === 1), 2); 14 | assertEquals(findLast([1, 2, 3], (value, _, obj) => obj[0] === value), 1); 15 | assertEquals( 16 | findLast([1, 2, 3], function (this: any) { 17 | if (this !== undefined) { 18 | throw new Error("Was not undefined."); 19 | } 20 | return true; 21 | }), 22 | 3, 23 | ); 24 | assertEquals( 25 | findLast([1, 2, 3], function (this: any[], value) { 26 | return this[0] === value; 27 | }, [2]), 28 | 2, 29 | ); 30 | assertEquals( 31 | findLast([ 32 | { number: 2, other: 0 }, 33 | { number: 2, other: 1 }, 34 | ], (o) => o.number === 2)!.other, 35 | 1, 36 | ); 37 | assertEquals( 38 | findLast([ 39 | { number: 2, other: 0 }, 40 | { number: 2, other: 1 }, 41 | ], (o) => o.number === 3), 42 | undefined, 43 | ); 44 | }); 45 | 46 | Deno.test("should find last for Uint8Array", () => { 47 | assertEquals(new Uint8Array([1, 2, 3]).findLast(() => true), 3); 48 | assertEquals(new Uint8Array([1, 2, 3]).findLastIndex(() => true), 2); 49 | assertEquals(new Uint8Array([1, 2, 3]).findLast((v) => v == 2), 2); 50 | assertEquals(new Uint8Array([1, 2, 3]).findLastIndex((v) => v == 2), 1); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/shim_project/mod.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | import { isDeno } from "https://deno.land/x/which_runtime@0.2.0/mod.ts"; 3 | import { 4 | addAsync, 5 | getCryptoKeyPair, 6 | localShimValue, 7 | throwDomException, 8 | } from "./mod.ts"; 9 | 10 | Deno.test("should add in test project", async () => { 11 | const result = await addAsync(1, 2); 12 | if (result !== 3) { 13 | throw new Error(`Result fail: ${result}`); 14 | } 15 | }); 16 | 17 | Deno.test("should get crypto key pair", async () => { 18 | const value = await getCryptoKeyPair([ 19 | "encrypt" as const, 20 | "decrypt" as const, 21 | ]); 22 | if (value.privateKey == null || value.publicKey == null) { 23 | throw new Error("Was null."); 24 | } 25 | }); 26 | 27 | Deno.test("should shim DOMException", () => { 28 | let err: DOMException | undefined = undefined; 29 | try { 30 | throwDomException(); 31 | } catch (caughtErr) { 32 | err = caughtErr as DOMException; 33 | } 34 | if (!(err instanceof DOMException)) { 35 | throw new Error("Expected to throw DOMException."); 36 | } 37 | }); 38 | 39 | Deno.test("should shim localValue", { ignore: isDeno }, () => { 40 | if (localShimValue().byteLength !== 100) { 41 | throw new Error("Did not equal expected."); 42 | } 43 | }); 44 | 45 | function _testTypes(headers: Headers, blob: Blob) { 46 | // this was previously erroring 47 | const _test: string | null = headers.get("some-header-name"); 48 | const createdHeaders = new Headers(); 49 | const _test2: string | null = createdHeaders.get("some-header-name"); 50 | 51 | const _testBlob1: number = blob.size; 52 | const createdBlob = new Blob([]); 53 | const _testBlob2: number = createdBlob.size; 54 | } 55 | -------------------------------------------------------------------------------- /rs-lib/src/scripts/createMergeProxy.ts: -------------------------------------------------------------------------------- 1 | export function createMergeProxy( 2 | baseObj: T, 3 | extObj: U, 4 | ): Omit & U { 5 | return new Proxy(baseObj, { 6 | get(_target, prop, _receiver) { 7 | if (prop in extObj) { 8 | return (extObj as any)[prop]; 9 | } else { 10 | return (baseObj as any)[prop]; 11 | } 12 | }, 13 | set(_target, prop, value) { 14 | if (prop in extObj) { 15 | delete (extObj as any)[prop]; 16 | } 17 | (baseObj as any)[prop] = value; 18 | return true; 19 | }, 20 | deleteProperty(_target, prop) { 21 | let success = false; 22 | if (prop in extObj) { 23 | delete (extObj as any)[prop]; 24 | success = true; 25 | } 26 | if (prop in baseObj) { 27 | delete (baseObj as any)[prop]; 28 | success = true; 29 | } 30 | return success; 31 | }, 32 | ownKeys(_target) { 33 | const baseKeys = Reflect.ownKeys(baseObj); 34 | const extKeys = Reflect.ownKeys(extObj); 35 | const extKeysSet = new Set(extKeys); 36 | return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys]; 37 | }, 38 | defineProperty(_target, prop, desc) { 39 | if (prop in extObj) { 40 | delete (extObj as any)[prop]; 41 | } 42 | Reflect.defineProperty(baseObj, prop, desc); 43 | return true; 44 | }, 45 | getOwnPropertyDescriptor(_target, prop) { 46 | if (prop in extObj) { 47 | return Reflect.getOwnPropertyDescriptor(extObj, prop); 48 | } else { 49 | return Reflect.getOwnPropertyDescriptor(baseObj, prop); 50 | } 51 | }, 52 | has(_target, prop) { 53 | return prop in extObj || prop in baseObj; 54 | }, 55 | }) as any; 56 | } 57 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/object_has_own.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::view::Node; 4 | 5 | use super::Polyfill; 6 | use super::PolyfillVisitContext; 7 | use crate::ScriptTarget; 8 | 9 | pub struct ObjectHasOwnPolyfill; 10 | 11 | impl Polyfill for ObjectHasOwnPolyfill { 12 | fn use_for_target(&self, _target: ScriptTarget) -> bool { 13 | true 14 | } 15 | 16 | fn visit_node(&self, node: Node, context: &PolyfillVisitContext) -> bool { 17 | context.has_global_property_access(node, "Object", "hasOwn") 18 | } 19 | 20 | fn get_file_text(&self) -> &'static str { 21 | include_str!("./scripts/esnext.object-has-own.ts") 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod test { 27 | use super::*; 28 | use crate::polyfills::PolyfillTester; 29 | 30 | #[test] 31 | pub fn finds_when_matches() { 32 | let tester = 33 | PolyfillTester::new(Box::new(|| Box::new(ObjectHasOwnPolyfill))); 34 | assert_eq!(tester.matches("Object.hasOwn"), true); 35 | assert_eq!(tester.matches("class Object {} Object.hasOwn"), false); 36 | assert_eq!(tester.matches("Other.hasOwn"), false); 37 | assert_eq!(tester.matches("Object.hasOther"), false); 38 | assert_eq!(tester.matches("const { hasOwn } = Object;"), true); 39 | assert_eq!(tester.matches("const { hasOwn: test } = Object;"), true); 40 | assert_eq!(tester.matches("const { \"hasOwn\": test } = Object;"), true); 41 | assert_eq!(tester.matches("const { hasOwn } = other;"), false); 42 | assert_eq!( 43 | tester.matches("class Object {} const { hasOwn } = Object;"), 44 | false 45 | ); 46 | assert_eq!(tester.matches("const { ...rest } = Object;"), true); // unknown, so true 47 | assert_eq!(tester.matches("const { [computed]: test } = Object;"), true); // unknown, so true 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@deno/dnt", 3 | "tasks": { 4 | "test": "deno test -A", 5 | "build": "deno run -A jsr:@deno/wasmbuild@0.19.2 --out lib/pkg" 6 | }, 7 | "lint": { 8 | "rules": { 9 | "exclude": [ 10 | "no-explicit-any", 11 | "camelcase" 12 | ] 13 | } 14 | }, 15 | "format": { 16 | "exclude": [ 17 | "!lib/pkg/" 18 | ] 19 | }, 20 | "publish": { 21 | "exclude": [ 22 | "!lib/pkg/", 23 | "rs-lib/", 24 | "tests/", 25 | "Cargo.lock", 26 | "deno.lock", 27 | ".clippy.toml", 28 | ".rustfmt.toml", 29 | ".github", 30 | ".gitattributes", 31 | "**/*.toml", 32 | "**/*.test.ts" 33 | ] 34 | }, 35 | "exclude": [ 36 | "target/", 37 | "wasm/target/", 38 | "lib/pkg/", 39 | "rs-lib/src/polyfills/scripts/", 40 | "tests/declaration_import_project/npm", 41 | "tests/import_map_project/npm", 42 | "tests/import_meta_project/npm", 43 | "tests/json_module_project/npm", 44 | "tests/module_mappings_project/npm", 45 | "tests/node_types_project/npm", 46 | "tests/package_mappings_project/npm", 47 | "tests/polyfill_array_find_last_project/npm", 48 | "tests/polyfill_project/npm", 49 | "tests/shim_project/npm", 50 | "tests/test_project/npm", 51 | "tests/tla_project/npm", 52 | "tests/undici_project/npm", 53 | "tests/web_socket_project/npm" 54 | ], 55 | "imports": { 56 | "@std/assert": "jsr:@std/assert@1", 57 | "@std/fmt": "jsr:@std/fmt@1", 58 | "@std/fs": "jsr:@std/fs@1", 59 | "@std/path": "jsr:@std/path@1", 60 | "@ts-morph/bootstrap": "jsr:@ts-morph/bootstrap@^0.27.0", 61 | "code-block-writer": "jsr:@david/code-block-writer@^13.0.3" 62 | }, 63 | "exports": { 64 | ".": "./mod.ts", 65 | "./transform": "./transform.ts" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rs-lib/src/scripts/createMergeProxy.test.ts: -------------------------------------------------------------------------------- 1 | import { createMergeProxy } from "./createMergeProxy.ts"; 2 | 3 | Deno.test("should merge two objects", () => { 4 | const baseObj = { 5 | shared1: "base_shared1", 6 | base1: "base_base1", 7 | }; 8 | const extObj = { 9 | shared1: "ext_shared1", 10 | ext1: "ext_ext1", 11 | }; 12 | const merged = createMergeProxy(baseObj, extObj); 13 | 14 | // get 15 | assertEqual(merged.base1, "base_base1"); 16 | assertEqual(merged.shared1, "ext_shared1"); 17 | assertEqual(merged.ext1, "ext_ext1"); 18 | 19 | // keys 20 | const keys = Object.keys(merged); 21 | assertEqual(keys.length, 3); 22 | assertEqual(keys[0], "base1"); 23 | assertEqual(keys[1], "shared1"); 24 | assertEqual(keys[2], "ext1"); 25 | 26 | // has own 27 | assertEqual(Object.hasOwn(merged, "ext1"), true); 28 | assertEqual(Object.hasOwn(merged, "base1"), true); 29 | assertEqual(Object.hasOwn(merged, "random"), false); 30 | 31 | // setting property 32 | merged.ext1 = "asdf"; 33 | assertEqual(merged.ext1, "asdf"); 34 | assertEqual((baseObj as any).ext1, "asdf"); 35 | assertEqual(extObj.ext1, undefined); 36 | 37 | // deleting property 38 | delete (merged as any).shared1; 39 | assertEqual(merged.shared1, undefined); 40 | assertEqual(baseObj.shared1, undefined); 41 | assertEqual(extObj.shared1, undefined); 42 | }); 43 | 44 | Deno.test("should allow spreading globalThis", () => { 45 | const extObj = { 46 | test: 5, 47 | }; 48 | const merged = createMergeProxy(globalThis, extObj); 49 | // was getting an error when not using Reflect.ownKeys 50 | const _result = { ...merged, extObj }; 51 | const { test } = merged; 52 | assertEqual(test, 5); 53 | }); 54 | 55 | function assertEqual(a: any, b: any) { 56 | if (a !== b) { 57 | throw new Error(`The value ${a} did not equal ${b}.`); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/array_from_async.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::view::Node; 4 | 5 | use super::Polyfill; 6 | use super::PolyfillVisitContext; 7 | use crate::ScriptTarget; 8 | 9 | pub struct ArrayFromAsyncPolyfill; 10 | 11 | impl Polyfill for ArrayFromAsyncPolyfill { 12 | fn use_for_target(&self, _target: ScriptTarget) -> bool { 13 | true 14 | } 15 | 16 | fn visit_node(&self, node: Node, context: &PolyfillVisitContext) -> bool { 17 | context.has_global_property_access(node, "Array", "fromAsync") 18 | } 19 | 20 | fn get_file_text(&self) -> &'static str { 21 | include_str!("./scripts/esnext.array-fromAsync.ts") 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod test { 27 | use super::*; 28 | use crate::polyfills::PolyfillTester; 29 | 30 | #[test] 31 | pub fn finds_when_matches() { 32 | let tester = 33 | PolyfillTester::new(Box::new(|| Box::new(ArrayFromAsyncPolyfill))); 34 | assert_eq!(tester.matches("Array.fromAsync"), true); 35 | assert_eq!(tester.matches("class Array {} Array.fromAsync"), false); 36 | assert_eq!(tester.matches("Other.fromAsync"), false); 37 | assert_eq!(tester.matches("Array.hasOther"), false); 38 | assert_eq!(tester.matches("const { fromAsync } = Array;"), true); 39 | assert_eq!(tester.matches("const { fromAsync: test } = Array;"), true); 40 | assert_eq!( 41 | tester.matches("const { \"fromAsync\": test } = Array;"), 42 | true 43 | ); 44 | assert_eq!(tester.matches("const { fromAsync } = other;"), false); 45 | assert_eq!( 46 | tester.matches("class Array {} const { fromAsync } = Array;"), 47 | false 48 | ); 49 | assert_eq!(tester.matches("const { ...rest } = Array;"), true); // unknown, so true 50 | assert_eq!(tester.matches("const { [computed]: test } = Array;"), true); // unknown, so true 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rs-lib/src/visitors/polyfill.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::HashSet; 4 | 5 | use deno_ast::swc::common::SyntaxContext; 6 | use deno_ast::view::*; 7 | 8 | use crate::polyfills::Polyfill; 9 | use crate::polyfills::PolyfillVisitContext; 10 | 11 | pub struct FillPolyfillsParams<'a, 'b> { 12 | pub program: Program<'b>, 13 | pub unresolved_context: SyntaxContext, 14 | pub top_level_decls: &'a HashSet, 15 | pub searching_polyfills: &'a mut Vec>, 16 | pub found_polyfills: &'a mut Vec>, 17 | } 18 | 19 | struct Context<'a, 'b> { 20 | visit_context: PolyfillVisitContext<'a, 'b>, 21 | searching_polyfills: &'a mut Vec>, 22 | found_polyfills: &'a mut Vec>, 23 | } 24 | 25 | pub fn fill_polyfills(params: &mut FillPolyfillsParams) { 26 | let mut context = Context { 27 | visit_context: PolyfillVisitContext { 28 | program: params.program, 29 | unresolved_context: params.unresolved_context, 30 | top_level_decls: params.top_level_decls, 31 | }, 32 | searching_polyfills: params.searching_polyfills, 33 | found_polyfills: params.found_polyfills, 34 | }; 35 | 36 | visit_children(context.visit_context.program.as_node(), &mut context); 37 | } 38 | 39 | fn visit_children(node: Node, context: &mut Context) { 40 | if context.searching_polyfills.is_empty() { 41 | return; 42 | } 43 | 44 | for child in node.children() { 45 | visit_children(child, context); 46 | } 47 | 48 | for i in (0..context.searching_polyfills.len()).rev() { 49 | if context.searching_polyfills[i].visit_node(node, &context.visit_context) { 50 | // move the searching polyfill over to the found one 51 | let found_polyfill = context.searching_polyfills.remove(i); 52 | context.found_polyfills.push(found_polyfill); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: [main] 5 | push: 6 | branches: [main] 7 | tags: 8 | - "*" 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ !contains(github.event.pull_request.labels.*.name, 'test-flaky-ci') && github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macos-latest] 17 | runs-on: ${{ matrix.os }} 18 | timeout-minutes: 30 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: dsherret/rust-toolchain-file@v1 22 | - uses: Swatinem/rust-cache@v2 23 | - uses: denoland/setup-deno@v2 24 | with: 25 | deno-version: 2.x 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: "20" 29 | registry-url: "https://registry.npmjs.org" 30 | 31 | - name: Format 32 | if: matrix.os == 'ubuntu-latest' 33 | run: | 34 | cargo fmt --all -- --check 35 | deno fmt --check 36 | - name: Lint (Cargo) 37 | if: matrix.os == 'ubuntu-latest' 38 | run: cargo clippy --all-targets --all-features --release 39 | 40 | - name: Build 41 | run: deno task build 42 | - name: Lint (Deno) 43 | run: deno lint 44 | - name: Test (Rust) 45 | run: cargo test --all-targets --all-features --release 46 | - name: Test (Deno) 47 | run: deno task test 48 | 49 | jsr: 50 | runs-on: ubuntu-latest 51 | timeout-minutes: 30 52 | permissions: 53 | contents: read 54 | id-token: write 55 | steps: 56 | - uses: actions/checkout@v4 57 | - uses: dsherret/rust-toolchain-file@v1 58 | - uses: Swatinem/rust-cache@v2 59 | - uses: denoland/setup-deno@v2 60 | with: 61 | deno-version: 2.x 62 | 63 | - name: Build 64 | run: deno task build 65 | - name: Publish on tag 66 | run: deno run -A jsr:@david/publish-on-tag@0.2.0 67 | -------------------------------------------------------------------------------- /lib/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | interface PackageJsonPerson { 4 | name: string; 5 | email?: string; 6 | url?: string; 7 | } 8 | 9 | interface PackageJsonBugs { 10 | url?: string; 11 | email?: string; 12 | } 13 | 14 | /** 15 | * Based on version 9.6.6 16 | */ 17 | export interface PackageJson { 18 | name: string; 19 | version: string; 20 | description?: string; 21 | keywords?: string[]; 22 | homepage?: string; 23 | bugs?: PackageJsonBugs | string; 24 | /** 25 | * Check https://spdx.org/licenses/ for valid licences 26 | */ 27 | license?: "MIT" | "ISC" | "UNLICENSED" | string; 28 | author?: PackageJsonPerson | string; 29 | contributors?: (PackageJsonPerson | string)[]; 30 | main?: string; 31 | types?: string; 32 | scripts?: { [key: string]: string }; 33 | repository?: string | { type: string; url: string; directory?: string }; 34 | dependencies?: { [packageName: string]: string }; 35 | devDependencies?: { [packageName: string]: string }; 36 | peerDependencies?: { [packageName: string]: string }; 37 | bundleDependencies?: { [packageName: string]: string }; 38 | optionalDependencies?: { [packageName: string]: string }; 39 | engines?: { [engineName: string]: string }; 40 | /** 41 | * A list of os like "darwin", "linux", "win32", OS names can be prefix by a "!" 42 | */ 43 | os?: string[]; 44 | /** 45 | * A list of cpu like "x64", "ia32", "arm", "mips", CPU names can be prefix by a "!" 46 | */ 47 | cpu?: string[]; 48 | private?: boolean; 49 | /** 50 | * rest of the fields 51 | */ 52 | [propertyName: string]: any; 53 | } 54 | 55 | // NOTICE: make sure to update `ScriptTarget` in the rust code when changing the names on this 56 | // todo(dsherret): code generate this from the Rust code to prevent out of sync issues 57 | 58 | /** Version of ECMAScript to compile the code to. */ 59 | export type ScriptTarget = 60 | | "ES3" 61 | | "ES5" 62 | | "ES2015" 63 | | "ES2016" 64 | | "ES2017" 65 | | "ES2018" 66 | | "ES2019" 67 | | "ES2020" 68 | | "ES2021" 69 | | "ES2022" 70 | | "ES2023" 71 | | "Latest"; 72 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/promise_with_resolvers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::view::Node; 4 | 5 | use super::Polyfill; 6 | use super::PolyfillVisitContext; 7 | use crate::ScriptTarget; 8 | 9 | pub struct PromiseWithResolversPolyfill; 10 | 11 | impl Polyfill for PromiseWithResolversPolyfill { 12 | fn use_for_target(&self, _target: ScriptTarget) -> bool { 13 | // (target as u32) < (ScriptTarget::ES2021 as u32) 14 | true // just always use it for the time being 15 | } 16 | 17 | fn visit_node(&self, node: Node, context: &PolyfillVisitContext) -> bool { 18 | context.has_global_property_access(node, "Promise", "withResolvers") 19 | } 20 | 21 | fn get_file_text(&self) -> &'static str { 22 | include_str!("./scripts/es2021.promise-withResolvers.ts") 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod test { 28 | use super::*; 29 | use crate::polyfills::PolyfillTester; 30 | 31 | #[test] 32 | pub fn finds_when_matches() { 33 | let tester = 34 | PolyfillTester::new(Box::new(|| Box::new(PromiseWithResolversPolyfill))); 35 | assert_eq!(tester.matches("Promise.withResolvers"), true); 36 | assert_eq!( 37 | tester.matches("class Promise {} Promise.withResolvers"), 38 | false 39 | ); 40 | assert_eq!(tester.matches("Other.withResolvers"), false); 41 | assert_eq!(tester.matches("Promise.withResolvers2"), false); 42 | assert_eq!(tester.matches("const { withResolvers } = Promise;"), true); 43 | assert_eq!( 44 | tester.matches("const { withResolvers: test } = Promise;"), 45 | true 46 | ); 47 | assert_eq!( 48 | tester.matches("const { \"withResolvers\": test } = Promise;"), 49 | true 50 | ); 51 | assert_eq!(tester.matches("const { withResolvers } = other;"), false); 52 | assert_eq!( 53 | tester.matches("class Promise {} const { withResolvers } = Promise;"), 54 | false 55 | ); 56 | assert_eq!(tester.matches("const { ...rest } = Promise;"), true); // unknown, so true 57 | assert_eq!( 58 | tester.matches("const { [computed]: test } = Promise;"), 59 | true 60 | ); // unknown, so true 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/npm_ignore.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import type { OutputFile } from "../transform.ts"; 4 | import type { SourceMapOptions } from "./compiler.ts"; 5 | 6 | export function getNpmIgnoreText(options: { 7 | sourceMap?: SourceMapOptions; 8 | inlineSources?: boolean; 9 | testFiles: OutputFile[]; 10 | declaration: "separate" | "inline" | false; 11 | includeScriptModule: boolean | undefined; 12 | includeEsModule: boolean | undefined; 13 | }) { 14 | // Try to make as little of this conditional in case a user edits settings 15 | // to exclude something, but then the output directory still has that file 16 | const lines = []; 17 | if (!isUsingSourceMaps() || options.inlineSources) { 18 | lines.push("/src/"); 19 | } 20 | for (const fileName of getTestFileNames()) { 21 | lines.push(fileName); 22 | } 23 | lines.push("yarn.lock", "pnpm-lock.yaml"); 24 | return Array.from(lines).join("\n") + "\n"; 25 | 26 | function* getTestFileNames() { 27 | for (const file of options.testFiles) { 28 | const filePath = file.filePath.replace(/\.ts$/i, ".js"); 29 | const dtsFilePath = file.filePath.replace(/\.ts$/i, ".d.ts"); 30 | if (options.includeEsModule) { 31 | const esmFilePath = `/esm/${filePath}`; 32 | yield esmFilePath; 33 | if (options.sourceMap === true) { 34 | yield `${esmFilePath}.map`; 35 | } 36 | if (options.declaration === "inline") { 37 | yield `/esm/${dtsFilePath}`; 38 | } 39 | } 40 | if (options.includeScriptModule) { 41 | const scriptFilePath = `/script/${filePath}`; 42 | yield scriptFilePath; 43 | if (options.sourceMap === true) { 44 | yield `${scriptFilePath}.map`; 45 | } 46 | if (options.declaration === "inline") { 47 | yield `/script/${dtsFilePath}`; 48 | } 49 | } 50 | if (options.declaration === "separate") { 51 | yield `/types/${dtsFilePath}`; 52 | } 53 | } 54 | yield "/test_runner.js"; 55 | } 56 | 57 | function isUsingSourceMaps() { 58 | return options?.sourceMap === "inline" || 59 | options?.sourceMap === true; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/compiler_transforms.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { ts } from "@ts-morph/bootstrap"; 4 | 5 | // transform `import.meta.url` to a replacement that works in script modules 6 | export const transformImportMeta: ts.TransformerFactory = ( 7 | context, 8 | ) => { 9 | const factory = context.factory; 10 | const compilerModule = context.getCompilerOptions().module; 11 | const isScriptModule = compilerModule === ts.ModuleKind.CommonJS || 12 | compilerModule === ts.ModuleKind.UMD; 13 | 14 | return (sourceFile) => ts.visitEachChild(sourceFile, visitNode, context); 15 | 16 | function visitNode(node: ts.Node): ts.Node { 17 | // find `import.meta` 18 | if (ts.isMetaProperty(node)) { 19 | if (isScriptModule) { 20 | return getReplacementImportMetaScript(); 21 | } else { 22 | return getReplacementImportMetaEsm(); 23 | } 24 | } 25 | 26 | return ts.visitEachChild(node, visitNode, context); 27 | } 28 | 29 | function getReplacementImportMeta( 30 | symbolFor: string, 31 | argumentsArray: readonly ts.Expression[], 32 | ) { 33 | // Copy and pasted from ts-ast-viewer.com 34 | // globalThis[Symbol.for('import-meta-ponyfill')](...args) 35 | return factory.createCallExpression( 36 | factory.createElementAccessExpression( 37 | factory.createIdentifier("globalThis"), 38 | factory.createCallExpression( 39 | factory.createPropertyAccessExpression( 40 | factory.createIdentifier("Symbol"), 41 | factory.createIdentifier("for"), 42 | ), 43 | undefined, 44 | [factory.createStringLiteral(symbolFor)], 45 | ), 46 | ), 47 | undefined, 48 | argumentsArray, 49 | ); 50 | } 51 | function getReplacementImportMetaScript() { 52 | return getReplacementImportMeta("import-meta-ponyfill-commonjs", [ 53 | factory.createIdentifier("require"), 54 | factory.createIdentifier("module"), 55 | ]); 56 | } 57 | function getReplacementImportMetaEsm() { 58 | return getReplacementImportMeta("import-meta-ponyfill-esmodule", [ 59 | factory.createMetaProperty( 60 | ts.SyntaxKind.ImportKeyword, 61 | factory.createIdentifier("meta"), 62 | ), 63 | ]); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /rs-lib/src/analyze/get_top_level_decls.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::HashSet; 4 | 5 | use deno_ast::swc::common::SyntaxContext; 6 | use deno_ast::view::*; 7 | use deno_ast::SourceRanged; 8 | 9 | pub fn get_top_level_decls( 10 | program: Program, 11 | top_level_context: SyntaxContext, 12 | ) -> HashSet { 13 | let mut results = HashSet::new(); 14 | 15 | visit_children(program.into(), top_level_context, &mut results); 16 | 17 | results 18 | } 19 | 20 | fn visit_children( 21 | node: Node, 22 | top_level_context: SyntaxContext, 23 | results: &mut HashSet, 24 | ) { 25 | if let Node::Ident(ident) = node { 26 | if ident.ctxt() == top_level_context && is_local_declaration_ident(node) { 27 | results.insert(ident.sym().to_string()); 28 | } 29 | } 30 | 31 | for child in node.children() { 32 | visit_children(child, top_level_context, results); 33 | } 34 | } 35 | 36 | fn is_local_declaration_ident(node: Node) -> bool { 37 | if let Some(parent) = node.parent() { 38 | match parent { 39 | Node::BindingIdent(decl) => decl.id.range().contains(&node.range()), 40 | Node::ClassDecl(decl) => decl.ident.range().contains(&node.range()), 41 | Node::ClassExpr(decl) => decl 42 | .ident 43 | .as_ref() 44 | .map(|i| i.range().contains(&node.range())) 45 | .unwrap_or(false), 46 | Node::TsInterfaceDecl(decl) => decl.id.range().contains(&node.range()), 47 | Node::FnDecl(decl) => decl.ident.range().contains(&node.range()), 48 | Node::FnExpr(decl) => decl 49 | .ident 50 | .as_ref() 51 | .map(|i| i.range().contains(&node.range())) 52 | .unwrap_or(false), 53 | Node::TsModuleDecl(decl) => decl.id.range().contains(&node.range()), 54 | Node::TsNamespaceDecl(decl) => decl.id.range().contains(&node.range()), 55 | Node::VarDeclarator(decl) => decl.name.range().contains(&node.range()), 56 | Node::ImportNamedSpecifier(decl) => { 57 | decl.local.range().contains(&node.range()) 58 | } 59 | Node::ImportDefaultSpecifier(decl) => { 60 | decl.local.range().contains(&node.range()) 61 | } 62 | Node::ImportStarAsSpecifier(decl) => decl.range().contains(&node.range()), 63 | Node::KeyValuePatProp(decl) => decl.key.range().contains(&node.range()), 64 | Node::AssignPatProp(decl) => decl.key.range().contains(&node.range()), 65 | _ => false, 66 | } 67 | } else { 68 | false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/shims.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { assertEquals } from "@std/assert"; 4 | import { shimOptionsToTransformShims } from "./shims.ts"; 5 | import type { PackageShim } from "../transform.ts"; 6 | 7 | Deno.test("should get when all true", () => { 8 | const result = shimOptionsToTransformShims({ 9 | deno: true, 10 | timers: true, 11 | prompts: true, 12 | blob: true, 13 | crypto: true, 14 | domException: true, 15 | undici: true, 16 | weakRef: true, 17 | webSocket: true, 18 | custom: [{ 19 | package: { 20 | name: "main", 21 | version: "^1.2.3", 22 | }, 23 | globalNames: ["main"], 24 | }], 25 | customDev: [{ 26 | package: { 27 | name: "test", 28 | version: "^1.2.3", 29 | }, 30 | globalNames: ["test"], 31 | }], 32 | }); 33 | 34 | assertEquals(result.shims.length, 10); 35 | assertEquals(result.testShims.length, 11); 36 | }); 37 | 38 | Deno.test("should get when all dev", () => { 39 | const result = shimOptionsToTransformShims({ 40 | deno: "dev", 41 | timers: "dev", 42 | prompts: "dev", 43 | blob: "dev", 44 | crypto: "dev", 45 | domException: "dev", 46 | undici: "dev", 47 | weakRef: "dev", 48 | webSocket: "dev", 49 | }); 50 | 51 | assertEquals(result.shims.length, 0); 52 | assertEquals(result.testShims.length, 9); 53 | }); 54 | 55 | Deno.test("should get when all false", () => { 56 | const result = shimOptionsToTransformShims({ 57 | deno: false, 58 | timers: false, 59 | prompts: false, 60 | blob: false, 61 | crypto: false, 62 | domException: false, 63 | undici: false, 64 | weakRef: false, 65 | webSocket: false, 66 | }); 67 | 68 | assertEquals(result.shims.length, 0); 69 | assertEquals(result.testShims.length, 0); 70 | }); 71 | 72 | Deno.test("should get when all undefined", () => { 73 | const result = shimOptionsToTransformShims({}); 74 | 75 | assertEquals(result.shims.length, 0); 76 | assertEquals(result.testShims.length, 0); 77 | }); 78 | 79 | Deno.test("should get for inner deno namespace", () => { 80 | const result = shimOptionsToTransformShims({ 81 | deno: { 82 | test: true, 83 | }, 84 | }); 85 | 86 | assertEquals(result.shims.length, 1); 87 | assertEquals( 88 | (result.shims[0] as PackageShim).package.name, 89 | "@deno/shim-deno-test", 90 | ); 91 | assertEquals(result.testShims.length, 1); 92 | assertEquals( 93 | (result.testShims[0] as PackageShim).package.name, 94 | "@deno/shim-deno-test", 95 | ); 96 | }); 97 | -------------------------------------------------------------------------------- /rs-lib/tests/integration/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | mod in_memory_loader; 4 | mod test_builder; 5 | 6 | pub use in_memory_loader::*; 7 | pub use test_builder::*; 8 | 9 | macro_rules! assert_files { 10 | ($actual: expr, $expected: expr) => {{ 11 | let mut actual = $actual; 12 | let expected = $expected; 13 | #[cfg(target_os = "windows")] 14 | for file in actual.iter_mut() { 15 | // normalize this on windows to forward slashes 16 | file.file_path = std::path::PathBuf::from( 17 | file 18 | .file_path 19 | .to_string_lossy() 20 | .to_string() 21 | .replace("\\", "/"), 22 | ); 23 | } 24 | actual.sort_by(|a, b| a.file_path.cmp(&b.file_path)); 25 | let mut expected = expected 26 | .iter() 27 | .map(|(file_path, file_text)| deno_node_transform::OutputFile { 28 | file_path: std::path::PathBuf::from(file_path), 29 | file_text: file_text.to_string(), 30 | }) 31 | .collect::>(); 32 | expected.sort_by(|a, b| a.file_path.cmp(&b.file_path)); 33 | 34 | pretty_assertions::assert_eq!(actual, expected); 35 | }}; 36 | } 37 | 38 | pub async fn assert_transforms(files: Vec<(&str, &str)>) { 39 | let files = files 40 | .into_iter() 41 | .enumerate() 42 | .map(|(i, file)| { 43 | ( 44 | format!( 45 | "mod{}.ts", 46 | if i == 0 { 47 | "".to_string() 48 | } else { 49 | i.to_string() 50 | } 51 | ), 52 | file, 53 | ) 54 | }) 55 | .collect::>(); 56 | let mut test_builder = TestBuilder::new(); 57 | test_builder 58 | .with_loader(|loader| { 59 | for (file_name, file) in files.iter() { 60 | loader.add_local_file(&format!("/{}", file_name), file.0); 61 | } 62 | loader.add_local_file("/example.js", ""); 63 | }) 64 | .add_default_shims(); 65 | 66 | for i in 1..files.len() { 67 | test_builder.add_entry_point(format!("file:///mod{}.ts", i)); 68 | } 69 | 70 | let result = test_builder.transform().await.unwrap(); 71 | let expected_files = files 72 | .into_iter() 73 | .map(|(file_name, file)| (file_name, file.1)) 74 | .collect::>(); 75 | let actual_files = result 76 | .main 77 | .files 78 | .into_iter() 79 | .filter(|f| { 80 | !f.file_path.ends_with("_dnt.shims.ts") 81 | && !f.file_path.ends_with("example.js") 82 | }) 83 | .collect::>(); 84 | assert_files!(actual_files, expected_files); 85 | } 86 | 87 | pub async fn assert_identity_transforms(files: Vec<&str>) { 88 | assert_transforms(files.into_iter().map(|text| (text, text)).collect()).await 89 | } 90 | -------------------------------------------------------------------------------- /rs-lib/src/visitors/deno_comment_directives.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::swc::common::comments::Comment; 4 | use deno_ast::view::*; 5 | use deno_ast::RootNode; 6 | use deno_ast::SourceRanged; 7 | use deno_ast::SourceRangedForSpanned; 8 | use deno_ast::SourceTextInfoProvider; 9 | use deno_ast::TextChange; 10 | use once_cell::sync::Lazy; 11 | use regex::Regex; 12 | 13 | // lifted from deno_graph 14 | /// Matched the `@deno-types` pragma. 15 | static DENO_TYPES_RE: Lazy = Lazy::new(|| { 16 | Regex::new(r#"(?i)^\s*@deno-types\s*=\s*(?:["']([^"']+)["']|(\S+))"#).unwrap() 17 | }); 18 | /// Matches a `/// ` comment reference. 19 | static TRIPLE_SLASH_REFERENCE_RE: Lazy = 20 | Lazy::new(|| Regex::new(r"(?i)^/\s*").unwrap()); 21 | /// Matches a types reference, which for JavaScript files indicates the 22 | /// location of types to use when type checking a program that includes it as 23 | /// a dependency. 24 | static TYPES_REFERENCE_RE: Lazy = 25 | Lazy::new(|| Regex::new(r#"(?i)\stypes\s*=\s*["']([^"']*)["']"#).unwrap()); 26 | 27 | pub fn get_deno_comment_directive_text_changes( 28 | program: Program, 29 | ) -> Vec { 30 | let mut text_changes = Vec::new(); 31 | 32 | // strip deno specific path triple slash references 33 | for comment in program.leading_comments_fast(program) { 34 | if TRIPLE_SLASH_REFERENCE_RE.is_match(&comment.text) { 35 | if let Some(captures) = TYPES_REFERENCE_RE.captures(&comment.text) { 36 | let specifier = captures.get(1).unwrap().as_str().to_lowercase(); 37 | if specifier.starts_with("./") 38 | || specifier.starts_with("../") 39 | || specifier.starts_with("https://") 40 | || specifier.starts_with("http://") 41 | { 42 | text_changes.push(TextChange { 43 | new_text: String::new(), 44 | range: get_extended_comment_range(program, comment), 45 | }); 46 | } 47 | } 48 | } 49 | } 50 | 51 | // strip all `@deno-types` comments 52 | for comment in program.comment_container().all_comments() { 53 | if DENO_TYPES_RE.is_match(&comment.text) { 54 | text_changes.push(TextChange { 55 | new_text: String::new(), 56 | range: get_extended_comment_range(program, comment), 57 | }); 58 | } 59 | } 60 | 61 | text_changes 62 | } 63 | 64 | fn get_extended_comment_range( 65 | program: Program, 66 | comment: &Comment, 67 | ) -> std::ops::Range { 68 | let text_info = program.text_info(); 69 | let start_pos = text_info.range().start; 70 | let range = comment.range(); 71 | let end_pos = range.end().as_byte_index(start_pos); 72 | range.start().as_byte_index(start_pos)..end_pos 73 | } 74 | -------------------------------------------------------------------------------- /lib/compiler_transforms.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { assertEquals } from "@std/assert"; 4 | import { ts } from "@ts-morph/bootstrap"; 5 | import { transformImportMeta } from "./compiler_transforms.ts"; 6 | 7 | function testImportReplacements( 8 | input: string, 9 | output: string, 10 | module: ts.ModuleKind, 11 | ) { 12 | const sourceFile = ts.createSourceFile( 13 | "file.ts", 14 | input, 15 | ts.ScriptTarget.Latest, 16 | ); 17 | const newSourceFile = ts.transform(sourceFile, [transformImportMeta], { 18 | module, 19 | }).transformed[0]; 20 | const text = ts.createPrinter({ 21 | newLine: ts.NewLineKind.LineFeed, 22 | }).printFile(newSourceFile); 23 | 24 | assertEquals(text, output); 25 | } 26 | const testImportReplacementsEsm = (input: string, output: string) => 27 | testImportReplacements(input, output, ts.ModuleKind.ES2015); 28 | const testImportReplacementsCjs = (input: string, output: string) => 29 | testImportReplacements(input, output, ts.ModuleKind.CommonJS); 30 | 31 | Deno.test("transform import.meta.url expressions in commonjs", () => { 32 | testImportReplacementsCjs( 33 | "function test() { new URL(import.meta.url); }", 34 | `function test() { new URL(globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url); }\n`, 35 | ); 36 | }); 37 | Deno.test("transform import.meta.url expressions in esModule", () => { 38 | testImportReplacementsEsm( 39 | "function test() { new URL(import.meta.url); }", 40 | `function test() { new URL(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url); }\n`, 41 | ); 42 | }); 43 | 44 | Deno.test("transform import.meta.main expressions in commonjs", () => { 45 | testImportReplacementsCjs( 46 | "if (import.meta.main) { console.log('main'); }", 47 | `if (globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).main) { 48 | console.log("main"); 49 | }\n`, 50 | ); 51 | }); 52 | 53 | Deno.test("transform import.meta.main expressions in esModule", () => { 54 | testImportReplacementsEsm( 55 | "export const isMain = import.meta.main;", 56 | `export const isMain = globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).main;\n`, 57 | ); 58 | }); 59 | 60 | Deno.test("transform import.meta.resolve expressions", () => { 61 | testImportReplacementsCjs( 62 | "function test(specifier) { import.meta.resolve(specifier); }", 63 | `function test(specifier) { globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).resolve(specifier); }\n`, 64 | ); 65 | }); 66 | 67 | Deno.test("transform import.meta.resolve expressions in esModule", () => { 68 | testImportReplacementsEsm( 69 | "function test(specifier) { import.meta.resolve(specifier); }", 70 | `function test(specifier) { globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).resolve(specifier); }\n`, 71 | ); 72 | }); 73 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { expandGlob } from "@std/fs/expand-glob"; 4 | import * as path from "@std/path"; 5 | 6 | /** Gets the files found in the provided root dir path based on the glob. */ 7 | export async function glob(options: { 8 | pattern: string; 9 | rootDir: string; 10 | excludeDirs: string[]; 11 | }) { 12 | const paths: string[] = []; 13 | const entries = expandGlob(options.pattern, { 14 | root: options.rootDir, 15 | extended: true, 16 | globstar: true, 17 | exclude: options.excludeDirs, 18 | }); 19 | for await (const entry of entries) { 20 | if (entry.isFile) { 21 | paths.push(entry.path); 22 | } 23 | } 24 | return paths; 25 | } 26 | 27 | export function runNpmCommand({ bin, args, cwd }: { 28 | bin: string; 29 | args: string[]; 30 | cwd: string; 31 | }) { 32 | return runCommand({ 33 | cmd: [bin, ...args], 34 | cwd, 35 | }); 36 | } 37 | 38 | export async function runCommand(opts: { 39 | cmd: string[]; 40 | cwd: string; 41 | }) { 42 | const [cmd, ...args] = getCmd(); 43 | await Deno.permissions.request({ name: "run", command: cmd }); 44 | 45 | try { 46 | const process = new Deno.Command(cmd, { 47 | args, 48 | cwd: opts.cwd, 49 | stderr: "inherit", 50 | stdout: "inherit", 51 | stdin: "inherit", 52 | }); 53 | 54 | const output = await process.output(); 55 | if (!output.success) { 56 | throw new Error( 57 | `${opts.cmd.join(" ")} failed with exit code ${output.code}`, 58 | ); 59 | } 60 | } catch (err) { 61 | // won't happen on Windows, but that's ok because cmd outputs 62 | // a message saying that the command doesn't exist 63 | if (err instanceof Deno.errors.NotFound) { 64 | throw new Error( 65 | `Could not find command '${ 66 | opts.cmd[0] 67 | }'. Ensure it is available on the path.`, 68 | { cause: err }, 69 | ); 70 | } else { 71 | throw err; 72 | } 73 | } 74 | 75 | function getCmd() { 76 | const cmd = [...opts.cmd]; 77 | if (Deno.build.os === "windows") { 78 | return ["cmd", "/c", ...opts.cmd]; 79 | } else { 80 | return cmd; 81 | } 82 | } 83 | } 84 | 85 | export function standardizePath(fileOrDirPath: string) { 86 | if (fileOrDirPath.startsWith("file:")) { 87 | return path.fromFileUrl(fileOrDirPath); 88 | } 89 | return path.resolve(fileOrDirPath); 90 | } 91 | 92 | export function valueToUrl(value: string) { 93 | const lowerCaseValue = value.toLowerCase(); 94 | if ( 95 | lowerCaseValue.startsWith("http:") || 96 | lowerCaseValue.startsWith("https:") || 97 | lowerCaseValue.startsWith("npm:") || 98 | lowerCaseValue.startsWith("jsr:") || 99 | lowerCaseValue.startsWith("node:") || 100 | lowerCaseValue.startsWith("file:") 101 | ) { 102 | return value; 103 | } else { 104 | return path.toFileUrl(path.resolve(value)).toString(); 105 | } 106 | } 107 | 108 | export function getDntVersion(url = import.meta.url) { 109 | return /\/dnt@([0-9]+\.[0-9]+\.[0-9]+)\//.exec(url)?.[1] ?? "dev"; 110 | } 111 | -------------------------------------------------------------------------------- /lib/compiler.test.ts: -------------------------------------------------------------------------------- 1 | import { ts } from "@ts-morph/bootstrap"; 2 | import { assertEquals, assertThrows } from "@std/assert"; 3 | import { 4 | getCompilerLibOption, 5 | getCompilerScriptTarget, 6 | getCompilerSourceMapOptions, 7 | getTopLevelAwaitLocation, 8 | libNamesToCompilerOption, 9 | type SourceMapOptions, 10 | } from "./compiler.ts"; 11 | import type { ScriptTarget } from "./types.ts"; 12 | 13 | Deno.test("script target should have expected outputs", () => { 14 | const cases: { 15 | [k in ScriptTarget]: ts.ScriptTarget; 16 | } = { 17 | "ES3": ts.ScriptTarget.ES3, 18 | "ES5": ts.ScriptTarget.ES5, 19 | "ES2015": ts.ScriptTarget.ES2015, 20 | "ES2016": ts.ScriptTarget.ES2016, 21 | "ES2017": ts.ScriptTarget.ES2017, 22 | "ES2018": ts.ScriptTarget.ES2018, 23 | "ES2019": ts.ScriptTarget.ES2019, 24 | "ES2020": ts.ScriptTarget.ES2020, 25 | "ES2021": ts.ScriptTarget.ES2021, 26 | "ES2022": ts.ScriptTarget.ES2022, 27 | "ES2023": ts.ScriptTarget.ES2023, 28 | "Latest": ts.ScriptTarget.Latest, 29 | }; 30 | 31 | for (const key in cases) { 32 | const scriptTarget = key as ScriptTarget; 33 | assertEquals(getCompilerScriptTarget(scriptTarget), cases[scriptTarget]); 34 | } 35 | 36 | assertThrows(() => getCompilerScriptTarget("invalid" as any)); 37 | }); 38 | 39 | Deno.test("compiler lib option should have expected outputs", () => { 40 | const cases: { 41 | [k in ScriptTarget]: string[]; 42 | } = { 43 | "ES3": [], 44 | "ES5": ["lib.es5.d.ts"], 45 | "ES2015": ["lib.es2015.d.ts"], 46 | "ES2016": ["lib.es2016.d.ts"], 47 | "ES2017": ["lib.es2017.d.ts"], 48 | "ES2018": ["lib.es2018.d.ts"], 49 | "ES2019": ["lib.es2019.d.ts"], 50 | "ES2020": ["lib.es2020.d.ts"], 51 | "ES2021": ["lib.es2021.d.ts"], 52 | "ES2022": ["lib.es2022.d.ts"], 53 | "ES2023": ["lib.es2023.d.ts"], 54 | "Latest": ["lib.esnext.d.ts"], 55 | }; 56 | 57 | for (const key in cases) { 58 | const scriptTarget = key as ScriptTarget; 59 | assertEquals( 60 | libNamesToCompilerOption(getCompilerLibOption(scriptTarget)), 61 | cases[scriptTarget], 62 | ); 63 | } 64 | 65 | assertThrows(() => getCompilerScriptTarget("invalid" as any)); 66 | }); 67 | 68 | Deno.test("get has top level await", () => { 69 | runTest("const some = code;class SomeOtherCode {}", undefined); 70 | runTest("async function test() { await 5; }", undefined); 71 | runTest( 72 | "async function test() { for await (const item of items) {} }", 73 | undefined, 74 | ); 75 | runTest("await test();", { 76 | line: 0, 77 | character: 0, 78 | }); 79 | runTest("for await (const item of items) {}", { 80 | line: 0, 81 | character: 0, 82 | }); 83 | runTest("if (condition) { await test() }", { 84 | line: 0, 85 | character: 17, 86 | }); 87 | runTest("const t = { prop: await test() };", { 88 | line: 0, 89 | character: 18, 90 | }); 91 | 92 | function runTest(code: string, expected: ts.LineAndCharacter | undefined) { 93 | const sourceFile = ts.createSourceFile( 94 | "file.ts", 95 | code, 96 | ts.ScriptTarget.Latest, 97 | ); 98 | assertEquals(getTopLevelAwaitLocation(sourceFile), expected); 99 | } 100 | }); 101 | 102 | Deno.test("get compiler options for source map option", () => { 103 | runTest("inline", { inlineSourceMap: true }); 104 | runTest(true, { sourceMap: true }); 105 | runTest(false, {}); 106 | runTest(undefined, {}); 107 | 108 | function runTest( 109 | useSourceMaps: SourceMapOptions | undefined, 110 | expected: { sourceMap?: boolean; inlineSourceMap?: boolean }, 111 | ) { 112 | assertEquals(getCompilerSourceMapOptions(useSourceMaps), expected); 113 | } 114 | }); 115 | -------------------------------------------------------------------------------- /rs-lib/src/loader/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::cell::RefCell; 4 | use std::collections::BTreeMap; 5 | use std::collections::HashMap; 6 | use std::rc::Rc; 7 | 8 | use deno_ast::ModuleSpecifier; 9 | use futures::future; 10 | 11 | mod specifier_mappers; 12 | 13 | pub use specifier_mappers::*; 14 | 15 | use crate::MappedSpecifier; 16 | use crate::PackageMappedSpecifier; 17 | 18 | #[derive(Debug, Default, Clone)] 19 | pub struct LoaderSpecifiers { 20 | pub mapped_packages: BTreeMap, 21 | pub mapped_modules: HashMap, 22 | } 23 | 24 | pub struct SourceLoader<'a> { 25 | loader: Rc, 26 | specifiers: RefCell, 27 | specifier_mappers: Vec>, 28 | specifier_mappings: &'a HashMap, 29 | } 30 | 31 | impl<'a> SourceLoader<'a> { 32 | pub fn new( 33 | loader: Rc, 34 | specifier_mappers: Vec>, 35 | specifier_mappings: &'a HashMap, 36 | ) -> Self { 37 | Self { 38 | loader, 39 | specifiers: Default::default(), 40 | specifier_mappers, 41 | specifier_mappings, 42 | } 43 | } 44 | 45 | pub fn into_specifiers(self) -> LoaderSpecifiers { 46 | self.specifiers.take() 47 | } 48 | } 49 | 50 | impl deno_graph::source::Loader for SourceLoader<'_> { 51 | fn load( 52 | &self, 53 | specifier: &ModuleSpecifier, 54 | load_options: deno_graph::source::LoadOptions, 55 | ) -> deno_graph::source::LoadFuture { 56 | let specifier = match self.specifier_mappings.get(specifier) { 57 | Some(MappedSpecifier::Package(mapping)) => { 58 | self 59 | .specifiers 60 | .borrow_mut() 61 | .mapped_packages 62 | .insert(specifier.clone(), mapping.clone()); 63 | // provide a dummy file so that this module can be analyzed later 64 | return get_dummy_module(specifier); 65 | } 66 | Some(MappedSpecifier::Module(redirect)) => { 67 | self 68 | .specifiers 69 | .borrow_mut() 70 | .mapped_modules 71 | .insert(specifier.clone(), redirect.clone()); 72 | redirect 73 | } 74 | None => { 75 | for mapper in self.specifier_mappers.iter() { 76 | if let Some(entry) = mapper.map(specifier) { 77 | self 78 | .specifiers 79 | .borrow_mut() 80 | .mapped_packages 81 | .insert(specifier.clone(), entry); 82 | 83 | // provide a dummy file so that this module can be analyzed later 84 | return get_dummy_module(specifier); 85 | } 86 | } 87 | specifier 88 | } 89 | }; 90 | 91 | let loader = self.loader.clone(); 92 | let specifier = specifier.to_owned(); 93 | Box::pin(async move { 94 | if specifier.scheme() == "node" { 95 | return Ok(Some(deno_graph::source::LoadResponse::External { 96 | specifier, 97 | })); 98 | } 99 | loader.load(&specifier, load_options).await 100 | }) 101 | } 102 | } 103 | 104 | fn get_dummy_module( 105 | specifier: &ModuleSpecifier, 106 | ) -> deno_graph::source::LoadFuture { 107 | let mut headers = HashMap::new(); 108 | headers.insert( 109 | "content-type".to_string(), 110 | "application/javascript".to_string(), 111 | ); 112 | Box::pin(future::ready(Ok(Some( 113 | deno_graph::source::LoadResponse::Module { 114 | specifier: specifier.clone(), 115 | content: b"".to_vec().into(), 116 | maybe_headers: Some(headers), 117 | mtime: None, 118 | }, 119 | )))) 120 | } 121 | -------------------------------------------------------------------------------- /lib/test_runner/test_runner.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { runTestDefinitions } from "./test_runner.ts"; 4 | import { assertEquals, assertRejects } from "@std/assert"; 5 | import { wildcardAssertEquals } from "../test_utils.ts"; 6 | 7 | Deno.test("no test definitions", async () => { 8 | const context = getContext(); 9 | await runTestDefinitions([], context); 10 | assertEquals(context.output, ""); 11 | }); 12 | 13 | Deno.test("failing test definitions", async () => { 14 | const context = getContext(); 15 | await assertRejects( 16 | async () => { 17 | await runTestDefinitions([{ 18 | name: "case 1", 19 | fn: () => { 20 | throw new Error("ERROR"); 21 | }, 22 | }, { 23 | name: "case 2", 24 | fn: async (t) => { 25 | if (t.origin !== "file:///file.ts") { 26 | throw new Error("Origin not equal."); 27 | } 28 | if (t.parent !== undefined) { 29 | throw new Error("Parent should have been undefined"); 30 | } 31 | await t.step("inner 1", async (tInner) => { 32 | if (t !== tInner.parent) { 33 | throw new Error("Parent should have equaled parent."); 34 | } 35 | await tInner.step("fail 1", () => { 36 | throw new Error("FAIL"); 37 | }); 38 | await tInner.step("success 1", () => {}); 39 | }); 40 | }, 41 | }], context); 42 | }, 43 | Error, 44 | "Exit code 1 thrown.", 45 | ); 46 | wildcardAssertEquals( 47 | context.output, 48 | `test case 1 ... RfailR 49 | test case 2 ... 50 | test inner 1 ... 51 | test fail 1 ... 52 | Error: FAIL 53 | at [WILDCARD] 54 | RfailR 55 | test success 1 ... GokG 56 | RfailR 57 | RfailR 58 | 59 | FAILURES 60 | 61 | case 1 62 | Error: ERROR 63 | at [WILDCARD] 64 | 65 | case 2 66 | Error: Had failing test step. 67 | at [WILDCARD]`, 68 | ); 69 | }); 70 | 71 | Deno.test("Ignored tests and test cases", async () => { 72 | const context = getContext(); 73 | await runTestDefinitions([{ 74 | name: "Ignored", 75 | ignore: true, 76 | fn: () => { 77 | throw new Error("FAIL"); 78 | }, 79 | }, { 80 | name: "Other", 81 | fn: async (t) => { 82 | await t.step({ 83 | name: "Ignored", 84 | fn: () => { 85 | throw new Error("FAIL"); 86 | }, 87 | ignore: true, 88 | }); 89 | }, 90 | }], context); 91 | 92 | assertEquals( 93 | context.output, 94 | `test Ignored ... YignoredY 95 | test Other ... 96 | test Ignored ... YignoredY 97 | GokG 98 | `, 99 | ); 100 | }); 101 | 102 | Deno.test("only test definitions", async () => { 103 | const context = getContext(); 104 | await assertRejects( 105 | async () => { 106 | await runTestDefinitions([{ 107 | name: "won't run", 108 | fn: () => { 109 | throw new Error("FAIL"); 110 | }, 111 | }, { 112 | only: true, 113 | name: "my test", 114 | fn: () => {}, // pass 115 | }], context); 116 | }, 117 | Error, 118 | "Exit code 1 thrown.", 119 | ); 120 | wildcardAssertEquals( 121 | context.output, 122 | `test my test[WILDCARD] 123 | error: Test failed because the "only" option was used.\n`, 124 | ); 125 | }); 126 | 127 | function getContext() { 128 | let output = ""; 129 | return { 130 | get output() { 131 | return output; 132 | }, 133 | origin: "file:///file.ts", 134 | pc: { 135 | red(text: string) { 136 | return `R${text}R`; 137 | }, 138 | green(text: string) { 139 | return `G${text}G`; 140 | }, 141 | gray(text: string) { 142 | return `Y${text}Y`; 143 | }, 144 | }, 145 | process: { 146 | stdout: { 147 | write(text: string) { 148 | output += text; 149 | }, 150 | }, 151 | exit(code: number) { 152 | throw new Error(`Exit code ${code} thrown.`); 153 | }, 154 | }, 155 | }; 156 | } 157 | -------------------------------------------------------------------------------- /rs-lib/tests/integration/in_memory_loader.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::HashMap; 4 | use std::path::PathBuf; 5 | 6 | use anyhow::Result; 7 | use deno_cache_dir::file_fetcher::HeaderMap; 8 | use deno_cache_dir::file_fetcher::HeaderName; 9 | use deno_cache_dir::file_fetcher::SendError; 10 | use deno_cache_dir::file_fetcher::SendResponse; 11 | 12 | use deno_node_transform::ModuleSpecifier; 13 | use sys_traits::impls::InMemorySys; 14 | use sys_traits::EnvSetCurrentDir; 15 | use sys_traits::EnvSetVar; 16 | use sys_traits::FsCreateDirAll; 17 | use sys_traits::FsWrite; 18 | 19 | type RemoteFileText = String; 20 | type RemoteFileHeaders = Option>; 21 | type RemoteFileResult = Result<(RemoteFileText, RemoteFileHeaders), String>; 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct InMemoryLoader { 25 | pub sys: InMemorySys, 26 | remote_files: HashMap, 27 | } 28 | 29 | impl InMemoryLoader { 30 | pub fn new() -> Self { 31 | let sys = InMemorySys::default(); 32 | let deno_dir_folder = if cfg!(windows) { "C:/.deno" } else { "/.deno" }; 33 | sys.env_set_var("DENO_DIR", deno_dir_folder); 34 | sys.fs_create_dir_all(deno_dir_folder).unwrap(); 35 | if cfg!(windows) { 36 | sys.env_set_current_dir("C:\\").unwrap(); 37 | } 38 | Self { 39 | sys, 40 | remote_files: HashMap::new(), 41 | } 42 | } 43 | 44 | pub fn add_local_file( 45 | &mut self, 46 | path: impl AsRef, 47 | text: impl AsRef, 48 | ) -> &mut Self { 49 | let path = path.as_ref(); 50 | let path = if cfg!(windows) && path.starts_with("/") { 51 | PathBuf::from(format!("C:{}", path)) 52 | } else { 53 | PathBuf::from(path) 54 | }; 55 | let parent_dir = path.parent().unwrap(); 56 | self.sys.fs_create_dir_all(parent_dir).unwrap(); 57 | self.sys.fs_write(path, text.as_ref().as_bytes()).unwrap(); 58 | self 59 | } 60 | 61 | pub fn add_remote_file( 62 | &mut self, 63 | specifier: impl AsRef, 64 | text: impl AsRef, 65 | ) -> &mut Self { 66 | self.remote_files.insert( 67 | ModuleSpecifier::parse(specifier.as_ref()).unwrap(), 68 | Ok((text.as_ref().to_string(), None)), 69 | ); 70 | self 71 | } 72 | 73 | pub fn add_remote_file_with_headers( 74 | &mut self, 75 | specifier: impl AsRef, 76 | text: impl AsRef, 77 | headers: &[(&str, &str)], 78 | ) -> &mut Self { 79 | let headers = headers 80 | .iter() 81 | .map(|(key, value)| (key.to_string(), value.to_string())) 82 | .collect(); 83 | self.remote_files.insert( 84 | ModuleSpecifier::parse(specifier.as_ref()).unwrap(), 85 | Ok((text.as_ref().to_string(), Some(headers))), 86 | ); 87 | self 88 | } 89 | 90 | pub fn add_remote_file_with_error( 91 | &mut self, 92 | specifier: impl AsRef, 93 | error_text: impl AsRef, 94 | ) -> &mut Self { 95 | self.remote_files.insert( 96 | ModuleSpecifier::parse(specifier.as_ref()).unwrap(), 97 | Err(error_text.as_ref().to_string()), 98 | ); 99 | self 100 | } 101 | } 102 | 103 | fn to_headers(src: HashMap) -> HeaderMap { 104 | let mut h = HeaderMap::with_capacity(src.len()); 105 | for (k, v) in src { 106 | let name = HeaderName::try_from(k.as_str()).unwrap(); 107 | let value = 108 | deno_cache_dir::file_fetcher::HeaderValue::from_str(&v).unwrap(); 109 | h.insert(name, value); 110 | } 111 | h 112 | } 113 | 114 | #[async_trait::async_trait(?Send)] 115 | impl deno_cache_dir::file_fetcher::HttpClient for InMemoryLoader { 116 | async fn send_no_follow( 117 | &self, 118 | specifier: &ModuleSpecifier, 119 | _headers: HeaderMap, 120 | ) -> Result { 121 | let result = self 122 | .remote_files 123 | .get(&specifier) 124 | .map(|result| match result { 125 | Ok(result) => Ok(SendResponse::Success( 126 | to_headers(result.1.clone().unwrap_or_default()), 127 | result.0.clone().into_bytes().into(), 128 | )), 129 | Err(err) => Err(SendError::Failed(err.clone().into())), 130 | }); 131 | match result { 132 | Some(result) => result, 133 | None => Err(SendError::NotFound), 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/test_runner/get_test_runner_code.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import CodeBlockWriter from "code-block-writer"; 4 | import { runTestDefinitions } from "./test_runner.ts"; 5 | 6 | export function getTestRunnerCode(options: { 7 | testEntryPoints: string[]; 8 | denoTestShimPackageName: string | undefined; 9 | includeEsModule: boolean | undefined; 10 | includeScriptModule: boolean | undefined; 11 | }) { 12 | const usesDenoTest = options.denoTestShimPackageName != null; 13 | const writer = createWriter(); 14 | writer.writeLine(`const pc = require("picocolors");`) 15 | .writeLine(`const process = require("process");`); 16 | if (usesDenoTest) { 17 | writer.writeLine(`const { pathToFileURL } = require("url");`); 18 | writer.writeLine( 19 | `const { testDefinitions } = require("${options.denoTestShimPackageName}");`, 20 | ); 21 | } 22 | writer.blankLine(); 23 | 24 | writer.writeLine("const filePaths = ["); 25 | writer.indent(() => { 26 | for (const entryPoint of options.testEntryPoints) { 27 | writer.quote(entryPoint.replace(/\.ts$/, ".js")).write(",").newLine(); 28 | } 29 | }); 30 | writer.writeLine("];").newLine(); 31 | 32 | writer.write("async function main()").block(() => { 33 | if (usesDenoTest) { 34 | writer.write("const testContext = ").inlineBlock(() => { 35 | writer.writeLine("process,"); 36 | writer.writeLine("pc,"); 37 | }).write(";").newLine(); 38 | } 39 | writer.write("for (const [i, filePath] of filePaths.entries())") 40 | .block(() => { 41 | writer.write("if (i > 0)").block(() => { 42 | writer.writeLine(`console.log("");`); 43 | }).blankLine(); 44 | 45 | if (options.includeScriptModule) { 46 | writer.writeLine(`const scriptPath = "./script/" + filePath;`); 47 | writer.writeLine( 48 | `console.log("Running tests in " + pc.underline(scriptPath) + "...\\n");`, 49 | ); 50 | writer.writeLine(`process.chdir(__dirname + "/script");`); 51 | if (usesDenoTest) { 52 | writer.write(`const scriptTestContext = `).inlineBlock(() => { 53 | writer.writeLine("origin: pathToFileURL(filePath).toString(),"); 54 | writer.writeLine("...testContext,"); 55 | }).write(";").newLine(); 56 | } 57 | writer.write("try ").inlineBlock(() => { 58 | writer.writeLine(`require(scriptPath);`); 59 | }).write(" catch(err)").block(() => { 60 | writer.writeLine("console.error(err);"); 61 | writer.writeLine("process.exit(1);"); 62 | }); 63 | if (usesDenoTest) { 64 | writer.writeLine( 65 | "await runTestDefinitions(testDefinitions.splice(0, testDefinitions.length), scriptTestContext);", 66 | ); 67 | } 68 | } 69 | 70 | if (options.includeEsModule) { 71 | if (options.includeScriptModule) { 72 | writer.blankLine(); 73 | } 74 | writer.writeLine(`const esmPath = "./esm/" + filePath;`); 75 | writer.writeLine( 76 | `console.log("\\nRunning tests in " + pc.underline(esmPath) + "...\\n");`, 77 | ); 78 | writer.writeLine(`process.chdir(__dirname + "/esm");`); 79 | if (usesDenoTest) { 80 | writer.write(`const esmTestContext = `).inlineBlock(() => { 81 | writer.writeLine("origin: pathToFileURL(filePath).toString(),"); 82 | writer.writeLine("...testContext,"); 83 | }).write(";").newLine(); 84 | } 85 | writer.writeLine(`await import(esmPath);`); 86 | if (usesDenoTest) { 87 | writer.writeLine( 88 | "await runTestDefinitions(testDefinitions.splice(0, testDefinitions.length), esmTestContext);", 89 | ); 90 | } 91 | } 92 | }); 93 | }); 94 | writer.blankLine(); 95 | 96 | if (options.denoTestShimPackageName != null) { 97 | writer.writeLine(`${getRunTestDefinitionsCode()}`); 98 | writer.blankLine(); 99 | } 100 | 101 | writer.writeLine("main();"); 102 | return writer.toString(); 103 | } 104 | 105 | function getRunTestDefinitionsCode() { 106 | return runTestDefinitions.toString().replace( 107 | "export async function", 108 | "async function", 109 | ); 110 | } 111 | 112 | function createWriter() { 113 | return new CodeBlockWriter({ 114 | indentNumberOfSpaces: 2, 115 | }); 116 | } 117 | -------------------------------------------------------------------------------- /lib/npm_ignore.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { assertEquals } from "@std/assert"; 4 | import { getNpmIgnoreText } from "./npm_ignore.ts"; 5 | import type { SourceMapOptions } from "./compiler.ts"; 6 | 7 | Deno.test("should include src directory when the source files are not necessary", () => { 8 | runTest({ 9 | sourceMaps: undefined, 10 | inlineSources: undefined, 11 | expectHasSrcFolder: true, 12 | includeScriptModule: true, 13 | includeEsModule: true, 14 | declaration: "inline", 15 | }); 16 | runTest({ 17 | sourceMaps: true, 18 | inlineSources: undefined, 19 | expectHasSrcFolder: false, 20 | includeScriptModule: true, 21 | includeEsModule: true, 22 | declaration: "inline", 23 | }); 24 | runTest({ 25 | sourceMaps: "inline", 26 | inlineSources: undefined, 27 | expectHasSrcFolder: false, 28 | includeScriptModule: true, 29 | includeEsModule: true, 30 | declaration: "inline", 31 | }); 32 | 33 | runTest({ 34 | sourceMaps: true, 35 | inlineSources: false, 36 | expectHasSrcFolder: false, 37 | includeScriptModule: true, 38 | includeEsModule: true, 39 | declaration: "inline", 40 | }); 41 | 42 | runTest({ 43 | sourceMaps: undefined, 44 | inlineSources: true, 45 | expectHasSrcFolder: true, 46 | includeScriptModule: true, 47 | includeEsModule: true, 48 | declaration: "inline", 49 | }); 50 | runTest({ 51 | sourceMaps: true, 52 | inlineSources: true, 53 | expectHasSrcFolder: true, 54 | includeScriptModule: true, 55 | includeEsModule: true, 56 | declaration: "inline", 57 | }); 58 | runTest({ 59 | sourceMaps: "inline", 60 | inlineSources: true, 61 | expectHasSrcFolder: true, 62 | includeScriptModule: true, 63 | includeEsModule: true, 64 | declaration: "inline", 65 | }); 66 | runTest({ 67 | sourceMaps: undefined, 68 | inlineSources: undefined, 69 | expectHasSrcFolder: true, 70 | includeScriptModule: false, 71 | includeEsModule: true, 72 | declaration: "inline", 73 | }); 74 | runTest({ 75 | sourceMaps: undefined, 76 | inlineSources: undefined, 77 | expectHasSrcFolder: true, 78 | includeScriptModule: true, 79 | includeEsModule: false, 80 | declaration: "inline", 81 | }); 82 | runTest({ 83 | sourceMaps: undefined, 84 | inlineSources: undefined, 85 | expectHasSrcFolder: true, 86 | includeScriptModule: true, 87 | includeEsModule: true, 88 | declaration: "separate", 89 | }); 90 | runTest({ 91 | sourceMaps: undefined, 92 | inlineSources: undefined, 93 | expectHasSrcFolder: true, 94 | includeScriptModule: true, 95 | includeEsModule: true, 96 | declaration: false, 97 | }); 98 | }); 99 | 100 | function runTest(options: { 101 | sourceMaps: SourceMapOptions | undefined; 102 | inlineSources: boolean | undefined; 103 | expectHasSrcFolder: boolean; 104 | declaration: "separate" | "inline" | false; 105 | includeScriptModule: boolean | undefined; 106 | includeEsModule: boolean | undefined; 107 | }) { 108 | const fileText = getNpmIgnoreText({ 109 | sourceMap: options.sourceMaps, 110 | inlineSources: options.inlineSources, 111 | testFiles: [{ 112 | filePath: "mod.test.ts", 113 | fileText: "", 114 | }], 115 | includeScriptModule: options.includeScriptModule, 116 | includeEsModule: options.includeEsModule, 117 | declaration: options.declaration, 118 | }); 119 | 120 | assertEquals(fileText, getExpectedText()); 121 | 122 | function getExpectedText() { 123 | let startText = options.expectHasSrcFolder ? "/src/\n" : ""; 124 | if (options.includeEsModule !== false) { 125 | startText += "/esm/mod.test.js\n"; 126 | if (options.sourceMaps === true) { 127 | startText += "/esm/mod.test.js.map\n"; 128 | } 129 | if (options.declaration === "inline") { 130 | startText += "/esm/mod.test.d.ts\n"; 131 | } 132 | } 133 | if (options.includeScriptModule !== false) { 134 | startText += "/script/mod.test.js\n"; 135 | if (options.sourceMaps === true) { 136 | startText += "/script/mod.test.js.map\n"; 137 | } 138 | if (options.declaration === "inline") { 139 | startText += "/script/mod.test.d.ts\n"; 140 | } 141 | } 142 | if (options.declaration === "separate") { 143 | startText += "/types/mod.test.d.ts\n"; 144 | } 145 | 146 | return startText + 147 | `/test_runner.js 148 | yarn.lock 149 | pnpm-lock.yaml 150 | `; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/scripts/esnext.array-fromAsync.ts: -------------------------------------------------------------------------------- 1 | // taken from https://github.com/denoland/deno/blob/7281775381cda79ef61df27820387dc2c74e0384/cli/tsc/dts/lib.esnext.array.d.ts#L21 2 | declare global { 3 | interface ArrayConstructor { 4 | fromAsync( 5 | iterableOrArrayLike: AsyncIterable | Iterable> | ArrayLike>, 6 | ): Promise; 7 | 8 | fromAsync( 9 | iterableOrArrayLike: AsyncIterable | Iterable | ArrayLike, 10 | mapFn: (value: Awaited) => U, 11 | thisArg?: any, 12 | ): Promise[]>; 13 | } 14 | } 15 | 16 | // From https://github.com/es-shims/array-from-async/blob/4a5ff83947b861f35b380d5d4f20da2f07698638/index.mjs 17 | // Tried to have dnt depend on the package instead, but it distributes as an 18 | // ES module, so doesn't work with CommonJS. 19 | // 20 | // Code below: 21 | // 22 | // Copyright 2021 J. S. Choi 23 | // 24 | // Redistribution and use in source and binary forms, with or without 25 | // modification, are permitted provided that the following conditions 26 | // are met: 27 | // 28 | // 1. Redistributions of source code must retain the above copyright 29 | // notice, this list of conditions and the following disclaimer. 30 | // 31 | // 2. Redistributions in binary form must reproduce the above copyright 32 | // notice, this list of conditions and the following disclaimer in the 33 | // documentation and/or other materials provided with the distribution. 34 | // 35 | // 3. Neither the name of the copyright holder nor the names of its 36 | // contributors may be used to endorse or promote products derived from 37 | // this software without specific prior written permission. 38 | // 39 | // **This software is provided by the copyright holders and contributors 40 | // "as is" and any express or implied warranties, including, but not 41 | // limited to, the implied warranties of merchantability and fitness for a 42 | // particular purpose are disclaimed. In no event shall the copyright 43 | // holder or contributors be liable for any direct, indirect, incidental, 44 | // special, exemplary, or consequential damages (including, but not limited 45 | // to, procurement of substitute goods or services; loss of use, data, or 46 | // profits; or business interruption) however caused and on any theory of 47 | // liability, whether in contract, strict liability, or tort (including 48 | // negligence or otherwise) arising in any way out of the use of this 49 | // software, even if advised of the possibility of such damage.** 50 | 51 | const { MAX_SAFE_INTEGER } = Number; 52 | const iteratorSymbol = Symbol.iterator; 53 | const asyncIteratorSymbol = Symbol.asyncIterator; 54 | const IntrinsicArray = Array; 55 | const tooLongErrorMessage = 56 | 'Input is too long and exceeded Number.MAX_SAFE_INTEGER times.'; 57 | 58 | function isConstructor(obj: any) { 59 | if (obj != null) { 60 | const prox: any = new Proxy(obj, { 61 | construct () { 62 | return prox; 63 | }, 64 | }); 65 | try { 66 | new prox; 67 | return true; 68 | } catch (err) { 69 | return false; 70 | } 71 | } else { 72 | return false; 73 | } 74 | } 75 | 76 | async function fromAsync(this: any, items: any, mapfn: any, thisArg: any) { 77 | const itemsAreIterable = ( 78 | asyncIteratorSymbol in items || 79 | iteratorSymbol in items 80 | ); 81 | 82 | if (itemsAreIterable) { 83 | const result = isConstructor(this) 84 | ? new this 85 | : IntrinsicArray(0); 86 | 87 | let i = 0; 88 | 89 | for await (const v of items) { 90 | if (i > MAX_SAFE_INTEGER) { 91 | throw TypeError(tooLongErrorMessage); 92 | } 93 | 94 | else if (mapfn) { 95 | result[i] = await mapfn.call(thisArg, v, i); 96 | } 97 | 98 | else { 99 | result[i] = v; 100 | } 101 | 102 | i ++; 103 | } 104 | 105 | result.length = i; 106 | return result; 107 | } 108 | 109 | else { 110 | // In this case, the items are assumed to be an arraylike object with 111 | // a length property and integer properties for each element. 112 | const { length } = items; 113 | const result = isConstructor(this) 114 | ? new this(length) 115 | : IntrinsicArray(length); 116 | 117 | let i = 0; 118 | 119 | while (i < length) { 120 | if (i > MAX_SAFE_INTEGER) { 121 | throw TypeError(tooLongErrorMessage); 122 | } 123 | 124 | const v = await items[i]; 125 | 126 | if (mapfn) { 127 | result[i] = await mapfn.call(thisArg, v, i); 128 | } 129 | 130 | else { 131 | result[i] = v; 132 | } 133 | 134 | i ++; 135 | } 136 | 137 | result.length = i; 138 | return result; 139 | } 140 | } 141 | 142 | if (!Array.fromAsync) { 143 | (Array as any).fromAsync = fromAsync; 144 | } 145 | 146 | export {}; -------------------------------------------------------------------------------- /rs-lib/src/polyfills/scripts/esnext.array-findLast.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/denoland/deno/blob/7fc5bfe51b7d405aaa5293ec6f1a8f1e9119aea2/cli/dts/lib.esnext.array.d.ts 2 | declare global { 3 | interface Array { 4 | /** 5 | * Returns the value of the last element in the array where predicate is true, and undefined 6 | * otherwise. 7 | * @param predicate find calls predicate once for each element of the array, in ascending 8 | * order, until it finds one where predicate returns true. If such an element is found, find 9 | * immediately returns that element value. Otherwise, find returns undefined. 10 | * @param thisArg If provided, it will be used as the this value for each invocation of 11 | * predicate. If it is not provided, undefined is used instead. 12 | */ 13 | findLast( 14 | predicate: (this: void, value: T, index: number, obj: T[]) => value is S, 15 | thisArg?: any, 16 | ): S | undefined; 17 | findLast( 18 | predicate: (value: T, index: number, obj: T[]) => unknown, 19 | thisArg?: any, 20 | ): T | undefined; 21 | 22 | /** 23 | * Returns the index of the last element in the array where predicate is true, and -1 24 | * otherwise. 25 | * @param predicate find calls predicate once for each element of the array, in ascending 26 | * order, until it finds one where predicate returns true. If such an element is found, 27 | * findIndex immediately returns that element index. Otherwise, findIndex returns -1. 28 | * @param thisArg If provided, it will be used as the this value for each invocation of 29 | * predicate. If it is not provided, undefined is used instead. 30 | */ 31 | findLastIndex( 32 | predicate: (value: T, index: number, obj: T[]) => unknown, 33 | thisArg?: any, 34 | ): number; 35 | } 36 | interface Uint8Array { 37 | /** 38 | * Returns the value of the last element in the array where predicate is true, and undefined 39 | * otherwise. 40 | * @param predicate findLast calls predicate once for each element of the array, in descending 41 | * order, until it finds one where predicate returns true. If such an element is found, findLast 42 | * immediately returns that element value. Otherwise, findLast returns undefined. 43 | * @param thisArg If provided, it will be used as the this value for each invocation of 44 | * predicate. If it is not provided, undefined is used instead. 45 | */ 46 | findLast( 47 | predicate: ( 48 | value: number, 49 | index: number, 50 | array: Uint8Array, 51 | ) => value is S, 52 | thisArg?: any, 53 | ): S | undefined; 54 | findLast( 55 | predicate: (value: number, index: number, array: Uint8Array) => unknown, 56 | thisArg?: any, 57 | ): number | undefined; 58 | 59 | /** 60 | * Returns the index of the last element in the array where predicate is true, and -1 61 | * otherwise. 62 | * @param predicate findLastIndex calls predicate once for each element of the array, in descending 63 | * order, until it finds one where predicate returns true. If such an element is found, 64 | * findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1. 65 | * @param thisArg If provided, it will be used as the this value for each invocation of 66 | * predicate. If it is not provided, undefined is used instead. 67 | */ 68 | findLastIndex( 69 | predicate: (value: number, index: number, array: Uint8Array) => unknown, 70 | thisArg?: any, 71 | ): number; 72 | } 73 | } 74 | 75 | function findLastIndex(self: any, callbackfn: any, that: any) { 76 | const boundFunc = that === undefined ? callbackfn : callbackfn.bind(that); 77 | let index = self.length - 1; 78 | while (index >= 0) { 79 | const result = boundFunc(self[index], index, self); 80 | if (result) { 81 | return index; 82 | } 83 | index--; 84 | } 85 | return -1; 86 | } 87 | 88 | function findLast(self: any, callbackfn: any, that: any) { 89 | const index = self.findLastIndex(callbackfn, that); 90 | return index === -1 ? undefined : self[index]; 91 | } 92 | 93 | if (!Array.prototype.findLastIndex) { 94 | Array.prototype.findLastIndex = function (callbackfn: any, that: any) { 95 | return findLastIndex(this, callbackfn, that); 96 | }; 97 | } 98 | 99 | if (!Array.prototype.findLast) { 100 | Array.prototype.findLast = function (callbackfn: any, that: any) { 101 | return findLast(this, callbackfn, that); 102 | }; 103 | } 104 | 105 | if (!Uint8Array.prototype.findLastIndex) { 106 | Uint8Array.prototype.findLastIndex = function (callbackfn: any, that: any) { 107 | return findLastIndex(this, callbackfn, that); 108 | }; 109 | } 110 | 111 | if (!Uint8Array.prototype.findLast) { 112 | Uint8Array.prototype.findLast = function (callbackfn: any, that: any) { 113 | return findLast(this, callbackfn, that); 114 | }; 115 | } 116 | 117 | export {}; 118 | -------------------------------------------------------------------------------- /lib/test_runner/get_test_runner_code.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { assertEquals } from "@std/assert"; 4 | import { getTestRunnerCode } from "./get_test_runner_code.ts"; 5 | import { runTestDefinitions } from "./test_runner.ts"; 6 | 7 | const runTestDefinitionsCode = runTestDefinitions.toString() 8 | .replace("export async function", "async function"); 9 | 10 | Deno.test("gets code when no shim used", () => { 11 | const code = getTestRunnerCode({ 12 | testEntryPoints: ["./test.ts"], 13 | denoTestShimPackageName: undefined, 14 | includeEsModule: true, 15 | includeScriptModule: true, 16 | }); 17 | assertEquals( 18 | code, 19 | `const pc = require("picocolors"); 20 | const process = require("process"); 21 | 22 | const filePaths = [ 23 | "./test.js", 24 | ]; 25 | 26 | async function main() { 27 | for (const [i, filePath] of filePaths.entries()) { 28 | if (i > 0) { 29 | console.log(""); 30 | } 31 | 32 | const scriptPath = "./script/" + filePath; 33 | console.log("Running tests in " + pc.underline(scriptPath) + "...\\n"); 34 | process.chdir(__dirname + "/script"); 35 | try { 36 | require(scriptPath); 37 | } catch(err) { 38 | console.error(err); 39 | process.exit(1); 40 | } 41 | 42 | const esmPath = "./esm/" + filePath; 43 | console.log("\\nRunning tests in " + pc.underline(esmPath) + "...\\n"); 44 | process.chdir(__dirname + "/esm"); 45 | await import(esmPath); 46 | } 47 | } 48 | 49 | main(); 50 | `, 51 | ); 52 | }); 53 | 54 | Deno.test("gets code when shim used", () => { 55 | const code = getTestRunnerCode({ 56 | testEntryPoints: ["./1.test.ts", "./2.test.ts"], 57 | denoTestShimPackageName: "test-shim-package/test-internals", 58 | includeEsModule: true, 59 | includeScriptModule: true, 60 | }); 61 | assertEquals( 62 | code, 63 | `const pc = require("picocolors"); 64 | const process = require("process"); 65 | const { pathToFileURL } = require("url"); 66 | const { testDefinitions } = require("test-shim-package/test-internals"); 67 | 68 | const filePaths = [ 69 | "./1.test.js", 70 | "./2.test.js", 71 | ]; 72 | 73 | async function main() { 74 | const testContext = { 75 | process, 76 | pc, 77 | }; 78 | for (const [i, filePath] of filePaths.entries()) { 79 | if (i > 0) { 80 | console.log(""); 81 | } 82 | 83 | const scriptPath = "./script/" + filePath; 84 | console.log("Running tests in " + pc.underline(scriptPath) + "...\\n"); 85 | process.chdir(__dirname + "/script"); 86 | const scriptTestContext = { 87 | origin: pathToFileURL(filePath).toString(), 88 | ...testContext, 89 | }; 90 | try { 91 | require(scriptPath); 92 | } catch(err) { 93 | console.error(err); 94 | process.exit(1); 95 | } 96 | await runTestDefinitions(testDefinitions.splice(0, testDefinitions.length), scriptTestContext); 97 | 98 | const esmPath = "./esm/" + filePath; 99 | console.log("\\nRunning tests in " + pc.underline(esmPath) + "...\\n"); 100 | process.chdir(__dirname + "/esm"); 101 | const esmTestContext = { 102 | origin: pathToFileURL(filePath).toString(), 103 | ...testContext, 104 | }; 105 | await import(esmPath); 106 | await runTestDefinitions(testDefinitions.splice(0, testDefinitions.length), esmTestContext); 107 | } 108 | } 109 | 110 | ${runTestDefinitionsCode} 111 | 112 | main(); 113 | `, 114 | ); 115 | }); 116 | 117 | Deno.test("gets code when cjs is not used", () => { 118 | const code = getTestRunnerCode({ 119 | testEntryPoints: ["./test.ts"], 120 | denoTestShimPackageName: undefined, 121 | includeEsModule: true, 122 | includeScriptModule: false, 123 | }); 124 | assertEquals( 125 | code, 126 | `const pc = require("picocolors"); 127 | const process = require("process"); 128 | 129 | const filePaths = [ 130 | "./test.js", 131 | ]; 132 | 133 | async function main() { 134 | for (const [i, filePath] of filePaths.entries()) { 135 | if (i > 0) { 136 | console.log(""); 137 | } 138 | 139 | const esmPath = "./esm/" + filePath; 140 | console.log("\\nRunning tests in " + pc.underline(esmPath) + "...\\n"); 141 | process.chdir(__dirname + "/esm"); 142 | await import(esmPath); 143 | } 144 | } 145 | 146 | main(); 147 | `, 148 | ); 149 | }); 150 | 151 | Deno.test("gets code when esm is not used", () => { 152 | const code = getTestRunnerCode({ 153 | testEntryPoints: ["./test.ts"], 154 | denoTestShimPackageName: undefined, 155 | includeEsModule: false, 156 | includeScriptModule: true, 157 | }); 158 | assertEquals( 159 | code, 160 | `const pc = require("picocolors"); 161 | const process = require("process"); 162 | 163 | const filePaths = [ 164 | "./test.js", 165 | ]; 166 | 167 | async function main() { 168 | for (const [i, filePath] of filePaths.entries()) { 169 | if (i > 0) { 170 | console.log(""); 171 | } 172 | 173 | const scriptPath = "./script/" + filePath; 174 | console.log("Running tests in " + pc.underline(scriptPath) + "...\\n"); 175 | process.chdir(__dirname + "/script"); 176 | try { 177 | require(scriptPath); 178 | } catch(err) { 179 | console.error(err); 180 | process.exit(1); 181 | } 182 | } 183 | } 184 | 185 | main(); 186 | `, 187 | ); 188 | }); 189 | -------------------------------------------------------------------------------- /rs-lib/src/visitors/imports_exports.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::HashMap; 4 | use std::path::PathBuf; 5 | 6 | use anyhow::Result; 7 | use deno_ast::view::*; 8 | use deno_ast::ModuleSpecifier; 9 | use deno_ast::SourcePos; 10 | use deno_ast::SourceRange; 11 | use deno_ast::SourceRanged; 12 | use deno_ast::SourceRangedForSpanned; 13 | use deno_ast::SourceTextInfoProvider; 14 | use deno_ast::TextChange; 15 | 16 | use crate::graph::ModuleGraph; 17 | use crate::mappings::Mappings; 18 | use crate::utils::get_relative_specifier; 19 | 20 | pub struct GetImportExportsTextChangesParams<'a> { 21 | pub specifier: &'a ModuleSpecifier, 22 | pub module_graph: &'a ModuleGraph, 23 | pub mappings: &'a Mappings, 24 | pub program: Program<'a>, 25 | pub package_specifier_mappings: &'a HashMap, 26 | } 27 | 28 | struct Context<'a> { 29 | program: Program<'a>, 30 | specifier: &'a ModuleSpecifier, 31 | module_graph: &'a ModuleGraph, 32 | mappings: &'a Mappings, 33 | output_file_path: &'a PathBuf, 34 | text_changes: Vec, 35 | package_specifier_mappings: &'a HashMap, 36 | } 37 | 38 | pub fn get_import_exports_text_changes( 39 | params: &GetImportExportsTextChangesParams<'_>, 40 | ) -> Result> { 41 | let mut context = Context { 42 | program: params.program, 43 | specifier: params.specifier, 44 | module_graph: params.module_graph, 45 | mappings: params.mappings, 46 | output_file_path: params.mappings.get_file_path(params.specifier), 47 | text_changes: Vec::new(), 48 | package_specifier_mappings: params.package_specifier_mappings, 49 | }; 50 | 51 | visit_children(params.program.as_node(), &mut context)?; 52 | 53 | Ok(context.text_changes) 54 | } 55 | 56 | fn visit_children(node: Node, context: &mut Context) -> Result<()> { 57 | for child in node.children() { 58 | match child { 59 | Node::ImportDecl(import_decl) => { 60 | visit_module_specifier(import_decl.src, context); 61 | if let Some(asserts) = import_decl.with { 62 | visit_import_attributes(asserts, context); 63 | } 64 | } 65 | Node::ExportAll(export_all) => { 66 | visit_module_specifier(export_all.src, context); 67 | if let Some(asserts) = export_all.with { 68 | visit_import_attributes(asserts, context); 69 | } 70 | } 71 | Node::NamedExport(named_export) => { 72 | if let Some(src) = &named_export.src { 73 | visit_module_specifier(src, context); 74 | } 75 | if let Some(asserts) = named_export.with { 76 | visit_import_attributes(asserts, context); 77 | } 78 | } 79 | Node::TsImportType(ts_import_type) => { 80 | visit_module_specifier(ts_import_type.arg, context); 81 | } 82 | Node::TsModuleDecl(module_decl) => { 83 | if let TsModuleName::Str(src) = &module_decl.id { 84 | visit_module_specifier(src, context); 85 | } 86 | } 87 | Node::CallExpr(call_expr) => { 88 | if matches!(call_expr.callee, Callee::Import(_)) { 89 | if let Some(Node::Str(src)) = 90 | call_expr.args.first().map(|a| a.expr.as_node()) 91 | { 92 | visit_module_specifier(src, context); 93 | if call_expr.args.len() > 1 { 94 | let assert_arg = call_expr.args[1]; 95 | let comma_token = 96 | assert_arg.previous_token_fast(context.program).unwrap(); 97 | context.text_changes.push(TextChange { 98 | range: create_range( 99 | comma_token.start(), 100 | assert_arg.end(), 101 | context, 102 | ), 103 | new_text: String::new(), 104 | }); 105 | } 106 | } 107 | } else { 108 | visit_children(child, context)?; 109 | } 110 | } 111 | _ => { 112 | visit_children(child, context)?; 113 | } 114 | } 115 | } 116 | 117 | Ok(()) 118 | } 119 | 120 | fn visit_module_specifier(str: &Str, context: &mut Context) { 121 | let value = str.value().to_string(); 122 | let specifier = context 123 | .module_graph 124 | .resolve_dependency(&value, context.specifier); 125 | let specifier = match specifier { 126 | Some(s) => s, 127 | None => return, 128 | }; 129 | 130 | let new_text = if let Some(bare_specifier) = 131 | context.package_specifier_mappings.get(&specifier) 132 | { 133 | bare_specifier.to_string() 134 | } else { 135 | let specifier_file_path = context.mappings.get_file_path(&specifier); 136 | get_relative_specifier(context.output_file_path, specifier_file_path) 137 | }; 138 | 139 | context.text_changes.push(TextChange { 140 | range: create_range(str.start() + 1, str.end() - 1, context), 141 | new_text, 142 | }); 143 | } 144 | 145 | fn visit_import_attributes(asserts: &ObjectLit, context: &mut Context) { 146 | let with_token = asserts.previous_token_fast(context.program).unwrap(); 147 | debug_assert!(matches!( 148 | with_token.text_fast(context.program), 149 | "with" | "assert" 150 | )); 151 | let previous_token = with_token.previous_token_fast(context.program).unwrap(); 152 | context.text_changes.push(TextChange { 153 | range: create_range(previous_token.end(), asserts.end(), context), 154 | new_text: String::new(), 155 | }); 156 | } 157 | 158 | fn create_range( 159 | start: SourcePos, 160 | end: SourcePos, 161 | context: &Context, 162 | ) -> std::ops::Range { 163 | SourceRange::new(start, end) 164 | .as_byte_range(context.program.text_info().range().start) 165 | } 166 | -------------------------------------------------------------------------------- /rs-lib/src/polyfills/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::HashSet; 4 | 5 | use deno_ast::swc::common::SyntaxContext; 6 | use deno_ast::view::Expr; 7 | use deno_ast::view::Node; 8 | use deno_ast::view::ObjectPatProp; 9 | use deno_ast::view::Pat; 10 | use deno_ast::view::Program; 11 | use deno_ast::view::PropName; 12 | use deno_ast::SourceRanged; 13 | 14 | use crate::Dependency; 15 | use crate::ScriptTarget; 16 | 17 | mod array_find_last; 18 | mod array_from_async; 19 | mod error_cause; 20 | mod import_meta; 21 | mod object_has_own; 22 | mod promise_with_resolvers; 23 | mod string_replace_all; 24 | 25 | pub trait Polyfill { 26 | fn use_for_target(&self, target: ScriptTarget) -> bool; 27 | fn visit_node( 28 | &self, 29 | node: Node, 30 | context: &PolyfillVisitContext<'_, '_>, 31 | ) -> bool; 32 | fn get_file_text(&self) -> &'static str; 33 | fn dependencies(&self) -> Vec { 34 | Vec::new() 35 | } 36 | } 37 | 38 | pub struct PolyfillVisitContext<'a, 'b> { 39 | pub program: Program<'b>, 40 | pub unresolved_context: SyntaxContext, 41 | pub top_level_decls: &'a HashSet, 42 | } 43 | 44 | impl PolyfillVisitContext<'_, '_> { 45 | pub fn has_global_property_access( 46 | &self, 47 | node: Node, 48 | global_name: &str, 49 | property_name: &str, 50 | ) -> bool { 51 | match node { 52 | // ex. Object.hasOwn 53 | Node::MemberExpr(member_expr) => { 54 | if let Expr::Ident(obj_ident) = &member_expr.obj { 55 | obj_ident.ctxt() == self.unresolved_context 56 | && !self.top_level_decls.contains(global_name) 57 | && obj_ident.text_fast(self.program) == global_name 58 | && member_expr.prop.text_fast(self.program) == property_name 59 | } else { 60 | false 61 | } 62 | } 63 | // ex. const { hasOwn } = Object; 64 | Node::VarDeclarator(decl) => { 65 | let init = match &decl.init { 66 | Some(Expr::Ident(ident)) => ident, 67 | _ => return false, 68 | }; 69 | let props = match &decl.name { 70 | Pat::Object(obj) => &obj.props, 71 | _ => return false, 72 | }; 73 | init.ctxt() == self.unresolved_context 74 | && !self.top_level_decls.contains(global_name) 75 | && init.text_fast(self.program) == global_name 76 | && props.iter().any(|prop| { 77 | match prop { 78 | ObjectPatProp::Rest(_) => true, // unknown, so include 79 | ObjectPatProp::Assign(assign) => { 80 | assign.key.text_fast(self.program) == property_name 81 | } 82 | ObjectPatProp::KeyValue(key_value) => match &key_value.key { 83 | PropName::BigInt(_) | PropName::Num(_) => false, 84 | PropName::Computed(_) => true, // unknown, so include 85 | PropName::Ident(ident) => { 86 | ident.text_fast(self.program) == property_name 87 | } 88 | PropName::Str(str) => str.value() == property_name, 89 | }, 90 | } 91 | }) 92 | } 93 | _ => false, 94 | } 95 | } 96 | } 97 | 98 | pub fn polyfills_for_target(target: ScriptTarget) -> Vec> { 99 | if matches!(target, ScriptTarget::Latest) { 100 | return Vec::new(); 101 | } 102 | 103 | all_polyfills() 104 | .into_iter() 105 | .filter(|p| p.use_for_target(target)) 106 | .collect() 107 | } 108 | 109 | fn all_polyfills() -> Vec> { 110 | vec![ 111 | Box::new(object_has_own::ObjectHasOwnPolyfill), 112 | Box::new(error_cause::ErrorCausePolyfill), 113 | Box::new(string_replace_all::StringReplaceAllPolyfill), 114 | Box::new(array_find_last::ArrayFindLastPolyfill), 115 | Box::new(array_from_async::ArrayFromAsyncPolyfill), 116 | Box::new(import_meta::ImportMetaPolyfill), 117 | Box::new(promise_with_resolvers::PromiseWithResolversPolyfill), 118 | ] 119 | } 120 | 121 | pub fn build_polyfill_file(polyfills: &[Box]) -> Option { 122 | if polyfills.is_empty() { 123 | return None; 124 | } 125 | 126 | let mut file_text = String::new(); 127 | 128 | for polyfill in polyfills { 129 | file_text.push_str(polyfill.get_file_text()); 130 | } 131 | 132 | Some(file_text) 133 | } 134 | 135 | #[cfg(test)] 136 | struct PolyfillTester { 137 | create_polyfill: Box Box>, 138 | } 139 | 140 | #[cfg(test)] 141 | impl PolyfillTester { 142 | pub fn new(create_polyfill: Box Box>) -> Self { 143 | Self { create_polyfill } 144 | } 145 | 146 | pub fn matches(&self, text: &str) -> bool { 147 | use deno_ast::MediaType; 148 | use deno_ast::ModuleSpecifier; 149 | use deno_graph::ast::EsParser; 150 | use deno_graph::ast::ParseOptions; 151 | 152 | use crate::analyze::get_top_level_decls; 153 | use crate::parser::ScopeAnalysisParser; 154 | use crate::visitors::fill_polyfills; 155 | use crate::visitors::FillPolyfillsParams; 156 | 157 | let parser = ScopeAnalysisParser; 158 | let parsed_source = parser 159 | .parse_program(ParseOptions { 160 | specifier: &ModuleSpecifier::parse("file://test.ts").unwrap(), 161 | source: text.into(), 162 | media_type: MediaType::TypeScript, 163 | scope_analysis: true, 164 | }) 165 | .unwrap(); 166 | parsed_source.with_view(|program| { 167 | let mut searching_polyfills = vec![(self.create_polyfill)()]; 168 | let mut found_polyfills = Vec::new(); 169 | let unresolved_context = parsed_source.unresolved_context(); 170 | let top_level_decls = get_top_level_decls(program, unresolved_context); 171 | fill_polyfills(&mut FillPolyfillsParams { 172 | program, 173 | unresolved_context, 174 | top_level_decls: &top_level_decls, 175 | searching_polyfills: &mut searching_polyfills, 176 | found_polyfills: &mut found_polyfills, 177 | }); 178 | !found_polyfills.is_empty() 179 | }) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /lib/compiler.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import { ts } from "@ts-morph/bootstrap"; 4 | import * as path from "@std/path"; 5 | import type { ScriptTarget } from "./types.ts"; 6 | 7 | export function outputDiagnostics(diagnostics: readonly ts.Diagnostic[]) { 8 | const host: ts.FormatDiagnosticsHost = { 9 | getCanonicalFileName: (fileName) => path.resolve(fileName), 10 | getCurrentDirectory: () => Deno.cwd(), 11 | getNewLine: () => "\n", 12 | }; 13 | const output = Deno.noColor 14 | ? ts.formatDiagnostics(diagnostics, host) 15 | : ts.formatDiagnosticsWithColorAndContext(diagnostics, host); 16 | console.error(output); 17 | } 18 | 19 | export function getCompilerScriptTarget(target: ScriptTarget) { 20 | switch (target) { 21 | case "ES3": 22 | return ts.ScriptTarget.ES3; 23 | case "ES5": 24 | return ts.ScriptTarget.ES5; 25 | case "ES2015": 26 | return ts.ScriptTarget.ES2015; 27 | case "ES2016": 28 | return ts.ScriptTarget.ES2016; 29 | case "ES2017": 30 | return ts.ScriptTarget.ES2017; 31 | case "ES2018": 32 | return ts.ScriptTarget.ES2018; 33 | case "ES2019": 34 | return ts.ScriptTarget.ES2019; 35 | case "ES2020": 36 | return ts.ScriptTarget.ES2020; 37 | case "ES2021": 38 | return ts.ScriptTarget.ES2021; 39 | case "ES2022": 40 | return ts.ScriptTarget.ES2022; 41 | case "ES2023": 42 | return ts.ScriptTarget.ES2023; 43 | case "Latest": 44 | return ts.ScriptTarget.Latest; 45 | default: 46 | throw new Error(`Unknown target compiler option: ${target}`); 47 | } 48 | } 49 | 50 | // Created from https://github.com/microsoft/TypeScript/blob/v5.2.2/src/compiler/commandLineParser.ts 51 | // then aligned with tsconfig.json's casing 52 | export type LibName = 53 | | "ES5" 54 | | "ES6" 55 | | "ES2015" 56 | | "ES7" 57 | | "ES2016" 58 | | "ES2017" 59 | | "ES2018" 60 | | "ES2019" 61 | | "ES2020" 62 | | "ES2021" 63 | | "ES2022" 64 | | "ES2023" 65 | | "ESNext" 66 | | "DOM" 67 | | "DOM.Iterable" 68 | | "WebWorker" 69 | | "WebWorker.ImportScripts" 70 | | "WebWorker.Iterable" 71 | | "ScriptHost" 72 | | "ES2015.Core" 73 | | "ES2015.Collection" 74 | | "ES2015.Generator" 75 | | "ES2015.Iterable" 76 | | "ES2015.Promise" 77 | | "ES2015.Proxy" 78 | | "ES2015.Reflect" 79 | | "ES2015.Symbol" 80 | | "ES2015.Symbol.WellKnown" 81 | | "ES2016.Array.Include" 82 | | "ES2017.Date" 83 | | "ES2017.Object" 84 | | "ES2017.SharedMemory" 85 | | "ES2017.String" 86 | | "ES2017.Intl" 87 | | "ES2017.TypedArrays" 88 | | "ES2018.AsyncGenerator" 89 | | "ES2018.AsyncIterable" 90 | | "ES2018.Intl" 91 | | "ES2018.Promise" 92 | | "ES2018.RegExp" 93 | | "ES2019.Array" 94 | | "ES2019.Object" 95 | | "ES2019.String" 96 | | "ES2019.Symbol" 97 | | "ES2019.Intl" 98 | | "ES2020.Bigint" 99 | | "ES2020.Date" 100 | | "ES2020.Promise" 101 | | "ES2020.SharedMemory" 102 | | "ES2020.String" 103 | | "ES2020.Symbol.WellKnown" 104 | | "ES2020.Intl" 105 | | "ES2020.Number" 106 | | "ES2021.Promise" 107 | | "ES2021.String" 108 | | "ES2021.WeakRef" 109 | | "ES2021.Intl" 110 | | "ES2022.Array" 111 | | "ES2022.Error" 112 | | "ES2022.Intl" 113 | | "ES2022.Object" 114 | | "ES2022.SharedMemory" 115 | | "ES2022.String" 116 | | "ES2022.RegExp" 117 | | "ES2023.Array" 118 | | "ES2023.Collection" 119 | | "ESNext.Array" 120 | | "ESNext.Collection" 121 | | "ESNext.Symbol" 122 | | "ESNext.AsyncIterable" 123 | | "ESNext.Intl" 124 | | "ESNext.Disposable" 125 | | "ESNext.BigInt" 126 | | "ESNext.String" 127 | | "ESNext.Promise" 128 | | "ESNext.WeakRef" 129 | | "ESNext.Decorators" 130 | | "Decorators" 131 | | "Decorators.Legacy"; 132 | 133 | export function getCompilerLibOption(target: ScriptTarget): LibName[] { 134 | switch (target) { 135 | case "ES3": 136 | return []; 137 | case "ES5": 138 | return ["ES5"]; 139 | case "ES2015": 140 | return ["ES2015"]; 141 | case "ES2016": 142 | return ["ES2016"]; 143 | case "ES2017": 144 | return ["ES2017"]; 145 | case "ES2018": 146 | return ["ES2018"]; 147 | case "ES2019": 148 | return ["ES2019"]; 149 | case "ES2020": 150 | return ["ES2020"]; 151 | case "ES2021": 152 | return ["ES2021"]; 153 | case "ES2022": 154 | return ["ES2022"]; 155 | case "ES2023": 156 | return ["ES2023"]; 157 | case "Latest": 158 | return ["ESNext"]; 159 | default: { 160 | const _assertNever: never = target; 161 | throw new Error(`Unknown target compiler option: ${target}`); 162 | } 163 | } 164 | } 165 | 166 | export function libNamesToCompilerOption(names: LibName[]) { 167 | const libFileNames: string[] = []; 168 | const libMap = (ts as any).libMap as Map; 169 | for (const name of names) { 170 | const fileName = libMap.get(name.toLowerCase()); 171 | if (fileName == null) { 172 | throw new Error(`Could not find filename for lib: ${name}`); 173 | } else { 174 | libFileNames.push(fileName); 175 | } 176 | } 177 | return libFileNames; 178 | } 179 | 180 | export type SourceMapOptions = "inline" | boolean; 181 | 182 | export function getCompilerSourceMapOptions( 183 | sourceMaps: SourceMapOptions | undefined, 184 | ): { inlineSourceMap?: boolean; sourceMap?: boolean } { 185 | switch (sourceMaps) { 186 | case "inline": 187 | return { inlineSourceMap: true }; 188 | case true: 189 | return { sourceMap: true }; 190 | default: 191 | return {}; 192 | } 193 | } 194 | 195 | export function getTopLevelAwaitLocation(sourceFile: ts.SourceFile) { 196 | const topLevelAwait = getTopLevelAwait(sourceFile); 197 | if (topLevelAwait !== undefined) { 198 | return sourceFile.getLineAndCharacterOfPosition( 199 | topLevelAwait.getStart(sourceFile), 200 | ); 201 | } 202 | return undefined; 203 | } 204 | 205 | function getTopLevelAwait(node: ts.Node): ts.Node | undefined { 206 | if (ts.isAwaitExpression(node)) { 207 | return node; 208 | } 209 | if (ts.isForOfStatement(node) && node.awaitModifier !== undefined) { 210 | return node; 211 | } 212 | return ts.forEachChild(node, (child) => { 213 | if ( 214 | !ts.isFunctionDeclaration(child) && !ts.isFunctionExpression(child) && 215 | !ts.isArrowFunction(child) && !ts.isMethodDeclaration(child) 216 | ) { 217 | return getTopLevelAwait(child); 218 | } 219 | }); 220 | } 221 | 222 | export function transformCodeToTarget(code: string, target: ts.ScriptTarget) { 223 | return ts.transpile(code, { 224 | target, 225 | }); 226 | } 227 | -------------------------------------------------------------------------------- /rs-lib/src/analyze/helpers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use deno_ast::view::*; 4 | use deno_ast::SourceRanged; 5 | 6 | pub fn is_in_type(mut node: Node) -> bool { 7 | // todo: add unit tests and investigate if there's something in swc that does this? 8 | while let Some(parent) = node.parent() { 9 | let is_type = match parent { 10 | Node::ArrayLit(_) 11 | | Node::ArrayPat(_) 12 | | Node::ArrowExpr(_) 13 | | Node::AssignExpr(_) 14 | | Node::AssignPat(_) 15 | | Node::AssignPatProp(_) 16 | | Node::AssignProp(_) 17 | | Node::AutoAccessor(_) 18 | | Node::AwaitExpr(_) 19 | | Node::BinExpr(_) 20 | | Node::BindingIdent(_) 21 | | Node::BlockStmt(_) 22 | | Node::BreakStmt(_) 23 | | Node::CallExpr(_) 24 | | Node::CatchClause(_) 25 | | Node::Class(_) 26 | | Node::ClassDecl(_) 27 | | Node::ClassExpr(_) 28 | | Node::ClassMethod(_) 29 | | Node::ClassProp(_) 30 | | Node::ComputedPropName(_) 31 | | Node::CondExpr(_) 32 | | Node::Constructor(_) 33 | | Node::ContinueStmt(_) 34 | | Node::DebuggerStmt(_) 35 | | Node::Decorator(_) 36 | | Node::DoWhileStmt(_) 37 | | Node::EmptyStmt(_) 38 | | Node::ExportAll(_) 39 | | Node::ExportDecl(_) 40 | | Node::ExportDefaultDecl(_) 41 | | Node::ExportDefaultExpr(_) 42 | | Node::ExportDefaultSpecifier(_) 43 | | Node::ExportNamedSpecifier(_) 44 | | Node::ExportNamespaceSpecifier(_) 45 | | Node::ExprOrSpread(_) 46 | | Node::ExprStmt(_) 47 | | Node::FnDecl(_) 48 | | Node::FnExpr(_) 49 | | Node::ForInStmt(_) 50 | | Node::ForOfStmt(_) 51 | | Node::ForStmt(_) 52 | | Node::Function(_) 53 | | Node::GetterProp(_) 54 | | Node::IdentName(_) 55 | | Node::IfStmt(_) 56 | | Node::Import(_) 57 | | Node::ImportDecl(_) 58 | | Node::ImportDefaultSpecifier(_) 59 | | Node::ImportNamedSpecifier(_) 60 | | Node::ImportStarAsSpecifier(_) 61 | | Node::Invalid(_) 62 | | Node::UnaryExpr(_) 63 | | Node::UpdateExpr(_) 64 | | Node::VarDecl(_) 65 | | Node::VarDeclarator(_) 66 | | Node::WhileStmt(_) 67 | | Node::WithStmt(_) 68 | | Node::YieldExpr(_) 69 | | Node::JSXAttr(_) 70 | | Node::JSXClosingElement(_) 71 | | Node::JSXClosingFragment(_) 72 | | Node::JSXElement(_) 73 | | Node::JSXEmptyExpr(_) 74 | | Node::JSXExprContainer(_) 75 | | Node::JSXFragment(_) 76 | | Node::JSXMemberExpr(_) 77 | | Node::JSXNamespacedName(_) 78 | | Node::JSXOpeningElement(_) 79 | | Node::JSXOpeningFragment(_) 80 | | Node::JSXSpreadChild(_) 81 | | Node::JSXText(_) 82 | | Node::KeyValuePatProp(_) 83 | | Node::KeyValueProp(_) 84 | | Node::LabeledStmt(_) 85 | | Node::MetaPropExpr(_) 86 | | Node::MethodProp(_) 87 | | Node::Module(_) 88 | | Node::NamedExport(_) 89 | | Node::NewExpr(_) 90 | | Node::ObjectLit(_) 91 | | Node::ObjectPat(_) 92 | | Node::OptCall(_) 93 | | Node::OptChainExpr(_) 94 | | Node::Param(_) 95 | | Node::ParenExpr(_) 96 | | Node::PrivateMethod(_) 97 | | Node::PrivateName(_) 98 | | Node::PrivateProp(_) 99 | | Node::Regex(_) 100 | | Node::RestPat(_) 101 | | Node::ReturnStmt(_) 102 | | Node::Script(_) 103 | | Node::SeqExpr(_) 104 | | Node::SetterProp(_) 105 | | Node::SpreadElement(_) 106 | | Node::StaticBlock(_) 107 | | Node::Super(_) 108 | | Node::SuperPropExpr(_) 109 | | Node::SwitchCase(_) 110 | | Node::SwitchStmt(_) 111 | | Node::TaggedTpl(_) 112 | | Node::ThisExpr(_) 113 | | Node::ThrowStmt(_) 114 | | Node::Tpl(_) 115 | | Node::TplElement(_) 116 | | Node::TryStmt(_) 117 | | Node::UsingDecl(_) 118 | | Node::TsEnumDecl(_) 119 | | Node::TsEnumMember(_) 120 | | Node::TsExportAssignment(_) 121 | | Node::TsExternalModuleRef(_) 122 | | Node::TsImportEqualsDecl(_) 123 | | Node::TsModuleBlock(_) 124 | | Node::TsModuleDecl(_) 125 | | Node::TsNamespaceDecl(_) 126 | | Node::TsNamespaceExportDecl(_) 127 | | Node::TsInstantiation(_) => Some(false), 128 | Node::TsArrayType(_) 129 | | Node::TsCallSignatureDecl(_) 130 | | Node::TsConditionalType(_) 131 | | Node::TsConstAssertion(_) 132 | | Node::TsConstructSignatureDecl(_) 133 | | Node::TsConstructorType(_) 134 | | Node::TsExprWithTypeArgs(_) 135 | | Node::TsFnType(_) 136 | | Node::TsGetterSignature(_) 137 | | Node::TsImportType(_) 138 | | Node::TsIndexSignature(_) 139 | | Node::TsIndexedAccessType(_) 140 | | Node::TsInferType(_) 141 | | Node::TsInterfaceBody(_) 142 | | Node::TsInterfaceDecl(_) 143 | | Node::TsIntersectionType(_) 144 | | Node::TsKeywordType(_) 145 | | Node::TsImportCallOptions(_) 146 | | Node::TsLitType(_) 147 | | Node::TsMappedType(_) 148 | | Node::TsMethodSignature(_) 149 | | Node::TsNonNullExpr(_) 150 | | Node::TsOptionalType(_) 151 | | Node::TsParamProp(_) 152 | | Node::TsParenthesizedType(_) 153 | | Node::TsPropertySignature(_) 154 | | Node::TsQualifiedName(_) 155 | | Node::TsRestType(_) 156 | | Node::TsSetterSignature(_) 157 | | Node::TsThisType(_) 158 | | Node::TsTplLitType(_) 159 | | Node::TsTupleElement(_) 160 | | Node::TsTupleType(_) 161 | | Node::TsTypeAliasDecl(_) 162 | | Node::TsTypeAnn(_) 163 | | Node::TsTypeLit(_) 164 | | Node::TsTypeOperator(_) 165 | | Node::TsTypeParam(_) 166 | | Node::TsTypeParamDecl(_) 167 | | Node::TsTypeParamInstantiation(_) 168 | | Node::TsTypePredicate(_) 169 | | Node::TsTypeQuery(_) 170 | | Node::TsTypeRef(_) 171 | | Node::TsUnionType(_) => Some(true), 172 | Node::TsTypeAssertion(expr) => { 173 | Some(expr.type_ann.range().contains(&node.range())) 174 | } 175 | Node::TsAsExpr(expr) => { 176 | Some(expr.type_ann.range().contains(&node.range())) 177 | } 178 | Node::TsSatisfiesExpr(expr) => { 179 | Some(expr.type_ann.range().contains(&node.range())) 180 | } 181 | Node::BigInt(_) 182 | | Node::Bool(_) 183 | | Node::Null(_) 184 | | Node::Number(_) 185 | | Node::MemberExpr(_) 186 | | Node::Str(_) 187 | | Node::Ident(_) => None, 188 | }; 189 | if let Some(is_type) = is_type { 190 | return is_type; 191 | } 192 | node = parent; 193 | } 194 | 195 | false 196 | } 197 | -------------------------------------------------------------------------------- /rs-lib/src/declaration_file_resolution.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::BTreeMap; 4 | use std::collections::HashSet; 5 | 6 | use anyhow::Result; 7 | use deno_ast::ModuleSpecifier; 8 | use deno_graph::JsModule; 9 | use deno_graph::Module; 10 | use deno_graph::Resolution; 11 | 12 | use crate::graph::ModuleGraph; 13 | use crate::PackageMappedSpecifier; 14 | 15 | #[derive(Debug)] 16 | pub struct DeclarationFileResolution { 17 | pub selected: TypesDependency, 18 | /// Specified declaration dependencies that were ignored. 19 | pub ignored: Vec, 20 | } 21 | 22 | #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] 23 | pub struct TypesDependency { 24 | /// The module being specified. 25 | pub specifier: ModuleSpecifier, 26 | /// The module that specified the specifier. 27 | pub referrer: ModuleSpecifier, 28 | } 29 | 30 | pub fn resolve_declaration_file_mappings( 31 | module_graph: &ModuleGraph, 32 | modules: &[&Module], 33 | mapped_specifiers: &BTreeMap, 34 | ) -> Result> { 35 | let mut type_dependencies = BTreeMap::new(); 36 | 37 | for module in modules.iter().filter_map(|m| m.js()) { 38 | fill_types_for_module(module_graph, module, &mut type_dependencies)?; 39 | } 40 | 41 | // get the resolved type dependencies 42 | let mut mappings = BTreeMap::new(); 43 | for (code_specifier, deps) in type_dependencies.into_iter() { 44 | // if this type_dependency is mapped, then pass it. 45 | if mapped_specifiers.contains_key(&code_specifier) { 46 | continue; 47 | } 48 | 49 | let deps = deps.into_iter().collect::>(); 50 | let selected_dep = 51 | select_best_types_dep(module_graph, &code_specifier, &deps); 52 | 53 | // get the declaration file specifiers that weren't used 54 | let mut ignored = deps 55 | .into_iter() 56 | .filter(|d| d.specifier != selected_dep.specifier) 57 | .collect::>(); 58 | ignored.sort(); 59 | 60 | mappings.insert( 61 | code_specifier, 62 | DeclarationFileResolution { 63 | selected: selected_dep, 64 | ignored, 65 | }, 66 | ); 67 | } 68 | 69 | Ok(mappings) 70 | } 71 | 72 | /// This resolution process works as follows: 73 | /// 74 | /// 1. Prefer using a declaration file specified in local code over remote. This allows the user 75 | /// to override what is potentially done remotely and in the worst case provide their own declaration file. 76 | /// 2. Next prefer when the referrer is from the declaration file itself (ex. x-deno-types header) 77 | /// 3. Finally use the declaration file that is the largest. 78 | fn select_best_types_dep( 79 | module_graph: &ModuleGraph, 80 | code_specifier: &ModuleSpecifier, 81 | deps: &[TypesDependency], 82 | ) -> TypesDependency { 83 | assert!(!deps.is_empty()); 84 | let mut selected_dep = &deps[0]; 85 | for dep in &deps[1..] { 86 | let is_dep_referrer_local = dep.referrer.scheme() == "file"; 87 | let is_dep_referrer_code = &dep.referrer == code_specifier; 88 | 89 | let is_selected_referrer_local = selected_dep.referrer.scheme() == "file"; 90 | let is_selected_referrer_code = &selected_dep.referrer == code_specifier; 91 | 92 | let should_replace = if is_dep_referrer_local && !is_selected_referrer_local 93 | { 94 | true 95 | } else if is_dep_referrer_local == is_selected_referrer_local { 96 | if is_selected_referrer_code { 97 | false 98 | } else if is_dep_referrer_code { 99 | true 100 | } else if let Some(dep_source) = 101 | module_graph.get(&dep.specifier).js().map(|m| &m.source) 102 | { 103 | // as a last resort, use the declaration file that's the largest 104 | if let Some(selected_source) = module_graph 105 | .get(&selected_dep.specifier) 106 | .js() 107 | .map(|m| &m.source) 108 | { 109 | dep_source.text.len() > selected_source.text.len() 110 | } else { 111 | true 112 | } 113 | } else { 114 | false 115 | } 116 | } else { 117 | false 118 | }; 119 | if should_replace { 120 | selected_dep = dep; 121 | } 122 | } 123 | selected_dep.clone() 124 | } 125 | 126 | fn fill_types_for_module( 127 | module_graph: &ModuleGraph, 128 | module: &JsModule, 129 | type_dependencies: &mut BTreeMap>, 130 | ) -> Result<()> { 131 | // check for the module specifying its type dependency 132 | match &module.maybe_types_dependency { 133 | Some(deno_graph::TypesDependency { 134 | specifier: text, 135 | dependency: Resolution::Err(err), 136 | }) => anyhow::bail!( 137 | "Error resolving types for {} with reference {}. {}", 138 | module.specifier, 139 | text, 140 | err.to_string() 141 | ), 142 | Some(deno_graph::TypesDependency { 143 | dependency: Resolution::Ok(resolved), 144 | .. 145 | }) => { 146 | add_type_dependency( 147 | module, 148 | &module.specifier, 149 | &resolved.specifier, 150 | type_dependencies, 151 | ); 152 | } 153 | _ => {} 154 | } 155 | 156 | // find any @deno-types 157 | for dep in module.dependencies.values() { 158 | if let Some(type_dep) = dep.get_type() { 159 | if let Some(code_dep) = dep.get_code() { 160 | if module_graph 161 | .get(type_dep) 162 | .js() 163 | .map(|module| is_declaration_file(module.media_type)) 164 | .unwrap_or(false) 165 | { 166 | add_type_dependency(module, code_dep, type_dep, type_dependencies); 167 | } 168 | } 169 | } 170 | } 171 | 172 | return Ok(()); 173 | 174 | fn add_type_dependency( 175 | module: &JsModule, 176 | code_specifier: &ModuleSpecifier, 177 | type_specifier: &ModuleSpecifier, 178 | type_dependencies: &mut BTreeMap>, 179 | ) { 180 | // if the code specifier is the same as the type specifier, then no 181 | // mapping is necessary 182 | if code_specifier == type_specifier { 183 | return; 184 | } 185 | 186 | type_dependencies 187 | .entry(code_specifier.clone()) 188 | .or_default() 189 | .insert(TypesDependency { 190 | referrer: module.specifier.clone(), 191 | specifier: type_specifier.clone(), 192 | }); 193 | } 194 | } 195 | 196 | fn is_declaration_file(media_type: deno_ast::MediaType) -> bool { 197 | // todo: use media_type.is_declaration() in deno_ast once available 198 | use deno_ast::MediaType::*; 199 | match media_type { 200 | Dts | Dmts | Dcts => true, 201 | JavaScript | Jsx | Mjs | Cjs | TypeScript | Mts | Cts | Tsx | Json 202 | | Wasm | Css | Html | Sql | SourceMap | Unknown => false, 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /transform.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | /** 4 | * Lower level `transform` functionality that's used by the CLI 5 | * to convert Deno code to Node code. 6 | * @module 7 | */ 8 | 9 | import * as wasm from "./lib/pkg/dnt_wasm.js"; 10 | import type { ScriptTarget } from "./lib/types.ts"; 11 | import { valueToUrl } from "./lib/utils.ts"; 12 | 13 | /** Specifier to specifier mappings. */ 14 | export interface SpecifierMappings { 15 | /** Map a specifier to another module or npm package. */ 16 | [specifier: string]: PackageMappedSpecifier | string; 17 | } 18 | 19 | export interface PackageMappedSpecifier { 20 | /** Name of the npm package specifier to map to. */ 21 | name: string; 22 | /** Version to use in the package.json file. 23 | * 24 | * Not specifying a version will exclude it from the package.json file. 25 | * This is useful for built-in modules such as "fs". 26 | */ 27 | version?: string; 28 | /** Sub path of the npm package to use in the module specifier. 29 | * 30 | * @remarks This should not include the package name and should not 31 | * include a leading slash. It will be concatenated to the package 32 | * name in the module specifier like so: `/` 33 | */ 34 | subPath?: string; 35 | /** If this should be a peer dependency. */ 36 | peerDependency?: boolean; 37 | } 38 | 39 | export interface GlobalName { 40 | /** Name to use as the global name. */ 41 | name: string; 42 | /** Name of the export from the package. 43 | * @remarks Defaults to the name. Specify `"default"` to use the default export. 44 | */ 45 | exportName?: string; 46 | /** Whether this is a name that only exists as a type declaration. */ 47 | typeOnly?: boolean; 48 | } 49 | 50 | export type Shim = PackageShim | ModuleShim; 51 | 52 | export interface PackageShim { 53 | /** Information about the npm package specifier to import. */ 54 | package: PackageMappedSpecifier; 55 | /** Npm package to include in the dev depedencies that has the type declarations. */ 56 | typesPackage?: Dependency; 57 | /** Named exports from the shim to use as globals. */ 58 | globalNames: (GlobalName | string)[]; 59 | } 60 | 61 | export interface ModuleShim { 62 | /** The module or bare specifier. */ 63 | module: string; 64 | /** Named exports from the shim to use as globals. */ 65 | globalNames: (GlobalName | string)[]; 66 | } 67 | 68 | export interface TransformOptions { 69 | entryPoints: string[]; 70 | testEntryPoints?: string[]; 71 | shims?: Shim[]; 72 | testShims?: Shim[]; 73 | mappings?: SpecifierMappings; 74 | target: ScriptTarget; 75 | /// Path or url to the import map. 76 | importMap?: string; 77 | configFile?: string; 78 | cwd: string; 79 | } 80 | 81 | /** Dependency in a package.json file. */ 82 | export interface Dependency { 83 | /** Name of the package. */ 84 | name: string; 85 | /** Version specifier (ex. `^1.0.0`). */ 86 | version: string; 87 | /** If this is suggested to be a peer dependency. */ 88 | peerDependency?: boolean; 89 | } 90 | 91 | export interface TransformOutput { 92 | main: TransformOutputEnvironment; 93 | test: TransformOutputEnvironment; 94 | warnings: string[]; 95 | } 96 | 97 | export interface TransformOutputEnvironment { 98 | entryPoints: string[]; 99 | dependencies: Dependency[]; 100 | files: OutputFile[]; 101 | } 102 | 103 | export interface OutputFile { 104 | filePath: string; 105 | fileText: string; 106 | } 107 | 108 | /** Analyzes the provided entry point to get all the dependended on modules and 109 | * outputs canonical TypeScript code in memory. The output of this function 110 | * can then be sent to the TypeScript compiler or a bundler for further processing. */ 111 | export function transform( 112 | options: TransformOptions, 113 | ): Promise { 114 | if (options.entryPoints.length === 0) { 115 | throw new Error("Specify one or more entry points."); 116 | } 117 | const newOptions = { 118 | ...options, 119 | mappings: Object.fromEntries( 120 | Object.entries(options.mappings ?? {}).map(([key, value]) => { 121 | return [valueToUrl(key), mapMappedSpecifier(value)]; 122 | }), 123 | ), 124 | entryPoints: options.entryPoints.map(valueToUrl), 125 | testEntryPoints: (options.testEntryPoints ?? []).map(valueToUrl), 126 | shims: (options.shims ?? []).map(mapShim), 127 | testShims: (options.testShims ?? []).map(mapShim), 128 | target: options.target, 129 | importMap: options.importMap == null 130 | ? undefined 131 | : valueToUrl(options.importMap), 132 | }; 133 | return wasm.transform(newOptions); 134 | } 135 | 136 | type SerializableMappedSpecifier = { 137 | kind: "package"; 138 | value: PackageMappedSpecifier; 139 | } | { 140 | kind: "module"; 141 | value: string; 142 | }; 143 | 144 | function mapMappedSpecifier( 145 | value: string | PackageMappedSpecifier, 146 | ): SerializableMappedSpecifier { 147 | if (typeof value === "string") { 148 | if (isPathOrUrl(value)) { 149 | return { 150 | kind: "module", 151 | value: valueToUrl(value), 152 | }; 153 | } else { 154 | return { 155 | kind: "package", 156 | value: { 157 | name: value, 158 | }, 159 | }; 160 | } 161 | } else { 162 | return { 163 | kind: "package", 164 | value, 165 | }; 166 | } 167 | } 168 | 169 | type SerializableShim = { kind: "package"; value: PackageShim } | { 170 | kind: "module"; 171 | value: ModuleShim; 172 | }; 173 | 174 | function mapShim(value: Shim): SerializableShim { 175 | const newValue: Shim = { 176 | ...value, 177 | globalNames: value.globalNames.map(mapToGlobalName), 178 | }; 179 | if (isPackageShim(newValue)) { 180 | return { kind: "package", value: newValue }; 181 | } else { 182 | return { 183 | kind: "module", 184 | value: { 185 | ...newValue, 186 | module: resolveBareSpecifierOrPath(newValue.module), 187 | }, 188 | }; 189 | } 190 | } 191 | 192 | function isPackageShim(value: Shim): value is PackageShim { 193 | return (value as PackageShim).package != null; 194 | } 195 | 196 | function mapToGlobalName(value: string | GlobalName): GlobalName { 197 | if (typeof value === "string") { 198 | return { 199 | name: value, 200 | typeOnly: false, 201 | }; 202 | } else { 203 | value.typeOnly ??= false; 204 | return value; 205 | } 206 | } 207 | 208 | function resolveBareSpecifierOrPath(value: string) { 209 | value = value.trim(); 210 | if (isPathOrUrl(value)) { 211 | return valueToUrl(value); 212 | } else { 213 | return value; 214 | } 215 | } 216 | 217 | function isPathOrUrl(value: string) { 218 | value = value.trim(); 219 | return /^[a-z]+:\/\//i.test(value) || // has scheme 220 | value.startsWith("./") || 221 | value.startsWith("../") || 222 | /\.[a-z]+$/i.test(value); // has extension 223 | } 224 | -------------------------------------------------------------------------------- /rs-lib/tests/integration/test_builder.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::HashMap; 4 | 5 | use anyhow::Result; 6 | use deno_node_transform::transform; 7 | use deno_node_transform::GlobalName; 8 | use deno_node_transform::MappedSpecifier; 9 | use deno_node_transform::ModuleSpecifier; 10 | use deno_node_transform::PackageMappedSpecifier; 11 | use deno_node_transform::PackageShim; 12 | use deno_node_transform::ScriptTarget; 13 | use deno_node_transform::Shim; 14 | use deno_node_transform::TransformOptions; 15 | use deno_node_transform::TransformOutput; 16 | use sys_traits::EnvCurrentDir; 17 | 18 | use super::InMemoryLoader; 19 | 20 | pub struct TestBuilder { 21 | loader: InMemoryLoader, 22 | entry_point: String, 23 | additional_entry_points: Vec, 24 | test_entry_points: Vec, 25 | specifier_mappings: HashMap, 26 | shims: Vec, 27 | test_shims: Vec, 28 | target: ScriptTarget, 29 | config_file: Option, 30 | import_map: Option, 31 | } 32 | 33 | impl TestBuilder { 34 | pub fn new() -> Self { 35 | Self { 36 | loader: InMemoryLoader::new(), 37 | entry_point: if cfg!(windows) { 38 | "file:///C:/mod.ts".to_string() 39 | } else { 40 | "file:///mod.ts".to_string() 41 | }, 42 | additional_entry_points: Vec::new(), 43 | test_entry_points: Vec::new(), 44 | specifier_mappings: Default::default(), 45 | shims: Default::default(), 46 | test_shims: Default::default(), 47 | target: ScriptTarget::ES5, 48 | config_file: None, 49 | import_map: None, 50 | } 51 | } 52 | 53 | pub fn with_loader( 54 | &mut self, 55 | mut action: impl FnMut(&mut InMemoryLoader), 56 | ) -> &mut Self { 57 | action(&mut self.loader); 58 | self 59 | } 60 | 61 | pub fn entry_point(&mut self, value: impl AsRef) -> &mut Self { 62 | self.entry_point = normalize_urls(value.as_ref()); 63 | self 64 | } 65 | 66 | pub fn add_entry_point(&mut self, value: impl AsRef) -> &mut Self { 67 | self 68 | .additional_entry_points 69 | .push(normalize_urls(value.as_ref())); 70 | self 71 | } 72 | 73 | pub fn add_test_entry_point(&mut self, value: impl AsRef) -> &mut Self { 74 | self.test_entry_points.push(normalize_urls(value.as_ref())); 75 | self 76 | } 77 | 78 | pub fn set_config_file(&mut self, url: impl AsRef) -> &mut Self { 79 | self.import_map = 80 | Some(ModuleSpecifier::parse(&normalize_urls(url.as_ref())).unwrap()); 81 | self 82 | } 83 | 84 | pub fn set_import_map(&mut self, url: impl AsRef) -> &mut Self { 85 | self.import_map = 86 | Some(ModuleSpecifier::parse(&normalize_urls(url.as_ref())).unwrap()); 87 | self 88 | } 89 | 90 | pub fn add_default_shims(&mut self) -> &mut Self { 91 | let deno_shim = Shim::Package(PackageShim { 92 | package: PackageMappedSpecifier { 93 | name: "@deno/shim-deno".to_string(), 94 | version: Some("^0.1.0".to_string()), 95 | sub_path: None, 96 | peer_dependency: false, 97 | }, 98 | types_package: None, 99 | global_names: vec![GlobalName { 100 | name: "Deno".to_string(), 101 | export_name: None, 102 | type_only: false, 103 | }], 104 | }); 105 | self.add_shim(deno_shim.clone()); 106 | self.add_test_shim(deno_shim); 107 | let timers_shim = Shim::Package(PackageShim { 108 | package: PackageMappedSpecifier { 109 | name: "@deno/shim-timers".to_string(), 110 | version: Some("^0.1.0".to_string()), 111 | sub_path: None, 112 | peer_dependency: false, 113 | }, 114 | types_package: None, 115 | global_names: vec![ 116 | GlobalName { 117 | name: "setTimeout".to_string(), 118 | export_name: None, 119 | type_only: false, 120 | }, 121 | GlobalName { 122 | name: "setInterval".to_string(), 123 | export_name: None, 124 | type_only: false, 125 | }, 126 | ], 127 | }); 128 | self.add_shim(timers_shim.clone()); 129 | self.add_test_shim(timers_shim); 130 | self 131 | } 132 | 133 | pub fn add_shim(&mut self, shim: Shim) -> &mut Self { 134 | self.shims.push(shim); 135 | self 136 | } 137 | 138 | pub fn add_test_shim(&mut self, shim: Shim) -> &mut Self { 139 | self.test_shims.push(shim); 140 | self 141 | } 142 | 143 | pub fn add_package_specifier_mapping( 144 | &mut self, 145 | specifier: impl AsRef, 146 | bare_specifier: impl AsRef, 147 | version: Option<&str>, 148 | path: Option<&str>, 149 | ) -> &mut Self { 150 | self.specifier_mappings.insert( 151 | ModuleSpecifier::parse(&normalize_urls(specifier.as_ref())).unwrap(), 152 | MappedSpecifier::Package(PackageMappedSpecifier { 153 | name: bare_specifier.as_ref().to_string(), 154 | version: version.map(|v| v.to_string()), 155 | sub_path: path.map(|v| v.to_string()), 156 | peer_dependency: false, 157 | }), 158 | ); 159 | self 160 | } 161 | 162 | pub fn add_module_specifier_mapping( 163 | &mut self, 164 | from: impl AsRef, 165 | to: impl AsRef, 166 | ) -> &mut Self { 167 | self.specifier_mappings.insert( 168 | ModuleSpecifier::parse(&normalize_urls(from.as_ref())).unwrap(), 169 | MappedSpecifier::Module( 170 | ModuleSpecifier::parse(&normalize_urls(to.as_ref())).unwrap(), 171 | ), 172 | ); 173 | self 174 | } 175 | 176 | pub fn set_target(&mut self, target: ScriptTarget) -> &mut Self { 177 | self.target = target; 178 | self 179 | } 180 | 181 | pub async fn transform(&self) -> Result { 182 | let mut entry_points = 183 | vec![ModuleSpecifier::parse(&self.entry_point).unwrap()]; 184 | entry_points.extend( 185 | self 186 | .additional_entry_points 187 | .iter() 188 | .map(|p| ModuleSpecifier::parse(p).unwrap()), 189 | ); 190 | transform( 191 | self.loader.sys.clone(), 192 | self.loader.clone(), 193 | TransformOptions { 194 | entry_points, 195 | test_entry_points: self 196 | .test_entry_points 197 | .iter() 198 | .map(|p| ModuleSpecifier::parse(p).unwrap()) 199 | .collect(), 200 | shims: self.shims.clone(), 201 | test_shims: self.test_shims.clone(), 202 | specifier_mappings: self.specifier_mappings.clone(), 203 | target: self.target, 204 | config_file: self.config_file.clone(), 205 | import_map: self.import_map.clone(), 206 | cwd: self.loader.sys.env_current_dir().unwrap(), 207 | }, 208 | ) 209 | .await 210 | } 211 | } 212 | 213 | pub fn normalize_urls(url: &str) -> String { 214 | if cfg!(windows) { 215 | url.replace("file:///", "file:///C:/") 216 | } else { 217 | url.to_string() 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /lib/test_runner/test_runner.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | export interface Picocolors { 4 | green(text: string): string; 5 | red(text: string): string; 6 | gray(text: string): string; 7 | } 8 | 9 | export interface NodeProcess { 10 | stdout: { 11 | write(text: string): void; 12 | }; 13 | exit(code: number): number; 14 | } 15 | 16 | export interface RunTestDefinitionsOptions { 17 | pc: Picocolors; 18 | process: NodeProcess; 19 | /** The file the tests are running in. */ 20 | origin: string; 21 | } 22 | 23 | export interface TestDefinition { 24 | name: string; 25 | fn: (context: TestContext) => Promise | void; 26 | only?: boolean; 27 | ignore?: boolean; 28 | } 29 | 30 | export interface TestContext { 31 | name: string; 32 | parent: TestContext | undefined; 33 | origin: string; 34 | err: any; 35 | children: TestContext[]; 36 | hasFailingChild: boolean; 37 | getOutput(): string; 38 | step( 39 | nameOrDefinition: string | TestDefinition, 40 | fn?: (context: TestContext) => void | Promise, 41 | ): Promise; 42 | status: "ok" | "fail" | "pending" | "ignored"; 43 | } 44 | 45 | export async function runTestDefinitions( 46 | testDefinitions: TestDefinition[], 47 | options: RunTestDefinitionsOptions, 48 | ) { 49 | const testFailures = []; 50 | const hasOnly = testDefinitions.some((d) => d.only); 51 | if (hasOnly) { 52 | testDefinitions = testDefinitions.filter((d) => d.only); 53 | } 54 | for (const definition of testDefinitions) { 55 | options.process.stdout.write("test " + definition.name + " ..."); 56 | if (definition.ignore) { 57 | options.process.stdout.write(` ${options.pc.gray("ignored")}\n`); 58 | continue; 59 | } 60 | const context = getTestContext(definition, undefined); 61 | let pass = false; 62 | try { 63 | await definition.fn(context); 64 | if (context.hasFailingChild) { 65 | testFailures.push({ 66 | name: definition.name, 67 | err: new Error("Had failing test step."), 68 | }); 69 | } else { 70 | pass = true; 71 | } 72 | } catch (err) { 73 | testFailures.push({ name: definition.name, err }); 74 | } 75 | const testStepOutput = context.getOutput(); 76 | if (testStepOutput.length > 0) { 77 | options.process.stdout.write(testStepOutput); 78 | } else { 79 | options.process.stdout.write(" "); 80 | } 81 | options.process.stdout.write(getStatusText(pass ? "ok" : "fail")); 82 | options.process.stdout.write("\n"); 83 | } 84 | 85 | if (testFailures.length > 0) { 86 | options.process.stdout.write("\nFAILURES"); 87 | for (const failure of testFailures) { 88 | options.process.stdout.write("\n\n"); 89 | options.process.stdout.write(failure.name + "\n"); 90 | options.process.stdout.write( 91 | indentText(((failure.err as any)?.stack ?? failure.err).toString(), 1), 92 | ); 93 | } 94 | options.process.exit(1); 95 | } else if (hasOnly) { 96 | options.process.stdout.write( 97 | 'error: Test failed because the "only" option was used.\n', 98 | ); 99 | options.process.exit(1); 100 | } 101 | 102 | function getTestContext( 103 | definition: TestDefinition, 104 | parent: TestContext | undefined, 105 | ): TestContext { 106 | return { 107 | name: definition.name, 108 | parent, 109 | origin: options.origin, 110 | /** @type {any} */ 111 | err: undefined, 112 | status: "ok", 113 | children: [], 114 | get hasFailingChild() { 115 | return this.children.some((c) => 116 | c.status === "fail" || c.status === "pending" 117 | ); 118 | }, 119 | getOutput() { 120 | let output = ""; 121 | if (this.parent) { 122 | output += "test " + this.name + " ..."; 123 | } 124 | if (this.children.length > 0) { 125 | output += "\n" + this.children.map((c) => 126 | indentText(c.getOutput(), 1) 127 | ).join("\n") + "\n"; 128 | } else if (!this.err) { 129 | output += " "; 130 | } 131 | if (this.parent && this.err) { 132 | output += "\n"; 133 | } 134 | if (this.err) { 135 | output += indentText((this.err.stack ?? this.err).toString(), 1); 136 | if (this.parent) { 137 | output += "\n"; 138 | } 139 | } 140 | if (this.parent) { 141 | output += getStatusText(this.status); 142 | } 143 | return output; 144 | }, 145 | async step(nameOrTestDefinition, fn) { 146 | const definition = getDefinition(); 147 | 148 | const context = getTestContext(definition, this); 149 | context.status = "pending"; 150 | this.children.push(context); 151 | 152 | if (definition.ignore) { 153 | context.status = "ignored"; 154 | return false; 155 | } 156 | 157 | try { 158 | await definition.fn(context); 159 | context.status = "ok"; 160 | if (context.hasFailingChild) { 161 | context.status = "fail"; 162 | return false; 163 | } 164 | return true; 165 | } catch (err) { 166 | context.status = "fail"; 167 | context.err = err; 168 | return false; 169 | } 170 | 171 | /** @returns {TestDefinition} */ 172 | function getDefinition() { 173 | if (typeof nameOrTestDefinition === "string") { 174 | if (!(fn instanceof Function)) { 175 | throw new TypeError("Expected function for second argument."); 176 | } 177 | return { 178 | name: nameOrTestDefinition, 179 | fn, 180 | }; 181 | } else if (typeof nameOrTestDefinition === "object") { 182 | return nameOrTestDefinition; 183 | } else { 184 | throw new TypeError( 185 | "Expected a test definition or name and function.", 186 | ); 187 | } 188 | } 189 | }, 190 | }; 191 | } 192 | 193 | function getStatusText(status: TestContext["status"]) { 194 | switch (status) { 195 | case "ok": 196 | return options.pc.green(status); 197 | case "fail": 198 | case "pending": 199 | return options.pc.red(status); 200 | case "ignored": 201 | return options.pc.gray(status); 202 | default: { 203 | const _assertNever: never = status; 204 | return status; 205 | } 206 | } 207 | } 208 | 209 | function indentText(text: string, indentLevel: number) { 210 | if (text === undefined) { 211 | text = "[undefined]"; 212 | } else if (text === null) { 213 | text = "[null]"; 214 | } else { 215 | text = text.toString(); 216 | } 217 | return text.split(/\r?\n/) 218 | .map((line) => " ".repeat(indentLevel) + line) 219 | .join("\n"); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /lib/package_json.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import type { EntryPoint, ShimOptions } from "../mod.ts"; 4 | import type { TransformOutput } from "../transform.ts"; 5 | import type { PackageJson } from "./types.ts"; 6 | import { getDntVersion } from "./utils.ts"; 7 | 8 | export interface GetPackageJsonOptions { 9 | transformOutput: TransformOutput; 10 | entryPoints: EntryPoint[]; 11 | package: PackageJson; 12 | includeEsModule: boolean | undefined; 13 | includeScriptModule: boolean | undefined; 14 | includeDeclarations: boolean | undefined; 15 | includeTsLib: boolean | undefined; 16 | testEnabled: boolean | undefined; 17 | shims: ShimOptions; 18 | } 19 | 20 | export function getPackageJson({ 21 | transformOutput, 22 | entryPoints, 23 | package: packageJsonObj, 24 | includeEsModule, 25 | includeScriptModule, 26 | includeDeclarations, 27 | includeTsLib, 28 | testEnabled, 29 | shims, 30 | }: GetPackageJsonOptions): Record { 31 | const finalEntryPoints = transformOutput 32 | .main.entryPoints.map((e, i) => ({ 33 | name: entryPoints[i].name, 34 | kind: entryPoints[i].kind ?? "export", 35 | path: e.replace(/\.tsx?$/i, ".js"), 36 | types: e.replace(/\.tsx?$/i, ".d.ts"), 37 | })); 38 | const exports = finalEntryPoints.filter((e) => e.kind === "export"); 39 | const binaries = finalEntryPoints.filter((e) => e.kind === "bin"); 40 | const dependencies = { 41 | // typescript helpers library (https://www.npmjs.com/package/tslib) 42 | ...(includeTsLib 43 | ? { 44 | tslib: "^2.6.2", 45 | } 46 | : {}), 47 | // add dependencies from transform 48 | ...Object.fromEntries( 49 | transformOutput.main.dependencies 50 | .filter((d) => !d.peerDependency) 51 | .map((d) => [d.name, d.version]), 52 | ), 53 | // override with specified dependencies 54 | ...(packageJsonObj.dependencies ?? {}), 55 | }; 56 | const peerDependencies = { 57 | // add dependencies from transform 58 | ...Object.fromEntries( 59 | transformOutput.main.dependencies 60 | .filter((d) => d.peerDependency) 61 | .map((d) => [d.name, d.version]), 62 | ), 63 | // override with specified dependencies 64 | ...(packageJsonObj.peerDependencies ?? {}), 65 | }; 66 | const testDevDependencies = testEnabled 67 | ? ({ 68 | ...(!Object.keys(dependencies).includes("picocolors") 69 | ? { 70 | "picocolors": "^1.0.0", 71 | } 72 | : {}), 73 | // add dependencies from transform 74 | ...Object.fromEntries( 75 | // ignore peer dependencies on this 76 | transformOutput.test.dependencies.map((d) => [d.name, d.version]) ?? 77 | [], 78 | ), 79 | }) 80 | : {}; 81 | const devDependencies = { 82 | ...(shouldIncludeTypesNode() 83 | ? { 84 | "@types/node": "^20.9.0", 85 | } 86 | : {}), 87 | ...testDevDependencies, 88 | // override with specified dependencies 89 | ...(packageJsonObj.devDependencies ?? {}), 90 | }; 91 | const scripts = testEnabled 92 | ? ({ 93 | test: "node test_runner.js", 94 | // override with specified scripts 95 | ...(packageJsonObj.scripts ?? {}), 96 | }) 97 | : packageJsonObj.scripts ?? {}; 98 | const mainExport = exports.length > 0 99 | ? { 100 | module: includeEsModule ? `./esm/${exports[0].path}` : undefined, 101 | main: includeScriptModule ? `./script/${exports[0].path}` : undefined, 102 | types: includeDeclarations ? `./types/${exports[0].types}` : undefined, 103 | } 104 | : {}; 105 | const binaryExport = binaries.length > 0 106 | ? { 107 | bin: Object.fromEntries(binaries.map((b) => [b.name, `./esm/${b.path}`])), 108 | } 109 | : {}; 110 | 111 | const final: Record = { 112 | ...mainExport, 113 | ...binaryExport, 114 | ...packageJsonObj, 115 | scripts: {}, 116 | ...deleteEmptyKeys({ 117 | exports: { 118 | ...(includeEsModule || exports.length > 1 119 | ? { 120 | ...(Object.fromEntries(exports.map((e) => { 121 | return [e.name, { 122 | import: includeEsModule 123 | ? getPathOrTypesObject(`./esm/${e.path}`) 124 | : undefined, 125 | require: includeScriptModule 126 | ? getPathOrTypesObject(`./script/${e.path}`) 127 | : undefined, 128 | ...(packageJsonObj.exports?.[e.name] ?? {}), 129 | }]; 130 | 131 | function getPathOrTypesObject(path: string) { 132 | if (includeDeclarations) { 133 | return { 134 | // "types" must always be first and "default" last 135 | types: 136 | (e.name === "." ? packageJsonObj.types : undefined) ?? 137 | `./types/${e.types}`, 138 | default: path, 139 | }; 140 | } else { 141 | return path; 142 | } 143 | } 144 | }))), 145 | } 146 | : {}), 147 | // allow someone to override 148 | ...(packageJsonObj.exports ?? {}), 149 | }, 150 | scripts, 151 | dependencies, 152 | peerDependencies, 153 | devDependencies, 154 | }), 155 | _generatedBy: `dnt@${getDntVersion()}`, 156 | }; 157 | return sortObject(final); 158 | 159 | function shouldIncludeTypesNode() { 160 | if (Object.keys(dependencies).includes("@types/node")) { 161 | return false; 162 | } 163 | 164 | if (typeof shims.deno === "object") { 165 | if (shims.deno.test) { 166 | return true; 167 | } else { 168 | return false; 169 | } 170 | } else if (shims.deno || shims.undici) { 171 | return true; 172 | } else { 173 | return false; 174 | } 175 | } 176 | 177 | function deleteEmptyKeys(obj: Record) { 178 | for (const key of Object.keys(obj)) { 179 | const value = obj[key]; 180 | if ( 181 | typeof value === "object" && value != null && 182 | Object.keys(value).length === 0 183 | ) { 184 | delete obj[key]; 185 | } 186 | } 187 | return obj; 188 | } 189 | } 190 | 191 | function sortObject(obj: Record) { 192 | const highPrecedence = [ 193 | "name", 194 | "version", 195 | "description", 196 | "keywords", 197 | "author", 198 | "homepage", 199 | "repository", 200 | "license", 201 | "bugs", 202 | "main", 203 | "module", 204 | "types", 205 | "typings", 206 | "exports", 207 | "scripts", 208 | ]; 209 | const lowPrecedence = [ 210 | "dependencies", 211 | "peerDependencies", 212 | "devDependencies", 213 | "_generatedBy", 214 | ]; 215 | const sortedObj: Record = {}; 216 | const finalEntries: Record = {}; 217 | for (const key of highPrecedence) { 218 | if (key in obj) { 219 | sortedObj[key] = obj[key]; 220 | delete obj[key]; 221 | } 222 | } 223 | for (const key of lowPrecedence) { 224 | if (key in obj) { 225 | finalEntries[key] = obj[key]; 226 | delete obj[key]; 227 | } 228 | } 229 | for (const key of Object.keys(obj)) { 230 | sortedObj[key] = obj[key]; 231 | } 232 | for (const [key, value] of Object.entries(finalEntries)) { 233 | sortedObj[key] = value; 234 | } 235 | 236 | return sortedObj; 237 | } 238 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "specifiers": { 4 | "jsr:@david/code-block-writer@^13.0.3": "13.0.3", 5 | "jsr:@std/assert@0.221": "0.221.0", 6 | "jsr:@std/assert@1": "1.0.13", 7 | "jsr:@std/bytes@^1.0.4": "1.0.4", 8 | "jsr:@std/csv@*": "1.0.6", 9 | "jsr:@std/fmt@0.221": "0.221.0", 10 | "jsr:@std/fmt@1": "1.0.8", 11 | "jsr:@std/fs@1": "1.0.19", 12 | "jsr:@std/internal@^1.0.1": "1.0.1", 13 | "jsr:@std/internal@^1.0.6": "1.0.9", 14 | "jsr:@std/internal@^1.0.9": "1.0.9", 15 | "jsr:@std/path@1": "1.0.2", 16 | "jsr:@std/path@^1.0.2": "1.0.2", 17 | "jsr:@std/path@^1.1.1": "1.1.1", 18 | "jsr:@ts-morph/bootstrap@0.27": "0.27.0", 19 | "jsr:@ts-morph/common@0.27": "0.27.0", 20 | "npm:@types/node@*": "18.16.19", 21 | "npm:using-statement@0.4": "0.4.2" 22 | }, 23 | "jsr": { 24 | "@david/code-block-writer@13.0.2": { 25 | "integrity": "14dd3baaafa3a2dea8bf7dfbcddeccaa13e583da2d21d666c01dc6d681cd74ad" 26 | }, 27 | "@david/code-block-writer@13.0.3": { 28 | "integrity": "f98c77d320f5957899a61bfb7a9bead7c6d83ad1515daee92dbacc861e13bb7f" 29 | }, 30 | "@std/assert@0.221.0": { 31 | "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a", 32 | "dependencies": [ 33 | "jsr:@std/fmt@0.221" 34 | ] 35 | }, 36 | "@std/assert@1.0.2": { 37 | "integrity": "ccacec332958126deaceb5c63ff8b4eaf9f5ed0eac9feccf124110435e59e49c", 38 | "dependencies": [ 39 | "jsr:@std/internal@^1.0.1" 40 | ] 41 | }, 42 | "@std/assert@1.0.13": { 43 | "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29", 44 | "dependencies": [ 45 | "jsr:@std/internal@^1.0.6" 46 | ] 47 | }, 48 | "@std/bytes@1.0.4": { 49 | "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" 50 | }, 51 | "@std/csv@1.0.1": { 52 | "integrity": "0704026df4361efa0fa7a278eb01cbc99f395fd0ac48f8df668fdc59cbd9f908" 53 | }, 54 | "@std/csv@1.0.6": { 55 | "integrity": "52ef0e62799a0028d278fa04762f17f9bd263fad9a8e7f98c14fbd371d62d9fd" 56 | }, 57 | "@std/fmt@0.221.0": { 58 | "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" 59 | }, 60 | "@std/fmt@1.0.0": { 61 | "integrity": "8a95c9fdbb61559418ccbc0f536080cf43341655e1444f9d375a66886ceaaa3d" 62 | }, 63 | "@std/fmt@1.0.4": { 64 | "integrity": "e14fe5bedee26f80877e6705a97a79c7eed599e81bb1669127ef9e8bc1e29a74" 65 | }, 66 | "@std/fmt@1.0.8": { 67 | "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" 68 | }, 69 | "@std/fs@1.0.1": { 70 | "integrity": "d6914ca2c21abe591f733b31dbe6331e446815e513e2451b3b9e472daddfefcb", 71 | "dependencies": [ 72 | "jsr:@std/path@^1.0.2" 73 | ] 74 | }, 75 | "@std/fs@1.0.10": { 76 | "integrity": "bf041f9d7a0a460817f0421be8946d0e06011b3433e6c83a215628de5e3c7c2c" 77 | }, 78 | "@std/fs@1.0.19": { 79 | "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", 80 | "dependencies": [ 81 | "jsr:@std/internal@^1.0.9", 82 | "jsr:@std/path@^1.1.1" 83 | ] 84 | }, 85 | "@std/internal@1.0.1": { 86 | "integrity": "6f8c7544d06a11dd256c8d6ba54b11ed870aac6c5aeafff499892662c57673e6" 87 | }, 88 | "@std/internal@1.0.6": { 89 | "integrity": "9533b128f230f73bd209408bb07a4b12f8d4255ab2a4d22a1fd6d87304aca9a4" 90 | }, 91 | "@std/internal@1.0.9": { 92 | "integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8" 93 | }, 94 | "@std/io@0.225.1": { 95 | "integrity": "0a055fa523288d596f09ba11af5e006f17cecc75bab949b233ceba7eba40293e", 96 | "dependencies": [ 97 | "jsr:@std/bytes" 98 | ] 99 | }, 100 | "@std/path@1.0.2": { 101 | "integrity": "a452174603f8c620bd278a380c596437a9eef50c891c64b85812f735245d9ec7" 102 | }, 103 | "@std/path@1.0.8": { 104 | "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" 105 | }, 106 | "@std/path@1.1.1": { 107 | "integrity": "fe00026bd3a7e6a27f73709b83c607798be40e20c81dde655ce34052fd82ec76", 108 | "dependencies": [ 109 | "jsr:@std/internal@^1.0.9" 110 | ] 111 | }, 112 | "@ts-morph/bootstrap@0.27.0": { 113 | "integrity": "b8d7bc8f7942ce853dde4161b28f9aa96769cef3d8eebafb379a81800b9e2448", 114 | "dependencies": [ 115 | "jsr:@ts-morph/common" 116 | ] 117 | }, 118 | "@ts-morph/common@0.27.0": { 119 | "integrity": "c7b73592d78ce8479b356fd4f3d6ec3c460d77753a8680ff196effea7a939052", 120 | "dependencies": [ 121 | "jsr:@std/fs", 122 | "jsr:@std/path@1" 123 | ] 124 | } 125 | }, 126 | "npm": { 127 | "@types/node@18.16.19": { 128 | "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==" 129 | }, 130 | "using-statement@0.4.2": { 131 | "integrity": "sha512-fBGez+wCZUy35EW9Ic/QFUaPNAVIxMp2xhwG1iOSSDmo0SC4GfHEcKNsFaccy4XK8NSSa+2KlqnZdy0DALBNOw==" 132 | } 133 | }, 134 | "remote": { 135 | "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", 136 | "https://deno.land/std@0.181.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", 137 | "https://deno.land/std@0.181.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", 138 | "https://deno.land/std@0.181.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", 139 | "https://deno.land/std@0.181.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", 140 | "https://deno.land/std@0.181.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", 141 | "https://deno.land/std@0.181.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", 142 | "https://deno.land/std@0.181.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", 143 | "https://deno.land/std@0.181.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", 144 | "https://deno.land/std@0.181.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", 145 | "https://deno.land/std@0.181.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", 146 | "https://deno.land/std@0.181.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", 147 | "https://deno.land/std@0.181.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", 148 | "https://deno.land/std@0.181.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", 149 | "https://deno.land/std@0.181.0/testing/asserts.ts": "e16d98b4d73ffc4ed498d717307a12500ae4f2cbe668f1a215632d19fcffc22f", 150 | "https://deno.land/x/code_block_writer@11.0.0/comment_char.ts": "22b66890bbdf7a2d59777ffd8231710c1fda1c11fadada67632a596937a1a314", 151 | "https://deno.land/x/code_block_writer@11.0.0/mod.ts": "dc43d56c3487bae02886a09754fb09c607da4ea866817e80f3e60632f3391d70", 152 | "https://deno.land/x/code_block_writer@11.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", 153 | "https://deno.land/x/which_runtime@0.2.0/mod.ts": "3e8f1ddb3a043818264a8035c0132aaf72d94977d45cd23ecfb03b2156f06126" 154 | }, 155 | "workspace": { 156 | "dependencies": [ 157 | "jsr:@david/code-block-writer@^13.0.3", 158 | "jsr:@std/assert@1", 159 | "jsr:@std/fmt@1", 160 | "jsr:@std/fs@1", 161 | "jsr:@std/path@1", 162 | "jsr:@ts-morph/bootstrap@0.27" 163 | ] 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /rs-lib/src/specifiers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::BTreeMap; 4 | use std::collections::HashMap; 5 | use std::collections::HashSet; 6 | 7 | use anyhow::Result; 8 | use deno_ast::ModuleSpecifier; 9 | use deno_graph::Module; 10 | use deno_graph::Resolution; 11 | use deno_semver::npm::NpmPackageReqReference; 12 | 13 | use crate::declaration_file_resolution::resolve_declaration_file_mappings; 14 | use crate::declaration_file_resolution::DeclarationFileResolution; 15 | use crate::graph::ModuleGraph; 16 | use crate::loader::LoaderSpecifiers; 17 | use crate::PackageMappedSpecifier; 18 | 19 | #[derive(Debug)] 20 | pub struct Specifiers { 21 | pub local: Vec, 22 | pub remote: Vec, 23 | pub types: BTreeMap, 24 | pub test_modules: HashSet, 25 | pub main: EnvironmentSpecifiers, 26 | pub test: EnvironmentSpecifiers, 27 | } 28 | 29 | impl Specifiers { 30 | pub fn has_mapped(&self, specifier: &ModuleSpecifier) -> bool { 31 | self.main.mapped.contains_key(specifier) 32 | || self.test.mapped.contains_key(specifier) 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | pub struct EnvironmentSpecifiers { 38 | pub mapped: BTreeMap, 39 | } 40 | 41 | pub fn get_specifiers<'a>( 42 | entry_points: &[ModuleSpecifier], 43 | mut specifiers: LoaderSpecifiers, 44 | module_graph: &ModuleGraph, 45 | modules: impl Iterator, 46 | ) -> Result { 47 | let mut local_specifiers = Vec::new(); 48 | let mut remote_specifiers = Vec::new(); 49 | 50 | let mut modules: BTreeMap<&ModuleSpecifier, &Module> = 51 | modules.map(|m| (m.specifier(), m)).collect(); 52 | 53 | let mut found_module_specifiers = Vec::new(); 54 | let mut found_mapped_specifiers = BTreeMap::new(); 55 | 56 | // search for all the non-test modules 57 | for entry_point in entry_points.iter() { 58 | let module = module_graph.get(entry_point); 59 | let mut pending = vec![module.specifier()]; 60 | 61 | while !pending.is_empty() { 62 | if let Some(module) = pending 63 | .pop() 64 | .and_then(|s| modules.remove(&module_graph.resolve(s))) 65 | { 66 | if let Some(mapped_entry) = 67 | specifiers.mapped_packages.remove(module.specifier()) 68 | { 69 | found_mapped_specifiers 70 | .insert(module.specifier().clone(), mapped_entry); 71 | } else if let Ok(npm_specifier) = 72 | deno_semver::npm::NpmPackageReqReference::from_specifier( 73 | module.specifier(), 74 | ) 75 | { 76 | found_mapped_specifiers.insert( 77 | module.specifier().clone(), 78 | PackageMappedSpecifier::from_npm_specifier(&npm_specifier), 79 | ); 80 | } else { 81 | found_module_specifiers.push(module.specifier().clone()); 82 | 83 | if let Some(module) = module.js() { 84 | for dep in module.dependencies.values() { 85 | if let Some(specifier) = dep.get_code() { 86 | pending.push(specifier); 87 | } 88 | if let Some(specifier) = dep.get_type() { 89 | pending.push(specifier); 90 | } 91 | } 92 | if let Some(deno_graph::TypesDependency { 93 | dependency: Resolution::Ok(resolved), 94 | .. 95 | }) = &module.maybe_types_dependency 96 | { 97 | pending.push(&resolved.specifier); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | // clear out all the mapped modules 106 | for specifier in specifiers.mapped_packages.keys() { 107 | modules.remove(specifier); 108 | } 109 | 110 | // at this point, the remaining modules are the test modules 111 | let test_modules = modules; 112 | let all_modules = test_modules 113 | .values() 114 | .copied() 115 | .chain(found_module_specifiers.iter().map(|s| module_graph.get(s))) 116 | .collect::>(); 117 | 118 | for module in all_modules.iter() { 119 | match module { 120 | Module::Js(_) | Module::Json(_) => { 121 | match module.specifier().scheme().to_lowercase().as_str() { 122 | "file" => local_specifiers.push(module.specifier().clone()), 123 | "http" | "https" => { 124 | remote_specifiers.push(module.specifier().clone()) 125 | } 126 | _ => { 127 | anyhow::bail!("Unhandled scheme on url: {}", module.specifier()); 128 | } 129 | } 130 | } 131 | Module::Npm(_) | Module::Node(_) => { 132 | // ignore 133 | } 134 | Module::Wasm(_) => { 135 | anyhow::bail!( 136 | "Not implemented support for Wasm modules: {}", 137 | module.specifier() 138 | ); 139 | } 140 | Module::External(module) => { 141 | let specifier = &module.specifier; 142 | if let Ok(npm_specifier) = 143 | NpmPackageReqReference::from_specifier(specifier) 144 | { 145 | if !found_mapped_specifiers.contains_key(specifier) { 146 | specifiers.mapped_packages.insert( 147 | specifier.clone(), 148 | PackageMappedSpecifier::from_npm_specifier(&npm_specifier), 149 | ); 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | let types = resolve_declaration_file_mappings( 157 | module_graph, 158 | &all_modules, 159 | &found_mapped_specifiers, 160 | )?; 161 | let mut declaration_specifiers = HashSet::new(); 162 | for value in types.values() { 163 | declaration_specifiers.insert(&value.selected.specifier); 164 | for dep in value.ignored.iter() { 165 | declaration_specifiers.insert(&dep.specifier); 166 | } 167 | } 168 | 169 | ensure_package_mapped_specifiers_valid( 170 | &found_mapped_specifiers, 171 | &specifiers.mapped_packages, 172 | )?; 173 | 174 | Ok(Specifiers { 175 | local: local_specifiers 176 | .into_iter() 177 | .filter(|l| !declaration_specifiers.contains(&l)) 178 | .collect(), 179 | remote: remote_specifiers 180 | .into_iter() 181 | .filter(|l| !declaration_specifiers.contains(&l)) 182 | .collect(), 183 | types, 184 | test_modules: test_modules 185 | .values() 186 | .map(|k| k.specifier().clone()) 187 | .collect(), 188 | main: EnvironmentSpecifiers { 189 | mapped: found_mapped_specifiers, 190 | }, 191 | test: EnvironmentSpecifiers { 192 | mapped: specifiers.mapped_packages, 193 | }, 194 | }) 195 | } 196 | 197 | fn ensure_package_mapped_specifiers_valid( 198 | mapped_specifiers: &BTreeMap, 199 | test_mapped_specifiers: &BTreeMap, 200 | ) -> Result<()> { 201 | let mut specifier_for_name: HashMap< 202 | String, 203 | (ModuleSpecifier, PackageMappedSpecifier), 204 | > = HashMap::new(); 205 | for (from_specifier, mapped_specifier) in mapped_specifiers 206 | .iter() 207 | .chain(test_mapped_specifiers.iter()) 208 | { 209 | if let Some(specifier) = specifier_for_name.get(&mapped_specifier.name) { 210 | if specifier.1.version != mapped_specifier.version { 211 | anyhow::bail!("Specifier {} with version {} did not match specifier {} with version {}.", 212 | specifier.0, 213 | specifier.1.version.as_deref().unwrap_or(""), 214 | from_specifier, 215 | mapped_specifier.version.as_deref().unwrap_or(""), 216 | ); 217 | } 218 | } else { 219 | specifier_for_name.insert( 220 | mapped_specifier.name.to_string(), 221 | (from_specifier.clone(), mapped_specifier.clone()), 222 | ); 223 | } 224 | } 225 | 226 | Ok(()) 227 | } 228 | -------------------------------------------------------------------------------- /lib/shims.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | import type { GlobalName, Shim } from "../transform.ts"; 4 | 5 | /** Provide `true` to use the shim in both the distributed code and test code, 6 | * `"dev"` to only use it in the test code, or `false` to not use the shim 7 | * at all. 8 | * 9 | * @remarks Defaults to `false`. 10 | */ 11 | export type ShimValue = boolean | "dev"; 12 | 13 | /** Provide `true` to use the shim in both the distributed code and test code, 14 | * `"dev"` to only use it in the test code, or `false` to not use the shim 15 | * at all. 16 | * 17 | * @remarks These all default to `false`. 18 | */ 19 | export interface ShimOptions { 20 | /** Shim the `Deno` namespace. */ 21 | deno?: ShimValue | { 22 | test: ShimValue; 23 | }; 24 | /** Shim the global `setTimeout` and `setInterval` functions with 25 | * Deno and browser compatible versions. 26 | */ 27 | timers?: ShimValue; 28 | /** Shim the global `confirm`, `alert`, and `prompt` functions. */ 29 | prompts?: ShimValue; 30 | /** Shim the `Blob` global with the one from the `"buffer"` module. */ 31 | blob?: ShimValue; 32 | /** Shim the `crypto` global. */ 33 | crypto?: ShimValue; 34 | /** Shim `DOMException` using the "domexception" package (https://www.npmjs.com/package/domexception) */ 35 | domException?: ShimValue; 36 | /** Shim `fetch`, `File`, `FormData`, `Headers`, `Request`, and `Response` by 37 | * using the "undici" package (https://www.npmjs.com/package/undici). 38 | */ 39 | undici?: ShimValue; 40 | /** Use a sham for the `WeakRef` global, which uses `globalThis.WeakRef` when 41 | * it exists. The sham will throw at runtime when calling `deref()` and `WeakRef` 42 | * doesn't globally exist, so this is only intended to help type check code that 43 | * won't actually use it. 44 | */ 45 | weakRef?: ShimValue; 46 | /** Shim `WebSocket` with the `ws` package (https://www.npmjs.com/package/ws). */ 47 | webSocket?: boolean | "dev"; 48 | /** Custom shims to use. */ 49 | custom?: Shim[]; 50 | /** Custom shims to use only for the test code. */ 51 | customDev?: Shim[]; 52 | } 53 | 54 | export interface DenoShimOptions { 55 | /** Only import the `Deno` namespace for `Deno.test`. 56 | * This may be useful for environments 57 | */ 58 | test: boolean | "dev"; 59 | } 60 | 61 | export function shimOptionsToTransformShims(options: ShimOptions) { 62 | const shims: Shim[] = []; 63 | const testShims: Shim[] = []; 64 | 65 | if (typeof options.deno === "object") { 66 | add(options.deno.test, getDenoTestShim); 67 | } else { 68 | add(options.deno, getDenoShim); 69 | } 70 | add(options.blob, getBlobShim); 71 | add(options.crypto, getCryptoShim); 72 | add(options.prompts, getPromptsShim); 73 | add(options.timers, getTimersShim); 74 | add(options.domException, getDomExceptionShim); 75 | add(options.undici, getUndiciShim); 76 | add(options.weakRef, getWeakRefShim); 77 | add(options.webSocket, getWebSocketShim); 78 | 79 | if (options.custom) { 80 | shims.push(...options.custom); 81 | testShims.push(...options.custom); 82 | } 83 | if (options.customDev) { 84 | testShims.push(...options.customDev); 85 | } 86 | 87 | return { 88 | shims, 89 | testShims, 90 | }; 91 | 92 | function add(option: boolean | "dev" | undefined, getShim: () => Shim) { 93 | if (option === true) { 94 | shims.push(getShim()); 95 | testShims.push(getShim()); 96 | } else if (option === "dev") { 97 | testShims.push(getShim()); 98 | } 99 | } 100 | } 101 | 102 | function getDenoShim(): Shim { 103 | return { 104 | package: { 105 | name: "@deno/shim-deno", 106 | version: "~0.18.0", 107 | }, 108 | globalNames: ["Deno"], 109 | }; 110 | } 111 | 112 | function getDenoTestShim(): Shim { 113 | return { 114 | package: { 115 | name: "@deno/shim-deno-test", 116 | version: "~0.5.0", 117 | }, 118 | globalNames: ["Deno"], 119 | }; 120 | } 121 | 122 | function getCryptoShim(): Shim { 123 | return { 124 | package: { 125 | name: "@deno/shim-crypto", 126 | version: "~0.3.1", 127 | }, 128 | globalNames: [ 129 | "crypto", 130 | typeOnly("Crypto"), 131 | typeOnly("SubtleCrypto"), 132 | typeOnly("AlgorithmIdentifier"), 133 | typeOnly("Algorithm"), 134 | typeOnly("RsaOaepParams"), 135 | typeOnly("BufferSource"), 136 | typeOnly("AesCtrParams"), 137 | typeOnly("AesCbcParams"), 138 | typeOnly("AesGcmParams"), 139 | typeOnly("CryptoKey"), 140 | typeOnly("KeyAlgorithm"), 141 | typeOnly("KeyType"), 142 | typeOnly("KeyUsage"), 143 | typeOnly("EcdhKeyDeriveParams"), 144 | typeOnly("HkdfParams"), 145 | typeOnly("HashAlgorithmIdentifier"), 146 | typeOnly("Pbkdf2Params"), 147 | typeOnly("AesDerivedKeyParams"), 148 | typeOnly("HmacImportParams"), 149 | typeOnly("JsonWebKey"), 150 | typeOnly("RsaOtherPrimesInfo"), 151 | typeOnly("KeyFormat"), 152 | typeOnly("RsaHashedKeyGenParams"), 153 | typeOnly("RsaKeyGenParams"), 154 | typeOnly("BigInteger"), 155 | typeOnly("EcKeyGenParams"), 156 | typeOnly("NamedCurve"), 157 | typeOnly("CryptoKeyPair"), 158 | typeOnly("AesKeyGenParams"), 159 | typeOnly("HmacKeyGenParams"), 160 | typeOnly("RsaHashedImportParams"), 161 | typeOnly("EcKeyImportParams"), 162 | typeOnly("AesKeyAlgorithm"), 163 | typeOnly("RsaPssParams"), 164 | typeOnly("EcdsaParams"), 165 | ], 166 | }; 167 | } 168 | 169 | function getBlobShim(): Shim { 170 | return { 171 | module: "buffer", 172 | globalNames: ["Blob"], 173 | }; 174 | } 175 | 176 | function getPromptsShim(): Shim { 177 | return { 178 | package: { 179 | name: "@deno/shim-prompts", 180 | version: "~0.1.0", 181 | }, 182 | globalNames: ["alert", "confirm", "prompt"], 183 | }; 184 | } 185 | 186 | function getTimersShim(): Shim { 187 | return { 188 | package: { 189 | name: "@deno/shim-timers", 190 | version: "~0.1.0", 191 | }, 192 | globalNames: ["setInterval", "setTimeout"], 193 | }; 194 | } 195 | 196 | function getUndiciShim(): Shim { 197 | return { 198 | package: { 199 | name: "undici", 200 | version: "^6.0.0", 201 | }, 202 | globalNames: [ 203 | "fetch", 204 | "File", 205 | "FormData", 206 | "Headers", 207 | "Request", 208 | "Response", 209 | typeOnly("BodyInit"), 210 | typeOnly("HeadersInit"), 211 | typeOnly("ReferrerPolicy"), 212 | typeOnly("RequestInit"), 213 | typeOnly("RequestCache"), 214 | typeOnly("RequestMode"), 215 | typeOnly("RequestRedirect"), 216 | typeOnly("ResponseInit"), 217 | ], 218 | }; 219 | } 220 | 221 | function getDomExceptionShim(): Shim { 222 | return { 223 | package: { 224 | name: "domexception", 225 | version: "^4.0.0", 226 | }, 227 | typesPackage: { 228 | name: "@types/domexception", 229 | version: "^4.0.0", 230 | }, 231 | globalNames: [{ 232 | name: "DOMException", 233 | exportName: "default", 234 | }], 235 | }; 236 | } 237 | 238 | function getWeakRefShim(): Shim { 239 | return { 240 | package: { 241 | name: "@deno/sham-weakref", 242 | version: "~0.1.0", 243 | }, 244 | globalNames: ["WeakRef", typeOnly("WeakRefConstructor")], 245 | }; 246 | } 247 | 248 | function getWebSocketShim(): Shim { 249 | return { 250 | package: { 251 | name: "ws", 252 | version: "^8.13.0", 253 | }, 254 | typesPackage: { 255 | name: "@types/ws", 256 | version: "^8.5.4", 257 | peerDependency: false, 258 | }, 259 | globalNames: [{ 260 | name: "WebSocket", 261 | exportName: "default", 262 | }], 263 | }; 264 | } 265 | 266 | function typeOnly(name: string): GlobalName { 267 | return { 268 | name, 269 | typeOnly: true, 270 | }; 271 | } 272 | -------------------------------------------------------------------------------- /rs-lib/src/graph.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. MIT license. 2 | 3 | use std::collections::BTreeMap; 4 | use std::collections::HashMap; 5 | use std::fmt::Write; 6 | use std::rc::Rc; 7 | 8 | use crate::loader::get_all_specifier_mappers; 9 | use crate::loader::SourceLoader; 10 | use crate::parser::ScopeAnalysisParser; 11 | use crate::specifiers::get_specifiers; 12 | use crate::specifiers::Specifiers; 13 | use crate::MappedSpecifier; 14 | 15 | use anyhow::bail; 16 | use anyhow::Result; 17 | use deno_ast::ModuleSpecifier; 18 | use deno_ast::ParseDiagnostic; 19 | use deno_ast::ParsedSource; 20 | use deno_graph::ast::CapturingModuleAnalyzer; 21 | use deno_graph::ast::EsParser; 22 | use deno_graph::ast::ParseOptions; 23 | use deno_graph::ast::ParsedSourceStore; 24 | use deno_graph::source::NullModuleInfoCacher; 25 | use deno_graph::JsModule; 26 | use deno_graph::Module; 27 | use deno_resolver::deno_json::CompilerOptionsResolver; 28 | use deno_resolver::deno_json::JsxImportSourceConfigResolver; 29 | use deno_resolver::factory::WorkspaceFactorySys; 30 | use deno_resolver::graph::DefaultDenoResolverRc; 31 | use deno_resolver::npm::DenoInNpmPackageChecker; 32 | use sys_traits::impls::RealSys; 33 | 34 | pub struct ModuleGraphOptions<'a, TSys: WorkspaceFactorySys> { 35 | pub entry_points: Vec, 36 | pub test_entry_points: Vec, 37 | pub loader: Rc, 38 | pub resolver: DefaultDenoResolverRc, 39 | pub specifier_mappings: &'a HashMap, 40 | pub compiler_options_resolver: Rc, 41 | pub cjs_tracker: 42 | Rc>, 43 | } 44 | 45 | /// Wrapper around deno_graph::ModuleGraph. 46 | pub struct ModuleGraph { 47 | graph: deno_graph::ModuleGraph, 48 | capturing_analyzer: CapturingModuleAnalyzer, 49 | } 50 | 51 | impl ModuleGraph { 52 | pub async fn build_with_specifiers( 53 | options: ModuleGraphOptions<'_, TSys>, 54 | ) -> Result<(Self, Specifiers)> { 55 | let resolver = options.resolver; 56 | let loader = options.loader; 57 | let loader = SourceLoader::new( 58 | loader, 59 | get_all_specifier_mappers(), 60 | options.specifier_mappings, 61 | ); 62 | let scoped_jsx_import_source_config = 63 | JsxImportSourceConfigResolver::from_compiler_options_resolver( 64 | &options.compiler_options_resolver, 65 | )?; 66 | let source_parser = ScopeAnalysisParser; 67 | let capturing_analyzer = 68 | CapturingModuleAnalyzer::new(Some(Box::new(source_parser)), None); 69 | let mut graph = deno_graph::ModuleGraph::new(deno_graph::GraphKind::All); 70 | let graph_resolver = resolver.as_graph_resolver( 71 | &options.cjs_tracker, 72 | &scoped_jsx_import_source_config, 73 | ); 74 | graph 75 | .build( 76 | options 77 | .entry_points 78 | .iter() 79 | .chain(options.test_entry_points.iter()) 80 | .map(|s| s.to_owned()) 81 | .collect(), 82 | Vec::new(), 83 | &loader, 84 | deno_graph::BuildOptions { 85 | is_dynamic: false, 86 | skip_dynamic_deps: false, 87 | resolver: Some(&graph_resolver), 88 | locker: None, 89 | module_analyzer: &capturing_analyzer, 90 | module_info_cacher: &NullModuleInfoCacher, 91 | reporter: None, 92 | npm_resolver: None, 93 | file_system: &RealSys, 94 | jsr_url_provider: Default::default(), 95 | executor: Default::default(), 96 | passthrough_jsr_specifiers: false, 97 | unstable_bytes_imports: false, 98 | unstable_text_imports: false, 99 | }, 100 | ) 101 | .await; 102 | 103 | let mut error_message = String::new(); 104 | for error in graph.module_errors() { 105 | if !error_message.is_empty() { 106 | error_message.push_str("\n\n"); 107 | } 108 | if let Some(range) = error.maybe_referrer() { 109 | write!(error_message, "{:#}\n at {}", error, range).unwrap(); 110 | } else { 111 | write!(error_message, "{:#}", error).unwrap(); 112 | } 113 | if !error_message.contains(error.specifier().as_str()) { 114 | error_message.push_str(&format!(" ({})", error.specifier())); 115 | } 116 | } 117 | if !error_message.is_empty() { 118 | bail!("{}", error_message); 119 | } 120 | 121 | let graph = Self { 122 | graph, 123 | capturing_analyzer, 124 | }; 125 | 126 | let loader_specifiers = loader.into_specifiers(); 127 | 128 | let not_found_module_mappings = options 129 | .specifier_mappings 130 | .iter() 131 | .filter_map(|(k, v)| match v { 132 | MappedSpecifier::Package(_) => None, 133 | MappedSpecifier::Module(_) => Some(k), 134 | }) 135 | .filter(|s| !loader_specifiers.mapped_modules.contains_key(s)) 136 | .collect::>(); 137 | if !not_found_module_mappings.is_empty() { 138 | bail!( 139 | "The following specifiers were indicated to be mapped to a module, but were not found:\n{}", 140 | format_specifiers_for_message(not_found_module_mappings), 141 | ); 142 | } 143 | 144 | let specifiers = get_specifiers( 145 | &options.entry_points, 146 | loader_specifiers, 147 | &graph, 148 | graph.all_modules(), 149 | )?; 150 | 151 | let not_found_package_specifiers = options 152 | .specifier_mappings 153 | .iter() 154 | .filter_map(|(k, v)| match v { 155 | MappedSpecifier::Package(_) => Some(k), 156 | MappedSpecifier::Module(_) => None, 157 | }) 158 | .filter(|s| !specifiers.has_mapped(s)) 159 | .collect::>(); 160 | if !not_found_package_specifiers.is_empty() { 161 | bail!( 162 | "The following specifiers were indicated to be mapped to a package, but were not found:\n{}", 163 | format_specifiers_for_message(not_found_package_specifiers), 164 | ); 165 | } 166 | 167 | Ok((graph, specifiers)) 168 | } 169 | 170 | pub fn redirects(&self) -> &BTreeMap { 171 | &self.graph.redirects 172 | } 173 | 174 | pub fn resolve<'a>( 175 | &'a self, 176 | specifier: &'a ModuleSpecifier, 177 | ) -> &'a ModuleSpecifier { 178 | self.graph.resolve(specifier) 179 | } 180 | 181 | pub fn get(&self, specifier: &ModuleSpecifier) -> &Module { 182 | self.graph.get(specifier).unwrap_or_else(|| { 183 | panic!("dnt bug - Did not find specifier: {}", specifier); 184 | }) 185 | } 186 | 187 | pub fn get_parsed_source( 188 | &self, 189 | js_module: &JsModule, 190 | ) -> Result { 191 | match self 192 | .capturing_analyzer 193 | .get_parsed_source(&js_module.specifier) 194 | { 195 | Some(parsed_source) => Ok(parsed_source), 196 | None => self.capturing_analyzer.parse_program(ParseOptions { 197 | specifier: &js_module.specifier, 198 | source: js_module.source.text.clone(), 199 | media_type: js_module.media_type, 200 | scope_analysis: false, 201 | }), 202 | } 203 | } 204 | 205 | pub fn resolve_dependency( 206 | &self, 207 | value: &str, 208 | referrer: &ModuleSpecifier, 209 | ) -> Option { 210 | self 211 | .graph 212 | .resolve_dependency(value, referrer, /* prefer_types */ false) 213 | .cloned() 214 | .or_else(|| { 215 | let value_lower = value.to_lowercase(); 216 | if value_lower.starts_with("https://") 217 | || value_lower.starts_with("http://") 218 | || value_lower.starts_with("file://") 219 | { 220 | ModuleSpecifier::parse(value).ok() 221 | } else if value_lower.starts_with("./") 222 | || value_lower.starts_with("../") 223 | { 224 | referrer.join(value).ok() 225 | } else { 226 | None 227 | } 228 | }) 229 | .filter(|s| !matches!(s.scheme(), "node")) 230 | } 231 | 232 | pub fn all_modules(&self) -> impl Iterator { 233 | self.graph.modules() 234 | } 235 | } 236 | 237 | fn format_specifiers_for_message( 238 | mut specifiers: Vec<&ModuleSpecifier>, 239 | ) -> String { 240 | specifiers.sort(); 241 | specifiers 242 | .into_iter() 243 | .map(|s| format!(" * {}", s)) 244 | .collect::>() 245 | .join("\n") 246 | } 247 | --------------------------------------------------------------------------------