diff --git a/Cargo.lock b/Cargo.lock index 4f6b82d02e..7b2076cee6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,9 +85,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] name = "arrayvec" @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "ash" -version = "0.33.2+1.2.186" +version = "0.33.3+1.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2b957a47afef62973ed5fa0a52afb3289c7a243bfbc3906090b8434971237" +checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" dependencies = [ "libloading", ] @@ -121,8 +121,8 @@ checksum = "f93f52ce8fac3d0e6720a92b0576d737c01b1b5db4dd786e962e5925f00bf755" dependencies = [ "darling", "pmutil", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "swc_macros_common", "syn 1.0.65", ] @@ -157,8 +157,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -168,8 +168,8 @@ version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -191,8 +191,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cbf586c80ada5e5ccdecae80d3ef0854f224e2dd74435f8d87e6831b8d0a38" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -291,9 +291,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" [[package]] name = "byteorder" @@ -309,9 +309,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.0.69" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cfg-if" @@ -382,9 +382,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c32f031ea41b4291d695026c023b95d59db2d8a2c7640800ed56bc8f510f22" +checksum = "fdab415d6744056100f40250a66bc430c1a46f7a02e20bc11c94c79a0f0464df" [[package]] name = "convert_case" @@ -428,9 +428,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.1.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] @@ -475,9 +475,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.2.4" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc209804a22c34a98fe26a32d997ac64d4284816f65cf1a529c4e31a256218a0" +checksum = "d12477e115c0d570c12a2dfd859f80b55b60ddb5075df210d3af06d133a69f45" dependencies = [ "generic-array", "rand_core 0.6.3", @@ -501,7 +501,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" dependencies = [ - "quote 1.0.9", + "quote 1.0.10", "syn 1.0.65", ] @@ -534,8 +534,8 @@ checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "strsim 0.9.3", "syn 1.0.65", ] @@ -547,7 +547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", - "quote 1.0.9", + "quote 1.0.10", "syn 1.0.65", ] @@ -753,9 +753,9 @@ dependencies = [ [[package]] name = "deno_doc" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afa937c8bd6a5055a7688d4e9ea1de65947b399ae0f60504ad93277d238facf" +checksum = "cac0571321c463d3cfa39d03084c9f823bcc812daa7202eb2e89b5f9abd6301f" dependencies = [ "cfg-if 1.0.0", "deno_ast", @@ -798,9 +798,9 @@ dependencies = [ [[package]] name = "deno_graph" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be296aae1be28b1f7cea57ce4283abbb04c5013b146410e63b169a96e6b4e184" +checksum = "1d856f43e3fd84399d9f90200708fdd5f507bb5ee43770a5a88b3dbdf667619e" dependencies = [ "anyhow", "cfg-if 1.0.0", @@ -1001,9 +1001,9 @@ dependencies = [ [[package]] name = "der" -version = "0.4.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e21d2d0f22cde6e88694108429775c0219760a07779bf96503b434a03d7412" +checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2" dependencies = [ "const-oid", "crypto-bigint", @@ -1016,8 +1016,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" dependencies = [ "convert_case", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "rustc_version 0.3.3", "syn 1.0.65", ] @@ -1039,9 +1039,9 @@ dependencies = [ [[package]] name = "dissimilar" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb" +checksum = "31ad93652f40969dead8d4bf897a41e9462095152eb21c56e5830537e41179dd" [[package]] name = "dlopen" @@ -1068,12 +1068,12 @@ dependencies = [ [[package]] name = "dprint-core" -version = "0.46.0" +version = "0.46.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f145afa493cba0217d2c52e256e7626730b8ed97c9db4e20e0db8c242458ca" +checksum = "df98b7d98583d9d57311b9df81f571f35a6ca3e8675d0128333b2e923dc85bcd" dependencies = [ "bumpalo", - "fnv", + "rustc-hash", "serde", ] @@ -1185,8 +1185,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" dependencies = [ "heck", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -1197,7 +1197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b940da354ae81ef0926c5eaa428207b8f4f091d3956c891dfbd124162bed99" dependencies = [ "pmutil", - "proc-macro2 1.0.28", + "proc-macro2 1.0.29", "swc_macros_common", "syn 1.0.65", ] @@ -1309,16 +1309,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cde5eb168cf5a056dd98f311cbfab7494c216394e4fb9eba0336827a8db93" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] [[package]] name = "flate2" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" dependencies = [ "cfg-if 1.0.0", "crc32fast", @@ -1364,7 +1364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0951635027ca477be98f8774abd6f0345233439d63f307e47101acb40c7cc63d" dependencies = [ "pmutil", - "proc-macro2 1.0.28", + "proc-macro2 1.0.29", "swc_macros_common", "syn 1.0.65", ] @@ -1391,9 +1391,9 @@ dependencies = [ [[package]] name = "fslock" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc585f4fe7227b37ef0216444c87ca8ab6051622e4e2bc75d4bed4ea5106148" +checksum = "57eafdd0c16f57161105ae1b98a1238f97645f2f588438b2949c99a2af9616bf" dependencies = [ "libc", "winapi 0.3.9", @@ -1455,8 +1455,8 @@ checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" dependencies = [ "autocfg 1.0.1", "proc-macro-hack", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -1558,9 +1558,9 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481459c44304a1dfed23bd650bb3912e12c9f77d7871f86d7ed7c9730a52e79" +checksum = "0e64cbb8d36508d3e19da95e56e196a84f674fc190881f2cc010000798838aa6" dependencies = [ "bitflags", "gpu-alloc-types", @@ -1608,9 +1608,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472" +checksum = "6c06815895acec637cd6ed6e9662c935b866d20a106f8361892893a7d9234964" dependencies = [ "bytes", "fnv", @@ -1693,9 +1693,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" dependencies = [ "bytes", "fnv", @@ -1733,9 +1733,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.12" +version = "0.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f67199e765030fa08fe0bd581af683f0d5bc04ea09c2b1102012c5fb90e7fd" +checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" dependencies = [ "bytes", "futures-channel", @@ -1748,7 +1748,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.1", + "socket2 0.4.2", "tokio", "tower-service", "tracing", @@ -1819,9 +1819,9 @@ dependencies = [ [[package]] name = "inotify" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b031475cb1b103ee221afb806a23d35e0570bf7271d7588762ceba8127ed43b3" +checksum = "9e5fc8f41dbaa9c8492a96c8afffda4f76896ee041d6a57606e70581b80c901f" dependencies = [ "bitflags", "inotify-sys", @@ -1854,9 +1854,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" dependencies = [ "cfg-if 1.0.0", ] @@ -1887,8 +1887,8 @@ checksum = "a322dd16d960e322c3d92f541b4c1a4f0a2e81e1fdeee430d8cecc8b72e8015f" dependencies = [ "Inflector", "pmutil", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -1990,9 +1990,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.101" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "libloading" @@ -2029,9 +2029,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ "scopeguard", ] @@ -2100,8 +2100,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca1d48da0e4a6100b4afd52fae99f36d47964a209624021280ad9ffdd410e83d" dependencies = [ "heck", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -2201,9 +2201,9 @@ dependencies = [ [[package]] name = "naga" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568344dfe26b6caee4003da5be57bf8c2ef16d9525a2a5561164bcc462687f92" +checksum = "8c5859e55c51da10b98e7a73068e0a0c5da7bbcae4fc38f86043d0c6d1b917cf" dependencies = [ "bit-set", "bitflags", @@ -2234,9 +2234,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7555d6c7164cc913be1ce7f95cbecdabda61eb2ccd89008524af306fb7f5031" +checksum = "d3bb9a13fa32bc5aeb64150cd3f32d6cf4c748f8f8a417cce5d2eb976a8370ba" dependencies = [ "bitflags", "cc", @@ -2430,9 +2430,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -2441,9 +2441,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if 1.0.0", "instant", @@ -2518,8 +2518,8 @@ dependencies = [ "phf_generator", "phf_shared", "proc-macro-hack", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -2547,8 +2547,8 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -2566,9 +2566,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs1" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "359e7852310174a810f078124edb73c66e88a1a731b2fd586dba34ee32dbe416" +checksum = "116bee8279d783c0cf370efa1a94632f2108e5ef0bb32df31f051647810a4e2c" dependencies = [ "der", "zeroize", @@ -2576,9 +2576,9 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbee84ed13e44dd82689fa18348a49934fa79cc774a344c42fc9b301c71b140a" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" dependencies = [ "der", "pkcs1", @@ -2588,9 +2588,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" [[package]] name = "pmutil" @@ -2598,8 +2598,8 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -2634,8 +2634,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", "version_check", ] @@ -2646,8 +2646,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "version_check", ] @@ -2674,9 +2674,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" dependencies = [ "unicode-xid 0.2.2", ] @@ -2725,11 +2725,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ - "proc-macro2 1.0.28", + "proc-macro2 1.0.29", ] [[package]] @@ -2903,9 +2903,9 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" [[package]] name = "reqwest" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22" +checksum = "51c732d463dd300362ffb44b7b125f299c23d2990411a4253824630ebc7467fb" dependencies = [ "async-compression", "base64 0.13.0", @@ -2926,6 +2926,7 @@ dependencies = [ "pin-project-lite", "rustls", "serde", + "serde_json", "serde_urlencoded", "tokio", "tokio-rustls", @@ -2950,9 +2951,9 @@ dependencies = [ [[package]] name = "retain_mut" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b" +checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9" [[package]] name = "ring" @@ -2971,9 +2972,9 @@ dependencies = [ [[package]] name = "ron" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064ea8613fb712a19faf920022ec8ddf134984f100090764a4e1d768f3827f1f" +checksum = "45005aa836116903a49cf3461474da697cfe66221762c6e95871092009ec86d6" dependencies = [ "base64 0.13.0", "bitflags", @@ -3015,6 +3016,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -3100,7 +3107,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "688599bdab9f42105d0ae1494335a9ffafdeb7d36325e6b10fd4abc5829d6284" dependencies = [ - "quote 1.0.9", + "quote 1.0.10", "syn 1.0.65", ] @@ -3166,9 +3173,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19133a286e494cc3311c165c4676ccb1fd47bed45b55f9d71fbd784ad4cea6f8" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" dependencies = [ "core-foundation-sys", "libc", @@ -3222,16 +3229,16 @@ version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] [[package]] name = "serde_json" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "indexmap", "itoa", @@ -3245,8 +3252,8 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -3274,9 +3281,9 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer", "cfg-if 1.0.0", @@ -3287,9 +3294,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ "block-buffer", "cfg-if 1.0.0", @@ -3325,9 +3332,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729a25c17d72b06c68cb47955d44fda88ad2d3e7d77e025663fdd69b93dd71a1" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" [[package]] name = "slab" @@ -3337,18 +3344,18 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "slotmap" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" dependencies = [ "version_check", ] [[package]] name = "smallvec" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "socket2" @@ -3363,9 +3370,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" dependencies = [ "libc", "winapi 0.3.9", @@ -3451,8 +3458,8 @@ checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" dependencies = [ "phf_generator", "phf_shared", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", ] [[package]] @@ -3462,8 +3469,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f584cc881e9e5f1fd6bf827b0444aa94c30d8fe6378cf241071b5f5700b2871f" dependencies = [ "pmutil", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "swc_macros_common", "syn 1.0.65", ] @@ -3498,9 +3505,9 @@ dependencies = [ [[package]] name = "swc_bundler" -version = "0.58.0" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d42b9e0dff902d05ea17813dc3537a29641a35634864d55ee91215c7cf5430f" +checksum = "acfdeafdcfbfb0f4ca1838d775d8124da81168c4131a340c5b6682f078b34719" dependencies = [ "ahash 0.7.4", "anyhow", @@ -3527,9 +3534,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca21695d45b5374d7eafedda065de3cab2337a4707642302f71caaa4c0d338a" +checksum = "acc9ad667d8c220e1625e189933ffd9d29b643317aca7a211db2600ef622879e" dependencies = [ "ahash 0.7.4", "ast_node", @@ -3567,9 +3574,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.70.2" +version = "0.70.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940bff62e5caf62fe6732ce4f07e52c3c208cb58cd9299f7f7c92dddab2bf72" +checksum = "e08e335585f54f85fa910ab771660f69d809f8f70160d4cf912c9a855970db81" dependencies = [ "bitflags", "num-bigint", @@ -3588,8 +3595,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51af418026cb4ea588e2b15fa206c44e09a3184b718e12a0919729c7c3ad20d3" dependencies = [ "pmutil", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "swc_macros_common", "syn 1.0.65", ] @@ -3608,9 +3615,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.18.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9695aa0b1394a1954da965a00a6a9624aa1c9f49148f72f1c01f5bd9c39d74c" +checksum = "c2f718f0335f9ab7437fecf9f3d73ae6a24c03a3d3f46910a68261703d407f03" dependencies = [ "anyhow", "fxhash", @@ -3623,9 +3630,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.70.2" +version = "0.70.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "042a901352b84cefbb64916a010ee33f621a7e341ced2b2fa60035858f3146a5" +checksum = "04b2e3c0ac2132a7ab0679fe8bb89ad0a3311c6c5f44a22bffc559647e1f2e47" dependencies = [ "either", "enum_kind", @@ -3664,9 +3671,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.31.1" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26e191df68943565f22059d31b02967e60a62c4f76533b5b5106546785a8e2e" +checksum = "fdcd1f3c1ca665605dcb2a7d6b536aeff1ed1da2eeebf32238d890e42b494312" dependencies = [ "fxhash", "once_cell", @@ -3762,9 +3769,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.40.2" +version = "0.40.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98099e3db58fb758715736ea9c8fa68d238e6527f0bfb4a3af0bf7ea063b9162" +checksum = "96bb017f32fd1559f10245112eeac1cf10e3b76415495e159b10c82ee667ac9e" dependencies = [ "fxhash", "serde", @@ -3827,8 +3834,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c8f200a2eaed938e7c1a685faaa66e6d42fa9e17da5f62572d3cbc335898f5e" dependencies = [ "pmutil", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -3839,8 +3846,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08ed2e930f5a1a4071fe62c90fd3a296f6030e5d94bfe13993244423caf59a78" dependencies = [ "pmutil", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -3862,8 +3869,8 @@ checksum = "e3b2825fee79f10d0166e8e650e79c7a862fb991db275743083f07555d7641f0" dependencies = [ "Inflector", "pmutil", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "swc_macros_common", "syn 1.0.65", ] @@ -3885,8 +3892,8 @@ version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "unicode-xid 0.2.2", ] @@ -3896,8 +3903,8 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", "unicode-xid 0.2.2", ] @@ -3991,21 +3998,21 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -4022,9 +4029,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" +checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" dependencies = [ "tinyvec_macros", ] @@ -4037,9 +4044,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92036be488bb6594459f2e03b60e42df6f937fe6ca5c5ffdcb539c6b84dc40f5" +checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" dependencies = [ "autocfg 1.0.1", "bytes", @@ -4057,12 +4064,12 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +checksum = "154794c8f499c2619acd19e839294703e9e32e7630ef5f46ea80d4ef0fbee5eb" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", ] @@ -4107,9 +4114,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" +checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd" dependencies = [ "bytes", "futures-core", @@ -4136,9 +4143,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.26" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", @@ -4147,9 +4154,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" dependencies = [ "lazy_static", ] @@ -4285,9 +4292,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" [[package]] name = "ucd-trie" @@ -4353,9 +4360,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-normalization" @@ -4374,9 +4381,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -4502,8 +4509,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" dependencies = [ "cfg-if 1.0.0", - "serde", - "serde_json", "wasm-bindgen-macro", ] @@ -4516,8 +4521,8 @@ dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", "wasm-bindgen-shared", ] @@ -4540,7 +4545,7 @@ version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" dependencies = [ - "quote 1.0.9", + "quote 1.0.10", "wasm-bindgen-macro-support", ] @@ -4550,8 +4555,8 @@ version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", "wasm-bindgen-backend", "wasm-bindgen-shared", @@ -4594,9 +4599,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.10.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a81393a8d78194c7433d63afed2bf5f64feb126aef9ddd18eca3b16e9fac0cea" +checksum = "f963c62473a36e3cef6c58181f2ed6d0d38d2043d970dbed46cb197190090c99" dependencies = [ "arrayvec 0.7.1", "bitflags", @@ -4617,9 +4622,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.10.2" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31bc8def88a1416997bff66b1f6788a4bd497546ad985db2d442d099c48f4246" +checksum = "27cd894b17bff1958ee93da1cc991fd64bf99667746d4bd2a7403855f4d37fe2" dependencies = [ "arrayvec 0.7.1", "ash", @@ -4739,30 +4744,30 @@ dependencies = [ [[package]] name = "winres" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4fb510bbfe5b8992ff15f77a2e6fe6cf062878f0eda00c0f44963a807ca5dc" +checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" dependencies = [ "toml", ] [[package]] name = "zeroize" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd" +checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1" +checksum = "bdff2024a851a322b08f179173ae2ba620445aef1e838f0c196820eade4ae0c7" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2 1.0.29", + "quote 1.0.10", "syn 1.0.65", "synstructure", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e4efdf3563..28a22b359d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -41,8 +41,8 @@ winres = "0.1.11" [dependencies] deno_ast = { version = "0.2.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } deno_core = { version = "0.102.0", path = "../core" } -deno_doc = "0.14.0" -deno_graph = "0.5.0" +deno_doc = "0.15.0" +deno_graph = "0.6.0" deno_lint = { version = "0.16.0", features = ["docs"] } deno_runtime = { version = "0.28.0", path = "../runtime" } deno_tls = { version = "0.7.0", path = "../ext/tls" } diff --git a/cli/ast/bundle_hook.rs b/cli/ast/bundle_hook.rs index 6b29bd8dd2..3f47946f7a 100644 --- a/cli/ast/bundle_hook.rs +++ b/cli/ast/bundle_hook.rs @@ -4,6 +4,7 @@ use deno_ast::swc::bundler::ModuleRecord; use deno_ast::swc::common::Span; use deno_core::error::AnyError; +/// This contains the logic for Deno to rewrite the `import.meta` when bundling. pub struct BundleHook; impl Hook for BundleHook { diff --git a/cli/cache.rs b/cli/cache.rs new file mode 100644 index 0000000000..2c94172b80 --- /dev/null +++ b/cli/cache.rs @@ -0,0 +1,349 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::disk_cache::DiskCache; +use crate::file_fetcher::FileFetcher; + +use deno_core::error::AnyError; +use deno_core::futures::future; +use deno_core::futures::FutureExt; +use deno_core::serde::Deserialize; +use deno_core::serde::Serialize; +use deno_core::serde_json; +use deno_core::ModuleSpecifier; +use deno_graph::source::CacheInfo; +use deno_graph::source::LoadFuture; +use deno_graph::source::LoadResponse; +use deno_graph::source::Loader; +use deno_runtime::permissions::Permissions; +use std::collections::HashMap; +use std::sync::Arc; + +#[derive(Debug, Deserialize, Serialize)] +pub struct EmitMetadata { + pub version_hash: String, +} + +pub(crate) enum CacheType { + Declaration, + Emit, + SourceMap, + TypeScriptBuildInfo, + Version, +} + +/// A trait which provides a concise implementation to getting and setting +/// values in a cache. +pub(crate) trait Cacher { + /// Get a value from the cache. + fn get( + &self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + ) -> Option; + /// Set a value in the cache. + fn set( + &mut self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + value: String, + ) -> Result<(), AnyError>; +} + +/// Combines the cacher trait along with the deno_graph Loader trait to provide +/// a single interface to be able to load and cache modules when building a +/// graph. +pub(crate) trait CacherLoader: Cacher + Loader { + fn as_cacher(&self) -> &dyn Cacher; + fn as_mut_loader(&mut self) -> &mut dyn Loader; + fn as_mut_cacher(&mut self) -> &mut dyn Cacher; +} + +/// A "wrapper" for the FileFetcher and DiskCache for the Deno CLI that provides +/// a concise interface to the DENO_DIR when building module graphs. +pub(crate) struct FetchCacher { + disk_cache: DiskCache, + dynamic_permissions: Permissions, + file_fetcher: Arc, + root_permissions: Permissions, +} + +impl FetchCacher { + pub fn new( + disk_cache: DiskCache, + file_fetcher: FileFetcher, + root_permissions: Permissions, + dynamic_permissions: Permissions, + ) -> Self { + let file_fetcher = Arc::new(file_fetcher); + + Self { + disk_cache, + dynamic_permissions, + file_fetcher, + root_permissions, + } + } + + fn get_emit_metadata( + &self, + specifier: &ModuleSpecifier, + ) -> Option { + let filename = self + .disk_cache + .get_cache_filename_with_extension(specifier, "meta")?; + let bytes = self.disk_cache.get(&filename).ok()?; + serde_json::from_slice(&bytes).ok() + } + + fn set_emit_metadata( + &self, + specifier: &ModuleSpecifier, + data: EmitMetadata, + ) -> Result<(), AnyError> { + let filename = self + .disk_cache + .get_cache_filename_with_extension(specifier, "meta") + .unwrap(); + let bytes = serde_json::to_vec(&data)?; + self.disk_cache.set(&filename, &bytes).map_err(|e| e.into()) + } +} + +impl Loader for FetchCacher { + fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option { + let local = self.file_fetcher.get_local_path(specifier)?; + if local.is_file() { + let location = &self.disk_cache.location; + let emit = self + .disk_cache + .get_cache_filename_with_extension(specifier, "js") + .map(|p| location.join(p)) + .filter(|p| p.is_file()); + let map = self + .disk_cache + .get_cache_filename_with_extension(specifier, "js.map") + .map(|p| location.join(p)) + .filter(|p| p.is_file()); + Some(CacheInfo { + local: Some(local), + emit, + map, + }) + } else { + None + } + } + + fn load( + &mut self, + specifier: &ModuleSpecifier, + is_dynamic: bool, + ) -> LoadFuture { + let specifier = specifier.clone(); + let mut permissions = if is_dynamic { + self.dynamic_permissions.clone() + } else { + self.root_permissions.clone() + }; + let file_fetcher = self.file_fetcher.clone(); + + async move { + let load_result = file_fetcher + .fetch(&specifier, &mut permissions) + .await + .map_or_else( + |err| { + if let Some(err) = err.downcast_ref::() { + if err.kind() == std::io::ErrorKind::NotFound { + return Ok(None); + } + } + Err(err) + }, + |file| { + Ok(Some(LoadResponse { + specifier: file.specifier, + maybe_headers: file.maybe_headers, + content: file.source, + })) + }, + ); + + (specifier, load_result) + } + .boxed() + } +} + +impl Cacher for FetchCacher { + fn get( + &self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + ) -> Option { + let extension = match cache_type { + CacheType::Declaration => "d.ts", + CacheType::Emit => "js", + CacheType::SourceMap => "js.map", + CacheType::TypeScriptBuildInfo => "buildinfo", + CacheType::Version => { + return self.get_emit_metadata(specifier).map(|d| d.version_hash) + } + }; + let filename = self + .disk_cache + .get_cache_filename_with_extension(specifier, extension)?; + self + .disk_cache + .get(&filename) + .ok() + .map(|b| String::from_utf8(b).ok()) + .flatten() + } + + fn set( + &mut self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + value: String, + ) -> Result<(), AnyError> { + let extension = match cache_type { + CacheType::Declaration => "d.ts", + CacheType::Emit => "js", + CacheType::SourceMap => "js.map", + CacheType::TypeScriptBuildInfo => "buildinfo", + CacheType::Version => { + let data = if let Some(mut data) = self.get_emit_metadata(specifier) { + data.version_hash = value; + data + } else { + EmitMetadata { + version_hash: value, + } + }; + return self.set_emit_metadata(specifier, data); + } + }; + let filename = self + .disk_cache + .get_cache_filename_with_extension(specifier, extension) + .unwrap(); + self + .disk_cache + .set(&filename, value.as_bytes()) + .map_err(|e| e.into()) + } +} + +impl CacherLoader for FetchCacher { + fn as_cacher(&self) -> &dyn Cacher { + self + } + + fn as_mut_loader(&mut self) -> &mut dyn Loader { + self + } + + fn as_mut_cacher(&mut self) -> &mut dyn Cacher { + self + } +} + +/// An in memory cache that is used by the runtime `Deno.emit()` API to provide +/// the same behavior as the disk cache when sources are provided. +#[derive(Debug)] +pub(crate) struct MemoryCacher { + sources: HashMap>, + declarations: HashMap, + emits: HashMap, + maps: HashMap, + build_infos: HashMap, + versions: HashMap, +} + +impl MemoryCacher { + pub fn new(sources: HashMap>) -> Self { + Self { + sources, + declarations: HashMap::default(), + emits: HashMap::default(), + maps: HashMap::default(), + build_infos: HashMap::default(), + versions: HashMap::default(), + } + } +} + +impl Loader for MemoryCacher { + fn load( + &mut self, + specifier: &ModuleSpecifier, + _is_dynamic: bool, + ) -> LoadFuture { + let mut specifier_str = specifier.to_string(); + if !self.sources.contains_key(&specifier_str) { + specifier_str = specifier_str.replace("file:///", "/"); + if !self.sources.contains_key(&specifier_str) { + specifier_str = specifier_str[3..].to_string(); + } + } + let response = self.sources.get(&specifier_str).map(|c| LoadResponse { + specifier: specifier.clone(), + maybe_headers: None, + content: c.to_owned(), + }); + Box::pin(future::ready((specifier.clone(), Ok(response)))) + } +} + +impl Cacher for MemoryCacher { + fn get( + &self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + ) -> Option { + match cache_type { + CacheType::Declaration => self.declarations.get(specifier).cloned(), + CacheType::Emit => self.emits.get(specifier).cloned(), + CacheType::SourceMap => self.maps.get(specifier).cloned(), + CacheType::TypeScriptBuildInfo => { + self.build_infos.get(specifier).cloned() + } + CacheType::Version => self.versions.get(specifier).cloned(), + } + } + + fn set( + &mut self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + value: String, + ) -> Result<(), AnyError> { + match cache_type { + CacheType::Declaration => { + self.declarations.insert(specifier.clone(), value) + } + CacheType::Emit => self.emits.insert(specifier.clone(), value), + CacheType::SourceMap => self.maps.insert(specifier.clone(), value), + CacheType::TypeScriptBuildInfo => { + self.build_infos.insert(specifier.clone(), value) + } + CacheType::Version => self.versions.insert(specifier.clone(), value), + }; + Ok(()) + } +} + +impl CacherLoader for MemoryCacher { + fn as_cacher(&self) -> &dyn Cacher { + self + } + + fn as_mut_loader(&mut self) -> &mut dyn Loader { + self + } + + fn as_mut_cacher(&mut self) -> &mut dyn Cacher { + self + } +} diff --git a/cli/compat.rs b/cli/compat.rs index 4b64a501d7..f120ccd046 100644 --- a/cli/compat.rs +++ b/cli/compat.rs @@ -4,6 +4,7 @@ use deno_core::url::Url; use std::collections::HashMap; static STD_NODE: &str = "https://deno.land/std/node/"; +static GLOBAL_MODULE: &str = "global.ts"; static SUPPORTED_MODULES: &[&str] = &[ "assert", @@ -50,8 +51,15 @@ static SUPPORTED_MODULES: &[&str] = &[ "zlib", ]; -pub fn get_node_globals_url() -> Url { - Url::parse(&format!("{}global.ts", STD_NODE)).unwrap() +lazy_static::lazy_static! { + static ref GLOBAL_URL_STR: String = format!("{}{}", STD_NODE, GLOBAL_MODULE); + pub(crate) static ref GLOBAL_URL: Url = Url::parse(&GLOBAL_URL_STR).unwrap(); + static ref COMPAT_IMPORT_URL: Url = Url::parse("flags:compat").unwrap(); +} + +/// Provide imports into a module graph when the compat flag is true. +pub(crate) fn get_node_imports() -> Vec<(Url, Vec)> { + vec![(COMPAT_IMPORT_URL.clone(), vec![GLOBAL_URL_STR.clone()])] } /// Create a map that can be used to update import map. diff --git a/cli/config_file.rs b/cli/config_file.rs index dd002ca97a..3a71d41a92 100644 --- a/cli/config_file.rs +++ b/cli/config_file.rs @@ -10,6 +10,7 @@ use deno_core::serde::Serializer; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; +use deno_core::ModuleSpecifier; use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt; @@ -401,6 +402,19 @@ impl ConfigFile { } } + /// If the configuration file contains "extra" modules (like TypeScript + /// `"types"`) options, return them as imports to be added to a module graph. + pub fn to_maybe_imports( + &self, + ) -> Option)>> { + let compiler_options_value = self.json.compiler_options.as_ref()?; + let compiler_options: CompilerOptions = + serde_json::from_value(compiler_options_value.clone()).ok()?; + let referrer = ModuleSpecifier::from_file_path(&self.path).ok()?; + let types = compiler_options.types?; + Some(vec![(referrer, types)]) + } + pub fn to_fmt_config(&self) -> Result, AnyError> { if let Some(config) = self.json.fmt.clone() { let fmt_config: FmtConfig = serde_json::from_value(config) diff --git a/cli/diagnostics.rs b/cli/diagnostics.rs index 4594751171..50d7213f10 100644 --- a/cli/diagnostics.rs +++ b/cli/diagnostics.rs @@ -6,9 +6,8 @@ use deno_core::serde::Deserialize; use deno_core::serde::Deserializer; use deno_core::serde::Serialize; use deno_core::serde::Serializer; -use deno_core::ModuleSpecifier; +use deno_graph::ModuleGraphError; use regex::Regex; -use std::collections::HashMap; use std::error::Error; use std::fmt; @@ -353,20 +352,17 @@ impl Diagnostics { Diagnostics(diagnostics) } - pub fn extend_graph_errors( - &mut self, - errors: HashMap, - ) { - self.0.extend(errors.into_iter().map(|(s, e)| Diagnostic { + pub fn extend_graph_errors(&mut self, errors: Vec) { + self.0.extend(errors.into_iter().map(|err| Diagnostic { category: DiagnosticCategory::Error, code: 900001, start: None, end: None, - message_text: Some(e), + message_text: Some(err.to_string()), message_chain: None, source: None, source_line: None, - file_name: Some(s.to_string()), + file_name: Some(err.specifier().to_string()), related_information: None, })); } diff --git a/cli/emit.rs b/cli/emit.rs new file mode 100644 index 0000000000..5a1cf61d16 --- /dev/null +++ b/cli/emit.rs @@ -0,0 +1,927 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +//! The collection of APIs to be able to take `deno_graph` module graphs and +//! populate a cache, emit files, and transform a graph into the structures for +//! loading into an isolate. + +use crate::ast; +use crate::cache::CacheType; +use crate::cache::Cacher; +use crate::colors; +use crate::config_file::ConfigFile; +use crate::config_file::IgnoredCompilerOptions; +use crate::config_file::TsConfig; +use crate::diagnostics::Diagnostics; +use crate::tsc; +use crate::version; + +use deno_ast::swc; +use deno_core::error::anyhow; +use deno_core::error::custom_error; +use deno_core::error::AnyError; +use deno_core::error::Context; +use deno_core::serde::Deserialize; +use deno_core::serde::Deserializer; +use deno_core::serde::Serialize; +use deno_core::serde::Serializer; +use deno_core::serde_json::json; +use deno_core::serde_json::Value; +use deno_core::ModuleSource; +use deno_core::ModuleSpecifier; +use deno_graph::MediaType; +use deno_graph::ModuleGraph; +use deno_graph::ModuleGraphError; +use deno_graph::ResolutionError; +use std::collections::HashMap; +use std::collections::HashSet; +use std::fmt; +use std::rc::Rc; +use std::result; +use std::sync::Arc; +use std::time::Instant; + +/// Represents the "default" type library that should be used when type +/// checking the code in the module graph. Note that a user provided config +/// of `"lib"` would override this value. +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) enum TypeLib { + DenoWindow, + DenoWorker, + UnstableDenoWindow, + UnstableDenoWorker, +} + +impl Default for TypeLib { + fn default() -> Self { + Self::DenoWindow + } +} + +impl Serialize for TypeLib { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let value = match self { + Self::DenoWindow => vec!["deno.window".to_string()], + Self::DenoWorker => vec!["deno.worker".to_string()], + Self::UnstableDenoWindow => { + vec!["deno.window".to_string(), "deno.unstable".to_string()] + } + Self::UnstableDenoWorker => { + vec!["deno.worker".to_string(), "deno.unstable".to_string()] + } + }; + Serialize::serialize(&value, serializer) + } +} + +type Modules = HashMap>; + +/// A structure representing stats from an emit operation for a graph. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub(crate) struct Stats(pub Vec<(String, u32)>); + +impl<'de> Deserialize<'de> for Stats { + fn deserialize(deserializer: D) -> result::Result + where + D: Deserializer<'de>, + { + let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?; + Ok(Stats(items)) + } +} + +impl Serialize for Stats { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Serialize::serialize(&self.0, serializer) + } +} + +impl fmt::Display for Stats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "Compilation statistics:")?; + for (key, value) in self.0.clone() { + writeln!(f, " {}: {}", key, value)?; + } + + Ok(()) + } +} + +/// An enum that represents the base tsc configuration to return. +pub(crate) enum ConfigType { + /// Return a configuration for bundling, using swc to emit the bundle. This is + /// independent of type checking. + Bundle, + /// Return a configuration to use tsc to type check and optionally emit. This + /// is independent of either bundling or just emitting via swc + Check { lib: TypeLib, tsc_emit: bool }, + /// Return a configuration to use swc to emit single module files. + Emit, + /// Return a configuration as a base for the runtime `Deno.emit()` API. + RuntimeEmit { tsc_emit: bool }, +} + +/// For a given configuration type and optionally a configuration file, return a +/// tuple of the resulting `TsConfig` struct and optionally any user +/// configuration options that were ignored. +pub(crate) fn get_ts_config( + config_type: ConfigType, + maybe_config_file: Option<&ConfigFile>, + maybe_user_config: Option<&HashMap>, +) -> Result<(TsConfig, Option), AnyError> { + let mut ts_config = match config_type { + ConfigType::Bundle => TsConfig::new(json!({ + "checkJs": false, + "emitDecoratorMetadata": false, + "importsNotUsedAsValues": "remove", + "inlineSourceMap": false, + "inlineSources": false, + "sourceMap": false, + "jsx": "react", + "jsxFactory": "React.createElement", + "jsxFragmentFactory": "React.Fragment", + })), + ConfigType::Check { tsc_emit, lib } => { + let mut ts_config = TsConfig::new(json!({ + "allowJs": true, + "experimentalDecorators": true, + "incremental": true, + "jsx": "react", + "isolatedModules": true, + "lib": lib, + "module": "esnext", + "strict": true, + "target": "esnext", + "tsBuildInfoFile": "deno:///.tsbuildinfo", + "useDefineForClassFields": true, + // TODO(@kitsonk) remove for Deno 2.0 + "useUnknownInCatchVariables": false, + })); + if tsc_emit { + ts_config.merge(&json!({ + "emitDecoratorMetadata": false, + "importsNotUsedAsValues": "remove", + "inlineSourceMap": true, + "inlineSources": true, + "outDir": "deno://", + "removeComments": true, + })); + } else { + ts_config.merge(&json!({ + "noEmit": true, + })); + } + ts_config + } + ConfigType::Emit => TsConfig::new(json!({ + "checkJs": false, + "emitDecoratorMetadata": false, + "importsNotUsedAsValues": "remove", + "inlineSourceMap": true, + // TODO(@kitsonk) make this actually work when https://github.com/swc-project/swc/issues/2218 addressed. + "inlineSources": true, + "sourceMap": false, + "jsx": "react", + "jsxFactory": "React.createElement", + "jsxFragmentFactory": "React.Fragment", + })), + ConfigType::RuntimeEmit { tsc_emit } => { + let mut ts_config = TsConfig::new(json!({ + "allowJs": true, + "checkJs": false, + "emitDecoratorMetadata": false, + "experimentalDecorators": true, + "importsNotUsedAsValues": "remove", + "incremental": true, + "isolatedModules": true, + "jsx": "react", + "jsxFactory": "React.createElement", + "jsxFragmentFactory": "React.Fragment", + "lib": TypeLib::DenoWindow, + "module": "esnext", + "removeComments": true, + "inlineSourceMap": false, + "inlineSources": false, + "sourceMap": true, + "strict": true, + "target": "esnext", + "tsBuildInfoFile": "deno:///.tsbuildinfo", + "useDefineForClassFields": true, + // TODO(@kitsonk) remove for Deno 2.0 + "useUnknownInCatchVariables": false, + })); + if tsc_emit { + ts_config.merge(&json!({ + "importsNotUsedAsValues": "remove", + "outDir": "deno://", + })); + } else { + ts_config.merge(&json!({ + "noEmit": true, + })); + } + ts_config + } + }; + let maybe_ignored_options = if let Some(user_options) = maybe_user_config { + ts_config.merge_user_config(user_options)? + } else { + ts_config.merge_tsconfig_from_config_file(maybe_config_file)? + }; + Ok((ts_config, maybe_ignored_options)) +} + +/// Transform the graph into root specifiers that we can feed `tsc`. We have to +/// provide the media type for root modules because `tsc` does not "resolve" the +/// media type like other modules, as well as a root specifier needs any +/// redirects resolved. If we aren't checking JavaScript, we need to include all +/// the emittable files in the roots, so they get type checked and optionally +/// emitted, otherwise they would be ignored if only imported into JavaScript. +fn get_root_names( + graph: &ModuleGraph, + check_js: bool, +) -> Vec<(ModuleSpecifier, MediaType)> { + if !check_js { + graph + .specifiers() + .into_iter() + .filter_map(|(_, r)| match r { + Ok((s, mt)) => match &mt { + MediaType::TypeScript | MediaType::Tsx | MediaType::Jsx => { + Some((s, mt)) + } + _ => None, + }, + _ => None, + }) + .collect() + } else { + graph + .roots + .iter() + .filter_map(|s| graph.get(s).map(|m| (m.specifier.clone(), m.media_type))) + .collect() + } +} + +/// A hashing function that takes the source code, version and optionally a +/// user provided config and generates a string hash which can be stored to +/// determine if the cached emit is valid or not. +fn get_version(source_bytes: &[u8], config_bytes: &[u8]) -> String { + crate::checksum::gen(&[ + source_bytes, + version::deno().as_bytes(), + config_bytes, + ]) +} + +/// Determine if a given media type is emittable or not. +fn is_emittable(media_type: &MediaType, include_js: bool) -> bool { + match &media_type { + MediaType::TypeScript | MediaType::Tsx | MediaType::Jsx => true, + MediaType::JavaScript => include_js, + _ => false, + } +} + +/// Options for performing a check of a module graph. Note that the decision to +/// emit or not is determined by the `ts_config` settings. +pub(crate) struct CheckOptions { + /// Set the debug flag on the TypeScript type checker. + pub debug: bool, + /// If true, any files emitted will be cached, even if there are diagnostics + /// produced. If false, if there are diagnostics, caching emitted files will + /// be skipped. + pub emit_with_diagnostics: bool, + /// The module specifier to the configuration file, passed to tsc so that + /// configuration related diagnostics are properly formed. + pub maybe_config_specifier: Option, + /// The derived tsconfig that should be used when checking. + pub ts_config: TsConfig, +} + +/// The result of a check or emit of a module graph. Note that the actual +/// emitted sources are stored in the cache and are not returned in the result. +#[derive(Debug, Default)] +pub(crate) struct CheckEmitResult { + pub diagnostics: Diagnostics, + pub stats: Stats, +} + +/// Given a module graph, type check the module graph and optionally emit +/// modules, updating the cache as appropriate. Emitting is determined by the +/// `ts_config` supplied in the options, and if emitting, the files are stored +/// in the cache. +/// +/// It is expected that it is determined if a check and/or emit is validated +/// before the function is called. +pub(crate) fn check_and_maybe_emit( + graph: Arc, + cache: &mut dyn Cacher, + options: CheckOptions, +) -> Result { + let check_js = options.ts_config.get_check_js(); + let root_names = get_root_names(&graph, check_js); + // while there might be multiple roots, we can't "merge" the build info, so we + // try to retrieve the build info for first root, which is the most common use + // case. + let maybe_tsbuildinfo = + cache.get(CacheType::TypeScriptBuildInfo, &graph.roots[0]); + // to make tsc build info work, we need to consistently hash modules, so that + // tsc can better determine if an emit is still valid or not, so we provide + // that data here. + let hash_data = vec![ + options.ts_config.as_bytes(), + version::deno().as_bytes().to_owned(), + ]; + let config_bytes = options.ts_config.as_bytes(); + + let response = tsc::exec(tsc::Request { + config: options.ts_config, + debug: options.debug, + graph: graph.clone(), + hash_data, + maybe_config_specifier: options.maybe_config_specifier, + maybe_tsbuildinfo, + root_names, + })?; + + if let Some(info) = &response.maybe_tsbuildinfo { + // while we retrieve the build info for just the first module, it can be + // used for all the roots in the graph, so we will cache it for all roots + for root in &graph.roots { + cache.set(CacheType::TypeScriptBuildInfo, root, info.clone())?; + } + } + // sometimes we want to emit when there are diagnostics, and sometimes we + // don't. tsc will always return an emit if there are diagnostics + if (response.diagnostics.is_empty() || options.emit_with_diagnostics) + && !response.emitted_files.is_empty() + { + for emit in response.emitted_files.into_iter() { + if let Some(specifiers) = emit.maybe_specifiers { + assert!(specifiers.len() == 1); + // The emitted specifier might not be the file specifier we want, so we + // resolve it via the graph. + let specifier = graph.resolve(&specifiers[0]); + let (media_type, source) = if let Some(module) = graph.get(&specifier) { + (&module.media_type, module.source.clone()) + } else { + log::debug!("module missing, skipping emit for {}", specifier); + continue; + }; + // Sometimes if `tsc` sees a CommonJS file it will _helpfully_ output it + // to ESM, which we don't really want to do unless someone has enabled + // check_js. + if !check_js && *media_type == MediaType::JavaScript { + log::debug!("skipping emit for {}", specifier); + continue; + } + match emit.media_type { + MediaType::JavaScript => { + let version = get_version(source.as_bytes(), &config_bytes); + cache.set(CacheType::Version, &specifier, version)?; + cache.set(CacheType::Emit, &specifier, emit.data)?; + } + MediaType::SourceMap => { + cache.set(CacheType::SourceMap, &specifier, emit.data)?; + } + // this only occurs with the runtime emit, but we are using the same + // code paths, so we handle it here. + MediaType::Dts => { + cache.set(CacheType::Declaration, &specifier, emit.data)?; + } + _ => unreachable!(), + } + } + } + } + + Ok(CheckEmitResult { + diagnostics: response.diagnostics, + stats: response.stats, + }) +} + +pub(crate) enum BundleType { + /// Return the emitted contents of the program as a single "flattened" ES + /// module. + Module, + /// Return the emitted contents of the program as a single script that + /// executes the program using an immediately invoked function execution + /// (IIFE). + Classic, +} + +impl From for swc::bundler::ModuleType { + fn from(bundle_type: BundleType) -> Self { + match bundle_type { + BundleType::Classic => Self::Iife, + BundleType::Module => Self::Es, + } + } +} + +pub(crate) struct BundleOptions { + pub bundle_type: BundleType, + pub ts_config: TsConfig, +} + +/// A module loader for swc which does the appropriate retrieval and transpiling +/// of modules from the graph. +struct BundleLoader<'a> { + cm: Rc, + emit_options: &'a ast::EmitOptions, + globals: &'a deno_ast::swc::common::Globals, + graph: &'a ModuleGraph, +} + +impl swc::bundler::Load for BundleLoader<'_> { + fn load( + &self, + file_name: &swc::common::FileName, + ) -> Result { + match file_name { + swc::common::FileName::Url(specifier) => { + if let Some(m) = self.graph.get(specifier) { + let (fm, module) = ast::transpile_module( + specifier, + &m.source, + m.media_type, + self.emit_options, + self.globals, + self.cm.clone(), + )?; + Ok(swc::bundler::ModuleData { + fm, + module, + helpers: Default::default(), + }) + } else { + Err(anyhow!( + "Module \"{}\" unexpectedly missing when bundling.", + specifier + )) + } + } + _ => unreachable!( + "Received a request for unsupported filename {:?}", + file_name + ), + } + } +} + +/// A resolver implementation for swc that resolves specifiers from the graph. +struct BundleResolver<'a>(&'a ModuleGraph); + +impl swc::bundler::Resolve for BundleResolver<'_> { + fn resolve( + &self, + referrer: &swc::common::FileName, + specifier: &str, + ) -> Result { + let referrer = if let swc::common::FileName::Url(referrer) = referrer { + referrer + } else { + unreachable!( + "An unexpected referrer was passed when bundling: {:?}", + referrer + ); + }; + if let Some(specifier) = + self.0.resolve_dependency(specifier, referrer, false) + { + Ok(deno_ast::swc::common::FileName::Url(specifier.clone())) + } else { + Err(anyhow!( + "Cannot resolve \"{}\" from \"{}\".", + specifier, + referrer + )) + } + } +} + +/// Given a module graph, generate and return a bundle of the graph and +/// optionally its source map. Unlike emitting with `check_and_maybe_emit` and +/// `emit`, which store the emitted modules in the cache, this function simply +/// returns the output. +pub(crate) fn bundle( + graph: &ModuleGraph, + options: BundleOptions, +) -> Result<(String, Option), AnyError> { + let emit_options: ast::EmitOptions = options.ts_config.into(); + + let cm = Rc::new(swc::common::SourceMap::new( + swc::common::FilePathMapping::empty(), + )); + let globals = swc::common::Globals::new(); + let loader = BundleLoader { + graph, + emit_options: &emit_options, + globals: &globals, + cm: cm.clone(), + }; + let resolver = BundleResolver(graph); + let config = swc::bundler::Config { + module: options.bundle_type.into(), + ..Default::default() + }; + // This hook will rewrite the `import.meta` when bundling to give a consistent + // behavior between bundled and unbundled code. + let hook = Box::new(ast::BundleHook); + let bundler = swc::bundler::Bundler::new( + &globals, + cm.clone(), + loader, + resolver, + config, + hook, + ); + let mut entries = HashMap::new(); + entries.insert( + "bundle".to_string(), + swc::common::FileName::Url(graph.roots[0].clone()), + ); + let output = bundler + .bundle(entries) + .context("Unable to output during bundling.")?; + let mut buf = Vec::new(); + let mut srcmap = Vec::new(); + { + let cfg = swc::codegen::Config { minify: false }; + let wr = Box::new(swc::codegen::text_writer::JsWriter::new( + cm.clone(), + "\n", + &mut buf, + Some(&mut srcmap), + )); + let mut emitter = swc::codegen::Emitter { + cfg, + cm: cm.clone(), + comments: None, + wr, + }; + emitter + .emit_module(&output[0].module) + .context("Unable to emit during bundling.")?; + } + let mut code = + String::from_utf8(buf).context("Emitted code is an invalid string.")?; + let mut maybe_map: Option = None; + { + let mut buf = Vec::new(); + cm.build_source_map_from(&mut srcmap, None) + .to_writer(&mut buf)?; + if emit_options.inline_source_map { + let encoded_map = format!( + "//# sourceMappingURL=data:application/json;base64,{}\n", + base64::encode(buf) + ); + code.push_str(&encoded_map); + } else if emit_options.source_map { + maybe_map = Some(String::from_utf8(buf)?); + } + } + + Ok((code, maybe_map)) +} + +pub(crate) struct EmitOptions { + pub ts_config: TsConfig, + pub reload_exclusions: HashSet, + pub reload: bool, +} + +/// Given a module graph, emit any appropriate modules and cache them. +pub(crate) fn emit( + graph: &ModuleGraph, + cache: &mut dyn Cacher, + options: EmitOptions, +) -> Result { + let start = Instant::now(); + let config_bytes = options.ts_config.as_bytes(); + let include_js = options.ts_config.get_check_js(); + let emit_options = options.ts_config.into(); + + let mut emit_count = 0_u32; + let mut file_count = 0_u32; + for module in graph.modules() { + file_count += 1; + if !is_emittable(&module.media_type, include_js) { + continue; + } + let needs_reload = + options.reload && !options.reload_exclusions.contains(&module.specifier); + let version = get_version(module.source.as_bytes(), &config_bytes); + let is_valid = cache + .get(CacheType::Version, &module.specifier) + .map_or(false, |v| { + v == get_version(module.source.as_bytes(), &config_bytes) + }); + if is_valid && !needs_reload { + continue; + } + let (emit, maybe_map) = + ast::transpile(&module.parsed_source, &emit_options)?; + emit_count += 1; + cache.set(CacheType::Emit, &module.specifier, emit)?; + if let Some(map) = maybe_map { + cache.set(CacheType::SourceMap, &module.specifier, map)?; + } + if !is_valid { + cache.set(CacheType::Version, &module.specifier, version)?; + } + } + + let stats = Stats(vec![ + ("Files".to_string(), file_count), + ("Emitted".to_string(), emit_count), + ("Total time".to_string(), start.elapsed().as_millis() as u32), + ]); + + Ok(CheckEmitResult { + diagnostics: Diagnostics::default(), + stats, + }) +} + +/// Check the sub-resource integrity of a module graph, exiting if the graph is +/// not valid. +pub(crate) fn lock(graph: &ModuleGraph) { + if let Err(err) = graph.lock() { + log::error!("{} {}", colors::red("error:"), err); + std::process::exit(10); + } +} + +/// Check a module graph to determine if the graph contains anything that +/// is required to be emitted to be valid. It determines what modules in the +/// graph are emittable and for those that are emittable, if there is currently +/// a valid emit in the cache. +pub(crate) fn valid_emit( + graph: &ModuleGraph, + cache: &dyn Cacher, + ts_config: &TsConfig, + reload: bool, + reload_exclusions: &HashSet, +) -> bool { + let config_bytes = ts_config.as_bytes(); + let emit_js = ts_config.get_check_js(); + graph + .specifiers() + .iter() + .filter(|(_, r)| match r { + Ok((_, MediaType::TypeScript)) + | Ok((_, MediaType::Tsx)) + | Ok((_, MediaType::Jsx)) => true, + Ok((_, MediaType::JavaScript)) => emit_js, + _ => false, + }) + .all(|(_, r)| { + if let Ok((s, _)) = r { + if reload && !reload_exclusions.contains(s) { + // we are reloading and the specifier isn't excluded from being + // reloaded + false + } else if let Some(version) = cache.get(CacheType::Version, s) { + if let Some(module) = graph.get(s) { + version == get_version(module.source.as_bytes(), &config_bytes) + } else { + // We have a source module in the graph we can't find, so the emit is + // clearly wrong + false + } + } else { + // A module that requires emitting doesn't have a version, so it doesn't + // have a valid emit + false + } + } else { + // Something in the module graph is missing, but that doesn't mean the + // emit is invalid + true + } + }) +} + +/// An adapter struct to make a deno_graph::ModuleGraphError display as expected +/// in the Deno CLI. +#[derive(Debug)] +pub(crate) struct GraphError(pub ModuleGraphError); + +impl std::error::Error for GraphError {} + +impl From for GraphError { + fn from(err: ModuleGraphError) -> Self { + Self(err) + } +} + +impl fmt::Display for GraphError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + ModuleGraphError::ResolutionError(err) => { + if matches!( + err, + ResolutionError::InvalidDowngrade(_, _) + | ResolutionError::InvalidLocalImport(_, _) + ) { + write!(f, "{}", err.to_string_with_span()) + } else { + self.0.fmt(f) + } + } + _ => self.0.fmt(f), + } + } +} + +/// Convert a module graph to a map of "files", which are used by the runtime +/// emit to be passed back to the caller. +pub(crate) fn to_file_map( + graph: &ModuleGraph, + cache: &dyn Cacher, +) -> HashMap { + let mut files = HashMap::new(); + for (_, result) in graph.specifiers().into_iter() { + if let Ok((specifier, media_type)) = result { + if let Some(emit) = cache.get(CacheType::Emit, &specifier) { + files.insert(format!("{}.js", specifier), emit); + if let Some(map) = cache.get(CacheType::SourceMap, &specifier) { + files.insert(format!("{}.js.map", specifier), map); + } + } else if media_type == MediaType::JavaScript + || media_type == MediaType::Unknown + { + if let Some(module) = graph.get(&specifier) { + files.insert(specifier.to_string(), module.source.to_string()); + } + } + if let Some(declaration) = cache.get(CacheType::Declaration, &specifier) { + files.insert(format!("{}.d.ts", specifier), declaration); + } + } + } + files +} + +/// Convert a module graph to a map of module sources, which are used by +/// `deno_core` to load modules into V8. +pub(crate) fn to_module_sources( + graph: &ModuleGraph, + cache: &dyn Cacher, +) -> Modules { + graph + .specifiers() + .into_iter() + .map(|(requested_specifier, r)| match r { + Err(err) => (requested_specifier, Err(err.into())), + Ok((found_specifier, media_type)) => { + // First we check to see if there is an emitted file in the cache. + if let Some(code) = cache.get(CacheType::Emit, &found_specifier) { + ( + requested_specifier.clone(), + Ok(ModuleSource { + code, + module_url_found: found_specifier.to_string(), + module_url_specified: requested_specifier.to_string(), + }), + ) + // Then if the file is JavaScript (or unknown) and wasn't emitted, we + // will load the original source code in the module. + } else if media_type == MediaType::JavaScript + || media_type == MediaType::Unknown + { + if let Some(module) = graph.get(&found_specifier) { + ( + requested_specifier.clone(), + Ok(ModuleSource { + code: module.source.as_str().to_string(), + module_url_found: module.specifier.to_string(), + module_url_specified: requested_specifier.to_string(), + }), + ) + } else { + unreachable!( + "unexpected module missing from graph: {}", + found_specifier + ) + } + // Otherwise we will add a not found error. + } else { + ( + requested_specifier.clone(), + Err(custom_error( + "NotFound", + format!( + "Emitted code for \"{}\" not found.", + requested_specifier + ), + )), + ) + } + } + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cache::MemoryCacher; + + #[test] + fn test_is_emittable() { + assert!(is_emittable(&MediaType::TypeScript, false)); + assert!(!is_emittable(&MediaType::Dts, false)); + assert!(is_emittable(&MediaType::Tsx, false)); + assert!(!is_emittable(&MediaType::JavaScript, false)); + assert!(is_emittable(&MediaType::JavaScript, true)); + assert!(is_emittable(&MediaType::Jsx, false)); + assert!(!is_emittable(&MediaType::Json, false)); + } + + async fn setup>( + root: S, + sources: Vec<(S, S)>, + ) -> (ModuleGraph, MemoryCacher) { + let roots = vec![ModuleSpecifier::parse(root.as_ref()).unwrap()]; + let sources = sources + .into_iter() + .map(|(s, c)| (s.as_ref().to_string(), Arc::new(c.as_ref().to_string()))) + .collect(); + let mut cache = MemoryCacher::new(sources); + let graph = deno_graph::create_graph( + roots, false, None, &mut cache, None, None, None, + ) + .await; + (graph, cache) + } + + #[tokio::test] + async fn test_to_module_sources_emitted() { + let (graph, mut cache) = setup( + "https://example.com/a.ts", + vec![("https://example.com/a.ts", r#"console.log("hello deno");"#)], + ) + .await; + let (ts_config, _) = get_ts_config(ConfigType::Emit, None, None).unwrap(); + emit( + &graph, + &mut cache, + EmitOptions { + ts_config, + reload_exclusions: HashSet::default(), + reload: false, + }, + ) + .unwrap(); + let modules = to_module_sources(&graph, &cache); + assert_eq!(modules.len(), 1); + let root = ModuleSpecifier::parse("https://example.com/a.ts").unwrap(); + let maybe_result = modules.get(&root); + assert!(maybe_result.is_some()); + let result = maybe_result.unwrap(); + assert!(result.is_ok()); + let module_source = result.as_ref().unwrap(); + assert!(module_source + .code + .starts_with(r#"console.log("hello deno");"#)); + } + + #[tokio::test] + async fn test_to_module_sources_not_emitted() { + let (graph, mut cache) = setup( + "https://example.com/a.js", + vec![("https://example.com/a.js", r#"console.log("hello deno");"#)], + ) + .await; + let (ts_config, _) = get_ts_config(ConfigType::Emit, None, None).unwrap(); + emit( + &graph, + &mut cache, + EmitOptions { + ts_config, + reload_exclusions: HashSet::default(), + reload: false, + }, + ) + .unwrap(); + let modules = to_module_sources(&graph, &cache); + assert_eq!(modules.len(), 1); + let root = ModuleSpecifier::parse("https://example.com/a.js").unwrap(); + let maybe_result = modules.get(&root); + assert!(maybe_result.is_some()); + let result = maybe_result.unwrap(); + assert!(result.is_ok()); + let module_source = result.as_ref().unwrap(); + assert_eq!(module_source.code, r#"console.log("hello deno");"#); + } +} diff --git a/cli/errors.rs b/cli/errors.rs index c26479b447..b09c3c01ae 100644 --- a/cli/errors.rs +++ b/cli/errors.rs @@ -9,8 +9,12 @@ //! Diagnostics are compile-time type errors, whereas JsErrors are runtime //! exceptions. +use crate::emit::GraphError; + use deno_ast::Diagnostic; use deno_core::error::AnyError; +use deno_graph::ModuleGraphError; +use deno_graph::ResolutionError; use import_map::ImportMapError; fn get_import_map_error_class(_: &ImportMapError) -> &'static str { @@ -21,6 +25,34 @@ fn get_diagnostic_class(_: &Diagnostic) -> &'static str { "SyntaxError" } +fn get_graph_error_class(err: &GraphError) -> &'static str { + get_module_graph_error_class(&err.0) +} + +pub(crate) fn get_module_graph_error_class( + err: &ModuleGraphError, +) -> &'static str { + match err { + ModuleGraphError::LoadingErr(_, err) => get_error_class_name(err.as_ref()), + ModuleGraphError::InvalidSource(_, _) => "SyntaxError", + ModuleGraphError::ParseErr(_, diagnostic) => { + get_diagnostic_class(diagnostic) + } + ModuleGraphError::ResolutionError(err) => get_resolution_error_class(err), + ModuleGraphError::UnsupportedMediaType(_, _) => "TypeError", + ModuleGraphError::Missing(_) => "NotFound", + } +} + +fn get_resolution_error_class(err: &ResolutionError) -> &'static str { + match err { + ResolutionError::ResolverError(err, _, _) => { + get_error_class_name(err.as_ref()) + } + _ => "TypeError", + } +} + pub(crate) fn get_error_class_name(e: &AnyError) -> &'static str { deno_runtime::errors::get_error_class_name(e) .or_else(|| { @@ -28,6 +60,15 @@ pub(crate) fn get_error_class_name(e: &AnyError) -> &'static str { .map(get_import_map_error_class) }) .or_else(|| e.downcast_ref::().map(get_diagnostic_class)) + .or_else(|| e.downcast_ref::().map(get_graph_error_class)) + .or_else(|| { + e.downcast_ref::() + .map(get_module_graph_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_resolution_error_class) + }) .unwrap_or_else(|| { panic!( "Error '{}' contains boxed error of unknown type:{}", diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index c4c89fec14..0527cbac1d 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -371,7 +371,9 @@ impl FileFetcher { })?; let mut headers = HashMap::new(); headers.insert("content-type".to_string(), content_type); - self.http_cache.set(specifier, headers, source.as_bytes())?; + self + .http_cache + .set(specifier, headers.clone(), source.as_bytes())?; Ok(File { local, @@ -379,7 +381,7 @@ impl FileFetcher { media_type, source: Arc::new(source), specifier: specifier.clone(), - maybe_headers: None, + maybe_headers: Some(headers), }) } @@ -433,7 +435,9 @@ impl FileFetcher { })?; let mut headers = HashMap::new(); headers.insert("content-type".to_string(), content_type); - self.http_cache.set(specifier, headers, source.as_bytes())?; + self + .http_cache + .set(specifier, headers.clone(), source.as_bytes())?; Ok(File { local, @@ -441,7 +445,7 @@ impl FileFetcher { media_type, source: Arc::new(source), specifier: specifier.clone(), - maybe_headers: None, + maybe_headers: Some(headers), }) } /// Asynchronously fetch remote source file specified by the URL following @@ -573,6 +577,14 @@ impl FileFetcher { } } + pub fn get_local_path(&self, specifier: &ModuleSpecifier) -> Option { + if specifier.scheme() == "file" { + specifier.to_file_path().ok() + } else { + self.http_cache.get_cache_filename(specifier) + } + } + /// Get the location of the current HTTP cache associated with the fetcher. pub fn get_http_cache_location(&self) -> PathBuf { self.http_cache.location.clone() diff --git a/cli/info.rs b/cli/info.rs deleted file mode 100644 index 0f062028ef..0000000000 --- a/cli/info.rs +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -use crate::colors; - -use deno_ast::MediaType; -use deno_core::resolve_url; -use deno_core::serde::Serialize; -use deno_core::ModuleSpecifier; -use std::collections::HashSet; -use std::fmt; -use std::iter::Iterator; -use std::path::PathBuf; - -const SIBLING_CONNECTOR: char = '├'; -const LAST_SIBLING_CONNECTOR: char = '└'; -const CHILD_DEPS_CONNECTOR: char = '┬'; -const CHILD_NO_DEPS_CONNECTOR: char = '─'; -const VERTICAL_CONNECTOR: char = '│'; -const EMPTY_CONNECTOR: char = ' '; - -#[derive(Debug, Serialize, Ord, PartialOrd, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct ModuleGraphInfoDep { - pub specifier: String, - #[serde(skip_serializing_if = "is_false")] - pub is_dynamic: bool, - #[serde(rename = "code", skip_serializing_if = "Option::is_none")] - pub maybe_code: Option, - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub maybe_type: Option, -} - -fn is_false(b: &bool) -> bool { - !b -} - -impl ModuleGraphInfoDep { - fn write_info + fmt::Display + Clone>( - &self, - f: &mut fmt::Formatter, - prefix: S, - last: bool, - modules: &[ModuleGraphInfoMod], - seen: &mut HashSet, - ) -> fmt::Result { - let maybe_code = self - .maybe_code - .as_ref() - .and_then(|s| modules.iter().find(|m| &m.specifier == s)); - let maybe_type = self - .maybe_type - .as_ref() - .and_then(|s| modules.iter().find(|m| &m.specifier == s)); - match (maybe_code, maybe_type) { - (Some(code), Some(types)) => { - code.write_info(f, prefix.clone(), false, false, modules, seen)?; - types.write_info(f, prefix, last, true, modules, seen) - } - (Some(code), None) => { - code.write_info(f, prefix, last, false, modules, seen) - } - (None, Some(types)) => { - types.write_info(f, prefix, last, true, modules, seen) - } - _ => Ok(()), - } - } -} - -#[derive(Debug, Serialize, Ord, PartialOrd, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct ModuleGraphInfoMod { - pub specifier: ModuleSpecifier, - pub dependencies: Vec, - #[serde(rename = "typeDependency", skip_serializing_if = "Option::is_none")] - pub maybe_type_dependency: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub size: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub media_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub local: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub checksum: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub emit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub map: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, -} - -impl Default for ModuleGraphInfoMod { - fn default() -> Self { - ModuleGraphInfoMod { - specifier: resolve_url("https://deno.land/x/mod.ts").unwrap(), - dependencies: Vec::new(), - maybe_type_dependency: None, - size: None, - media_type: None, - local: None, - checksum: None, - emit: None, - map: None, - error: None, - } - } -} - -impl ModuleGraphInfoMod { - fn write_info + fmt::Display>( - &self, - f: &mut fmt::Formatter, - prefix: S, - last: bool, - type_dep: bool, - modules: &[ModuleGraphInfoMod], - seen: &mut HashSet, - ) -> fmt::Result { - let was_seen = seen.contains(&self.specifier); - let sibling_connector = if last { - LAST_SIBLING_CONNECTOR - } else { - SIBLING_CONNECTOR - }; - let child_connector = if (self.dependencies.is_empty() - && self.maybe_type_dependency.is_none()) - || was_seen - { - CHILD_NO_DEPS_CONNECTOR - } else { - CHILD_DEPS_CONNECTOR - }; - let (size, specifier) = if self.error.is_some() { - ( - colors::red_bold(" (error)").to_string(), - colors::red(&self.specifier).to_string(), - ) - } else if was_seen { - let name = if type_dep { - colors::italic_gray(&self.specifier).to_string() - } else { - colors::gray(&self.specifier).to_string() - }; - (colors::gray(" *").to_string(), name) - } else { - let name = if type_dep { - colors::italic(&self.specifier).to_string() - } else { - self.specifier.to_string() - }; - ( - colors::gray(format!( - " ({})", - human_size(self.size.unwrap_or(0) as f64) - )) - .to_string(), - name, - ) - }; - - seen.insert(self.specifier.clone()); - - writeln!( - f, - "{} {}{}", - colors::gray(format!( - "{}{}─{}", - prefix, sibling_connector, child_connector - )), - specifier, - size - )?; - - if !was_seen { - let mut prefix = prefix.to_string(); - if last { - prefix.push(EMPTY_CONNECTOR); - } else { - prefix.push(VERTICAL_CONNECTOR); - } - prefix.push(EMPTY_CONNECTOR); - let dep_count = self.dependencies.len(); - for (idx, dep) in self.dependencies.iter().enumerate() { - dep.write_info( - f, - &prefix, - idx == dep_count - 1 && self.maybe_type_dependency.is_none(), - modules, - seen, - )?; - } - if let Some(dep) = &self.maybe_type_dependency { - dep.write_info(f, &prefix, true, modules, seen)?; - } - } - - Ok(()) - } -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ModuleGraphInfo { - pub root: ModuleSpecifier, - pub modules: Vec, - pub size: usize, -} - -impl fmt::Display for ModuleGraphInfo { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let root = self - .modules - .iter() - .find(|m| m.specifier == self.root) - .unwrap(); - if let Some(err) = &root.error { - writeln!(f, "{} {}", colors::red("error:"), err) - } else { - if let Some(local) = &root.local { - writeln!(f, "{} {}", colors::bold("local:"), local.to_string_lossy())?; - } - if let Some(media_type) = &root.media_type { - writeln!(f, "{} {}", colors::bold("type:"), media_type)?; - } - if let Some(emit) = &root.emit { - writeln!(f, "{} {}", colors::bold("emit:"), emit.to_string_lossy())?; - } - if let Some(map) = &root.map { - writeln!(f, "{} {}", colors::bold("map:"), map.to_string_lossy())?; - } - let dep_count = self.modules.len() - 1; - writeln!( - f, - "{} {} unique {}", - colors::bold("dependencies:"), - dep_count, - colors::gray(format!("(total {})", human_size(self.size as f64))) - )?; - writeln!( - f, - "\n{} {}", - self.root, - colors::gray(format!( - "({})", - human_size(root.size.unwrap_or(0) as f64) - )) - )?; - let mut seen = HashSet::new(); - let dep_len = root.dependencies.len(); - for (idx, dep) in root.dependencies.iter().enumerate() { - dep.write_info( - f, - "", - idx == dep_len - 1 && root.maybe_type_dependency.is_none(), - &self.modules, - &mut seen, - )?; - } - if let Some(dep) = &root.maybe_type_dependency { - dep.write_info(f, "", true, &self.modules, &mut seen)?; - } - Ok(()) - } - } -} - -/// An entry in the `ModuleInfoMap` the provides the size of the module and -/// a vector of its dependencies, which should also be available as entries -/// in the map. -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ModuleInfoMapItem { - pub deps: Vec, - pub size: usize, -} - -/// A function that converts a float to a string the represents a human -/// readable version of that number. -pub fn human_size(size: f64) -> String { - let negative = if size.is_sign_positive() { "" } else { "-" }; - let size = size.abs(); - let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - if size < 1_f64 { - return format!("{}{}{}", negative, size, "B"); - } - let delimiter = 1024_f64; - let exponent = std::cmp::min( - (size.ln() / delimiter.ln()).floor() as i32, - (units.len() - 1) as i32, - ); - let pretty_bytes = format!("{:.2}", size / delimiter.powi(exponent)) - .parse::() - .unwrap() - * 1_f64; - let unit = units[exponent as usize]; - format!("{}{}{}", negative, pretty_bytes, unit) -} - -#[cfg(test)] -mod test { - use super::*; - use deno_core::resolve_url; - use deno_core::serde_json::json; - - #[test] - fn human_size_test() { - assert_eq!(human_size(16_f64), "16B"); - assert_eq!(human_size((16 * 1024) as f64), "16KB"); - assert_eq!(human_size((16 * 1024 * 1024) as f64), "16MB"); - assert_eq!(human_size(16_f64 * 1024_f64.powf(3.0)), "16GB"); - assert_eq!(human_size(16_f64 * 1024_f64.powf(4.0)), "16TB"); - assert_eq!(human_size(16_f64 * 1024_f64.powf(5.0)), "16PB"); - assert_eq!(human_size(16_f64 * 1024_f64.powf(6.0)), "16EB"); - assert_eq!(human_size(16_f64 * 1024_f64.powf(7.0)), "16ZB"); - assert_eq!(human_size(16_f64 * 1024_f64.powf(8.0)), "16YB"); - } - - fn get_fixture() -> ModuleGraphInfo { - let specifier_a = resolve_url("https://deno.land/x/a.ts").unwrap(); - let specifier_b = resolve_url("https://deno.land/x/b.ts").unwrap(); - let specifier_c_js = resolve_url("https://deno.land/x/c.js").unwrap(); - let specifier_c_dts = resolve_url("https://deno.land/x/c.d.ts").unwrap(); - let specifier_d_js = resolve_url("https://deno.land/x/d.js").unwrap(); - let specifier_d_dts = resolve_url("https://deno.land/x/d.d.ts").unwrap(); - let modules = vec![ - ModuleGraphInfoMod { - specifier: specifier_a.clone(), - dependencies: vec![ModuleGraphInfoDep { - specifier: "./b.ts".to_string(), - is_dynamic: false, - maybe_code: Some(specifier_b.clone()), - maybe_type: None, - }], - size: Some(123), - media_type: Some(MediaType::TypeScript), - local: Some(PathBuf::from("/cache/deps/https/deno.land/x/a.ts")), - checksum: Some("abcdef".to_string()), - emit: Some(PathBuf::from("/cache/emit/https/deno.land/x/a.js")), - ..Default::default() - }, - ModuleGraphInfoMod { - specifier: specifier_b, - dependencies: vec![ModuleGraphInfoDep { - specifier: "./c.js".to_string(), - is_dynamic: false, - maybe_code: Some(specifier_c_js.clone()), - maybe_type: Some(specifier_c_dts.clone()), - }], - size: Some(456), - media_type: Some(MediaType::TypeScript), - local: Some(PathBuf::from("/cache/deps/https/deno.land/x/b.ts")), - checksum: Some("def123".to_string()), - emit: Some(PathBuf::from("/cache/emit/https/deno.land/x/b.js")), - ..Default::default() - }, - ModuleGraphInfoMod { - specifier: specifier_c_js, - dependencies: vec![ModuleGraphInfoDep { - specifier: "./d.js".to_string(), - is_dynamic: false, - maybe_code: Some(specifier_d_js.clone()), - maybe_type: None, - }], - size: Some(789), - media_type: Some(MediaType::JavaScript), - local: Some(PathBuf::from("/cache/deps/https/deno.land/x/c.js")), - checksum: Some("9876abcef".to_string()), - ..Default::default() - }, - ModuleGraphInfoMod { - specifier: specifier_c_dts, - size: Some(999), - media_type: Some(MediaType::Dts), - local: Some(PathBuf::from("/cache/deps/https/deno.land/x/c.d.ts")), - checksum: Some("a2b3c4d5".to_string()), - ..Default::default() - }, - ModuleGraphInfoMod { - specifier: specifier_d_js, - size: Some(987), - maybe_type_dependency: Some(ModuleGraphInfoDep { - specifier: "/x/d.d.ts".to_string(), - is_dynamic: false, - maybe_code: None, - maybe_type: Some(specifier_d_dts.clone()), - }), - media_type: Some(MediaType::JavaScript), - local: Some(PathBuf::from("/cache/deps/https/deno.land/x/d.js")), - checksum: Some("5k6j7h8g".to_string()), - ..Default::default() - }, - ModuleGraphInfoMod { - specifier: specifier_d_dts, - size: Some(67), - media_type: Some(MediaType::Dts), - local: Some(PathBuf::from("/cache/deps/https/deno.land/x/d.d.ts")), - checksum: Some("0h0h0h0h0h".to_string()), - ..Default::default() - }, - ]; - ModuleGraphInfo { - root: specifier_a, - modules, - size: 99999, - } - } - - #[test] - fn text_module_graph_info_display() { - let fixture = get_fixture(); - let text = fixture.to_string(); - let actual = test_util::strip_ansi_codes(&text); - let expected = r#"local: /cache/deps/https/deno.land/x/a.ts -type: TypeScript -emit: /cache/emit/https/deno.land/x/a.js -dependencies: 5 unique (total 97.66KB) - -https://deno.land/x/a.ts (123B) -└─┬ https://deno.land/x/b.ts (456B) - ├─┬ https://deno.land/x/c.js (789B) - │ └─┬ https://deno.land/x/d.js (987B) - │ └── https://deno.land/x/d.d.ts (67B) - └── https://deno.land/x/c.d.ts (999B) -"#; - assert_eq!(actual, expected); - } - - #[test] - fn test_module_graph_info_json() { - let fixture = get_fixture(); - let actual = json!(fixture); - assert_eq!( - actual, - json!({ - "root": "https://deno.land/x/a.ts", - "modules": [ - { - "specifier": "https://deno.land/x/a.ts", - "dependencies": [ - { - "specifier": "./b.ts", - "code": "https://deno.land/x/b.ts" - } - ], - "size": 123, - "mediaType": "TypeScript", - "local": "/cache/deps/https/deno.land/x/a.ts", - "checksum": "abcdef", - "emit": "/cache/emit/https/deno.land/x/a.js" - }, - { - "specifier": "https://deno.land/x/b.ts", - "dependencies": [ - { - "specifier": "./c.js", - "code": "https://deno.land/x/c.js", - "type": "https://deno.land/x/c.d.ts" - } - ], - "size": 456, - "mediaType": "TypeScript", - "local": "/cache/deps/https/deno.land/x/b.ts", - "checksum": "def123", - "emit": "/cache/emit/https/deno.land/x/b.js" - }, - { - "specifier": "https://deno.land/x/c.js", - "dependencies": [ - { - "specifier": "./d.js", - "code": "https://deno.land/x/d.js" - } - ], - "size": 789, - "mediaType": "JavaScript", - "local": "/cache/deps/https/deno.land/x/c.js", - "checksum": "9876abcef" - }, - { - "specifier": "https://deno.land/x/c.d.ts", - "dependencies": [], - "size": 999, - "mediaType": "Dts", - "local": "/cache/deps/https/deno.land/x/c.d.ts", - "checksum": "a2b3c4d5" - }, - { - "specifier": "https://deno.land/x/d.js", - "dependencies": [], - "typeDependency": { - "specifier": "/x/d.d.ts", - "type": "https://deno.land/x/d.d.ts" - }, - "size": 987, - "mediaType": "JavaScript", - "local": "/cache/deps/https/deno.land/x/d.js", - "checksum": "5k6j7h8g" - }, - { - "specifier": "https://deno.land/x/d.d.ts", - "dependencies": [], - "size": 67, - "mediaType": "Dts", - "local": "/cache/deps/https/deno.land/x/d.d.ts", - "checksum": "0h0h0h0h0h" - } - ], - "size": 99999 - }) - ); - } -} diff --git a/cli/lockfile.rs b/cli/lockfile.rs index 0b52f5ee7b..6b553bd15d 100644 --- a/cli/lockfile.rs +++ b/cli/lockfile.rs @@ -1,11 +1,16 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use deno_core::parking_lot::Mutex; use deno_core::serde_json; use deno_core::serde_json::json; +use deno_core::ModuleSpecifier; use log::debug; +use std::cell::RefCell; use std::collections::BTreeMap; use std::io::Result; use std::path::PathBuf; +use std::rc::Rc; +use std::sync::Arc; #[derive(Debug, Clone)] pub struct Lockfile { @@ -81,6 +86,43 @@ impl Lockfile { } } +#[derive(Debug)] +pub(crate) struct Locker(Option>>); + +impl deno_graph::source::Locker for Locker { + fn check_or_insert( + &mut self, + specifier: &ModuleSpecifier, + source: &str, + ) -> bool { + if let Some(lock_file) = &self.0 { + let mut lock_file = lock_file.lock(); + lock_file.check_or_insert(specifier.as_str(), source) + } else { + true + } + } + + fn get_checksum(&self, content: &str) -> String { + crate::checksum::gen(&[content.as_bytes()]) + } + + fn get_filename(&self) -> Option { + let lock_file = self.0.as_ref()?.lock(); + lock_file.filename.to_str().map(|s| s.to_string()) + } +} + +pub(crate) fn as_maybe_locker( + lockfile: Option>>, +) -> Option>>> { + lockfile.as_ref().map(|lf| { + Rc::new(RefCell::new( + Box::new(Locker(Some(lf.clone()))) as Box + )) + }) +} + #[cfg(test)] mod tests { use super::*; diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index d1cf14500e..a0718e8b32 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -6,12 +6,12 @@ use super::tsc; use crate::ast; use crate::ast::Location; use crate::lsp::documents::DocumentData; -use crate::module_graph::parse_deno_types; -use crate::module_graph::parse_ts_reference; -use crate::module_graph::TypeScriptReference; use crate::tools::lint::create_linter; use deno_ast::swc::ast as swc_ast; +use deno_ast::swc::common::comments::Comment; +use deno_ast::swc::common::BytePos; +use deno_ast::swc::common::Span; use deno_ast::swc::common::DUMMY_SP; use deno_ast::swc::visit::Node; use deno_ast::swc::visit::Visit; @@ -68,10 +68,71 @@ lazy_static::lazy_static! { .collect(); static ref IMPORT_SPECIFIER_RE: Regex = Regex::new(r#"\sfrom\s+["']([^"']*)["']"#).unwrap(); + + static ref DENO_TYPES_RE: Regex = + Regex::new(r#"(?i)^\s*@deno-types\s*=\s*(?:["']([^"']+)["']|(\S+))"#) + .unwrap(); + static ref TRIPLE_SLASH_REFERENCE_RE: Regex = + Regex::new(r"(?i)^/\s*").unwrap(); + static ref PATH_REFERENCE_RE: Regex = + Regex::new(r#"(?i)\spath\s*=\s*["']([^"']*)["']"#).unwrap(); + static ref TYPES_REFERENCE_RE: Regex = + Regex::new(r#"(?i)\stypes\s*=\s*["']([^"']*)["']"#).unwrap(); + } const SUPPORTED_EXTENSIONS: &[&str] = &[".ts", ".tsx", ".js", ".jsx", ".mjs"]; +// TODO(@kitsonk) remove after deno_graph migration +#[derive(Debug, Clone, Eq, PartialEq)] +enum TypeScriptReference { + Path(String), + Types(String), +} + +fn match_to_span(comment: &Comment, m: ®ex::Match) -> Span { + Span { + lo: comment.span.lo + BytePos((m.start() + 1) as u32), + hi: comment.span.lo + BytePos((m.end() + 1) as u32), + ctxt: comment.span.ctxt, + } +} + +// TODO(@kitsonk) remove after deno_graph migration +fn parse_deno_types(comment: &Comment) -> Option<(String, Span)> { + let captures = DENO_TYPES_RE.captures(&comment.text)?; + if let Some(m) = captures.get(1) { + Some((m.as_str().to_string(), match_to_span(comment, &m))) + } else if let Some(m) = captures.get(2) { + Some((m.as_str().to_string(), match_to_span(comment, &m))) + } else { + unreachable!(); + } +} + +// TODO(@kitsonk) remove after deno_graph migration +fn parse_ts_reference( + comment: &Comment, +) -> Option<(TypeScriptReference, Span)> { + if !TRIPLE_SLASH_REFERENCE_RE.is_match(&comment.text) { + None + } else if let Some(captures) = PATH_REFERENCE_RE.captures(&comment.text) { + let m = captures.get(1).unwrap(); + Some(( + TypeScriptReference::Path(m.as_str().to_string()), + match_to_span(comment, &m), + )) + } else { + TYPES_REFERENCE_RE.captures(&comment.text).map(|captures| { + let m = captures.get(1).unwrap(); + ( + TypeScriptReference::Types(m.as_str().to_string()), + match_to_span(comment, &m), + ) + }) + } +} + /// Category of self-generated diagnostic messages (those not coming from) /// TypeScript. #[derive(Debug, PartialEq, Eq)] diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs new file mode 100644 index 0000000000..39b9dca601 --- /dev/null +++ b/cli/lsp/cache.rs @@ -0,0 +1,86 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::cache::CacherLoader; +use crate::cache::FetchCacher; +use crate::flags::Flags; +use crate::proc_state::ProcState; +use crate::resolver::ImportMapResolver; +use crate::tokio_util::create_basic_runtime; + +use deno_core::error::anyhow; +use deno_core::error::AnyError; +use deno_core::ModuleSpecifier; +use deno_runtime::permissions::Permissions; +use import_map::ImportMap; +use std::path::PathBuf; +use std::thread; +use tokio::sync::mpsc; +use tokio::sync::oneshot; + +type Request = (Vec, oneshot::Sender>); + +/// A "server" that handles requests from the language server to cache modules +/// in its own thread. +#[derive(Debug)] +pub(crate) struct CacheServer(mpsc::UnboundedSender); + +impl CacheServer { + pub async fn new( + maybe_cache_path: Option, + maybe_import_map: Option, + ) -> Self { + let (tx, mut rx) = mpsc::unbounded_channel::(); + let _join_handle = thread::spawn(move || { + let runtime = create_basic_runtime(); + runtime.block_on(async { + let ps = ProcState::build(Flags { + cache_path: maybe_cache_path, + ..Default::default() + }) + .await + .unwrap(); + let maybe_resolver = + maybe_import_map.as_ref().map(ImportMapResolver::new); + let mut cache = FetchCacher::new( + ps.dir.gen_cache.clone(), + ps.file_fetcher.clone(), + Permissions::allow_all(), + Permissions::allow_all(), + ); + + while let Some((roots, tx)) = rx.recv().await { + let graph = deno_graph::create_graph( + roots, + false, + None, + cache.as_mut_loader(), + maybe_resolver.as_ref().map(|r| r.as_resolver()), + None, + None, + ) + .await; + + if tx.send(graph.valid().map_err(|err| err.into())).is_err() { + log::warn!("cannot send to client"); + } + } + }) + }); + + Self(tx) + } + + /// Attempt to cache the supplied module specifiers and their dependencies in + /// the current DENO_DIR, returning any errors, so they can be returned to the + /// client. + pub async fn cache( + &self, + roots: Vec, + ) -> Result<(), AnyError> { + let (tx, rx) = oneshot::channel::>(); + if self.0.send((roots, tx)).is_err() { + return Err(anyhow!("failed to send request to cache thread")); + } + rx.await? + } +} diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index ef659cd05a..524a591919 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -29,6 +29,7 @@ use super::analysis::ts_changes_to_edit; use super::analysis::CodeActionCollection; use super::analysis::CodeActionData; use super::analysis::ResolvedDependency; +use super::cache::CacheServer; use super::capabilities; use super::code_lens; use super::completions; @@ -44,7 +45,6 @@ use super::parent_process_checker; use super::performance::Performance; use super::refactor; use super::registries; -use super::sources; use super::sources::Sources; use super::text; use super::text::LineIndex; @@ -99,6 +99,8 @@ pub(crate) struct Inner { /// An optional path to the DENO_DIR which has been specified in the client /// options. maybe_cache_path: Option, + /// A lazily created "server" for handling cache requests. + maybe_cache_server: Option, /// An optional configuration file which has been specified in the client /// options. maybe_config_file: Option, @@ -149,6 +151,7 @@ impl Inner { diagnostics_server, documents: Default::default(), maybe_cache_path: None, + maybe_cache_server: None, maybe_config_file: None, maybe_config_uri: None, maybe_import_map: None, @@ -424,6 +427,7 @@ impl Inner { pub fn update_cache(&mut self) -> Result<(), AnyError> { let mark = self.performance.mark("update_cache", None::<()>); self.performance.measure(mark); + self.maybe_cache_server = None; let (maybe_cache, maybe_root_uri) = { let config = &self.config; ( @@ -479,6 +483,7 @@ impl Inner { pub async fn update_import_map(&mut self) -> Result<(), AnyError> { let mark = self.performance.mark("update_import_map", None::<()>); + self.maybe_cache_server = None; let (maybe_import_map, maybe_root_uri) = { let config = &self.config; ( @@ -2630,34 +2635,30 @@ impl Inner { } let mark = self.performance.mark("cache", Some(¶ms)); - if !params.uris.is_empty() { - for identifier in ¶ms.uris { - let specifier = self.url_map.normalize_url(&identifier.uri); - sources::cache( - &specifier, - &self.maybe_import_map, - &self.maybe_config_file, - &self.maybe_cache_path, - ) - .await - .map_err(|err| { - error!("{}", err); - LspError::internal_error() - })?; - } + let roots = if !params.uris.is_empty() { + params + .uris + .iter() + .map(|t| self.url_map.normalize_url(&t.uri)) + .collect() } else { - sources::cache( - &referrer, - &self.maybe_import_map, - &self.maybe_config_file, - &self.maybe_cache_path, - ) - .await - .map_err(|err| { - error!("{}", err); - LspError::internal_error() - })?; + vec![referrer.clone()] + }; + + if self.maybe_cache_server.is_none() { + self.maybe_cache_server = Some( + CacheServer::new( + self.maybe_cache_path.clone(), + self.maybe_import_map.clone(), + ) + .await, + ); } + let cache_server = self.maybe_cache_server.as_ref().unwrap(); + if let Err(err) = cache_server.cache(roots).await { + self.client.show_message(MessageType::Warning, err).await; + } + // now that we have dependencies loaded, we need to re-analyze them and // invalidate some diagnostics if self.documents.contains_key(&referrer) { diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs index fda2ac82bd..e6cc882089 100644 --- a/cli/lsp/mod.rs +++ b/cli/lsp/mod.rs @@ -5,6 +5,7 @@ use lspower::LspService; use lspower::Server; mod analysis; +mod cache; mod capabilities; mod code_lens; mod completions; diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs index bd5835cf21..69de6d976c 100644 --- a/cli/lsp/sources.rs +++ b/cli/lsp/sources.rs @@ -6,18 +6,12 @@ use super::text::LineIndex; use super::tsc; use super::urls::INVALID_SPECIFIER; -use crate::config_file::ConfigFile; use crate::file_fetcher::get_source_from_bytes; use crate::file_fetcher::map_content_type; use crate::file_fetcher::SUPPORTED_SCHEMES; -use crate::flags::Flags; use crate::http_cache; use crate::http_cache::HttpCache; -use crate::module_graph::GraphBuilder; -use crate::proc_state::ProcState; -use crate::specifier_handler::FetchHandler; use crate::text_encoding; -use import_map::ImportMap; use deno_ast::MediaType; use deno_core::error::anyhow; @@ -25,7 +19,7 @@ use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::serde_json; use deno_core::ModuleSpecifier; -use deno_runtime::permissions::Permissions; +use import_map::ImportMap; use std::collections::HashMap; use std::fs; use std::path::Path; @@ -34,27 +28,6 @@ use std::sync::Arc; use std::time::SystemTime; use tsc::NavigationTree; -pub async fn cache( - specifier: &ModuleSpecifier, - maybe_import_map: &Option, - maybe_config_file: &Option, - maybe_cache_path: &Option, -) -> Result<(), AnyError> { - let ps = ProcState::build(Flags { - cache_path: maybe_cache_path.clone(), - ..Default::default() - }) - .await?; - let handler = Arc::new(Mutex::new(FetchHandler::new( - &ps, - Permissions::allow_all(), - Permissions::allow_all(), - )?)); - let mut builder = GraphBuilder::new(handler, maybe_import_map.clone(), None); - builder.analyze_config_file(maybe_config_file).await?; - builder.add(specifier, false).await -} - fn get_remote_headers( cache_filename: &Path, ) -> Option> { diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 05606cb793..9899fad724 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -44,12 +44,15 @@ use log::warn; use lspower::lsp; use regex::Captures; use regex::Regex; +use std::borrow::Cow; +use std::cmp; +use std::collections::HashMap; use std::collections::HashSet; +use std::path::Path; use std::sync::Arc; use std::thread; -use std::{borrow::Cow, cmp}; -use std::{collections::HashMap, path::Path}; -use text_size::{TextRange, TextSize}; +use text_size::TextRange; +use text_size::TextSize; use tokio::sync::mpsc; use tokio::sync::oneshot; @@ -3375,7 +3378,7 @@ mod tests { #[test] fn test_modify_sources() { let (mut runtime, state_snapshot, location) = setup( - true, + false, json!({ "target": "esnext", "module": "esnext", diff --git a/cli/main.rs b/cli/main.rs index 4ffa8340c7..8a85cacfbc 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -2,6 +2,7 @@ mod ast; mod auth_tokens; +mod cache; mod checksum; mod compat; mod config_file; @@ -9,6 +10,7 @@ mod deno_dir; mod diagnostics; mod diff; mod disk_cache; +mod emit; mod errors; mod file_fetcher; mod file_watcher; @@ -18,16 +20,14 @@ mod fmt_errors; mod fs_util; mod http_cache; mod http_util; -mod info; mod lockfile; mod logger; mod lsp; -mod module_graph; mod module_loader; mod ops; mod proc_state; +mod resolver; mod source_maps; -mod specifier_handler; mod standalone; mod text_encoding; mod tokio_util; @@ -59,8 +59,8 @@ use crate::flags::UpgradeFlags; use crate::fmt_errors::PrettyJsError; use crate::module_loader::CliModuleLoader; use crate::proc_state::ProcState; +use crate::resolver::ImportMapResolver; use crate::source_maps::apply_source_map; -use crate::specifier_handler::FetchHandler; use crate::tools::installer::infer_name_from_url; use deno_ast::MediaType; use deno_core::error::generic_error; @@ -68,7 +68,6 @@ use deno_core::error::AnyError; use deno_core::futures::future::FutureExt; use deno_core::futures::Future; use deno_core::located_script_name; -use deno_core::parking_lot::Mutex; use deno_core::resolve_url_or_path; use deno_core::serde_json; use deno_core::serde_json::json; @@ -299,7 +298,7 @@ where fn print_cache_info( state: &ProcState, json: bool, - location: Option, + location: Option<&deno_core::url::Url>, ) -> Result<(), AnyError> { let deno_dir = &state.dir.root; let modules_cache = &state.file_fetcher.get_http_cache_location(); @@ -402,19 +401,9 @@ async fn compile_command( "An executable name was not provided. One could not be inferred from the URL. Aborting.", ))?; - let module_graph = create_module_graph_and_maybe_check( - module_specifier.clone(), - ps.clone(), - debug, - ) - .await?; - - info!( - "{} {}", - colors::green("Bundle"), - module_specifier.to_string() - ); - let bundle_str = bundle_module_graph(module_graph, ps.clone(), flags, debug)?; + let graph = + create_graph_and_maybe_check(module_specifier.clone(), &ps, debug).await?; + let (bundle_str, _) = bundle_module_graph(graph.as_ref(), &ps, &flags)?; info!( "{} {}", @@ -449,36 +438,38 @@ async fn info_command( flags: Flags, info_flags: InfoFlags, ) -> Result<(), AnyError> { - let location = flags.location.clone(); let ps = ProcState::build(flags).await?; if let Some(specifier) = info_flags.file { let specifier = resolve_url_or_path(&specifier)?; - let handler = Arc::new(Mutex::new(specifier_handler::FetchHandler::new( - &ps, - // info accesses dynamically imported modules just for their information - // so we allow access to all of them. + let mut cache = cache::FetchCacher::new( + ps.dir.gen_cache.clone(), + ps.file_fetcher.clone(), Permissions::allow_all(), Permissions::allow_all(), - )?)); - let mut builder = module_graph::GraphBuilder::new( - handler, - ps.maybe_import_map.clone(), - ps.lockfile.clone(), ); - builder.add(&specifier, false).await?; - builder.analyze_config_file(&ps.maybe_config_file).await?; - let graph = builder.get_graph(); - let info = graph.info()?; + let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); + let maybe_resolver = + ps.maybe_import_map.as_ref().map(ImportMapResolver::new); + let graph = deno_graph::create_graph( + vec![specifier], + false, + None, + &mut cache, + maybe_resolver.as_ref().map(|r| r.as_resolver()), + maybe_locker, + None, + ) + .await; if info_flags.json { - write_json_to_stdout(&json!(info)) + write_json_to_stdout(&json!(graph)) } else { - write_to_stdout_ignore_sigpipe(info.to_string().as_bytes()) + write_to_stdout_ignore_sigpipe(graph.to_string().as_bytes()) .map_err(|err| err.into()) } } else { // If it was just "deno info" print location of caches and exit - print_cache_info(&ps, info_flags.json, location) + print_cache_info(&ps, info_flags.json, ps.flags.location.as_ref()) } } @@ -541,23 +532,25 @@ async fn cache_command( cache_flags: CacheFlags, ) -> Result<(), AnyError> { let lib = if flags.unstable { - module_graph::TypeLib::UnstableDenoWindow + emit::TypeLib::UnstableDenoWindow } else { - module_graph::TypeLib::DenoWindow + emit::TypeLib::DenoWindow }; let ps = ProcState::build(flags).await?; for file in cache_flags.files { let specifier = resolve_url_or_path(&file)?; ps.prepare_module_load( - specifier, + vec![specifier], + false, lib.clone(), Permissions::allow_all(), Permissions::allow_all(), - false, - ps.maybe_import_map.clone(), ) .await?; + if let Some(graph_error) = ps.maybe_graph_error.lock().take() { + return Err(graph_error.into()); + } } Ok(()) @@ -567,8 +560,10 @@ async fn eval_command( flags: Flags, eval_flags: EvalFlags, ) -> Result<(), AnyError> { - // Force TypeScript compile. - let main_module = resolve_url_or_path("./$deno$eval.ts").unwrap(); + // deno_graph works off of extensions for local files to determine the media + // type, and so our "fake" specifier needs to have the proper extension. + let main_module = + resolve_url_or_path(&format!("./$deno$eval.{}", eval_flags.ext)).unwrap(); let permissions = Permissions::from_options(&flags.clone().into()); let ps = ProcState::build(flags.clone()).await?; let mut worker = @@ -584,15 +579,7 @@ async fn eval_command( let file = File { local: main_module.clone().to_file_path().unwrap(), maybe_types: None, - media_type: if eval_flags.ext.as_str() == "ts" { - MediaType::TypeScript - } else if eval_flags.ext.as_str() == "tsx" { - MediaType::Tsx - } else if eval_flags.ext.as_str() == "js" { - MediaType::JavaScript - } else { - MediaType::Jsx - }, + media_type: MediaType::Unknown, source: Arc::new(String::from_utf8(source_code)?), specifier: main_module.clone(), maybe_headers: None, @@ -603,9 +590,7 @@ async fn eval_command( ps.file_fetcher.insert_cached(file); debug!("main_module {}", &main_module); if flags.compat { - worker - .execute_side_module(&compat::get_node_globals_url()) - .await?; + worker.execute_side_module(&compat::GLOBAL_URL).await?; } worker.execute_main_module(&main_module).await?; worker.execute_script( @@ -620,75 +605,133 @@ async fn eval_command( Ok(()) } -async fn create_module_graph_and_maybe_check( - module_specifier: ModuleSpecifier, - ps: ProcState, +async fn create_graph_and_maybe_check( + root: ModuleSpecifier, + ps: &ProcState, debug: bool, -) -> Result { - let handler = Arc::new(Mutex::new(FetchHandler::new( - &ps, - // when bundling, dynamic imports are only access for their type safety, - // therefore we will allow the graph to access any module. +) -> Result, AnyError> { + let mut cache = cache::FetchCacher::new( + ps.dir.gen_cache.clone(), + ps.file_fetcher.clone(), Permissions::allow_all(), Permissions::allow_all(), - )?)); - let mut builder = module_graph::GraphBuilder::new( - handler, - ps.maybe_import_map.clone(), - ps.lockfile.clone(), ); - builder.add(&module_specifier, false).await?; - builder.analyze_config_file(&ps.maybe_config_file).await?; - let module_graph = builder.get_graph(); + let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); + let maybe_imports = ps + .maybe_config_file + .as_ref() + .map(|cf| cf.to_maybe_imports()) + .flatten(); + let maybe_resolver = ps.maybe_import_map.as_ref().map(ImportMapResolver::new); + let graph = Arc::new( + deno_graph::create_graph( + vec![root], + false, + maybe_imports, + &mut cache, + maybe_resolver.as_ref().map(|r| r.as_resolver()), + maybe_locker, + None, + ) + .await, + ); + + // Ensure that all non-dynamic, non-type only imports are properly loaded and + // if not, error with the first issue encountered. + graph.valid().map_err(emit::GraphError::from)?; + // If there was a locker, validate the integrity of all the modules in the + // locker. + emit::lock(graph.as_ref()); if !ps.flags.no_check { - // TODO(@kitsonk) support bundling for workers + graph.valid_types_only().map_err(emit::GraphError::from)?; let lib = if ps.flags.unstable { - module_graph::TypeLib::UnstableDenoWindow + emit::TypeLib::UnstableDenoWindow } else { - module_graph::TypeLib::DenoWindow + emit::TypeLib::DenoWindow }; - let result_info = - module_graph.clone().check(module_graph::CheckOptions { - debug, - emit: false, + let (ts_config, maybe_ignored_options) = emit::get_ts_config( + emit::ConfigType::Check { + tsc_emit: false, lib, - maybe_config_file: ps.maybe_config_file.clone(), - reload: ps.flags.reload, - ..Default::default() - })?; - - debug!("{}", result_info.stats); - if let Some(ignored_options) = result_info.maybe_ignored_options { + }, + ps.maybe_config_file.as_ref(), + None, + )?; + log::info!("{} {}", colors::green("Check"), graph.roots[0]); + if let Some(ignored_options) = maybe_ignored_options { eprintln!("{}", ignored_options); } - if !result_info.diagnostics.is_empty() { - return Err(generic_error(result_info.diagnostics.to_string())); + let maybe_config_specifier = ps + .maybe_config_file + .as_ref() + .map(|cf| ModuleSpecifier::from_file_path(&cf.path).unwrap()); + let check_result = emit::check_and_maybe_emit( + graph.clone(), + &mut cache, + emit::CheckOptions { + debug, + emit_with_diagnostics: false, + maybe_config_specifier, + ts_config, + }, + )?; + debug!("{}", check_result.stats); + if !check_result.diagnostics.is_empty() { + return Err(check_result.diagnostics.into()); } } - Ok(module_graph) + Ok(graph) } fn bundle_module_graph( - module_graph: module_graph::Graph, - ps: ProcState, - flags: Flags, - debug: bool, -) -> Result { - let (bundle, stats, maybe_ignored_options) = - module_graph.bundle(module_graph::BundleOptions { - debug, - maybe_config_file: ps.maybe_config_file.clone(), - })?; - match maybe_ignored_options { - Some(ignored_options) if flags.no_check => { + graph: &deno_graph::ModuleGraph, + ps: &ProcState, + flags: &Flags, +) -> Result<(String, Option), AnyError> { + info!("{} {}", colors::green("Bundle"), graph.roots[0]); + + let (ts_config, maybe_ignored_options) = emit::get_ts_config( + emit::ConfigType::Bundle, + ps.maybe_config_file.as_ref(), + None, + )?; + if flags.no_check { + if let Some(ignored_options) = maybe_ignored_options { eprintln!("{}", ignored_options); } - _ => {} } - debug!("{}", stats); - Ok(bundle) + + emit::bundle( + graph, + emit::BundleOptions { + bundle_type: emit::BundleType::Module, + ts_config, + }, + ) +} + +/// A function that converts a float to a string the represents a human +/// readable version of that number. +fn human_size(size: f64) -> String { + let negative = if size.is_sign_positive() { "" } else { "-" }; + let size = size.abs(); + let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + if size < 1_f64 { + return format!("{}{}{}", negative, size, "B"); + } + let delimiter = 1024_f64; + let exponent = std::cmp::min( + (size.ln() / delimiter.ln()).floor() as i32, + (units.len() - 1) as i32, + ); + let pretty_bytes = format!("{:.2}", size / delimiter.powi(exponent)) + .parse::() + .unwrap() + * 1_f64; + let unit = units[exponent as usize]; + format!("{}{}{}", negative, pretty_bytes, unit) } async fn bundle_command( @@ -707,17 +750,18 @@ async fn bundle_command( debug!(">>>>> bundle START"); let ps = ProcState::build(flags.clone()).await?; - let module_graph = create_module_graph_and_maybe_check( - module_specifier, - ps.clone(), - debug, - ) - .await?; + let graph = + create_graph_and_maybe_check(module_specifier, &ps, debug).await?; - let mut paths_to_watch: Vec = module_graph - .get_modules() + let mut paths_to_watch: Vec = graph + .specifiers() .iter() - .filter_map(|specifier| specifier.to_file_path().ok()) + .filter_map(|(_, r)| { + r.as_ref() + .ok() + .map(|(s, _)| s.to_file_path().ok()) + .flatten() + }) .collect(); if let Some(import_map) = ps.flags.import_map_path.as_ref() { @@ -725,12 +769,12 @@ async fn bundle_command( .push(fs_util::resolve_from_cwd(std::path::Path::new(import_map))?); } - Ok((paths_to_watch, module_graph, ps)) + Ok((paths_to_watch, graph, ps)) } .map(move |result| match result { - Ok((paths_to_watch, module_graph, ps)) => ResolutionResult::Restart { + Ok((paths_to_watch, graph, ps)) => ResolutionResult::Restart { paths_to_watch, - result: Ok((ps, module_graph)), + result: Ok((ps, graph)), }, Err(e) => ResolutionResult::Restart { paths_to_watch: vec![PathBuf::from(source_file2)], @@ -739,28 +783,43 @@ async fn bundle_command( }) }; - let operation = |(ps, module_graph): (ProcState, module_graph::Graph)| { + let operation = |(ps, graph): (ProcState, Arc)| { let flags = flags.clone(); let out_file = bundle_flags.out_file.clone(); async move { - info!("{} {}", colors::green("Bundle"), module_graph.info()?.root); - - let output = bundle_module_graph(module_graph, ps, flags, debug)?; - + let (bundle_emit, maybe_bundle_map) = + bundle_module_graph(graph.as_ref(), &ps, &flags)?; debug!(">>>>> bundle END"); if let Some(out_file) = out_file.as_ref() { - let output_bytes = output.as_bytes(); + let output_bytes = bundle_emit.as_bytes(); let output_len = output_bytes.len(); fs_util::write_file(out_file, output_bytes, 0o644)?; info!( "{} {:?} ({})", colors::green("Emit"), out_file, - colors::gray(&info::human_size(output_len as f64)) + colors::gray(human_size(output_len as f64)) ); + if let Some(bundle_map) = maybe_bundle_map { + let map_bytes = bundle_map.as_bytes(); + let map_len = map_bytes.len(); + let ext = if let Some(curr_ext) = out_file.extension() { + format!("{}.map", curr_ext.to_string_lossy()) + } else { + "map".to_string() + }; + let map_out_file = out_file.with_extension(ext); + fs_util::write_file(&map_out_file, map_bytes, 0o644)?; + info!( + "{} {:?} ({})", + colors::green("Emit"), + map_out_file, + colors::gray(human_size(map_len as f64)) + ); + } } else { - println!("{}", output); + println!("{}", bundle_emit); } Ok(()) @@ -825,9 +884,7 @@ async fn run_repl(flags: Flags, repl_flags: ReplFlags) -> Result<(), AnyError> { let mut worker = create_main_worker(&ps, main_module.clone(), permissions, None); if flags.compat { - worker - .execute_side_module(&compat::get_node_globals_url()) - .await?; + worker.execute_side_module(&compat::GLOBAL_URL).await?; } worker.run_event_loop(false).await?; @@ -858,9 +915,7 @@ async fn run_from_stdin(flags: Flags) -> Result<(), AnyError> { debug!("main_module {}", main_module); if flags.compat { - worker - .execute_side_module(&compat::get_node_globals_url()) - .await?; + worker.execute_side_module(&compat::GLOBAL_URL).await?; } worker.execute_main_module(&main_module).await?; worker.execute_script( @@ -883,25 +938,42 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> { async move { let main_module = resolve_url_or_path(&script1)?; let ps = ProcState::build(flags).await?; - let handler = Arc::new(Mutex::new(FetchHandler::new( - &ps, + let mut cache = cache::FetchCacher::new( + ps.dir.gen_cache.clone(), + ps.file_fetcher.clone(), Permissions::allow_all(), Permissions::allow_all(), - )?)); - let mut builder = module_graph::GraphBuilder::new( - handler, - ps.maybe_import_map.clone(), - ps.lockfile.clone(), ); - builder.add(&main_module, false).await?; - builder.analyze_config_file(&ps.maybe_config_file).await?; - let module_graph = builder.get_graph(); + let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); + let maybe_imports = ps + .maybe_config_file + .as_ref() + .map(|cf| cf.to_maybe_imports()) + .flatten(); + let maybe_resolver = + ps.maybe_import_map.as_ref().map(ImportMapResolver::new); + let graph = deno_graph::create_graph( + vec![main_module.clone()], + false, + maybe_imports, + &mut cache, + maybe_resolver.as_ref().map(|r| r.as_resolver()), + maybe_locker, + None, + ) + .await; + graph.valid()?; // Find all local files in graph - let mut paths_to_watch: Vec = module_graph - .get_modules() + let mut paths_to_watch: Vec = graph + .specifiers() .iter() - .filter_map(|specifier| specifier.to_file_path().ok()) + .filter_map(|(_, r)| { + r.as_ref() + .ok() + .map(|(s, _)| s.to_file_path().ok()) + .flatten() + }) .collect(); if let Some(import_map) = ps.flags.import_map_path.as_ref() { @@ -948,10 +1020,7 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> { main_module: &ModuleSpecifier, ) -> Result<(), AnyError> { if self.compat { - self - .worker - .execute_side_module(&compat::get_node_globals_url()) - .await?; + self.worker.execute_side_module(&compat::GLOBAL_URL).await?; } self.worker.execute_main_module(main_module).await?; self.worker.execute_script( @@ -1046,9 +1115,7 @@ async fn run_command( debug!("main_module {}", main_module); if flags.compat { - worker - .execute_side_module(&compat::get_node_globals_url()) - .await?; + worker.execute_side_module(&compat::GLOBAL_URL).await?; } worker.execute_main_module(&main_module).await?; worker.execute_script( diff --git a/cli/module_graph.rs b/cli/module_graph.rs deleted file mode 100644 index 7bea57e62f..0000000000 --- a/cli/module_graph.rs +++ /dev/null @@ -1,2834 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use crate::ast; -use crate::ast::transpile; -use crate::ast::transpile_module; -use crate::ast::BundleHook; -use crate::ast::Location; -use crate::checksum; -use crate::colors; -use crate::config_file::CompilerOptions; -use crate::config_file::ConfigFile; -use crate::config_file::IgnoredCompilerOptions; -use crate::config_file::TsConfig; -use crate::diagnostics::Diagnostics; -use crate::info; -use crate::lockfile::Lockfile; -use crate::specifier_handler::CachedModule; -use crate::specifier_handler::Dependency; -use crate::specifier_handler::DependencyMap; -use crate::specifier_handler::Emit; -use crate::specifier_handler::FetchFuture; -use crate::specifier_handler::SpecifierHandler; -use crate::tsc; -use crate::version; -use deno_ast::swc::common::comments::Comment; -use deno_ast::swc::common::BytePos; -use deno_ast::swc::common::Span; -use deno_ast::MediaType; -use deno_ast::ParsedSource; -use deno_ast::SourceTextInfo; -use deno_core::error::anyhow; -use deno_core::error::custom_error; -use deno_core::error::get_custom_error_class; -use deno_core::error::AnyError; -use deno_core::error::Context; -use deno_core::futures::stream::FuturesUnordered; -use deno_core::futures::stream::StreamExt; -use deno_core::parking_lot::Mutex; -use deno_core::resolve_import; -use deno_core::resolve_url_or_path; -use deno_core::serde::Deserialize; -use deno_core::serde::Deserializer; -use deno_core::serde::Serialize; -use deno_core::serde::Serializer; -use deno_core::serde_json; -use deno_core::serde_json::json; -use deno_core::serde_json::Value; -use deno_core::url::Url; -use deno_core::ModuleResolutionError; -use deno_core::ModuleSource; -use deno_core::ModuleSpecifier; -use deno_graph::analyze_dependencies; -use import_map::ImportMap; -use import_map::ImportMapError; -use log::debug; -use regex::Regex; -use std::collections::HashMap; -use std::collections::HashSet; -use std::error::Error; -use std::fmt; -use std::path::PathBuf; -use std::rc::Rc; -use std::result; -use std::sync::Arc; -use std::time::Instant; - -lazy_static::lazy_static! { - /// Matched the `@deno-types` pragma. - static ref DENO_TYPES_RE: Regex = - Regex::new(r#"(?i)^\s*@deno-types\s*=\s*(?:["']([^"']+)["']|(\S+))"#) - .unwrap(); - /// Matches a `/// ` comment reference. - static ref TRIPLE_SLASH_REFERENCE_RE: Regex = - Regex::new(r"(?i)^/\s*").unwrap(); - /// Matches a path reference, which adds a dependency to a module - static ref PATH_REFERENCE_RE: Regex = - Regex::new(r#"(?i)\spath\s*=\s*["']([^"']*)["']"#).unwrap(); - /// Matches a types reference, which for JavaScript files indicates the - /// location of types to use when type checking a program that includes it as - /// a dependency. - static ref TYPES_REFERENCE_RE: Regex = - Regex::new(r#"(?i)\stypes\s*=\s*["']([^"']*)["']"#).unwrap(); -} - -/// A group of errors that represent errors that can occur when interacting with -/// a module graph. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum GraphError { - /// A module using the HTTPS protocol is trying to import a module with an - /// HTTP schema. - InvalidDowngrade(ModuleSpecifier, Location), - /// A remote module is trying to import a local module. - InvalidLocalImport(ModuleSpecifier, Location), - /// The source code is invalid, as it does not match the expected hash in the - /// lockfile. - InvalidSource(ModuleSpecifier, PathBuf), - /// An unexpected dependency was requested for a module. - MissingDependency(ModuleSpecifier, String), - /// An unexpected specifier was requested. - MissingSpecifier(ModuleSpecifier), - /// The current feature is not supported. - NotSupported(String), - /// A unsupported media type was attempted to be imported as a module. - UnsupportedImportType(ModuleSpecifier, MediaType), -} - -impl fmt::Display for GraphError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - GraphError::InvalidDowngrade(ref specifier, ref location) => write!(f, "Modules imported via https are not allowed to import http modules.\n Importing: {}\n at {}", specifier, location), - GraphError::InvalidLocalImport(ref specifier, ref location) => write!(f, "Remote modules are not allowed to import local modules. Consider using a dynamic import instead.\n Importing: {}\n at {}", specifier, location), - GraphError::InvalidSource(ref specifier, ref lockfile) => write!(f, "The source code is invalid, as it does not match the expected hash in the lock file.\n Specifier: {}\n Lock file: {}", specifier, lockfile.to_str().unwrap()), - GraphError::MissingDependency(ref referrer, specifier) => write!( - f, - "The graph is missing a dependency.\n Specifier: {} from {}", - specifier, referrer - ), - GraphError::MissingSpecifier(ref specifier) => write!( - f, - "The graph is missing a specifier.\n Specifier: {}", - specifier - ), - GraphError::NotSupported(ref msg) => write!(f, "{}", msg), - GraphError::UnsupportedImportType(ref specifier, ref media_type) => write!(f, "An unsupported media type was attempted to be imported as a module.\n Specifier: {}\n MediaType: {}", specifier, media_type), - } - } -} - -impl Error for GraphError {} - -/// A structure for handling bundle loading, which is implemented here, to -/// avoid a circular dependency with `ast`. -struct BundleLoader<'a> { - cm: Rc, - emit_options: &'a ast::EmitOptions, - globals: &'a deno_ast::swc::common::Globals, - graph: &'a Graph, -} - -impl<'a> BundleLoader<'a> { - pub fn new( - graph: &'a Graph, - emit_options: &'a ast::EmitOptions, - globals: &'a deno_ast::swc::common::Globals, - cm: Rc, - ) -> Self { - BundleLoader { - cm, - emit_options, - globals, - graph, - } - } -} - -impl deno_ast::swc::bundler::Load for BundleLoader<'_> { - fn load( - &self, - file: &deno_ast::swc::common::FileName, - ) -> Result { - match file { - deno_ast::swc::common::FileName::Url(specifier) => { - if let Some(src) = self.graph.get_source(specifier) { - let media_type = self - .graph - .get_media_type(specifier) - .context("Looking up media type during bundling.")?; - let (source_file, module) = transpile_module( - specifier, - &src, - media_type, - self.emit_options, - self.globals, - self.cm.clone(), - )?; - Ok(deno_ast::swc::bundler::ModuleData { - fm: source_file, - module, - helpers: Default::default(), - }) - } else { - Err( - GraphError::MissingDependency( - specifier.clone(), - "".to_string(), - ) - .into(), - ) - } - } - _ => unreachable!("Received request for unsupported filename {:?}", file), - } - } -} - -/// An enum which represents the parsed out values of references in source code. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum TypeScriptReference { - Path(String), - Types(String), -} - -fn match_to_span(comment: &Comment, m: ®ex::Match) -> Span { - Span { - lo: comment.span.lo + BytePos((m.start() + 1) as u32), - hi: comment.span.lo + BytePos((m.end() + 1) as u32), - ctxt: comment.span.ctxt, - } -} - -/// Determine if a comment contains a triple slash reference and optionally -/// return its kind and value. -pub fn parse_ts_reference( - comment: &Comment, -) -> Option<(TypeScriptReference, Span)> { - if !TRIPLE_SLASH_REFERENCE_RE.is_match(&comment.text) { - None - } else if let Some(captures) = PATH_REFERENCE_RE.captures(&comment.text) { - let m = captures.get(1).unwrap(); - Some(( - TypeScriptReference::Path(m.as_str().to_string()), - match_to_span(comment, &m), - )) - } else { - TYPES_REFERENCE_RE.captures(&comment.text).map(|captures| { - let m = captures.get(1).unwrap(); - ( - TypeScriptReference::Types(m.as_str().to_string()), - match_to_span(comment, &m), - ) - }) - } -} - -/// Determine if a comment contains a `@deno-types` pragma and optionally return -/// its value. -pub fn parse_deno_types(comment: &Comment) -> Option<(String, Span)> { - let captures = DENO_TYPES_RE.captures(&comment.text)?; - if let Some(m) = captures.get(1) { - Some((m.as_str().to_string(), match_to_span(comment, &m))) - } else if let Some(m) = captures.get(2) { - Some((m.as_str().to_string(), match_to_span(comment, &m))) - } else { - unreachable!(); - } -} - -/// A hashing function that takes the source code, version and optionally a -/// user provided config and generates a string hash which can be stored to -/// determine if the cached emit is valid or not. -fn get_version(source: &str, version: &str, config: &[u8]) -> String { - crate::checksum::gen(&[source.as_bytes(), version.as_bytes(), config]) -} - -/// A logical representation of a module within a graph. -#[derive(Debug, Clone)] -pub struct Module { - pub dependencies: DependencyMap, - is_dirty: bool, - is_parsed: bool, - maybe_emit: Option, - maybe_emit_path: Option<(PathBuf, Option)>, - maybe_import_map: Option>>, - maybe_types: Option<(String, ModuleSpecifier)>, - maybe_version: Option, - media_type: MediaType, - specifier: ModuleSpecifier, - text_info: SourceTextInfo, - source_path: PathBuf, -} - -impl Default for Module { - fn default() -> Self { - Module { - dependencies: HashMap::new(), - is_dirty: false, - is_parsed: false, - maybe_emit: None, - maybe_emit_path: None, - maybe_import_map: None, - maybe_types: None, - maybe_version: None, - media_type: MediaType::Unknown, - specifier: deno_core::resolve_url("file:///example.js").unwrap(), - text_info: SourceTextInfo::from_string("".to_string()), - source_path: PathBuf::new(), - } - } -} - -impl Module { - pub fn new( - cached_module: CachedModule, - is_root: bool, - maybe_import_map: Option>>, - ) -> Self { - // If this is a local root file, and its media type is unknown, set the - // media type to JavaScript. This allows easier ability to create "shell" - // scripts with Deno. - let media_type = if is_root - && !cached_module.is_remote - && cached_module.media_type == MediaType::Unknown - { - MediaType::JavaScript - } else { - cached_module.media_type - }; - let mut module = Module { - specifier: cached_module.specifier, - maybe_import_map, - media_type, - text_info: SourceTextInfo::new(cached_module.source), - source_path: cached_module.source_path, - maybe_emit: cached_module.maybe_emit, - maybe_emit_path: cached_module.maybe_emit_path, - maybe_version: cached_module.maybe_version, - is_dirty: false, - ..Self::default() - }; - if module.maybe_import_map.is_none() { - if let Some(dependencies) = cached_module.maybe_dependencies { - module.dependencies = dependencies; - module.is_parsed = true; - } - } - module.maybe_types = cached_module.maybe_types.map(|specifier| { - ( - specifier.clone(), - module - .resolve_import(&specifier, None) - .expect("could not resolve module"), - ) - }); - module - } - - /// Return `true` if the current hash of the module matches the stored - /// version. - pub fn is_emit_valid(&self, config: &[u8]) -> bool { - if let Some(version) = self.maybe_version.clone() { - version - == get_version(self.text_info.text_str(), &version::deno(), config) - } else { - false - } - } - - /// Parse a module, populating the structure with data retrieved from the - /// source of the module. - pub fn parse(&mut self) -> Result { - let parsed_module = deno_ast::parse_module(deno_ast::ParseParams { - specifier: self.specifier.as_str().to_string(), - source: self.text_info.clone(), - media_type: self.media_type, - capture_tokens: false, - maybe_syntax: None, - })?; - - // parse out any triple slash references - for comment in parsed_module.get_leading_comments().iter() { - if let Some((ts_reference, _)) = parse_ts_reference(comment) { - let location = Location::from_pos(&parsed_module, comment.span.lo); - match ts_reference { - TypeScriptReference::Path(import) => { - let specifier = - self.resolve_import(&import, Some(location.clone()))?; - let dep = self - .dependencies - .entry(import) - .or_insert_with(|| Dependency::new(location)); - dep.maybe_code = Some(specifier); - } - TypeScriptReference::Types(import) => { - let specifier = - self.resolve_import(&import, Some(location.clone()))?; - if self.media_type == MediaType::JavaScript - || self.media_type == MediaType::Jsx - { - // TODO(kitsonk) we need to specifically update the cache when - // this value changes - self.maybe_types = Some((import.clone(), specifier)); - } else { - let dep = self - .dependencies - .entry(import) - .or_insert_with(|| Dependency::new(location)); - dep.maybe_type = Some(specifier); - } - } - } - } - } - - // Parse out all the syntactical dependencies for a module - let dependencies = analyze_dependencies(&parsed_module); - for desc in dependencies.iter().filter(|desc| { - desc.kind != deno_ast::swc::dep_graph::DependencyKind::Require - }) { - let location = Location::from_pos(&parsed_module, desc.span.lo); - - // In situations where there is a potential issue with resolving the - // import specifier, that ends up being a module resolution error for a - // code dependency, we should not throw in the `ModuleGraph` but instead - // wait until runtime and throw there, as with dynamic imports they need - // to be catchable, which means they need to be resolved at runtime. - let maybe_specifier = - match self.resolve_import(&desc.specifier, Some(location.clone())) { - Ok(specifier) => Some(specifier), - Err(any_error) => { - match any_error.downcast_ref::() { - Some(ModuleResolutionError::ImportPrefixMissing(..)) => { - Some(Url::parse(&format!("bare:{}", &desc.specifier)).unwrap()) - } - _ => match any_error.downcast_ref::() { - Some(ImportMapError::UnmappedBareSpecifier(..)) => Some( - Url::parse(&format!("bare:{}", &desc.specifier)).unwrap(), - ), - _ => { - return Err(any_error); - } - }, - } - } - }; - - // Parse out any `@deno-types` pragmas and modify dependency - let maybe_type = if !desc.leading_comments.is_empty() { - let comment = desc.leading_comments.last().unwrap(); - if let Some((deno_types, _)) = parse_deno_types(comment).as_ref() { - Some(self.resolve_import(deno_types, Some(location.clone()))?) - } else { - None - } - } else { - None - }; - - let dep = self - .dependencies - .entry(desc.specifier.to_string()) - .or_insert_with(|| Dependency::new(location)); - dep.is_dynamic = desc.is_dynamic; - dep.maybe_code = maybe_specifier; - // If there is a `@deno-types` pragma, we will add it to the dependency - // if one doesn't already exist. - if maybe_type.is_some() && dep.maybe_type.is_none() { - dep.maybe_type = maybe_type; - } - } - Ok(parsed_module) - } - - fn resolve_import( - &self, - specifier: &str, - maybe_location: Option, - ) -> Result { - let maybe_resolve = if let Some(import_map) = self.maybe_import_map.clone() - { - let import_map = import_map.lock(); - Some(import_map.resolve(specifier, self.specifier.as_str())?) - } else { - None - }; - let mut remapped_import = false; - let specifier = if let Some(module_specifier) = maybe_resolve { - remapped_import = true; - module_specifier - } else { - deno_core::resolve_import(specifier, self.specifier.as_str())? - }; - - let referrer_scheme = self.specifier.scheme(); - let specifier_scheme = specifier.scheme(); - let location = maybe_location.unwrap_or(Location { - specifier: self.specifier.to_string(), - line: 0, - col: 0, - }); - - // Disallow downgrades from HTTPS to HTTP - if referrer_scheme == "https" && specifier_scheme == "http" { - return Err( - GraphError::InvalidDowngrade(specifier.clone(), location).into(), - ); - } - - // Disallow a remote URL from trying to import a local URL, unless it is a - // remapped import via the import map - if (referrer_scheme == "https" || referrer_scheme == "http") - && !(specifier_scheme == "https" || specifier_scheme == "http") - && !remapped_import - { - return Err( - GraphError::InvalidLocalImport(specifier.clone(), location).into(), - ); - } - - Ok(specifier) - } - - pub fn set_emit(&mut self, code: String, maybe_map: Option) { - self.maybe_emit = Some(Emit::Cli((code, maybe_map))); - } - - /// Calculate the hashed version of the module and update the `maybe_version`. - pub fn set_version(&mut self, config: &[u8]) { - self.maybe_version = Some(get_version( - self.text_info.text_str(), - &version::deno(), - config, - )) - } - - pub fn size(&self) -> usize { - self.text_info.text_str().len() - } -} - -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Stats(pub Vec<(String, u32)>); - -impl<'de> Deserialize<'de> for Stats { - fn deserialize(deserializer: D) -> result::Result - where - D: Deserializer<'de>, - { - let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?; - Ok(Stats(items)) - } -} - -impl Serialize for Stats { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - Serialize::serialize(&self.0, serializer) - } -} - -impl fmt::Display for Stats { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "Compilation statistics:")?; - for (key, value) in self.0.clone() { - writeln!(f, " {}: {}", key, value)?; - } - - Ok(()) - } -} - -/// A structure that provides information about a module graph result. -#[derive(Debug, Default)] -pub struct ResultInfo { - /// A structure which provides diagnostic information (usually from `tsc`) - /// about the code in the module graph. - pub diagnostics: Diagnostics, - /// A map of specifiers to the result of their resolution in the module graph. - pub loadable_modules: - HashMap>, - /// Optionally ignored compiler options that represent any options that were - /// ignored if there was a user provided configuration. - pub maybe_ignored_options: Option, - /// A structure providing key metrics around the operation performed, in - /// milliseconds. - pub stats: Stats, -} - -/// Represents the "default" type library that should be used when type -/// checking the code in the module graph. Note that a user provided config -/// of `"lib"` would override this value. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum TypeLib { - DenoWindow, - DenoWorker, - UnstableDenoWindow, - UnstableDenoWorker, -} - -impl Default for TypeLib { - fn default() -> Self { - TypeLib::DenoWindow - } -} - -impl Serialize for TypeLib { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let value = match self { - TypeLib::DenoWindow => vec!["deno.window".to_string()], - TypeLib::DenoWorker => vec!["deno.worker".to_string()], - TypeLib::UnstableDenoWindow => { - vec!["deno.window".to_string(), "deno.unstable".to_string()] - } - TypeLib::UnstableDenoWorker => { - vec!["deno.worker".to_string(), "deno.unstable".to_string()] - } - }; - Serialize::serialize(&value, serializer) - } -} - -#[derive(Debug, Default)] -pub struct BundleOptions { - /// If `true` then debug logging will be output from the isolate. - pub debug: bool, - /// An optional config file with user supplied TypeScript configuration - /// that augments the the default configuration passed to the TypeScript - /// compiler. - pub maybe_config_file: Option, -} - -#[derive(Debug, Default)] -pub struct CheckOptions { - /// If `true` then debug logging will be output from the isolate. - pub debug: bool, - /// Utilise the emit from `tsc` to update the emitted code for modules. - pub emit: bool, - /// The base type libraries that should be used when type checking. - pub lib: TypeLib, - /// An optional config file with user supplied TypeScript configuration - /// that augments the the default configuration passed to the TypeScript - /// compiler. - pub maybe_config_file: Option, - /// Ignore any previously emits and ensure that all files are emitted from - /// source. - pub reload: bool, - /// A set of module specifiers to be excluded from the effect of - /// `CheckOptions::reload` if it is `true`. Perhaps because they have already - /// reloaded once in this process. - pub reload_exclusions: HashSet, -} - -#[derive(Debug, Eq, PartialEq)] -pub enum BundleType { - /// Return the emitted contents of the program as a single "flattened" ES - /// module. - Module, - /// Return the emitted contents of the program as a single script that - /// executes the program using an immediately invoked function execution - /// (IIFE). - Classic, - /// Do not bundle the emit, instead returning each of the modules that are - /// part of the program as individual files. - None, -} - -impl Default for BundleType { - fn default() -> Self { - BundleType::None - } -} - -#[derive(Debug, Default)] -pub struct EmitOptions { - /// If true, then code will be type checked, otherwise type checking will be - /// skipped. If false, then swc will be used for the emit, otherwise tsc will - /// be used. - pub check: bool, - /// Indicate the form the result of the emit should take. - pub bundle_type: BundleType, - /// If `true` then debug logging will be output from the isolate. - pub debug: bool, - /// An optional map that contains user supplied TypeScript compiler - /// configuration options that are passed to the TypeScript compiler. - pub maybe_user_config: Option>, -} - -/// A structure which provides options when transpiling modules. -#[derive(Debug, Default)] -pub struct TranspileOptions { - /// If `true` then debug logging will be output from the isolate. - pub debug: bool, - /// An optional config file with user supplied TypeScript configuration - /// that augments the the default configuration passed to the TypeScript - /// compiler. - pub maybe_config_file: Option, - /// Ignore any previously emits and ensure that all files are emitted from - /// source. - pub reload: bool, - /// A set of module specifiers to be excluded from the effect of - /// `CheckOptions::reload` if it is `true`. Perhaps because they have already - /// reloaded once in this process. - pub reload_exclusions: HashSet, -} - -#[derive(Debug, Clone)] -enum ModuleSlot { - /// The module fetch resulted in a non-recoverable error. - Err(Arc), - /// The the fetch resulted in a module. - Module(Box), - /// Used to denote a module that isn't part of the graph. - None, - /// The fetch of the module is pending. - Pending, -} - -/// A dependency graph of modules, were the modules that have been inserted via -/// the builder will be loaded into the graph. Also provides an interface to -/// be able to manipulate and handle the graph. -#[derive(Debug, Clone)] -pub struct Graph { - /// A reference to the specifier handler that will retrieve and cache modules - /// for the graph. - handler: Arc>, - /// Optional TypeScript build info that will be passed to `tsc` if `tsc` is - /// invoked. - maybe_tsbuildinfo: Option, - /// The modules that are part of the graph. - modules: HashMap, - /// A map of redirects, where a module specifier is redirected to another - /// module specifier by the handler. All modules references should be - /// resolved internally via this, before attempting to access the module via - /// the handler, to make sure the correct modules is being dealt with. - redirects: HashMap, - /// The module specifiers that have been uniquely added to the graph, which - /// does not include any transient dependencies. - roots: Vec, - /// If all of the root modules are dynamically imported, then this is true. - /// This is used to ensure correct `--reload` behavior, where subsequent - /// calls to a module graph where the emit is already valid do not cause the - /// graph to re-emit. - roots_dynamic: bool, - // A reference to lock file that will be used to check module integrity. - maybe_lockfile: Option>>, -} - -/// Convert a specifier and a module slot in a result to the module source which -/// is needed by Deno core for loading the module. -fn to_module_result( - (specifier, module_slot): (&ModuleSpecifier, &ModuleSlot), -) -> (ModuleSpecifier, Result) { - match module_slot { - ModuleSlot::Err(err) => (specifier.clone(), Err(anyhow!(err.to_string()))), - ModuleSlot::Module(module) => ( - specifier.clone(), - if let Some(emit) = &module.maybe_emit { - match emit { - Emit::Cli((code, _)) => Ok(ModuleSource { - code: code.clone(), - module_url_found: module.specifier.to_string(), - module_url_specified: specifier.to_string(), - }), - } - } else { - match module.media_type { - MediaType::JavaScript | MediaType::Unknown => Ok(ModuleSource { - code: module.text_info.text_str().to_string(), - module_url_found: module.specifier.to_string(), - module_url_specified: specifier.to_string(), - }), - _ => Err(custom_error( - "NotFound", - format!("Compiled module not found \"{}\"", specifier), - )), - } - }, - ), - _ => ( - specifier.clone(), - Err(anyhow!("Module \"{}\" unavailable.", specifier)), - ), - } -} - -impl Graph { - /// Create a new instance of a graph, ready to have modules loaded it. - /// - /// The argument `handler` is an instance of a structure that implements the - /// `SpecifierHandler` trait. - /// - pub fn new( - handler: Arc>, - maybe_lockfile: Option>>, - ) -> Self { - Graph { - handler, - maybe_tsbuildinfo: None, - modules: HashMap::new(), - redirects: HashMap::new(), - roots: Vec::new(), - roots_dynamic: true, - maybe_lockfile, - } - } - - /// Transform the module graph into a single JavaScript module which is - /// returned as a `String` in the result. - pub fn bundle( - &self, - options: BundleOptions, - ) -> Result<(String, Stats, Option), AnyError> { - if self.roots.is_empty() || self.roots.len() > 1 { - return Err(GraphError::NotSupported(format!("Bundling is only supported when there is a single root module in the graph. Found: {}", self.roots.len())).into()); - } - - let start = Instant::now(); - let root_specifier = self.roots[0].clone(); - let mut ts_config = TsConfig::new(json!({ - "checkJs": false, - "emitDecoratorMetadata": false, - "importsNotUsedAsValues": "remove", - "inlineSourceMap": false, - "inlineSources": false, - "sourceMap": false, - "jsx": "react", - "jsxFactory": "React.createElement", - "jsxFragmentFactory": "React.Fragment", - })); - let maybe_ignored_options = ts_config - .merge_tsconfig_from_config_file(options.maybe_config_file.as_ref())?; - - let (src, _) = self.emit_bundle( - &root_specifier, - &ts_config.into(), - &BundleType::Module, - )?; - let stats = Stats(vec![ - ("Files".to_string(), self.modules.len() as u32), - ("Total time".to_string(), start.elapsed().as_millis() as u32), - ]); - - Ok((src, stats, maybe_ignored_options)) - } - - /// Type check the module graph, corresponding to the options provided. - pub fn check(self, options: CheckOptions) -> Result { - self.validate()?; - let mut config = TsConfig::new(json!({ - "allowJs": true, - // TODO(@kitsonk) is this really needed? - "esModuleInterop": true, - // Enabled by default to align to transpile/swc defaults - "experimentalDecorators": true, - "incremental": true, - "jsx": "react", - "isolatedModules": true, - "lib": options.lib, - "module": "esnext", - "strict": true, - "target": "esnext", - "tsBuildInfoFile": "deno:///.tsbuildinfo", - "useDefineForClassFields": true, - // TODO(@kitsonk) remove for Deno 1.15 - "useUnknownInCatchVariables": false, - })); - if options.emit { - config.merge(&json!({ - // TODO(@kitsonk) consider enabling this by default - // see: https://github.com/denoland/deno/issues/7732 - "emitDecoratorMetadata": false, - "importsNotUsedAsValues": "remove", - "inlineSourceMap": true, - "inlineSources": true, - "outDir": "deno://", - "removeComments": true, - })); - } else { - config.merge(&json!({ - "noEmit": true, - })); - } - let maybe_ignored_options = config - .merge_tsconfig_from_config_file(options.maybe_config_file.as_ref())?; - - let needs_reload = options.reload - && !self - .roots - .iter() - .all(|u| options.reload_exclusions.contains(u)); - // Short circuit if none of the modules require an emit, or all of the - // modules that require an emit have a valid emit. - if !self.needs_emit(&config) || self.is_emit_valid(&config) && !needs_reload - { - debug!("graph does not need to be checked or emitted."); - return Ok(ResultInfo { - maybe_ignored_options, - loadable_modules: self.get_loadable_modules(), - ..Default::default() - }); - } - - // TODO(@kitsonk) not totally happy with this here, but this is the first - // point where we know we are actually going to check the program. If we - // moved it out of here, we wouldn't know until after the check has already - // happened, which isn't informative to the users. - for specifier in &self.roots { - log::info!("{} {}", colors::green("Check"), specifier); - } - - let root_names = self.get_root_names(!config.get_check_js())?; - let maybe_tsbuildinfo = self.maybe_tsbuildinfo.clone(); - let hash_data = - vec![config.as_bytes(), version::deno().as_bytes().to_owned()]; - let graph = Arc::new(Mutex::new(self)); - - let maybe_config_specifier = - if let Some(config_file) = &options.maybe_config_file { - ModuleSpecifier::from_file_path(&config_file.path).ok() - } else { - None - }; - debug!("maybe_config_specifier: {:?}", maybe_config_specifier); - - let response = tsc::exec(tsc::Request { - config: config.clone(), - debug: options.debug, - graph: graph.clone(), - hash_data, - maybe_config_specifier, - maybe_tsbuildinfo, - root_names, - })?; - - let mut graph = graph.lock(); - graph.maybe_tsbuildinfo = response.maybe_tsbuildinfo; - // Only process changes to the graph if there are no diagnostics and there - // were files emitted. - if response.diagnostics.is_empty() { - if !response.emitted_files.is_empty() { - let mut codes = HashMap::new(); - let mut maps = HashMap::new(); - let check_js = config.get_check_js(); - for emit in &response.emitted_files { - if let Some(specifiers) = &emit.maybe_specifiers { - assert!(specifiers.len() == 1, "Unexpected specifier length"); - // The specifier emitted might not be the redirected specifier, and - // therefore we need to ensure it is the correct one. - let specifier = graph.resolve_specifier(&specifiers[0]); - // Sometimes if tsc sees a CommonJS file it will _helpfully_ output it - // to ESM, which we don't really want unless someone has enabled the - // check_js option. - if !check_js - && graph.get_media_type(specifier) == Some(MediaType::JavaScript) - { - debug!("skipping emit for {}", specifier); - continue; - } - match emit.media_type { - MediaType::JavaScript => { - codes.insert(specifier.clone(), emit.data.clone()); - } - MediaType::SourceMap => { - maps.insert(specifier.clone(), emit.data.clone()); - } - _ => unreachable!(), - } - } - } - let config = config.as_bytes(); - for (specifier, code) in codes.iter() { - if let ModuleSlot::Module(module) = - graph.get_module_mut(specifier).unwrap() - { - module.set_emit(code.clone(), maps.get(specifier).cloned()); - module.set_version(&config); - module.is_dirty = true; - } else { - return Err(GraphError::MissingSpecifier(specifier.clone()).into()); - } - } - } - graph.flush()?; - } - - Ok(ResultInfo { - diagnostics: response.diagnostics, - loadable_modules: graph.get_loadable_modules(), - maybe_ignored_options, - stats: response.stats, - }) - } - - /// Indicates if the module graph contains the supplied specifier or not. - pub fn contains(&self, specifier: &ModuleSpecifier) -> bool { - matches!(self.get_module(specifier), ModuleSlot::Module(_)) - } - - /// Emit the module graph in a specific format. This is specifically designed - /// to be an "all-in-one" API for access by the runtime, allowing both - /// emitting single modules as well as bundles, using Deno module resolution - /// or supplied sources. - pub fn emit( - mut self, - options: EmitOptions, - ) -> Result<(HashMap, ResultInfo), AnyError> { - let mut config = TsConfig::new(json!({ - "allowJs": true, - "checkJs": false, - // TODO(@kitsonk) consider enabling this by default - // see: https://github.com/denoland/deno/issues/7732 - "emitDecoratorMetadata": false, - "esModuleInterop": true, - "experimentalDecorators": true, - "importsNotUsedAsValues": "remove", - "inlineSourceMap": false, - "inlineSources": false, - "sourceMap": false, - "isolatedModules": true, - "jsx": "react", - "jsxFactory": "React.createElement", - "jsxFragmentFactory": "React.Fragment", - "lib": TypeLib::DenoWindow, - "module": "esnext", - "strict": true, - "target": "esnext", - "useDefineForClassFields": true, - // TODO(@kitsonk) remove for Deno 1.15 - "useUnknownInCatchVariables": false, - })); - let opts = match options.bundle_type { - BundleType::Module | BundleType::Classic => json!({ - "noEmit": true, - "removeComments": true, - "sourceMap": true, - }), - BundleType::None => json!({ - "outDir": "deno://", - "removeComments": true, - "sourceMap": true, - }), - }; - config.merge(&opts); - let maybe_ignored_options = - if let Some(user_options) = &options.maybe_user_config { - config.merge_user_config(user_options)? - } else { - None - }; - - if !options.check && config.get_declaration() { - return Err(anyhow!("The option of `check` is false, but the compiler option of `declaration` is true which is not currently supported.")); - } - if options.bundle_type != BundleType::None && config.get_declaration() { - return Err(anyhow!("The bundle option is set, but the compiler option of `declaration` is true which is not currently supported.")); - } - - let mut emitted_files = HashMap::new(); - if options.check { - let root_names = self.get_root_names(!config.get_check_js())?; - let hash_data = - vec![config.as_bytes(), version::deno().as_bytes().to_owned()]; - let graph = Arc::new(Mutex::new(self)); - let response = tsc::exec(tsc::Request { - config: config.clone(), - debug: options.debug, - graph: graph.clone(), - hash_data, - maybe_config_specifier: None, - maybe_tsbuildinfo: None, - root_names, - })?; - - let graph = graph.lock(); - match options.bundle_type { - BundleType::Module | BundleType::Classic => { - assert!( - response.emitted_files.is_empty(), - "No files should have been emitted from tsc." - ); - assert_eq!( - graph.roots.len(), - 1, - "Only a single root module supported." - ); - let specifier = &graph.roots[0]; - let (src, maybe_src_map) = graph.emit_bundle( - specifier, - &config.into(), - &options.bundle_type, - )?; - emitted_files.insert("deno:///bundle.js".to_string(), src); - if let Some(src_map) = maybe_src_map { - emitted_files.insert("deno:///bundle.js.map".to_string(), src_map); - } - } - BundleType::None => { - for emitted_file in &response.emitted_files { - assert!( - emitted_file.maybe_specifiers.is_some(), - "Orphaned file emitted." - ); - let specifiers = emitted_file.maybe_specifiers.clone().unwrap(); - assert_eq!( - specifiers.len(), - 1, - "An unexpected number of specifiers associated with emitted file." - ); - let specifier = specifiers[0].clone(); - let extension = match emitted_file.media_type { - MediaType::JavaScript => ".js", - MediaType::SourceMap => ".js.map", - MediaType::Dts => ".d.ts", - _ => unreachable!(), - }; - let key = format!("{}{}", specifier, extension); - emitted_files.insert(key, emitted_file.data.clone()); - } - } - }; - - Ok(( - emitted_files, - ResultInfo { - diagnostics: response.diagnostics, - loadable_modules: graph.get_loadable_modules(), - maybe_ignored_options, - stats: response.stats, - }, - )) - } else { - let start = Instant::now(); - let mut emit_count = 0_u32; - match options.bundle_type { - BundleType::Module | BundleType::Classic => { - assert_eq!( - self.roots.len(), - 1, - "Only a single root module supported." - ); - let specifier = &self.roots[0]; - let (src, maybe_src_map) = self.emit_bundle( - specifier, - &config.into(), - &options.bundle_type, - )?; - emit_count += 1; - emitted_files.insert("deno:///bundle.js".to_string(), src); - if let Some(src_map) = maybe_src_map { - emitted_files.insert("deno:///bundle.js.map".to_string(), src_map); - } - } - BundleType::None => { - let check_js = config.get_check_js(); - let emit_options: ast::EmitOptions = config.into(); - for (_, module_slot) in self.modules.iter_mut() { - if let ModuleSlot::Module(module) = module_slot { - if !(check_js - || module.media_type == MediaType::Jsx - || module.media_type == MediaType::Tsx - || module.media_type == MediaType::TypeScript) - { - emitted_files.insert( - module.specifier.to_string(), - module.text_info.text_str().to_string(), - ); - } - let parsed_module = module.parse()?; - let (code, maybe_map) = transpile(&parsed_module, &emit_options)?; - emit_count += 1; - emitted_files.insert(format!("{}.js", module.specifier), code); - if let Some(map) = maybe_map { - emitted_files - .insert(format!("{}.js.map", module.specifier), map); - } - } - } - self.flush()?; - } - } - - let stats = Stats(vec![ - ("Files".to_string(), self.modules.len() as u32), - ("Emitted".to_string(), emit_count), - ("Total time".to_string(), start.elapsed().as_millis() as u32), - ]); - - Ok(( - emitted_files, - ResultInfo { - diagnostics: Default::default(), - loadable_modules: self.get_loadable_modules(), - maybe_ignored_options, - stats, - }, - )) - } - } - - /// Shared between `bundle()` and `emit()`. - fn emit_bundle( - &self, - specifier: &ModuleSpecifier, - emit_options: &ast::EmitOptions, - bundle_type: &BundleType, - ) -> Result<(String, Option), AnyError> { - let cm = Rc::new(deno_ast::swc::common::SourceMap::new( - deno_ast::swc::common::FilePathMapping::empty(), - )); - let globals = deno_ast::swc::common::Globals::new(); - let loader = BundleLoader::new(self, emit_options, &globals, cm.clone()); - let hook = Box::new(BundleHook); - let module = match bundle_type { - BundleType::Module => deno_ast::swc::bundler::ModuleType::Es, - BundleType::Classic => deno_ast::swc::bundler::ModuleType::Iife, - _ => unreachable!("invalid bundle type"), - }; - let bundler = deno_ast::swc::bundler::Bundler::new( - &globals, - cm.clone(), - loader, - self, - deno_ast::swc::bundler::Config { - module, - ..Default::default() - }, - hook, - ); - let mut entries = HashMap::new(); - entries.insert( - "bundle".to_string(), - deno_ast::swc::common::FileName::Url(specifier.clone()), - ); - let output = bundler - .bundle(entries) - .context("Unable to output bundle during Graph::bundle().")?; - let mut buf = Vec::new(); - let mut src_map_buf = Vec::new(); - { - let mut emitter = deno_ast::swc::codegen::Emitter { - cfg: deno_ast::swc::codegen::Config { minify: false }, - cm: cm.clone(), - comments: None, - wr: Box::new(deno_ast::swc::codegen::text_writer::JsWriter::new( - cm.clone(), - "\n", - &mut buf, - Some(&mut src_map_buf), - )), - }; - - emitter - .emit_module(&output[0].module) - .context("Unable to emit bundle during Graph::bundle().")?; - } - let mut src = String::from_utf8(buf) - .context("Emitted bundle is an invalid utf-8 string.")?; - let mut map: Option = None; - { - let mut buf = Vec::new(); - cm.build_source_map_from(&mut src_map_buf, None) - .to_writer(&mut buf)?; - - if emit_options.inline_source_map { - src.push_str("//# sourceMappingURL=data:application/json;base64,"); - let encoded_map = base64::encode(buf); - src.push_str(&encoded_map); - } else if emit_options.source_map { - map = Some(String::from_utf8(buf)?); - } - } - - Ok((src, map)) - } - - /// Update the handler with any modules that are marked as _dirty_ and update - /// any build info if present. - fn flush(&mut self) -> Result<(), AnyError> { - let mut handler = self.handler.lock(); - for (_, module_slot) in self.modules.iter_mut() { - if let ModuleSlot::Module(module) = module_slot { - if module.is_dirty { - if let Some(emit) = &module.maybe_emit { - handler.set_cache(&module.specifier, emit)?; - } - if let Some(version) = &module.maybe_version { - handler.set_version(&module.specifier, version.clone())?; - } - module.is_dirty = false; - } - } - } - for root_specifier in self.roots.iter() { - if let Some(tsbuildinfo) = &self.maybe_tsbuildinfo { - handler.set_tsbuildinfo(root_specifier, tsbuildinfo.to_owned())?; - } - } - - Ok(()) - } - - /// Retrieve the first module loading error from the graph and return it. - pub fn get_errors(&self) -> HashMap { - self - .modules - .iter() - .filter_map(|(s, sl)| match sl { - ModuleSlot::Err(err) => Some((s.clone(), err.to_string())), - _ => None, - }) - .collect() - } - - /// Retrieve a map that contains a representation of each module in the graph - /// which can be used to provide code to a module loader without holding all - /// the state to be able to operate on the graph. - pub fn get_loadable_modules( - &self, - ) -> HashMap> { - let mut loadable_modules: HashMap< - ModuleSpecifier, - Result, - > = self.modules.iter().map(to_module_result).collect(); - for (specifier, _) in self.redirects.iter() { - if let Some(module_slot) = - self.modules.get(self.resolve_specifier(specifier)) - { - let (_, result) = to_module_result((specifier, module_slot)); - loadable_modules.insert(specifier.clone(), result); - } - } - loadable_modules - } - - pub fn get_media_type( - &self, - specifier: &ModuleSpecifier, - ) -> Option { - if let ModuleSlot::Module(module) = self.get_module(specifier) { - Some(module.media_type) - } else { - None - } - } - - fn get_module(&self, specifier: &ModuleSpecifier) -> &ModuleSlot { - let s = self.resolve_specifier(specifier); - if let Some(module_slot) = self.modules.get(s) { - module_slot - } else { - &ModuleSlot::None - } - } - - fn get_module_mut( - &mut self, - specifier: &ModuleSpecifier, - ) -> Option<&mut ModuleSlot> { - // this is duplicated code because `.resolve_specifier` requires an - // immutable borrow, but if `.resolve_specifier` is mut, then everything - // that calls it is is mut - let mut s = specifier; - while let Some(redirect) = self.redirects.get(s) { - s = redirect; - } - self.modules.get_mut(s) - } - - pub fn get_specifier( - &self, - specifier: &ModuleSpecifier, - ) -> Result<&Module, AnyError> { - let s = self.resolve_specifier(specifier); - match self.get_module(s) { - ModuleSlot::Module(m) => Ok(m.as_ref()), - ModuleSlot::Err(e) => Err(anyhow!(e.to_string())), - _ => Err(GraphError::MissingSpecifier(specifier.clone()).into()), - } - } - - /// Consume graph and return list of all module specifiers contained in the - /// graph. - pub fn get_modules(&self) -> Vec { - self.modules.keys().map(|s| s.to_owned()).collect() - } - - /// Transform `self.roots` into something that works for `tsc`, because `tsc` - /// doesn't like root names without extensions that match its expectations, - /// nor does it have any concept of redirection, so we have to resolve all - /// that upfront before feeding it to `tsc`. In addition, if checkJs is not - /// true, we should pass all emittable files in as the roots, so that `tsc` - /// type checks them and potentially emits them. - fn get_root_names( - &self, - include_emittable: bool, - ) -> Result, AnyError> { - let root_names: Vec = if include_emittable { - // in situations where there is `allowJs` with tsc, but not `checkJs`, - // then tsc will not parse the whole module graph, meaning that any - // JavaScript importing TypeScript will get ignored, meaning that those - // files will not get emitted. To counter act that behavior, we will - // include all modules that are emittable. - let mut specifiers = HashSet::<&ModuleSpecifier>::new(); - for (_, module_slot) in self.modules.iter() { - if let ModuleSlot::Module(module) = module_slot { - if module.media_type == MediaType::Jsx - || module.media_type == MediaType::TypeScript - || module.media_type == MediaType::Tsx - { - specifiers.insert(&module.specifier); - } - } - } - // We should include all the original roots as well. - for specifier in self.roots.iter() { - specifiers.insert(specifier); - } - specifiers.into_iter().cloned().collect() - } else { - self.roots.clone() - }; - let mut root_types = vec![]; - for ms in root_names { - // if the root module has a types specifier, we should be sending that - // to tsc instead of the original specifier - let specifier = self.resolve_specifier(&ms); - let module = match self.get_module(specifier) { - ModuleSlot::Module(module) => module, - ModuleSlot::Err(error) => { - // It would be great if we could just clone the error here... - if let Some(class) = get_custom_error_class(error) { - return Err(custom_error(class, error.to_string())); - } else { - panic!("unsupported ModuleSlot error"); - } - } - _ => { - panic!("missing module"); - } - }; - let specifier = if let Some((_, types_specifier)) = &module.maybe_types { - self.resolve_specifier(types_specifier) - } else { - specifier - }; - root_types.push(( - // root modules can be redirects, so before we pass it to tsc we need - // to resolve the redirect - specifier.clone(), - self.get_media_type(specifier).unwrap(), - )); - } - Ok(root_types) - } - - /// Get the source for a given module specifier. If the module is not part - /// of the graph, the result will be `None`. - pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option> { - if let ModuleSlot::Module(module) = self.get_module(specifier) { - Some(module.text_info.text()) - } else { - None - } - } - - /// Return a structure which provides information about the module graph and - /// the relationship of the modules in the graph. This structure is used to - /// provide information for the `info` subcommand. - pub fn info(&self) -> Result { - if self.roots.is_empty() || self.roots.len() > 1 { - return Err(GraphError::NotSupported(format!("Info is only supported when there is a single root module in the graph. Found: {}", self.roots.len())).into()); - } - - let root = self.resolve_specifier(&self.roots[0]).clone(); - let mut modules: Vec = self - .modules - .iter() - .filter_map(|(sp, sl)| match sl { - ModuleSlot::Module(module) => { - let mut dependencies: Vec = module - .dependencies - .iter() - .map(|(k, v)| info::ModuleGraphInfoDep { - specifier: k.clone(), - is_dynamic: v.is_dynamic, - maybe_code: v - .maybe_code - .clone() - .map(|s| self.resolve_specifier(&s).clone()), - maybe_type: v - .maybe_type - .clone() - .map(|s| self.resolve_specifier(&s).clone()), - }) - .collect(); - dependencies.sort(); - let (emit, map) = - if let Some((emit, maybe_map)) = &module.maybe_emit_path { - (Some(emit.clone()), maybe_map.clone()) - } else { - (None, None) - }; - let maybe_type_dependency = - module.maybe_types.clone().map(|(specifier, _type)| { - info::ModuleGraphInfoDep { - specifier, - is_dynamic: false, - maybe_code: None, - maybe_type: Some(_type), - } - }); - Some(info::ModuleGraphInfoMod { - specifier: sp.clone(), - dependencies, - maybe_type_dependency, - size: Some(module.size()), - media_type: Some(module.media_type), - local: Some(module.source_path.clone()), - checksum: Some(checksum::gen(&[module - .text_info - .text_str() - .as_bytes()])), - emit, - map, - ..Default::default() - }) - } - ModuleSlot::Err(err) => Some(info::ModuleGraphInfoMod { - specifier: sp.clone(), - error: Some(err.to_string()), - ..Default::default() - }), - _ => None, - }) - .collect(); - - modules.sort(); - - let size = modules.iter().fold(0_usize, |acc, m| { - if let Some(size) = &m.size { - acc + size - } else { - acc - } - }); - - Ok(info::ModuleGraphInfo { - root, - modules, - size, - }) - } - - /// Determines if all of the modules in the graph that require an emit have - /// a valid emit. Returns `true` if all the modules have a valid emit, - /// otherwise false. - fn is_emit_valid(&self, config: &TsConfig) -> bool { - let check_js = config.get_check_js(); - let config = config.as_bytes(); - self.modules.iter().all(|(_, m)| { - if let ModuleSlot::Module(m) = m { - let needs_emit = match m.media_type { - MediaType::TypeScript | MediaType::Tsx | MediaType::Jsx => true, - MediaType::JavaScript => check_js, - _ => false, - }; - if needs_emit { - m.is_emit_valid(&config) - } else { - true - } - } else { - true - } - }) - } - - /// Verify the subresource integrity of the graph based upon the optional - /// lockfile, updating the lockfile with any missing resources. This will - /// error if any of the resources do not match their lock status. - pub fn lock(&self) { - if let Some(lf) = self.maybe_lockfile.as_ref() { - let mut lockfile = lf.lock(); - for (ms, module_slot) in self.modules.iter() { - if let ModuleSlot::Module(module) = module_slot { - let specifier = module.specifier.to_string(); - let valid = - lockfile.check_or_insert(&specifier, module.text_info.text_str()); - if !valid { - eprintln!( - "{}", - GraphError::InvalidSource(ms.clone(), lockfile.filename.clone()) - ); - std::process::exit(10); - } - } - } - } - } - - /// Determines if any of the modules in the graph are required to be emitted. - /// This is similar to `emit_valid()` except that the actual emit isn't - /// checked to determine if it is valid. - fn needs_emit(&self, config: &TsConfig) -> bool { - let check_js = config.get_check_js(); - self.modules.iter().any(|(_, m)| match m { - ModuleSlot::Module(m) => match m.media_type { - MediaType::TypeScript | MediaType::Tsx | MediaType::Jsx => true, - MediaType::JavaScript => check_js, - _ => false, - }, - _ => false, - }) - } - - /// Given a string specifier and a referring module specifier, provide the - /// resulting module specifier and media type for the module that is part of - /// the graph. - /// - /// # Arguments - /// - /// * `specifier` - The string form of the module specifier that needs to be - /// resolved. - /// * `referrer` - The referring `ModuleSpecifier`. - /// * `prefer_types` - When resolving to a module specifier, determine if a - /// type dependency is preferred over a code dependency. This is set to - /// `true` when resolving module names for `tsc` as it needs the type - /// dependency over the code, while other consumers do not handle type only - /// dependencies. - pub fn resolve( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - prefer_types: bool, - ) -> Result { - let module = if let ModuleSlot::Module(module) = self.get_module(referrer) { - module - } else { - return Err(GraphError::MissingSpecifier(referrer.clone()).into()); - }; - if !module.dependencies.contains_key(specifier) { - return Err( - GraphError::MissingDependency( - referrer.to_owned(), - specifier.to_owned(), - ) - .into(), - ); - } - let dependency = module.dependencies.get(specifier).unwrap(); - // If there is a @deno-types pragma that impacts the dependency, then the - // maybe_type property will be set with that specifier, otherwise we use the - // specifier that point to the runtime code. - let resolved_specifier = if prefer_types && dependency.maybe_type.is_some() - { - dependency.maybe_type.clone().unwrap() - } else if let Some(code_specifier) = dependency.maybe_code.clone() { - code_specifier - } else { - return Err( - GraphError::MissingDependency( - referrer.to_owned(), - specifier.to_owned(), - ) - .into(), - ); - }; - let dep_module = if let ModuleSlot::Module(dep_module) = - self.get_module(&resolved_specifier) - { - dep_module - } else { - return Err( - GraphError::MissingDependency( - referrer.to_owned(), - resolved_specifier.to_string(), - ) - .into(), - ); - }; - // In the case that there is a X-TypeScript-Types or a triple-slash types, - // then the `maybe_types` specifier will be populated and we should use that - // instead. - let result = if prefer_types && dep_module.maybe_types.is_some() { - let (_, types) = dep_module.maybe_types.clone().unwrap(); - // It is possible that `types` points to a redirected specifier, so we - // need to ensure it resolves to the final specifier in the graph. - self.resolve_specifier(&types).clone() - } else { - dep_module.specifier.clone() - }; - - Ok(result) - } - - /// Takes a module specifier and returns the "final" specifier, accounting for - /// any redirects that may have occurred. - fn resolve_specifier<'a>( - &'a self, - specifier: &'a ModuleSpecifier, - ) -> &'a ModuleSpecifier { - let mut s = specifier; - let mut seen = HashSet::new(); - seen.insert(s.clone()); - while let Some(redirect) = self.redirects.get(s) { - if !seen.insert(redirect.clone()) { - eprintln!("An infinite loop of module redirections detected.\n Original specifier: {}", specifier); - break; - } - s = redirect; - if seen.len() > 5 { - eprintln!("An excessive number of module redirections detected.\n Original specifier: {}", specifier); - break; - } - } - s - } - - /// Transpile (only transform) the graph, updating any emitted modules - /// with the specifier handler. The result contains any performance stats - /// from the compiler and optionally any user provided configuration compiler - /// options that were ignored. - /// - /// # Arguments - /// - /// * `options` - A structure of options which impact how the code is - /// transpiled. - /// - pub fn transpile( - &mut self, - options: TranspileOptions, - ) -> Result { - let start = Instant::now(); - - let mut ts_config = TsConfig::new(json!({ - "checkJs": false, - "emitDecoratorMetadata": false, - "importsNotUsedAsValues": "remove", - "inlineSourceMap": true, - // TODO(@kitsonk) make this actually work when https://github.com/swc-project/swc/issues/2218 addressed. - "inlineSources": true, - "sourceMap": false, - "jsx": "react", - "jsxFactory": "React.createElement", - "jsxFragmentFactory": "React.Fragment", - })); - - let maybe_ignored_options = ts_config - .merge_tsconfig_from_config_file(options.maybe_config_file.as_ref())?; - - let config = ts_config.as_bytes(); - let check_js = ts_config.get_check_js(); - let emit_options: ast::EmitOptions = ts_config.into(); - let mut emit_count = 0_u32; - for (specifier, module_slot) in self.modules.iter_mut() { - if let ModuleSlot::Module(module) = module_slot { - // TODO(kitsonk) a lot of this logic should be refactored into `Module` as - // we start to support other methods on the graph. Especially managing - // the dirty state is something the module itself should "own". - - // if the module is a Dts file we should skip it - if module.media_type == MediaType::Dts { - continue; - } - // if we don't have check_js enabled, we won't touch non TypeScript or JSX - // modules - if !(check_js - || module.media_type == MediaType::Jsx - || module.media_type == MediaType::Tsx - || module.media_type == MediaType::TypeScript) - { - continue; - } - - let needs_reload = - options.reload && !options.reload_exclusions.contains(specifier); - // skip modules that already have a valid emit - if module.is_emit_valid(&config) && !needs_reload { - continue; - } - let parsed_module = module.parse()?; - let emit = transpile(&parsed_module, &emit_options)?; - emit_count += 1; - module.maybe_emit = Some(Emit::Cli(emit)); - module.set_version(&config); - module.is_dirty = true; - } - } - self.flush()?; - - let stats = Stats(vec![ - ("Files".to_string(), self.modules.len() as u32), - ("Emitted".to_string(), emit_count), - ("Total time".to_string(), start.elapsed().as_millis() as u32), - ]); - - Ok(ResultInfo { - diagnostics: Default::default(), - loadable_modules: self.get_loadable_modules(), - maybe_ignored_options, - stats, - }) - } - - /// Validate that the module graph is "valid" in that there are not module - /// slots that have errorred that should be available to be able to statically - /// analyze. In certain situations, we can spin up tsc with an "invalid" - /// graph. - fn validate(&self) -> Result<(), AnyError> { - fn validate_module( - specifier: &ModuleSpecifier, - seen: &mut HashSet, - get_module: &F, - ) -> Result<(), AnyError> - where - F: Fn(&ModuleSpecifier) -> ModuleSlot, - { - if seen.contains(specifier) { - return Ok(()); - } - seen.insert(specifier.clone()); - match get_module(specifier) { - ModuleSlot::Err(err) => Err(anyhow!(err.to_string())), - ModuleSlot::Module(module) => { - for (_, dep) in module.dependencies.iter() { - // a dynamic import should be skipped, because while it might not - // be available to statically analyze, it might be available at - // runtime. - if !dep.is_dynamic { - if let Some(code_specifier) = &dep.maybe_code { - validate_module(code_specifier, seen, get_module)?; - } - if let Some(type_specifier) = &dep.maybe_type { - validate_module(type_specifier, seen, get_module)?; - } - } - } - Ok(()) - }, - ModuleSlot::None => Err(custom_error("NotFound", format!("The specifier \"{}\" is unexpectedly not in the module graph.", specifier))), - ModuleSlot::Pending => Err(custom_error("InvalidState", format!("The specifier \"{}\" is in an unexpected state in the module graph.", specifier))), - } - } - - let mut seen = HashSet::new(); - for specifier in &self.roots { - validate_module(specifier, &mut seen, &|s| self.get_module(s).clone())?; - } - Ok(()) - } -} - -impl deno_ast::swc::bundler::Resolve for Graph { - fn resolve( - &self, - referrer: &deno_ast::swc::common::FileName, - specifier: &str, - ) -> Result { - let referrer = - if let deno_ast::swc::common::FileName::Url(referrer) = referrer { - referrer - } else { - unreachable!( - "An unexpected referrer was passed when bundling: {:?}", - referrer - ) - }; - let specifier = self.resolve(specifier, referrer, false)?; - - Ok(deno_ast::swc::common::FileName::Url(specifier)) - } -} - -/// A structure for building a dependency graph of modules. -pub struct GraphBuilder { - graph: Graph, - maybe_import_map: Option>>, - pending: FuturesUnordered, -} - -impl GraphBuilder { - pub fn new( - handler: Arc>, - maybe_import_map: Option, - maybe_lockfile: Option>>, - ) -> Self { - let internal_import_map = - maybe_import_map.map(|import_map| Arc::new(Mutex::new(import_map))); - GraphBuilder { - graph: Graph::new(handler, maybe_lockfile), - maybe_import_map: internal_import_map, - pending: FuturesUnordered::new(), - } - } - - /// Add a module into the graph based on a module specifier. The module - /// and any dependencies will be fetched from the handler. The module will - /// also be treated as a _root_ module in the graph. - pub async fn add( - &mut self, - specifier: &ModuleSpecifier, - is_dynamic: bool, - ) -> Result<(), AnyError> { - self.insert(specifier, is_dynamic).await?; - - if !self.graph.roots.contains(specifier) { - self.graph.roots.push(specifier.clone()); - self.graph.roots_dynamic = self.graph.roots_dynamic && is_dynamic; - if self.graph.maybe_tsbuildinfo.is_none() { - let handler = self.graph.handler.lock(); - self.graph.maybe_tsbuildinfo = handler.get_tsbuildinfo(specifier)?; - } - } - - Ok(()) - } - - /// Analyze compiler options, identifying any specifiers that need to be - /// resolved and added to the graph. - pub async fn analyze_compiler_options( - &mut self, - maybe_compiler_options: &Option>, - ) -> Result<(), AnyError> { - if let Some(user_config) = maybe_compiler_options { - if let Some(value) = user_config.get("types") { - let types: Vec = serde_json::from_value(value.clone())?; - for specifier in types { - if let Ok(specifier) = resolve_url_or_path(&specifier) { - self.insert(&specifier, false).await?; - } - } - } - } - Ok(()) - } - - /// Analyze a config file, identifying any specifiers that need to be resolved - /// and added to the graph. - pub async fn analyze_config_file( - &mut self, - maybe_config_file: &Option, - ) -> Result<(), AnyError> { - if let Some(config_file) = maybe_config_file { - let referrer = ModuleSpecifier::from_file_path(&config_file.path) - .map_err(|_| { - anyhow!("Could not convert file path: \"{:?}\"", config_file.path) - })?; - if let Some(compiler_options) = &config_file.json.compiler_options { - let compiler_options: CompilerOptions = - serde_json::from_value(compiler_options.clone())?; - if let Some(types) = compiler_options.types { - for specifier in types { - if let Ok(specifier) = - resolve_import(&specifier, &referrer.to_string()) - { - self.insert(&specifier, false).await?; - } - } - } - } - } - Ok(()) - } - - /// Request a module to be fetched from the handler and queue up its future - /// to be awaited to be resolved. - fn fetch( - &mut self, - specifier: &ModuleSpecifier, - maybe_referrer: &Option, - is_dynamic: bool, - ) { - if !self.graph.modules.contains_key(specifier) { - self - .graph - .modules - .insert(specifier.clone(), ModuleSlot::Pending); - let mut handler = self.graph.handler.lock(); - let future = - handler.fetch(specifier.clone(), maybe_referrer.clone(), is_dynamic); - self.pending.push(future); - } - } - - /// An internal method that fetches the specifier and recursively fetches any - /// of the dependencies, adding them to the graph. - async fn insert( - &mut self, - specifier: &ModuleSpecifier, - is_dynamic: bool, - ) -> Result<(), AnyError> { - self.fetch(specifier, &None, is_dynamic); - - loop { - match self.pending.next().await { - Some(Err((specifier, err))) => { - self - .graph - .modules - .insert(specifier, ModuleSlot::Err(Arc::new(err))); - } - Some(Ok(cached_module)) => { - let is_root = &cached_module.specifier == specifier; - self.visit(cached_module, is_root, is_dynamic)?; - } - _ => {} - } - if self.pending.is_empty() { - break; - } - } - - Ok(()) - } - - /// Visit a module that has been fetched, hydrating the module, analyzing its - /// dependencies if required, fetching those dependencies, and inserting the - /// module into the graph. - fn visit( - &mut self, - cached_module: CachedModule, - is_root: bool, - is_root_dynamic: bool, - ) -> Result<(), AnyError> { - let specifier = cached_module.specifier.clone(); - let requested_specifier = cached_module.requested_specifier.clone(); - let mut module = - Module::new(cached_module, is_root, self.maybe_import_map.clone()); - match module.media_type { - MediaType::Json - | MediaType::SourceMap - | MediaType::TsBuildInfo - | MediaType::Unknown => { - return Err( - GraphError::UnsupportedImportType( - module.specifier, - module.media_type, - ) - .into(), - ); - } - _ => (), - } - if !module.is_parsed { - let has_types = module.maybe_types.is_some(); - module.parse()?; - if self.maybe_import_map.is_none() { - let mut handler = self.graph.handler.lock(); - handler.set_deps(&specifier, module.dependencies.clone())?; - if !has_types { - if let Some((types, _)) = module.maybe_types.clone() { - handler.set_types(&specifier, types)?; - } - } - } - } - for (_, dep) in module.dependencies.iter() { - let maybe_referrer = Some(dep.location.clone()); - for maybe_specifier in &[dep.maybe_code.as_ref(), dep.maybe_type.as_ref()] - { - if let Some(&dep_specifier) = maybe_specifier.as_ref() { - if dep_specifier.scheme() == "bare" { - self.graph.modules.insert( - dep_specifier.clone(), - ModuleSlot::Err(Arc::new( - ModuleResolutionError::ImportPrefixMissing( - dep_specifier.path().to_string(), - Some(specifier.to_string()), - ) - .into(), - )), - ); - } else { - self.fetch( - dep_specifier, - &maybe_referrer, - is_root_dynamic || dep.is_dynamic, - ); - } - } - } - } - if let Some((_, specifier)) = module.maybe_types.as_ref() { - self.fetch(specifier, &None, is_root_dynamic); - } - if specifier != requested_specifier { - self - .graph - .redirects - .insert(requested_specifier, specifier.clone()); - } - self - .graph - .modules - .insert(specifier, ModuleSlot::Module(Box::new(module))); - - Ok(()) - } - - /// Move out the graph from the builder to be utilized further. An optional - /// lockfile can be provided, where if the sources in the graph do not match - /// the expected lockfile, an error will be logged and the process will exit. - pub fn get_graph(self) -> Graph { - self.graph.lock(); - self.graph - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - - use crate::specifier_handler::MemoryHandler; - use deno_core::futures::future; - use deno_core::parking_lot::Mutex; - use std::fs; - use std::path::PathBuf; - - macro_rules! map ( - { $($key:expr => $value:expr),+ } => { - { - let mut m = ::std::collections::HashMap::new(); - $( - m.insert($key, $value); - )+ - m - } - }; - ); - - /// This is a testing mock for `SpecifierHandler` that uses a special file - /// system renaming to mock local and remote modules as well as provides - /// "spies" for the critical methods for testing purposes. - #[derive(Debug, Default)] - pub struct MockSpecifierHandler { - pub fixtures: PathBuf, - pub maybe_tsbuildinfo: Option, - pub tsbuildinfo_calls: Vec<(ModuleSpecifier, String)>, - pub cache_calls: Vec<(ModuleSpecifier, Emit)>, - pub deps_calls: Vec<(ModuleSpecifier, DependencyMap)>, - pub types_calls: Vec<(ModuleSpecifier, String)>, - pub version_calls: Vec<(ModuleSpecifier, String)>, - } - - impl MockSpecifierHandler { - fn get_cache( - &self, - specifier: ModuleSpecifier, - ) -> Result { - let specifier_text = specifier - .to_string() - .replace(":///", "_") - .replace("://", "_") - .replace("/", "-"); - let source_path = self.fixtures.join(specifier_text); - let media_type = MediaType::from(&source_path); - let source = Arc::new( - fs::read_to_string(&source_path) - .map_err(|err| (specifier.clone(), err.into()))?, - ); - let is_remote = specifier.scheme() != "file"; - - Ok(CachedModule { - source, - requested_specifier: specifier.clone(), - source_path, - specifier, - media_type, - is_remote, - ..CachedModule::default() - }) - } - } - - impl SpecifierHandler for MockSpecifierHandler { - fn fetch( - &mut self, - specifier: ModuleSpecifier, - _maybe_referrer: Option, - _is_dynamic: bool, - ) -> FetchFuture { - Box::pin(future::ready(self.get_cache(specifier))) - } - fn get_tsbuildinfo( - &self, - _specifier: &ModuleSpecifier, - ) -> Result, AnyError> { - Ok(self.maybe_tsbuildinfo.clone()) - } - fn set_cache( - &mut self, - specifier: &ModuleSpecifier, - emit: &Emit, - ) -> Result<(), AnyError> { - self.cache_calls.push((specifier.clone(), emit.clone())); - Ok(()) - } - fn set_types( - &mut self, - specifier: &ModuleSpecifier, - types: String, - ) -> Result<(), AnyError> { - self.types_calls.push((specifier.clone(), types)); - Ok(()) - } - fn set_tsbuildinfo( - &mut self, - specifier: &ModuleSpecifier, - tsbuildinfo: String, - ) -> Result<(), AnyError> { - self.maybe_tsbuildinfo = Some(tsbuildinfo.clone()); - self - .tsbuildinfo_calls - .push((specifier.clone(), tsbuildinfo)); - Ok(()) - } - fn set_deps( - &mut self, - specifier: &ModuleSpecifier, - dependencies: DependencyMap, - ) -> Result<(), AnyError> { - self.deps_calls.push((specifier.clone(), dependencies)); - Ok(()) - } - fn set_version( - &mut self, - specifier: &ModuleSpecifier, - version: String, - ) -> Result<(), AnyError> { - self.version_calls.push((specifier.clone(), version)); - Ok(()) - } - } - - async fn setup( - specifier: ModuleSpecifier, - ) -> (Graph, Arc>) { - let fixtures = test_util::testdata_path().join("module_graph"); - let handler = Arc::new(Mutex::new(MockSpecifierHandler { - fixtures, - ..MockSpecifierHandler::default() - })); - let mut builder = GraphBuilder::new(handler.clone(), None, None); - builder - .add(&specifier, false) - .await - .expect("module not inserted"); - - (builder.get_graph(), handler) - } - - async fn setup_memory( - specifier: ModuleSpecifier, - sources: HashMap<&str, &str>, - ) -> Graph { - let sources: HashMap> = sources - .iter() - .map(|(k, v)| (k.to_string(), Arc::new(v.to_string()))) - .collect(); - let handler = Arc::new(Mutex::new(MemoryHandler::new(sources))); - let mut builder = GraphBuilder::new(handler.clone(), None, None); - builder - .add(&specifier, false) - .await - .expect("module not inserted"); - - builder.get_graph() - } - - #[test] - fn test_get_version() { - let doc_a = "console.log(42);"; - let version_a = get_version(doc_a, "1.2.3", b""); - let doc_b = "console.log(42);"; - let version_b = get_version(doc_b, "1.2.3", b""); - assert_eq!(version_a, version_b); - - let version_c = get_version(doc_a, "1.2.3", b"options"); - assert_ne!(version_a, version_c); - - let version_d = get_version(doc_b, "1.2.3", b"options"); - assert_eq!(version_c, version_d); - - let version_e = get_version(doc_a, "1.2.4", b""); - assert_ne!(version_a, version_e); - - let version_f = get_version(doc_b, "1.2.4", b""); - assert_eq!(version_e, version_f); - } - - #[test] - fn test_module_emit_valid() { - let source = "console.log(42);"; - let maybe_version = Some(get_version(source, &version::deno(), b"")); - let module = Module { - maybe_version, - text_info: SourceTextInfo::from_string(source.to_string()), - ..Module::default() - }; - assert!(module.is_emit_valid(b"")); - - let source = "console.log(42);"; - let old_source = "console.log(43);"; - let maybe_version = Some(get_version(old_source, &version::deno(), b"")); - let module = Module { - maybe_version, - text_info: SourceTextInfo::from_string(source.to_string()), - ..Module::default() - }; - assert!(!module.is_emit_valid(b"")); - - let source = "console.log(42);"; - let maybe_version = Some(get_version(source, "0.0.0", b"")); - let module = Module { - maybe_version, - text_info: SourceTextInfo::from_string(source.to_string()), - ..Module::default() - }; - assert!(!module.is_emit_valid(b"")); - - let source = "console.log(42);"; - let module = Module { - text_info: SourceTextInfo::from_string(source.to_string()), - ..Module::default() - }; - assert!(!module.is_emit_valid(b"")); - } - - #[test] - fn test_module_set_version() { - let source = "console.log(42);"; - let expected = Some(get_version(source, &version::deno(), b"")); - let mut module = Module { - text_info: SourceTextInfo::from_string(source.to_string()), - ..Module::default() - }; - assert!(module.maybe_version.is_none()); - module.set_version(b""); - assert_eq!(module.maybe_version, expected); - } - - #[tokio::test] - async fn test_graph_bundle() { - let tests = vec![ - ("file:///tests/fixture01.ts", "fixture01.out"), - ("file:///tests/fixture02.ts", "fixture02.out"), - ("file:///tests/fixture03.ts", "fixture03.out"), - ("file:///tests/fixture04.ts", "fixture04.out"), - ("file:///tests/fixture05.ts", "fixture05.out"), - ("file:///tests/fixture06.ts", "fixture06.out"), - ("file:///tests/fixture07.ts", "fixture07.out"), - ("file:///tests/fixture08.ts", "fixture08.out"), - ("file:///tests/fixture09.ts", "fixture09.out"), - ("file:///tests/fixture10.ts", "fixture10.out"), - ("file:///tests/fixture11.ts", "fixture11.out"), - ("file:///tests/fixture12.ts", "fixture12.out"), - ("file:///tests/fixture13.ts", "fixture13.out"), - ("file:///tests/fixture14.ts", "fixture14.out"), - ("file:///tests/fixture15.ts", "fixture15.out"), - ]; - let fixtures = test_util::testdata_path().join("bundle"); - - for (specifier, expected_str) in tests { - let specifier = resolve_url_or_path(specifier).unwrap(); - let handler = Arc::new(Mutex::new(MockSpecifierHandler { - fixtures: fixtures.clone(), - ..MockSpecifierHandler::default() - })); - let mut builder = GraphBuilder::new(handler.clone(), None, None); - builder - .add(&specifier, false) - .await - .expect("module not inserted"); - let graph = builder.get_graph(); - let (actual, stats, maybe_ignored_options) = graph - .bundle(BundleOptions::default()) - .expect("could not bundle"); - assert_eq!(stats.0.len(), 2); - assert_eq!(maybe_ignored_options, None); - let expected_path = fixtures.join(expected_str); - let expected = fs::read_to_string(expected_path).unwrap(); - assert_eq!(actual, expected, "fixture: {}", specifier); - } - } - - #[tokio::test] - async fn test_graph_check_emit() { - let specifier = resolve_url_or_path("file:///tests/main.ts") - .expect("could not resolve module"); - let (graph, handler) = setup(specifier).await; - let result_info = graph - .check(CheckOptions { - debug: false, - emit: true, - lib: TypeLib::DenoWindow, - maybe_config_file: None, - reload: false, - ..Default::default() - }) - .expect("should have checked"); - assert!(result_info.maybe_ignored_options.is_none()); - assert_eq!(result_info.stats.0.len(), 12); - assert!(result_info.diagnostics.is_empty()); - let h = handler.lock(); - assert_eq!(h.cache_calls.len(), 2); - assert_eq!(h.tsbuildinfo_calls.len(), 1); - } - - #[tokio::test] - async fn test_graph_check_ignores_dynamic_import_errors() { - let specifier = resolve_url_or_path("file:///tests/dynamicimport.ts") - .expect("could not resolve module"); - let (graph, _) = setup(specifier).await; - let result_info = graph - .check(CheckOptions { - debug: false, - emit: false, - lib: TypeLib::DenoWindow, - maybe_config_file: None, - reload: false, - ..Default::default() - }) - .expect("should have checked"); - assert!(result_info.diagnostics.is_empty()); - } - - #[tokio::test] - async fn fix_graph_check_emit_diagnostics() { - let specifier = resolve_url_or_path("file:///tests/diag.ts") - .expect("could not resolve module"); - let (graph, handler) = setup(specifier).await; - let result_info = graph - .check(CheckOptions { - debug: false, - emit: true, - lib: TypeLib::DenoWindow, - maybe_config_file: None, - reload: false, - ..Default::default() - }) - .expect("should have checked"); - assert!(result_info.maybe_ignored_options.is_none()); - assert_eq!(result_info.stats.0.len(), 12); - assert!(!result_info.diagnostics.is_empty()); - let h = handler.lock(); - // we shouldn't cache any files or write out tsbuildinfo if there are - // diagnostic errors - assert_eq!(h.cache_calls.len(), 0); - assert_eq!(h.tsbuildinfo_calls.len(), 0); - } - - #[tokio::test] - async fn test_graph_check_no_emit() { - let specifier = resolve_url_or_path("file:///tests/main.ts") - .expect("could not resolve module"); - let (graph, handler) = setup(specifier).await; - let result_info = graph - .check(CheckOptions { - debug: false, - emit: false, - lib: TypeLib::DenoWindow, - maybe_config_file: None, - reload: false, - ..Default::default() - }) - .expect("should have checked"); - assert!(result_info.maybe_ignored_options.is_none()); - assert_eq!(result_info.stats.0.len(), 12); - assert!(result_info.diagnostics.is_empty()); - let h = handler.lock(); - assert_eq!(h.cache_calls.len(), 0); - assert_eq!(h.tsbuildinfo_calls.len(), 1); - } - - #[tokio::test] - async fn fix_graph_check_mjs_root() { - let specifier = resolve_url_or_path("file:///tests/a.mjs") - .expect("could not resolve module"); - let (graph, handler) = setup(specifier).await; - let result_info = graph - .check(CheckOptions { - debug: false, - emit: true, - lib: TypeLib::DenoWindow, - maybe_config_file: None, - reload: false, - ..Default::default() - }) - .expect("should have checked"); - assert!(result_info.maybe_ignored_options.is_none()); - assert!(result_info.diagnostics.is_empty()); - let h = handler.lock(); - assert_eq!(h.cache_calls.len(), 1); - assert_eq!(h.tsbuildinfo_calls.len(), 1); - } - - #[tokio::test] - async fn fix_graph_check_types_root() { - let specifier = resolve_url_or_path("file:///typesref.js") - .expect("could not resolve module"); - let (graph, _) = setup(specifier).await; - let result_info = graph - .check(CheckOptions { - debug: false, - emit: false, - lib: TypeLib::DenoWindow, - maybe_config_file: None, - reload: false, - ..Default::default() - }) - .expect("should have checked"); - assert!(result_info.diagnostics.is_empty()); - } - - #[tokio::test] - async fn test_graph_check_user_config() { - let specifier = resolve_url_or_path("file:///tests/checkwithconfig.ts") - .expect("could not resolve module"); - let (graph, handler) = setup(specifier.clone()).await; - let config_file = ConfigFile::read( - test_util::testdata_path().join("module_graph/tsconfig_01.json"), - ) - .unwrap(); - let result_info = graph - .check(CheckOptions { - debug: false, - emit: true, - lib: TypeLib::DenoWindow, - maybe_config_file: Some(config_file), - reload: true, - ..Default::default() - }) - .expect("should have checked"); - assert!(result_info.maybe_ignored_options.is_none()); - assert!(result_info.diagnostics.is_empty()); - let (ver0, ver1) = { - let h = handler.lock(); - assert_eq!(h.version_calls.len(), 2); - (h.version_calls[0].1.clone(), h.version_calls[1].1.clone()) - }; - - // let's do it all over again to ensure that the versions are determinstic - let (graph, handler) = setup(specifier).await; - let config_file = ConfigFile::read( - test_util::testdata_path().join("module_graph/tsconfig_01.json"), - ) - .unwrap(); - let result_info = graph - .check(CheckOptions { - debug: false, - emit: true, - lib: TypeLib::DenoWindow, - maybe_config_file: Some(config_file), - reload: true, - ..Default::default() - }) - .expect("should have checked"); - assert!(result_info.maybe_ignored_options.is_none()); - assert!(result_info.diagnostics.is_empty()); - let h = handler.lock(); - assert_eq!(h.version_calls.len(), 2); - assert!(h.version_calls[0].1 == ver0 || h.version_calls[0].1 == ver1); - assert!(h.version_calls[1].1 == ver0 || h.version_calls[1].1 == ver1); - } - - #[tokio::test] - async fn test_graph_emit() { - let specifier = resolve_url_or_path("file:///a.ts").unwrap(); - let graph = setup_memory( - specifier, - map!( - "/a.ts" => r#" - import * as b from "./b.ts"; - - console.log(b); - "#, - "/b.ts" => r#" - export const b = "b"; - "# - ), - ) - .await; - let (emitted_files, result_info) = graph - .emit(EmitOptions { - check: true, - bundle_type: BundleType::None, - debug: false, - maybe_user_config: None, - }) - .expect("should have emitted"); - assert!(result_info.diagnostics.is_empty()); - assert!(result_info.maybe_ignored_options.is_none()); - assert_eq!(emitted_files.len(), 4); - let out_a = emitted_files.get("file:///a.ts.js"); - assert!(out_a.is_some()); - let out_a = out_a.unwrap(); - assert!(out_a.starts_with("import * as b from")); - assert!(emitted_files.contains_key("file:///a.ts.js.map")); - let out_b = emitted_files.get("file:///b.ts.js"); - assert!(out_b.is_some()); - let out_b = out_b.unwrap(); - assert!(out_b.starts_with("export const b = \"b\";")); - assert!(emitted_files.contains_key("file:///b.ts.js.map")); - } - - #[tokio::test] - async fn test_graph_emit_bundle() { - let specifier = resolve_url_or_path("file:///a.ts").unwrap(); - let graph = setup_memory( - specifier, - map!( - "/a.ts" => r#" - import * as b from "./b.ts"; - - console.log(b); - "#, - "/b.ts" => r#" - export const b = "b"; - "# - ), - ) - .await; - let (emitted_files, result_info) = graph - .emit(EmitOptions { - check: true, - bundle_type: BundleType::Module, - debug: false, - maybe_user_config: None, - }) - .expect("should have emitted"); - assert!(result_info.diagnostics.is_empty()); - assert!(result_info.maybe_ignored_options.is_none()); - assert_eq!(emitted_files.len(), 2); - let actual = emitted_files.get("deno:///bundle.js"); - assert!(actual.is_some()); - assert!(emitted_files.contains_key("deno:///bundle.js.map")); - let actual = actual.unwrap(); - assert!(actual.contains("const b = \"b\";")); - assert!(actual.contains("console.log(mod);")); - } - - #[tokio::test] - async fn fix_graph_emit_declaration() { - let specifier = resolve_url_or_path("file:///a.ts").unwrap(); - let graph = setup_memory( - specifier, - map!( - "/a.ts" => r#" - import * as b from "./b.ts"; - - console.log(b); - "#, - "/b.ts" => r#" - export const b = "b"; - "# - ), - ) - .await; - let mut user_config = HashMap::::new(); - user_config.insert("declaration".to_string(), json!(true)); - let (emitted_files, result_info) = graph - .emit(EmitOptions { - check: true, - bundle_type: BundleType::None, - debug: false, - maybe_user_config: Some(user_config), - }) - .expect("should have emitted"); - assert!(result_info.diagnostics.is_empty()); - assert!(result_info.maybe_ignored_options.is_none()); - assert_eq!(emitted_files.len(), 6); - let out_a = emitted_files.get("file:///a.ts.js"); - assert!(out_a.is_some()); - let out_a = out_a.unwrap(); - assert!(out_a.starts_with("import * as b from")); - assert!(emitted_files.contains_key("file:///a.ts.js.map")); - assert!(emitted_files.contains_key("file:///a.ts.d.ts")); - let out_b = emitted_files.get("file:///b.ts.js"); - assert!(out_b.is_some()); - let out_b = out_b.unwrap(); - assert!(out_b.starts_with("export const b = \"b\";")); - assert!(emitted_files.contains_key("file:///b.ts.js.map")); - assert!(emitted_files.contains_key("file:///b.ts.d.ts")); - } - - #[tokio::test] - async fn test_graph_info() { - let specifier = resolve_url_or_path("file:///tests/main.ts") - .expect("could not resolve module"); - let (graph, _) = setup(specifier.clone()).await; - let info = graph.info().expect("could not get info"); - assert_eq!(info.root, specifier); - assert_eq!(info.modules.len(), 7); - assert_eq!(info.size, 518); - } - - #[tokio::test] - async fn test_graph_import_json() { - let specifier = resolve_url_or_path("file:///tests/importjson.ts") - .expect("could not resolve module"); - let fixtures = test_util::testdata_path().join("module_graph"); - let handler = Arc::new(Mutex::new(MockSpecifierHandler { - fixtures, - ..MockSpecifierHandler::default() - })); - let mut builder = GraphBuilder::new(handler.clone(), None, None); - builder - .add(&specifier, false) - .await - .expect_err("should have errored"); - } - - #[tokio::test] - async fn test_graph_transpile() { - // This is a complex scenario of transpiling, where we have TypeScript - // importing a JavaScript file (with type definitions) which imports - // TypeScript, JavaScript, and JavaScript with type definitions. - // For scenarios where we transpile, we only want the TypeScript files - // to be actually emitted. - // - // This also exercises "@deno-types" and type references. - let specifier = resolve_url_or_path("file:///tests/main.ts") - .expect("could not resolve module"); - let (mut graph, handler) = setup(specifier).await; - let result_info = graph.transpile(TranspileOptions::default()).unwrap(); - assert_eq!(result_info.stats.0.len(), 3); - assert_eq!(result_info.maybe_ignored_options, None); - let h = handler.lock(); - assert_eq!(h.cache_calls.len(), 2); - match &h.cache_calls[0].1 { - Emit::Cli((code, maybe_map)) => { - assert!( - code.contains("# sourceMappingURL=data:application/json;base64,") - ); - assert!(maybe_map.is_none()); - } - }; - match &h.cache_calls[1].1 { - Emit::Cli((code, maybe_map)) => { - assert!( - code.contains("# sourceMappingURL=data:application/json;base64,") - ); - assert!(maybe_map.is_none()); - } - }; - assert_eq!(h.deps_calls.len(), 7); - assert_eq!( - h.deps_calls[0].0, - resolve_url_or_path("file:///tests/main.ts").unwrap() - ); - assert_eq!(h.deps_calls[0].1.len(), 1); - assert_eq!( - h.deps_calls[1].0, - resolve_url_or_path("https://deno.land/x/lib/mod.js").unwrap() - ); - assert_eq!(h.deps_calls[1].1.len(), 3); - assert_eq!( - h.deps_calls[2].0, - resolve_url_or_path("https://deno.land/x/lib/mod.d.ts").unwrap() - ); - assert_eq!(h.deps_calls[2].1.len(), 3, "should have 3 dependencies"); - // sometimes the calls are not deterministic, and so checking the contents - // can cause some failures - assert_eq!(h.deps_calls[3].1.len(), 0, "should have no dependencies"); - assert_eq!(h.deps_calls[4].1.len(), 0, "should have no dependencies"); - assert_eq!(h.deps_calls[5].1.len(), 0, "should have no dependencies"); - assert_eq!(h.deps_calls[6].1.len(), 0, "should have no dependencies"); - } - - #[tokio::test] - async fn test_graph_transpile_user_config() { - let specifier = resolve_url_or_path("https://deno.land/x/transpile.tsx") - .expect("could not resolve module"); - let (mut graph, handler) = setup(specifier).await; - let config_file = ConfigFile::read( - test_util::testdata_path().join("module_graph/tsconfig.json"), - ) - .unwrap(); - let result_info = graph - .transpile(TranspileOptions { - debug: false, - maybe_config_file: Some(config_file), - reload: false, - ..Default::default() - }) - .unwrap(); - assert_eq!( - result_info.maybe_ignored_options.unwrap().items, - vec!["target".to_string()], - "the 'target' options should have been ignored" - ); - let h = handler.lock(); - assert_eq!(h.cache_calls.len(), 1, "only one file should be emitted"); - // FIXME(bartlomieju): had to add space in `
`, probably a quirk in swc_ecma_codegen - match &h.cache_calls[0].1 { - Emit::Cli((code, _)) => { - assert!( - code.contains("
Hello world!
"), - "jsx should have been preserved" - ); - } - } - } - - #[tokio::test] - async fn test_graph_import_map_remote_to_local() { - let fixtures = test_util::testdata_path().join("module_graph"); - let maybe_import_map = Some( - ImportMap::from_json( - "file:///tests/importmap.json", - r#"{ - "imports": { - "https://deno.land/x/b/mod.js": "./b/mod.js" - } - } - "#, - ) - .expect("could not parse import map"), - ); - let handler = Arc::new(Mutex::new(MockSpecifierHandler { - fixtures, - ..Default::default() - })); - let mut builder = GraphBuilder::new(handler, maybe_import_map, None); - let specifier = resolve_url_or_path("file:///tests/importremap.ts") - .expect("could not resolve module"); - builder.add(&specifier, false).await.expect("could not add"); - builder.get_graph(); - } - - #[tokio::test] - async fn test_graph_with_lockfile() { - let fixtures = test_util::testdata_path().join("module_graph"); - let lockfile_path = fixtures.join("lockfile.json"); - let lockfile = - Lockfile::new(lockfile_path, false).expect("could not load lockfile"); - let maybe_lockfile = Some(Arc::new(Mutex::new(lockfile))); - let handler = Arc::new(Mutex::new(MockSpecifierHandler { - fixtures, - ..MockSpecifierHandler::default() - })); - let mut builder = GraphBuilder::new(handler.clone(), None, maybe_lockfile); - let specifier = resolve_url_or_path("file:///tests/main.ts") - .expect("could not resolve module"); - builder - .add(&specifier, false) - .await - .expect("module not inserted"); - builder.get_graph(); - } -} diff --git a/cli/module_loader.rs b/cli/module_loader.rs index c14c953b20..e66e4e4fc7 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -1,7 +1,8 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use crate::module_graph::TypeLib; +use crate::emit::TypeLib; use crate::proc_state::ProcState; + use deno_core::error::AnyError; use deno_core::futures::future::FutureExt; use deno_core::futures::Future; @@ -10,16 +11,12 @@ use deno_core::ModuleLoader; use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_runtime::permissions::Permissions; -use import_map::ImportMap; use std::cell::RefCell; use std::pin::Pin; use std::rc::Rc; use std::str; -pub struct CliModuleLoader { - /// When flags contains a `.import_map_path` option, the content of the - /// import map file will be resolved and set. - pub import_map: Option, +pub(crate) struct CliModuleLoader { pub lib: TypeLib, /// The initial set of permissions used to resolve the static imports in the /// worker. They are decoupled from the worker (dynamic) permissions since @@ -36,10 +33,7 @@ impl CliModuleLoader { TypeLib::DenoWindow }; - let import_map = ps.maybe_import_map.clone(); - Rc::new(CliModuleLoader { - import_map, lib, root_permissions: Permissions::allow_all(), ps, @@ -54,7 +48,6 @@ impl CliModuleLoader { }; Rc::new(CliModuleLoader { - import_map: None, lib, root_permissions: permissions, ps, @@ -67,44 +60,25 @@ impl ModuleLoader for CliModuleLoader { &self, specifier: &str, referrer: &str, - is_main: bool, + _is_main: bool, ) -> Result { - // FIXME(bartlomieju): hacky way to provide compatibility with repl - let referrer = if referrer.is_empty() && self.ps.flags.repl { - deno_core::DUMMY_SPECIFIER - } else { - referrer - }; - - // TODO(ry) I think we can remove this conditional. At the time of writing - // we don't have any tests that fail if it was removed. - // https://github.com/WICG/import-maps/issues/157 - if !is_main { - if let Some(import_map) = &self.import_map { - return import_map - .resolve(specifier, referrer) - .map_err(AnyError::from); - } - } - - let module_specifier = deno_core::resolve_import(specifier, referrer)?; - - Ok(module_specifier) + self.ps.resolve(specifier, referrer) } fn load( &self, module_specifier: &ModuleSpecifier, maybe_referrer: Option, - _is_dynamic: bool, + is_dynamic: bool, ) -> Pin> { let module_specifier = module_specifier.clone(); let ps = self.ps.clone(); - // NOTE: this block is async only because of `deno_core` - // interface requirements; module was already loaded - // when constructing module graph during call to `prepare_load`. - async move { ps.load(module_specifier, maybe_referrer) }.boxed_local() + // NOTE: this block is async only because of `deno_core` interface + // requirements; module was already loaded when constructing module graph + // during call to `prepare_load`. + async move { ps.load(module_specifier, maybe_referrer, is_dynamic) } + .boxed_local() } fn prepare_load( @@ -117,24 +91,31 @@ impl ModuleLoader for CliModuleLoader { ) -> Pin>>> { let specifier = specifier.clone(); let ps = self.ps.clone(); - let maybe_import_map = self.import_map.clone(); let state = op_state.borrow(); - let root_permissions = self.root_permissions.clone(); let dynamic_permissions = state.borrow::().clone(); + let root_permissions = if is_dynamic { + dynamic_permissions.clone() + } else { + self.root_permissions.clone() + }; - let lib = self.lib.clone(); + let lib = match self.lib { + TypeLib::DenoWindow => crate::emit::TypeLib::DenoWindow, + TypeLib::DenoWorker => crate::emit::TypeLib::DenoWorker, + TypeLib::UnstableDenoWindow => crate::emit::TypeLib::UnstableDenoWindow, + TypeLib::UnstableDenoWorker => crate::emit::TypeLib::UnstableDenoWorker, + }; drop(state); // TODO(bartlomieju): `prepare_module_load` should take `load_id` param async move { ps.prepare_module_load( - specifier, + vec![specifier], + is_dynamic, lib, root_permissions, dynamic_permissions, - is_dynamic, - maybe_import_map, ) .await } diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index d63d97f587..ab6992d19c 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -1,27 +1,30 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use crate::module_graph::BundleType; -use crate::module_graph::EmitOptions; -use crate::module_graph::GraphBuilder; +use crate::cache; +use crate::config_file::IgnoredCompilerOptions; +use crate::diagnostics::Diagnostics; +use crate::emit; +use crate::errors::get_error_class_name; use crate::proc_state::ProcState; -use crate::specifier_handler::FetchHandler; -use crate::specifier_handler::MemoryHandler; -use crate::specifier_handler::SpecifierHandler; +use crate::resolver::ImportMapResolver; +use deno_core::error::custom_error; use deno_core::error::generic_error; -use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::error::Context; -use deno_core::parking_lot::Mutex; use deno_core::resolve_url_or_path; +use deno_core::serde_json; use deno_core::serde_json::Value; +use deno_core::ModuleSpecifier; use deno_core::OpState; +use deno_graph; use deno_runtime::permissions::Permissions; use import_map::ImportMap; use serde::Deserialize; use serde::Serialize; use std::cell::RefCell; use std::collections::HashMap; +use std::collections::HashSet; use std::rc::Rc; use std::sync::Arc; @@ -37,6 +40,15 @@ enum RuntimeBundleType { Classic, } +impl<'a> From<&'a RuntimeBundleType> for emit::BundleType { + fn from(bundle_type: &'a RuntimeBundleType) -> Self { + match bundle_type { + RuntimeBundleType::Classic => Self::Classic, + RuntimeBundleType::Module => Self::Module, + } + } +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct EmitArgs { @@ -52,10 +64,21 @@ struct EmitArgs { #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct EmitResult { - diagnostics: crate::diagnostics::Diagnostics, + diagnostics: Diagnostics, files: HashMap, - ignored_options: Option, - stats: crate::module_graph::Stats, + #[serde(rename = "ignoredOptions")] + maybe_ignored_options: Option, + stats: emit::Stats, +} + +fn to_maybe_imports( + referrer: &ModuleSpecifier, + maybe_options: Option<&HashMap>, +) -> Option)>> { + let options = maybe_options.as_ref()?; + let types_value = options.get("types")?; + let types: Vec = serde_json::from_value(types_value.clone()).ok()?; + Some(vec![(referrer.clone(), types)]) } async fn op_emit( @@ -65,23 +88,19 @@ async fn op_emit( ) -> Result { deno_runtime::ops::check_unstable2(&state, "Deno.emit"); let root_specifier = args.root_specifier; - let ps = state.borrow().borrow::().clone(); - let mut runtime_permissions = { - let state = state.borrow(); - state.borrow::().clone() - }; - // when we are actually resolving modules without provided sources, we should - // treat the root module as a dynamic import so that runtime permissions are - // applied. - let handler: Arc> = - if let Some(sources) = args.sources { - Arc::new(Mutex::new(MemoryHandler::new(sources))) + let state = state.borrow(); + let ps = state.borrow::(); + let mut runtime_permissions = { state.borrow::().clone() }; + let mut cache: Box = + if let Some(sources) = &args.sources { + Box::new(cache::MemoryCacher::new(sources.clone())) } else { - Arc::new(Mutex::new(FetchHandler::new( - &ps, + Box::new(cache::FetchCacher::new( + ps.dir.gen_cache.clone(), + ps.file_fetcher.clone(), runtime_permissions.clone(), runtime_permissions.clone(), - )?)) + )) }; let maybe_import_map = if let Some(import_map_str) = args.import_map_path { let import_map_specifier = resolve_url_or_path(&import_map_str) @@ -107,37 +126,125 @@ async fn op_emit( } else { None }; - let mut builder = GraphBuilder::new(handler, maybe_import_map, None); - let root_specifier = resolve_url_or_path(&root_specifier)?; - builder.add(&root_specifier, false).await.map_err(|_| { - type_error(format!( - "Unable to handle the given specifier: {}", - &root_specifier - )) - })?; - builder - .analyze_compiler_options(&args.compiler_options) - .await?; - let bundle_type = match args.bundle { - Some(RuntimeBundleType::Module) => BundleType::Module, - Some(RuntimeBundleType::Classic) => BundleType::Classic, - None => BundleType::None, - }; - let graph = builder.get_graph(); + let roots = vec![resolve_url_or_path(&root_specifier)?]; + let maybe_imports = + to_maybe_imports(&roots[0], args.compiler_options.as_ref()); + let maybe_resolver = maybe_import_map.as_ref().map(ImportMapResolver::new); + let graph = Arc::new( + deno_graph::create_graph( + roots, + true, + maybe_imports, + cache.as_mut_loader(), + maybe_resolver.as_ref().map(|r| r.as_resolver()), + None, + None, + ) + .await, + ); + // There are certain graph errors that we want to return as an error of an op, + // versus something that gets returned as a diagnostic of the op, this is + // handled here. + if let Err(err) = graph.valid() { + let err: AnyError = err.into(); + if get_error_class_name(&err) == "PermissionDenied" { + return Err(err); + } + } + let check = args.check.unwrap_or(true); let debug = ps.flags.log_level == Some(log::Level::Debug); - let graph_errors = graph.get_errors(); - let (files, mut result_info) = graph.emit(EmitOptions { - bundle_type, - check: args.check.unwrap_or(true), - debug, - maybe_user_config: args.compiler_options, - })?; - result_info.diagnostics.extend_graph_errors(graph_errors); + let tsc_emit = check && args.bundle.is_none(); + let (ts_config, maybe_ignored_options) = emit::get_ts_config( + emit::ConfigType::RuntimeEmit { tsc_emit }, + None, + args.compiler_options.as_ref(), + )?; + let (files, mut diagnostics, stats) = if check && args.bundle.is_none() { + let (diagnostics, stats) = if args.sources.is_none() + && emit::valid_emit( + graph.as_ref(), + cache.as_cacher(), + &ts_config, + ps.flags.reload, + &HashSet::default(), + ) { + log::debug!( + "cache is valid for \"{}\", skipping check/emit", + root_specifier + ); + (Diagnostics::default(), emit::Stats::default()) + } else { + let emit_result = emit::check_and_maybe_emit( + graph.clone(), + cache.as_mut_cacher(), + emit::CheckOptions { + debug, + emit_with_diagnostics: true, + maybe_config_specifier: None, + ts_config, + }, + )?; + (emit_result.diagnostics, emit_result.stats) + }; + let files = emit::to_file_map(graph.as_ref(), cache.as_mut_cacher()); + (files, diagnostics, stats) + } else if let Some(bundle) = &args.bundle { + let (diagnostics, stats) = if check { + if ts_config.get_declaration() { + return Err(custom_error("TypeError", "The bundle option is set, but the compiler option of `declaration` is true which is not currently supported.")); + } + let emit_result = emit::check_and_maybe_emit( + graph.clone(), + cache.as_mut_cacher(), + emit::CheckOptions { + debug, + emit_with_diagnostics: true, + maybe_config_specifier: None, + ts_config: ts_config.clone(), + }, + )?; + (emit_result.diagnostics, emit_result.stats) + } else { + (Diagnostics::default(), Default::default()) + }; + let (emit, maybe_map) = emit::bundle( + graph.as_ref(), + emit::BundleOptions { + bundle_type: bundle.into(), + ts_config, + }, + )?; + let mut files = HashMap::new(); + files.insert("deno:///bundle.js".to_string(), emit); + if let Some(map) = maybe_map { + files.insert("deno:///bundle.js.map".to_string(), map); + } + (files, diagnostics, stats) + } else { + if ts_config.get_declaration() { + return Err(custom_error("TypeError", "The option of `check` is false, but the compiler option of `declaration` is true which is not currently supported.")); + } + let emit_result = emit::emit( + graph.as_ref(), + cache.as_mut_cacher(), + emit::EmitOptions { + reload: ps.flags.reload, + ts_config, + reload_exclusions: HashSet::default(), + }, + )?; + let files = emit::to_file_map(graph.as_ref(), cache.as_mut_cacher()); + (files, emit_result.diagnostics, emit_result.stats) + }; + + // we want to add any errors that were returned as an `Err` earlier by adding + // them to the diagnostics. + diagnostics.extend_graph_errors(graph.errors()); Ok(EmitResult { - diagnostics: result_info.diagnostics, + diagnostics, files, - ignored_options: result_info.maybe_ignored_options, - stats: result_info.stats, + maybe_ignored_options, + stats, }) } diff --git a/cli/proc_state.rs b/cli/proc_state.rs index 2e1fb0e317..fe707754f7 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -1,23 +1,24 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +use crate::cache; use crate::colors; use crate::compat; use crate::config_file::ConfigFile; use crate::deno_dir; +use crate::emit; +use crate::errors::get_module_graph_error_class; use crate::file_fetcher::CacheSetting; use crate::file_fetcher::FileFetcher; use crate::flags; use crate::http_cache; +use crate::lockfile::as_maybe_locker; use crate::lockfile::Lockfile; -use crate::module_graph::CheckOptions; -use crate::module_graph::GraphBuilder; -use crate::module_graph::TranspileOptions; -use crate::module_graph::TypeLib; +use crate::resolver::ImportMapResolver; use crate::source_maps::SourceMapGetter; -use crate::specifier_handler::FetchHandler; use crate::version; use deno_core::error::anyhow; +use deno_core::error::custom_error; use deno_core::error::get_custom_error_class; use deno_core::error::AnyError; use deno_core::error::Context; @@ -36,9 +37,6 @@ use deno_tls::rustls::RootCertStore; use deno_tls::rustls_native_certs::load_native_certs; use deno_tls::webpki_roots::TLS_SERVER_ROOTS; use import_map::ImportMap; -use log::debug; -use log::info; -use log::warn; use std::collections::HashMap; use std::collections::HashSet; use std::env; @@ -59,12 +57,25 @@ pub struct Inner { pub dir: deno_dir::DenoDir, pub coverage_dir: Option, pub file_fetcher: FileFetcher, - pub modules: - Arc>>>, + modules: Arc>>>, pub lockfile: Option>>, pub maybe_config_file: Option, pub maybe_import_map: Option, pub maybe_inspector_server: Option>, + // deno_graph detects all sorts of issues at build time (prepare_module_load) + // but if they are errors at that stage, the don't cause the correct behaviors + // so we cache the error and then surface it when appropriate (e.g. load) + pub(crate) maybe_graph_error: + Arc>>, + // because the graph detects resolution issues early, but is build and dropped + // during the `prepare_module_load` method, we need to extract out the module + // resolution map so that those errors can be surfaced at the appropriate time + resolution_map: + Arc>>>, + // in some cases we want to provide the span where the resolution error + // occurred but need to surface it on load, but on load we don't know who the + // referrer and span was, so we need to cache those + resolved_map: Arc>>, pub root_cert_store: Option, pub blob_store: BlobStore, pub broadcast_channel: InMemoryBroadcastChannel, @@ -222,11 +233,11 @@ impl ProcState { let diagnostics = import_map.update_imports(node_builtins)?; if !diagnostics.is_empty() { - info!("Some Node built-ins were not added to the import map:"); + log::info!("Some Node built-ins were not added to the import map:"); for diagnostic in diagnostics { - info!(" - {}", diagnostic); + log::info!(" - {}", diagnostic); } - info!("If you want to use Node built-ins provided by Deno remove listed specifiers from \"imports\" mapping in the import map file."); + log::info!("If you want to use Node built-ins provided by Deno remove listed specifiers from \"imports\" mapping in the import map file."); } maybe_import_map = Some(import_map); @@ -252,6 +263,9 @@ impl ProcState { maybe_config_file, maybe_import_map, maybe_inspector_server, + maybe_graph_error: Default::default(), + resolution_map: Default::default(), + resolved_map: Default::default(), root_cert_store: Some(root_cert_store.clone()), blob_store, broadcast_channel, @@ -260,72 +274,174 @@ impl ProcState { }))) } - /// Prepares a set of module specifiers for loading in one shot. - pub async fn prepare_module_graph( + /// Return any imports that should be brought into the scope of the module + /// graph. + fn get_maybe_imports(&self) -> Option)>> { + let mut imports = Vec::new(); + if let Some(config_file) = &self.maybe_config_file { + if let Some(config_imports) = config_file.to_maybe_imports() { + imports.extend(config_imports); + } + } + if self.flags.compat { + imports.extend(compat::get_node_imports()); + } + if imports.is_empty() { + None + } else { + Some(imports) + } + } + + /// This method is called when a module requested by the `JsRuntime` is not + /// available, or in other sub-commands that need to "load" a module graph. + /// The method will collect all the dependencies of the provided specifier, + /// optionally checks their integrity, optionally type checks them, and + /// ensures that any modules that needs to be transpiled is transpiled. + /// + /// It then populates the `loadable_modules` with what can be loaded into v8. + pub(crate) async fn prepare_module_load( &self, - specifiers: Vec, - lib: TypeLib, + roots: Vec, + is_dynamic: bool, + lib: emit::TypeLib, root_permissions: Permissions, dynamic_permissions: Permissions, - maybe_import_map: Option, ) -> Result<(), AnyError> { - let handler = Arc::new(Mutex::new(FetchHandler::new( - self, - root_permissions, - dynamic_permissions, - )?)); + let mut cache = cache::FetchCacher::new( + self.dir.gen_cache.clone(), + self.file_fetcher.clone(), + root_permissions.clone(), + dynamic_permissions.clone(), + ); + let maybe_locker = as_maybe_locker(self.lockfile.clone()); + let maybe_imports = self.get_maybe_imports(); + let maybe_resolver = + self.maybe_import_map.as_ref().map(ImportMapResolver::new); + let graph = deno_graph::create_graph( + roots, + is_dynamic, + maybe_imports, + &mut cache, + maybe_resolver.as_ref().map(|im| im.as_resolver()), + maybe_locker, + None, + ) + .await; + // If there was a locker, validate the integrity of all the modules in the + // locker. + emit::lock(&graph); - let mut builder = - GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone()); - - for specifier in specifiers { - builder.add(&specifier, false).await?; - } - builder.analyze_config_file(&self.maybe_config_file).await?; - - let mut graph = builder.get_graph(); - let debug = self.flags.log_level == Some(log::Level::Debug); - let maybe_config_file = self.maybe_config_file.clone(); - let reload_exclusions = { + // Determine any modules that have already been emitted this session and + // should be skipped. + let reload_exclusions: HashSet = { let modules = self.modules.lock(); - modules.keys().cloned().collect::>() + modules.keys().cloned().collect() }; - let result_modules = if self.flags.no_check { - let result_info = graph.transpile(TranspileOptions { - debug, - maybe_config_file, - reload: self.flags.reload, - reload_exclusions, - })?; - debug!("{}", result_info.stats); - if let Some(ignored_options) = result_info.maybe_ignored_options { - warn!("{}", ignored_options); - } - result_info.loadable_modules + let config_type = if self.flags.no_check { + emit::ConfigType::Emit } else { - let result_info = graph.check(CheckOptions { - debug, - emit: true, + emit::ConfigType::Check { + tsc_emit: true, lib, - maybe_config_file, - reload: self.flags.reload, - reload_exclusions, - })?; - - debug!("{}", result_info.stats); - if let Some(ignored_options) = result_info.maybe_ignored_options { - eprintln!("{}", ignored_options); } - if !result_info.diagnostics.is_empty() { - return Err(anyhow!(result_info.diagnostics)); - } - result_info.loadable_modules }; - let mut loadable_modules = self.modules.lock(); - loadable_modules.extend(result_modules); + let (ts_config, maybe_ignored_options) = + emit::get_ts_config(config_type, self.maybe_config_file.as_ref(), None)?; + let graph = Arc::new(graph); + // we will store this in proc state later, as if we were to return it from + // prepare_load, some dynamic errors would not be catchable + let maybe_graph_error = graph.valid().err(); + + if emit::valid_emit( + graph.as_ref(), + &cache, + &ts_config, + self.flags.reload, + &reload_exclusions, + ) { + if let Some(root) = graph.roots.get(0) { + log::debug!("specifier \"{}\" and dependencies have valid emit, skipping checking and emitting", root); + } else { + log::debug!("rootless graph, skipping checking and emitting"); + } + } else { + if let Some(ignored_options) = maybe_ignored_options { + log::warn!("{}", ignored_options); + } + let emit_result = if self.flags.no_check { + let options = emit::EmitOptions { + ts_config, + reload_exclusions, + reload: self.flags.reload, + }; + emit::emit(graph.as_ref(), &mut cache, options)? + } else { + // here, we are type checking, so we want to error here if any of the + // type only dependencies are missing or we have other errors with them + // where as if we are not type checking, we shouldn't care about these + // errors, and they don't get returned in `graph.valid()` above. + graph.valid_types_only()?; + + let maybe_config_specifier = self + .maybe_config_file + .as_ref() + .map(|cf| ModuleSpecifier::from_file_path(&cf.path).unwrap()); + let options = emit::CheckOptions { + debug: self.flags.log_level == Some(log::Level::Debug), + emit_with_diagnostics: true, + maybe_config_specifier, + ts_config, + }; + for root in &graph.roots { + let root_str = root.to_string(); + // `$deno$` specifiers are internal specifiers, printing out that + // they are being checked is confusing to a user, since they don't + // actually exist, so we will simply indicate that a generated module + // is being checked instead of the cryptic internal module + if !root_str.contains("$deno$") { + log::info!("{} {}", colors::green("Check"), root); + } else { + log::info!("{} a generated module", colors::green("Check")) + } + } + emit::check_and_maybe_emit(graph.clone(), &mut cache, options)? + }; + log::debug!("{}", emit_result.stats); + // if the graph is not valid then the diagnostics returned are bogus and + // should just be ignored so that module loading can proceed to allow the + // "real" error to be surfaced + if !emit_result.diagnostics.is_empty() && maybe_graph_error.is_none() { + return Err(anyhow!(emit_result.diagnostics)); + } + } + + // we iterate over the graph, looking for any modules that were emitted, or + // should be loaded as their un-emitted source and add them to the in memory + // cache of modules for loading by deno_core. + { + let mut modules = self.modules.lock(); + modules.extend(emit::to_module_sources(graph.as_ref(), &cache)); + } + + // since we can't store the graph in proc state, because proc state needs to + // be thread safe because of the need to provide source map resolution and + // the graph needs to not be thread safe (due to wasmbind_gen constraints), + // we have no choice but to extract out other meta data from the graph to + // provide the correct loading behaviors for CLI + { + let mut resolution_map = self.resolution_map.lock(); + resolution_map.extend(graph.resolution_map()); + } + { + let mut self_maybe_graph_error = self.maybe_graph_error.lock(); + *self_maybe_graph_error = maybe_graph_error; + } + + // any updates to the lockfile should be updated now if let Some(ref lockfile) = self.lockfile { let g = lockfile.lock(); g.write()?; @@ -334,127 +450,116 @@ impl ProcState { Ok(()) } - /// This function is called when new module load is initialized by the JsRuntime. Its - /// resposibility is to collect all dependencies and if it is required then also perform TS - /// typecheck and traspilation. - pub async fn prepare_module_load( + pub(crate) fn resolve( &self, - specifier: ModuleSpecifier, - lib: TypeLib, - root_permissions: Permissions, - dynamic_permissions: Permissions, - is_dynamic: bool, - maybe_import_map: Option, - ) -> Result<(), AnyError> { - let specifier = specifier.clone(); - let handler = Arc::new(Mutex::new(FetchHandler::new( - self, - root_permissions, - dynamic_permissions, - )?)); - let mut builder = - GraphBuilder::new(handler, maybe_import_map, self.lockfile.clone()); - if self.flags.compat { - builder.add(&compat::get_node_globals_url(), false).await?; - } - builder.add(&specifier, is_dynamic).await?; - builder.analyze_config_file(&self.maybe_config_file).await?; - let mut graph = builder.get_graph(); - let debug = self.flags.log_level == Some(log::Level::Debug); - let maybe_config_file = self.maybe_config_file.clone(); - let reload_exclusions = { - let modules = self.modules.lock(); - modules.keys().cloned().collect::>() - }; - - let result_modules = if self.flags.no_check { - let result_info = graph.transpile(TranspileOptions { - debug, - maybe_config_file, - reload: self.flags.reload, - reload_exclusions, - })?; - debug!("{}", result_info.stats); - if let Some(ignored_options) = result_info.maybe_ignored_options { - warn!("{}", ignored_options); + specifier: &str, + referrer: &str, + ) -> Result { + let resolution_map = self.resolution_map.lock(); + if let Some((_, Some(map))) = deno_core::resolve_url_or_path(referrer) + .ok() + .map(|s| (s.clone(), resolution_map.get(&s))) + { + if let Some(resolved) = map.get(specifier) { + match resolved { + Some(Ok((specifier, span))) => { + let mut resolved_map = self.resolved_map.lock(); + resolved_map.insert(specifier.clone(), span.clone()); + return Ok(specifier.clone()); + } + Some(Err(err)) => { + return Err(custom_error( + "TypeError", + format!("{}\n", err.to_string_with_span()), + )) + } + _ => (), + } } - result_info.loadable_modules + } + // FIXME(bartlomieju): hacky way to provide compatibility with repl + let referrer = if referrer.is_empty() && self.flags.repl { + deno_core::DUMMY_SPECIFIER } else { - let result_info = graph.check(CheckOptions { - debug, - emit: true, - lib, - maybe_config_file, - reload: self.flags.reload, - reload_exclusions, - })?; - - debug!("{}", result_info.stats); - if let Some(ignored_options) = result_info.maybe_ignored_options { - eprintln!("{}", ignored_options); - } - if !result_info.diagnostics.is_empty() { - return Err(anyhow!(result_info.diagnostics)); - } - result_info.loadable_modules + referrer }; - - let mut loadable_modules = self.modules.lock(); - loadable_modules.extend(result_modules); - - if let Some(ref lockfile) = self.lockfile { - let g = lockfile.lock(); - g.write()?; + if let Some(import_map) = &self.maybe_import_map { + import_map + .resolve(specifier, referrer) + .map_err(|err| err.into()) + } else { + deno_core::resolve_import(specifier, referrer).map_err(|err| err.into()) } - - Ok(()) } pub fn load( &self, specifier: ModuleSpecifier, maybe_referrer: Option, + is_dynamic: bool, ) -> Result { + log::debug!( + "specifier: {} maybe_referrer: {} is_dynamic: {}", + specifier, + maybe_referrer + .as_ref() + .map(|s| s.to_string()) + .unwrap_or_else(|| "".to_string()), + is_dynamic + ); let modules = self.modules.lock(); modules .get(&specifier) .map(|r| match r { Ok(module_source) => Ok(module_source.clone()), Err(err) => { - // TODO(@kitsonk) this feels a bit hacky but it works, without - // introducing another enum to have to try to deal with. - if get_custom_error_class(err) == Some("NotFound") { - let message = if let Some(referrer) = &maybe_referrer { - format!("{}\n From: {}\n If the source module contains only types, use `import type` and `export type` to import it instead.", err, referrer) - } else { - format!("{}\n If the source module contains only types, use `import type` and `export type` to import it instead.", err) - }; - warn!("{}: {}", crate::colors::yellow("warning"), message); - Ok(ModuleSource { - code: "".to_string(), - module_url_found: specifier.to_string(), - module_url_specified: specifier.to_string(), - }) + // this is the "pending" error we will return + let err = if let Some(error_class) = get_custom_error_class(err) { + if error_class == "NotFound" && maybe_referrer.is_some() && !is_dynamic { + let resolved_map = self.resolved_map.lock(); + // in situations where we were to try to load a module that wasn't + // emitted and we can't run the original source code (it isn't) + // JavaScript, we will load a blank module instead. This is + // usually caused by people exporting type only exports and not + // type checking. + if let Some(span) = resolved_map.get(&specifier) { + log::warn!("{}: Cannot load module \"{}\".\n at {}\n If the source module contains only types, use `import type` and `export type` to import it instead.", colors::yellow("warning"), specifier, span); + return Ok(ModuleSource { + code: "".to_string(), + module_url_found: specifier.to_string(), + module_url_specified: specifier.to_string(), + }); + } + } + custom_error(error_class, err.to_string()) } else { - // anyhow errors don't support cloning, so we have to manage this - // ourselves - Err(anyhow!(err.to_string())) + anyhow!(err.to_string()) + }; + // if there is a pending graph error though we haven't returned, we + // will return that one + let mut maybe_graph_error = self.maybe_graph_error.lock(); + if let Some(graph_error) = maybe_graph_error.take() { + log::debug!("returning cached graph error"); + let resolved_map = self.resolved_map.lock(); + if let Some(span) = resolved_map.get(&specifier) { + if !span.specifier.as_str().contains("$deno") { + return Err(custom_error(get_module_graph_error_class(&graph_error), format!("{}\n at {}", graph_error, span))); + } + } + Err(graph_error.into()) + } else { + Err(err) } - }, + } }) .unwrap_or_else(|| { - if let Some(referrer) = maybe_referrer { - Err(anyhow!( - "Module \"{}\" is missing from the graph.\n From: {}", - specifier, - referrer - )) - } else { - Err(anyhow!( - "Module \"{}\" is missing from the graph.", - specifier - )) + if maybe_referrer.is_some() && !is_dynamic { + let resolved_map = self.resolved_map.lock(); + if let Some(span) = resolved_map.get(&specifier) { + return Err(custom_error("NotFound", format!("Cannot load module \"{}\".\n at {}", specifier, span))); + } } + Err(custom_error("NotFound", format!("Cannot load module \"{}\".", specifier))) }) } @@ -497,7 +602,7 @@ impl SourceMapGetter for ProcState { if let Some((code, maybe_map)) = self.get_emit(&specifier) { let code = String::from_utf8(code).unwrap(); source_map_from_code(code).or(maybe_map) - } else if let Ok(source) = self.load(specifier, None) { + } else if let Ok(source) = self.load(specifier, None, false) { source_map_from_code(source.code) } else { None diff --git a/cli/resolver.rs b/cli/resolver.rs new file mode 100644 index 0000000000..d3427c58b5 --- /dev/null +++ b/cli/resolver.rs @@ -0,0 +1,35 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::ModuleSpecifier; +use deno_graph::source::Resolver; +use import_map::ImportMap; + +/// Wraps an import map to be used when building a deno_graph module graph. +/// This is done to avoid having `import_map` be a direct dependency of +/// `deno_graph`. +#[derive(Debug)] +pub(crate) struct ImportMapResolver<'a>(&'a ImportMap); + +impl<'a> ImportMapResolver<'a> { + pub fn new(import_map: &'a ImportMap) -> Self { + Self(import_map) + } + + pub fn as_resolver(&'a self) -> &'a dyn Resolver { + self + } +} + +impl Resolver for ImportMapResolver<'_> { + fn resolve( + &self, + specifier: &str, + referrer: &ModuleSpecifier, + ) -> Result { + self + .0 + .resolve(specifier, referrer.as_str()) + .map_err(|err| err.into()) + } +} diff --git a/cli/specifier_handler.rs b/cli/specifier_handler.rs deleted file mode 100644 index aec35a7387..0000000000 --- a/cli/specifier_handler.rs +++ /dev/null @@ -1,776 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -use crate::ast::Location; -use crate::disk_cache::DiskCache; -use crate::file_fetcher::FileFetcher; -use crate::proc_state::ProcState; - -use deno_ast::MediaType; -use deno_core::error::custom_error; -use deno_core::error::AnyError; -use deno_core::futures::future; -use deno_core::futures::Future; -use deno_core::futures::FutureExt; -use deno_core::serde::Deserialize; -use deno_core::serde::Serialize; -use deno_core::serde_json; -use deno_core::ModuleSpecifier; -use deno_runtime::permissions::Permissions; -use log::debug; -use std::collections::HashMap; -use std::fmt; -use std::path::PathBuf; -use std::pin::Pin; -use std::sync::Arc; - -pub type DependencyMap = HashMap; -type FetchFutureOutput = Result; -pub type FetchFuture = Pin + Send>>; - -/// A group of errors that represent errors that can occur with an -/// an implementation of `SpecifierHandler`. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum HandlerError { - /// A fetch error, where we have a location associated with it. - FetchErrorWithLocation(String, Location), -} - -impl fmt::Display for HandlerError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - HandlerError::FetchErrorWithLocation(ref err, ref location) => { - write!(f, "{}\n at {}", err, location) - } - } - } -} - -impl std::error::Error for HandlerError {} - -#[derive(Debug, Clone)] -pub struct CachedModule { - pub is_remote: bool, - pub maybe_dependencies: Option, - pub maybe_emit: Option, - pub maybe_emit_path: Option<(PathBuf, Option)>, - pub maybe_types: Option, - pub maybe_version: Option, - pub media_type: MediaType, - pub requested_specifier: ModuleSpecifier, - pub source: Arc, - pub source_path: PathBuf, - pub specifier: ModuleSpecifier, -} - -impl Default for CachedModule { - fn default() -> Self { - let specifier = deno_core::resolve_url("file:///example.js").unwrap(); - CachedModule { - is_remote: false, - maybe_dependencies: None, - maybe_emit: None, - maybe_emit_path: None, - maybe_types: None, - maybe_version: None, - media_type: MediaType::Unknown, - requested_specifier: specifier.clone(), - source: Arc::new(String::default()), - source_path: PathBuf::new(), - specifier, - } - } -} - -/// An enum to own the a specific emit. -/// -/// Currently there is only one type of emit that is cacheable, but this has -/// been added to future proof the ability for the specifier handler -/// implementations to be able to handle other types of emits, like form a -/// runtime API which might have a different configuration. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Emit { - /// Code that was emitted for use by the CLI - Cli((String, Option)), -} - -impl Default for Emit { - fn default() -> Self { - Emit::Cli(("".to_string(), None)) - } -} - -#[derive(Debug, Clone)] -pub struct Dependency { - /// Flags if the dependency is a dynamic import or not. - pub is_dynamic: bool, - /// The location in the source code where the dependency statement occurred. - pub location: Location, - /// The module specifier that resolves to the runtime code dependency for the - /// module. - pub maybe_code: Option, - /// The module specifier that resolves to the type only dependency for the - /// module. - pub maybe_type: Option, -} - -impl Dependency { - pub fn new(location: Location) -> Self { - Dependency { - is_dynamic: false, - location, - maybe_code: None, - maybe_type: None, - } - } -} - -pub trait SpecifierHandler: Sync + Send { - /// Instructs the handler to fetch a specifier or retrieve its value from the - /// cache. - fn fetch( - &mut self, - specifier: ModuleSpecifier, - maybe_location: Option, - is_dynamic: bool, - ) -> FetchFuture; - - /// Get the optional build info from the cache for a given module specifier. - /// Because build infos are only associated with the "root" modules, they are - /// not expected to be cached for each module, but are "lazily" checked when - /// a root module is identified. The `emit_type` also indicates what form - /// of the module the build info is valid for. - fn get_tsbuildinfo( - &self, - specifier: &ModuleSpecifier, - ) -> Result, AnyError>; - - /// Set the emit for the module specifier. - fn set_cache( - &mut self, - specifier: &ModuleSpecifier, - emit: &Emit, - ) -> Result<(), AnyError>; - - /// When parsed out of a JavaScript module source, the triple slash reference - /// to the types should be stored in the cache. - fn set_types( - &mut self, - specifier: &ModuleSpecifier, - types: String, - ) -> Result<(), AnyError>; - - /// Set the build info for a module specifier, also providing the cache type. - fn set_tsbuildinfo( - &mut self, - specifier: &ModuleSpecifier, - tsbuildinfo: String, - ) -> Result<(), AnyError>; - - /// Set the graph dependencies for a given module specifier. - fn set_deps( - &mut self, - specifier: &ModuleSpecifier, - dependencies: DependencyMap, - ) -> Result<(), AnyError>; - - /// Set the version of the source for a given module, which is used to help - /// determine if a module needs to be re-emitted. - fn set_version( - &mut self, - specifier: &ModuleSpecifier, - version: String, - ) -> Result<(), AnyError>; -} - -impl fmt::Debug for dyn SpecifierHandler { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "SpecifierHandler {{ }}") - } -} - -/// A representation of meta data for a compiled file. -/// -/// *Note* this is currently just a copy of what is located in `tsc.rs` but will -/// be refactored to be able to store dependencies and type information in the -/// future. -#[derive(Deserialize, Serialize)] -pub struct CompiledFileMetadata { - pub version_hash: String, -} - -impl CompiledFileMetadata { - pub fn from_bytes(bytes: &[u8]) -> Result { - let metadata_string = std::str::from_utf8(bytes)?; - serde_json::from_str::(metadata_string).map_err(|e| e.into()) - } - - pub fn to_json_string(&self) -> Result { - serde_json::to_string(self).map_err(|e| e.into()) - } -} - -/// An implementation of the `SpecifierHandler` trait that integrates with the -/// existing `file_fetcher` interface, which will eventually be refactored to -/// align it more to the `SpecifierHandler` trait. -pub struct FetchHandler { - /// An instance of disk where generated (emitted) files are stored. - disk_cache: DiskCache, - /// The set permissions which are used for root modules (static imports). - root_permissions: Permissions, - /// The set of permissions which are used for dynamic imports. - dynamic_permissions: Permissions, - /// A clone of the `ps` file fetcher. - file_fetcher: FileFetcher, -} - -impl FetchHandler { - pub fn new( - ps: &ProcState, - root_permissions: Permissions, - dynamic_permissions: Permissions, - ) -> Result { - let disk_cache = ps.dir.gen_cache.clone(); - let file_fetcher = ps.file_fetcher.clone(); - - Ok(FetchHandler { - disk_cache, - root_permissions, - dynamic_permissions, - file_fetcher, - }) - } -} - -impl SpecifierHandler for FetchHandler { - fn fetch( - &mut self, - requested_specifier: ModuleSpecifier, - maybe_location: Option, - is_dynamic: bool, - ) -> FetchFuture { - // When the module graph fetches dynamic modules, the set of dynamic - // permissions need to be applied. Other static imports have all - // permissions. - let mut permissions = if is_dynamic { - self.dynamic_permissions.clone() - } else { - self.root_permissions.clone() - }; - let file_fetcher = self.file_fetcher.clone(); - let disk_cache = self.disk_cache.clone(); - - async move { - let source_file = file_fetcher - .fetch(&requested_specifier, &mut permissions) - .await - .map_err(|err| { - let err = if let Some(e) = err.downcast_ref::() { - if e.kind() == std::io::ErrorKind::NotFound { - let message = if let Some(location) = &maybe_location { - format!( - "Cannot resolve module \"{}\" from \"{}\".", - requested_specifier, location.specifier - ) - } else { - format!("Cannot resolve module \"{}\".", requested_specifier) - }; - custom_error("NotFound", message) - } else { - err - } - } else { - err - }; - if let Some(location) = maybe_location { - // Injected modules (like test and eval) come with locations, but - // they are confusing to the user to print out the location because - // they cannot actually get to the source code that is quoted, as - // it only exists in the runtime memory of Deno. - if !location.specifier.contains("$deno$") { - ( - requested_specifier.clone(), - HandlerError::FetchErrorWithLocation(err.to_string(), location) - .into(), - ) - } else { - (requested_specifier.clone(), err) - } - } else { - (requested_specifier.clone(), err) - } - })?; - let url = &source_file.specifier; - let is_remote = !(url.scheme() == "file" - || url.scheme() == "data" - || url.scheme() == "blob"); - let filename = disk_cache.get_cache_filename_with_extension(url, "meta"); - let maybe_version = if let Some(filename) = filename { - if let Ok(bytes) = disk_cache.get(&filename) { - if let Ok(compiled_file_metadata) = - CompiledFileMetadata::from_bytes(&bytes) - { - Some(compiled_file_metadata.version_hash) - } else { - None - } - } else { - None - } - } else { - None - }; - - let mut maybe_map_path = None; - let map_path = - disk_cache.get_cache_filename_with_extension(url, "js.map"); - let maybe_map = if let Some(map_path) = map_path { - if let Ok(map) = disk_cache.get(&map_path) { - maybe_map_path = Some(disk_cache.location.join(map_path)); - Some(String::from_utf8(map).unwrap()) - } else { - None - } - } else { - None - }; - let mut maybe_emit = None; - let mut maybe_emit_path = None; - let emit_path = disk_cache.get_cache_filename_with_extension(url, "js"); - if let Some(emit_path) = emit_path { - if let Ok(code) = disk_cache.get(&emit_path) { - maybe_emit = - Some(Emit::Cli((String::from_utf8(code).unwrap(), maybe_map))); - maybe_emit_path = - Some((disk_cache.location.join(emit_path), maybe_map_path)); - } - }; - - Ok(CachedModule { - is_remote, - maybe_dependencies: None, - maybe_emit, - maybe_emit_path, - maybe_types: source_file.maybe_types, - maybe_version, - media_type: source_file.media_type, - requested_specifier, - source: source_file.source, - source_path: source_file.local, - specifier: source_file.specifier, - }) - } - .boxed() - } - - fn get_tsbuildinfo( - &self, - specifier: &ModuleSpecifier, - ) -> Result, AnyError> { - let filename = self - .disk_cache - .get_cache_filename_with_extension(specifier, "buildinfo"); - if let Some(filename) = filename { - if let Ok(tsbuildinfo) = self.disk_cache.get(&filename) { - Ok(Some(String::from_utf8(tsbuildinfo)?)) - } else { - Ok(None) - } - } else { - Ok(None) - } - } - - fn set_tsbuildinfo( - &mut self, - specifier: &ModuleSpecifier, - tsbuildinfo: String, - ) -> Result<(), AnyError> { - let filename = self - .disk_cache - .get_cache_filename_with_extension(specifier, "buildinfo") - .unwrap(); - debug!("set_tsbuildinfo - filename {:?}", filename); - self - .disk_cache - .set(&filename, tsbuildinfo.as_bytes()) - .map_err(|e| e.into()) - } - - fn set_cache( - &mut self, - specifier: &ModuleSpecifier, - emit: &Emit, - ) -> Result<(), AnyError> { - match emit { - Emit::Cli((code, maybe_map)) => { - let filename = self - .disk_cache - .get_cache_filename_with_extension(specifier, "js") - .unwrap(); - self.disk_cache.set(&filename, code.as_bytes())?; - - if let Some(map) = maybe_map { - let filename = self - .disk_cache - .get_cache_filename_with_extension(specifier, "js.map") - .unwrap(); - self.disk_cache.set(&filename, map.as_bytes())?; - } - } - }; - - Ok(()) - } - - fn set_deps( - &mut self, - _specifier: &ModuleSpecifier, - _dependencies: DependencyMap, - ) -> Result<(), AnyError> { - // file_fetcher doesn't have the concept of caching dependencies - Ok(()) - } - - fn set_types( - &mut self, - _specifier: &ModuleSpecifier, - _types: String, - ) -> Result<(), AnyError> { - // file_fetcher doesn't have the concept of caching of the types - Ok(()) - } - - fn set_version( - &mut self, - specifier: &ModuleSpecifier, - version_hash: String, - ) -> Result<(), AnyError> { - let compiled_file_metadata = CompiledFileMetadata { version_hash }; - let filename = self - .disk_cache - .get_cache_filename_with_extension(specifier, "meta") - .unwrap(); - - self - .disk_cache - .set( - &filename, - compiled_file_metadata.to_json_string()?.as_bytes(), - ) - .map_err(|e| e.into()) - } -} - -pub struct MemoryHandler { - sources: HashMap>, -} - -impl MemoryHandler { - pub fn new(sources: HashMap>) -> Self { - Self { sources } - } -} - -impl SpecifierHandler for MemoryHandler { - fn fetch( - &mut self, - specifier: ModuleSpecifier, - _maybe_referrer: Option, - _is_dynamic: bool, - ) -> FetchFuture { - let mut specifier_text = specifier.to_string(); - if !self.sources.contains_key(&specifier_text) { - specifier_text = specifier_text.replace("file:///", "/"); - if !self.sources.contains_key(&specifier_text) { - // Convert `C:/a/path/file.ts` to `/a/path/file.ts` - specifier_text = specifier_text[3..].to_string() - } - } - let result = if let Some(source) = self.sources.get(&specifier_text) { - let media_type = MediaType::from(&specifier); - let is_remote = specifier.scheme() != "file"; - - Ok(CachedModule { - source: source.clone(), - requested_specifier: specifier.clone(), - specifier, - media_type, - is_remote, - ..Default::default() - }) - } else { - Err(( - specifier.clone(), - custom_error( - "NotFound", - format!("Unable to find specifier in sources: {}", specifier), - ), - )) - }; - - Box::pin(future::ready(result)) - } - - fn get_tsbuildinfo( - &self, - _specifier: &ModuleSpecifier, - ) -> Result, AnyError> { - Ok(None) - } - - fn set_cache( - &mut self, - _specifier: &ModuleSpecifier, - _emit: &Emit, - ) -> Result<(), AnyError> { - Ok(()) - } - - fn set_types( - &mut self, - _specifier: &ModuleSpecifier, - _types: String, - ) -> Result<(), AnyError> { - Ok(()) - } - - fn set_tsbuildinfo( - &mut self, - _specifier: &ModuleSpecifier, - _tsbuildinfo: String, - ) -> Result<(), AnyError> { - Ok(()) - } - - fn set_deps( - &mut self, - _specifier: &ModuleSpecifier, - _dependencies: DependencyMap, - ) -> Result<(), AnyError> { - Ok(()) - } - - fn set_version( - &mut self, - _specifier: &ModuleSpecifier, - _version: String, - ) -> Result<(), AnyError> { - Ok(()) - } -} - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::deno_dir::DenoDir; - use crate::file_fetcher::CacheSetting; - use crate::http_cache::HttpCache; - use deno_core::resolve_url_or_path; - use deno_runtime::deno_web::BlobStore; - use tempfile::TempDir; - - macro_rules! map ( - { $($key:expr => $value:expr),+ } => { - { - let mut m = ::std::collections::HashMap::new(); - $( - m.insert($key, $value); - )+ - m - } - }; - ); - - fn setup() -> (TempDir, FetchHandler) { - let temp_dir = TempDir::new().expect("could not setup"); - let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf())) - .expect("could not setup"); - - let file_fetcher = FileFetcher::new( - HttpCache::new(&temp_dir.path().to_path_buf().join("deps")), - CacheSetting::Use, - true, - None, - BlobStore::default(), - None, - ) - .expect("could not setup"); - let disk_cache = deno_dir.gen_cache; - - let fetch_handler = FetchHandler { - disk_cache, - root_permissions: Permissions::allow_all(), - dynamic_permissions: Permissions::default(), - file_fetcher, - }; - - (temp_dir, fetch_handler) - } - - #[tokio::test] - async fn test_fetch_handler_fetch() { - let _http_server_guard = test_util::http_server(); - let (_, mut file_fetcher) = setup(); - let specifier = - resolve_url_or_path("http://localhost:4545/subdir/mod2.ts").unwrap(); - let cached_module: CachedModule = file_fetcher - .fetch(specifier.clone(), None, false) - .await - .unwrap(); - assert!(cached_module.maybe_emit.is_none()); - assert!(cached_module.maybe_dependencies.is_none()); - assert_eq!(cached_module.media_type, MediaType::TypeScript); - assert_eq!( - cached_module.source.as_str(), - "export { printHello } from \"./print_hello.ts\";\n" - ); - assert_eq!(cached_module.specifier, specifier); - } - - #[tokio::test] - async fn test_fetch_handler_set_cache() { - let _http_server_guard = test_util::http_server(); - let (_, mut file_fetcher) = setup(); - let specifier = - resolve_url_or_path("http://localhost:4545/subdir/mod2.ts").unwrap(); - let cached_module: CachedModule = file_fetcher - .fetch(specifier.clone(), None, false) - .await - .unwrap(); - assert!(cached_module.maybe_emit.is_none()); - let code = String::from("some code"); - file_fetcher - .set_cache(&specifier, &Emit::Cli((code, None))) - .expect("could not set cache"); - let cached_module: CachedModule = file_fetcher - .fetch(specifier.clone(), None, false) - .await - .unwrap(); - assert_eq!( - cached_module.maybe_emit, - Some(Emit::Cli(("some code".to_string(), None))) - ); - } - - #[tokio::test] - async fn test_fetch_handler_is_remote() { - let _http_server_guard = test_util::http_server(); - let (_, mut file_fetcher) = setup(); - let specifier = - resolve_url_or_path("http://localhost:4545/subdir/mod2.ts").unwrap(); - let cached_module: CachedModule = - file_fetcher.fetch(specifier, None, false).await.unwrap(); - assert!(cached_module.is_remote); - let specifier = resolve_url_or_path( - test_util::testdata_path() - .join("subdir/mod1.ts") - .as_os_str() - .to_str() - .unwrap(), - ) - .unwrap(); - let cached_module: CachedModule = - file_fetcher.fetch(specifier, None, false).await.unwrap(); - assert!(!cached_module.is_remote); - } - - #[tokio::test] - async fn test_memory_handler_fetch() { - let a_src = r#" - import * as b from "./b.ts"; - console.log(b); - "#; - let b_src = r#" - export const b = "b"; - "#; - let c_src = r#" - export const c = "c"; - "#; - let d_src = r#" - export const d: string; - "#; - let sources = map!( - "/a.ts" => a_src, - "/b.ts" => b_src, - "https://deno.land/x/c.js" => c_src, - "https://deno.land/x/d.d.ts" => d_src - ); - let sources: HashMap> = sources - .iter() - .map(|(k, v)| (k.to_string(), Arc::new(v.to_string()))) - .collect(); - let mut handler = MemoryHandler::new(sources); - let specifier = resolve_url_or_path("file:///a.ts").unwrap(); - let actual: CachedModule = handler - .fetch(specifier.clone(), None, false) - .await - .expect("could not fetch module"); - assert_eq!(actual.source.as_str(), a_src); - assert_eq!(actual.requested_specifier, specifier); - assert_eq!(actual.specifier, specifier); - assert_eq!(actual.media_type, MediaType::TypeScript); - assert!(!actual.is_remote); - - let specifier = resolve_url_or_path("file:///b.ts").unwrap(); - let actual: CachedModule = handler - .fetch(specifier.clone(), None, false) - .await - .expect("could not fetch module"); - assert_eq!(actual.source.as_str(), b_src); - assert_eq!(actual.requested_specifier, specifier); - assert_eq!(actual.specifier, specifier); - assert_eq!(actual.media_type, MediaType::TypeScript); - assert!(!actual.is_remote); - - let specifier = resolve_url_or_path("https://deno.land/x/c.js").unwrap(); - let actual: CachedModule = handler - .fetch(specifier.clone(), None, false) - .await - .expect("could not fetch module"); - assert_eq!(actual.source.as_str(), c_src); - assert_eq!(actual.requested_specifier, specifier); - assert_eq!(actual.specifier, specifier); - assert_eq!(actual.media_type, MediaType::JavaScript); - assert!(actual.is_remote); - - let specifier = resolve_url_or_path("https://deno.land/x/d.d.ts").unwrap(); - let actual: CachedModule = handler - .fetch(specifier.clone(), None, false) - .await - .expect("could not fetch module"); - assert_eq!(actual.source.as_str(), d_src); - assert_eq!(actual.requested_specifier, specifier); - assert_eq!(actual.specifier, specifier); - assert_eq!(actual.media_type, MediaType::Dts); - assert!(actual.is_remote); - - let specifier = - resolve_url_or_path("https://deno.land/x/missing.ts").unwrap(); - handler - .fetch(specifier.clone(), None, false) - .await - .expect_err("should have errored"); - - let specifier = resolve_url_or_path("/a.ts").unwrap(); - let actual: CachedModule = handler - .fetch(specifier.clone(), None, false) - .await - .expect("could not fetch module"); - assert_eq!(actual.source.as_str(), a_src); - assert_eq!(actual.requested_specifier, specifier); - assert_eq!(actual.specifier, specifier); - assert_eq!(actual.media_type, MediaType::TypeScript); - assert!(!actual.is_remote); - - let specifier = resolve_url_or_path("file:///C:/a.ts").unwrap(); - let actual: CachedModule = handler - .fetch(specifier.clone(), None, false) - .await - .expect("could not fetch module"); - assert_eq!(actual.source.as_str(), a_src); - assert_eq!(actual.requested_specifier, specifier); - assert_eq!(actual.specifier, specifier); - assert_eq!(actual.media_type, MediaType::TypeScript); - assert!(!actual.is_remote); - } -} diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 0b06beed45..215ba1a240 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -2761,7 +2761,6 @@ fn lsp_diagnostics_warn() { .unwrap(); assert!(maybe_err.is_none()); assert!(maybe_res.is_some()); - let (method, _) = client.read_notification::().unwrap(); assert_eq!(method, "textDocument/publishDiagnostics"); let (method, _) = client.read_notification::().unwrap(); diff --git a/cli/tests/integration/watcher_tests.rs b/cli/tests/integration/watcher_tests.rs index 6148bcf2bd..feda1bac70 100644 --- a/cli/tests/integration/watcher_tests.rs +++ b/cli/tests/integration/watcher_tests.rs @@ -206,6 +206,7 @@ fn bundle_js_watch() { let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); std::thread::sleep(std::time::Duration::from_secs(1)); + assert_contains!(stderr_lines.next().unwrap(), "Check"); assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js"); assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); let file = PathBuf::from(&bundle); @@ -214,6 +215,7 @@ fn bundle_js_watch() { write(&file_to_watch, "console.log('Hello world2');").unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); + assert_contains!(stderr_lines.next().unwrap(), "Check"); assert_contains!(stderr_lines.next().unwrap(), "File change detected!"); assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js"); assert_contains!(stderr_lines.next().unwrap(), "mod6.bundle.js"); @@ -261,6 +263,7 @@ fn bundle_watch_not_exit() { // Make sure the watcher actually restarts and works fine with the proper syntax write(&file_to_watch, "console.log(42);").unwrap(); std::thread::sleep(std::time::Duration::from_secs(1)); + assert_contains!(stderr_lines.next().unwrap(), "Check"); assert_contains!(stderr_lines.next().unwrap(), "File change detected!"); assert_contains!(stderr_lines.next().unwrap(), "file_to_watch.js"); assert_contains!(stderr_lines.next().unwrap(), "target.js"); diff --git a/cli/tests/testdata/041_dyn_import_eval.out b/cli/tests/testdata/041_dyn_import_eval.out index 12a45b8da9..a1d6c3687a 100644 --- a/cli/tests/testdata/041_dyn_import_eval.out +++ b/cli/tests/testdata/041_dyn_import_eval.out @@ -1 +1,2 @@ +[WILDCARD] Module { isMod4: true } diff --git a/cli/tests/testdata/055_info_file_json.out b/cli/tests/testdata/055_info_file_json.out index 4753ef0ef1..a85bc83c4d 100644 --- a/cli/tests/testdata/055_info_file_json.out +++ b/cli/tests/testdata/055_info_file_json.out @@ -1,53 +1,91 @@ { - "root": "file://[WILDCARD]/005_more_imports.ts", + "roots": [ + "file://[WILDCARD]/005_more_imports.ts" + ], "modules": [ { - "specifier": "file://[WILDCARD]/005_more_imports.ts", "dependencies": [ { "specifier": "./subdir/mod1.ts", - "code": "file://[WILDCARD]/subdir/mod1.ts" + "code": { + "specifier": "file://[WILDCARD]/subdir/mod1.ts", + "span": { + "start": { + "line": 0, + "character": 52 + }, + "end": { + "line": 0, + "character": 70 + } + } + } } ], - "size": 211, - "mediaType": "TypeScript", "local": "[WILDCARD]005_more_imports.ts", [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/005_more_imports.ts" }, { - "specifier": "file://[WILDCARD]/subdir/mod1.ts", "dependencies": [ { "specifier": "./subdir2/mod2.ts", - "code": "file://[WILDCARD]/subdir/subdir2/mod2.ts" + "code": { + "specifier": "file://[WILDCARD]/subdir/subdir2/mod2.ts", + "span": { + "start": { + "line": 0, + "character": 40 + }, + "end": { + "line": 0, + "character": 59 + } + } + } } ], - "size": 308, - "mediaType": "TypeScript", "local": "[WILDCARD]mod1.ts", [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/subdir/mod1.ts" }, { - "specifier": "file://[WILDCARD]/subdir/print_hello.ts", "dependencies": [], - "size": 57, - "mediaType": "TypeScript", "local": "[WILDCARD]print_hello.ts", [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/subdir/print_hello.ts" }, { - "specifier": "file://[WILDCARD]/subdir/subdir2/mod2.ts", "dependencies": [ { "specifier": "../print_hello.ts", - "code": "file://[WILDCARD]/subdir/print_hello.ts" + "code": { + "specifier": "file://[WILDCARD]/subdir/print_hello.ts", + "span": { + "start": { + "line": 0, + "character": 27 + }, + "end": { + "line": 0, + "character": 46 + } + } + } } ], - "size": 157, - "mediaType": "TypeScript", "local": "[WILDCARD]mod2.ts", [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/subdir/subdir2/mod2.ts" } ], - "size": 733 + "redirects": {} } diff --git a/cli/tests/testdata/076_info_json_deps_order.out b/cli/tests/testdata/076_info_json_deps_order.out index bcb2c70ed7..69727e6e80 100644 --- a/cli/tests/testdata/076_info_json_deps_order.out +++ b/cli/tests/testdata/076_info_json_deps_order.out @@ -1,78 +1,164 @@ { - "root": "file://[WILDCARD]/076_info_json_deps_order.ts", + "roots": [ + "file://[WILDCARD]/076_info_json_deps_order.ts" + ], "modules": [ { - "specifier": "file://[WILDCARD]/076_info_json_deps_order.ts", "dependencies": [ { "specifier": "./recursive_imports/A.ts", - "code": "file://[WILDCARD]/recursive_imports/A.ts" + "code": { + "specifier": "file://[WILDCARD]/recursive_imports/A.ts", + "span": { + "start": { + "line": 1, + "character": 18 + }, + "end": { + "line": 1, + "character": 44 + } + } + } } ], - "size": 81, - "mediaType": "TypeScript", "local": "[WILDCARD]076_info_json_deps_order.ts", - "checksum": "5dd40fe33e5924cca513489ce568e86c9b9fe318a87975403c8923629018680d" + [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/076_info_json_deps_order.ts" }, { - "specifier": "file://[WILDCARD]/recursive_imports/A.ts", "dependencies": [ { "specifier": "./B.ts", - "code": "file://[WILDCARD]/recursive_imports/B.ts" + "code": { + "specifier": "file://[WILDCARD]/recursive_imports/B.ts", + "span": { + "start": { + "line": 0, + "character": 18 + }, + "end": { + "line": 0, + "character": 26 + } + } + } }, { "specifier": "./common.ts", - "code": "file://[WILDCARD]/recursive_imports/common.ts" + "code": { + "specifier": "file://[WILDCARD]/recursive_imports/common.ts", + "span": { + "start": { + "line": 1, + "character": 22 + }, + "end": { + "line": 1, + "character": 35 + } + } + } } ], - "size": 108, - "mediaType": "TypeScript", "local": "[WILDCARD]A.ts", - "checksum": "3b45a105d892584298490cb73372b2cac57118e1e42a677a1d5cacea704d8d3a" + [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/recursive_imports/A.ts" }, { - "specifier": "file://[WILDCARD]/recursive_imports/B.ts", "dependencies": [ { "specifier": "./C.ts", - "code": "file://[WILDCARD]/recursive_imports/C.ts" + "code": { + "specifier": "file://[WILDCARD]/recursive_imports/C.ts", + "span": { + "start": { + "line": 0, + "character": 18 + }, + "end": { + "line": 0, + "character": 26 + } + } + } }, { "specifier": "./common.ts", - "code": "file://[WILDCARD]/recursive_imports/common.ts" + "code": { + "specifier": "file://[WILDCARD]/recursive_imports/common.ts", + "span": { + "start": { + "line": 1, + "character": 22 + }, + "end": { + "line": 1, + "character": 35 + } + } + } } ], - "size": 108, - "mediaType": "TypeScript", "local": "[WILDCARD]B.ts", - "checksum": "b12b0437ef9a91c4a4b1f66e8e4339f986b60bd8134031ccb296ce49df15b54e" + [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/recursive_imports/B.ts" }, { - "specifier": "file://[WILDCARD]/recursive_imports/C.ts", "dependencies": [ { "specifier": "./A.ts", - "code": "file://[WILDCARD]/recursive_imports/A.ts" + "code": { + "specifier": "file://[WILDCARD]/recursive_imports/A.ts", + "span": { + "start": { + "line": 0, + "character": 18 + }, + "end": { + "line": 0, + "character": 26 + } + } + } }, { "specifier": "./common.ts", - "code": "file://[WILDCARD]/recursive_imports/common.ts" + "code": { + "specifier": "file://[WILDCARD]/recursive_imports/common.ts", + "span": { + "start": { + "line": 1, + "character": 22 + }, + "end": { + "line": 1, + "character": 35 + } + } + } } ], - "size": 126, - "mediaType": "TypeScript", "local": "[WILDCARD]C.ts", - "checksum": "605875a410741bfaeeade28cbccf45f219ad99d987ea695e35eda75d2c53a658" + [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/recursive_imports/C.ts" }, { - "specifier": "file://[WILDCARD]/recursive_imports/common.ts", "dependencies": [], - "size": 28, - "mediaType": "TypeScript", "local": "[WILDCARD]common.ts", - "checksum": "c70025f0b936c02980c3be1fbd78f6f36b6241927c44ea67580821a6e664d8b3" + [WILDCARD] + "mediaType": "TypeScript", + [WILDCARD] + "specifier": "file://[WILDCARD]/recursive_imports/common.ts" } ], - "size": 451 + "redirects": {} } diff --git a/cli/tests/testdata/092_import_map_unmapped_bare_specifier.ts.out b/cli/tests/testdata/092_import_map_unmapped_bare_specifier.ts.out index 1a55e352b8..6e82ad4108 100644 --- a/cli/tests/testdata/092_import_map_unmapped_bare_specifier.ts.out +++ b/cli/tests/testdata/092_import_map_unmapped_bare_specifier.ts.out @@ -1,4 +1,7 @@ -[WILDCARD]error: Uncaught (in promise) TypeError: Relative import path "unmapped" not prefixed with / or ./ or ../ and not in import map from "[WILDCARD]" +[WILDCARD] +error: Uncaught (in promise) TypeError: Relative import path "unmapped" not prefixed with / or ./ or ../ and not in import map from "file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts" + at file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts:1:14 + await import("unmapped"); ^ - at [WILDCARD] + at async file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts:1:1 diff --git a/cli/tests/testdata/cafile_ts_fetch.ts.out b/cli/tests/testdata/cafile_ts_fetch.ts.out index e965047ad7..699b756edc 100644 --- a/cli/tests/testdata/cafile_ts_fetch.ts.out +++ b/cli/tests/testdata/cafile_ts_fetch.ts.out @@ -1 +1,2 @@ +[WILDCARD] Hello diff --git a/cli/tests/testdata/cafile_ts_fetch_unsafe_ssl.ts.out b/cli/tests/testdata/cafile_ts_fetch_unsafe_ssl.ts.out index 1dc61c837a..a0934e5841 100644 --- a/cli/tests/testdata/cafile_ts_fetch_unsafe_ssl.ts.out +++ b/cli/tests/testdata/cafile_ts_fetch_unsafe_ssl.ts.out @@ -1,2 +1,3 @@ DANGER: TLS certificate validation is disabled for all hostnames +[WILDCARD] Hello diff --git a/cli/tests/testdata/compat/existing_import_map.out b/cli/tests/testdata/compat/existing_import_map.out index cbff0cc510..46125d4116 100644 --- a/cli/tests/testdata/compat/existing_import_map.out +++ b/cli/tests/testdata/compat/existing_import_map.out @@ -1,6 +1,7 @@ [WILDCARD] Some Node built-ins were not added to the import map: - - "fs/promises" already exists and is mapped to "[WILDCARD]non_existent_file.js" + - "fs/promises" already exists and is mapped to "file://[WILDCARD]/non_existent_file.js" If you want to use Node built-ins provided by Deno remove listed specifiers from "imports" mapping in the import map file. [WILDCARD] -error: Cannot resolve module [WILDCARD] +error: Cannot load module "file://[WILDCARD]/non_existent_file.js". + at file://[WILDCARD]/fs_promises.js:1:16 diff --git a/cli/tests/testdata/compiler_api_test.ts b/cli/tests/testdata/compiler_api_test.ts index 92eb4c519e..b743a8612b 100644 --- a/cli/tests/testdata/compiler_api_test.ts +++ b/cli/tests/testdata/compiler_api_test.ts @@ -313,10 +313,9 @@ Deno.test({ Deno.test({ name: "Deno.emit() - invalid syntax does not panic", async fn() { - await assertThrowsAsync(async () => { - await Deno.emit("/main.js", { - sources: { - "/main.js": ` + const { diagnostics } = await Deno.emit("/main.js", { + sources: { + "/main.js": ` export class Foo { constructor() { console.log("foo"); @@ -325,9 +324,14 @@ Deno.test({ console.log("bar"); } }`, - }, - }); + }, }); + assertEquals(diagnostics.length, 1); + assert( + diagnostics[0].messageText!.startsWith( + "The module's source code could not be parsed: Unexpected token `get`. Expected * for generator, private key, identifier or async at file:", + ), + ); }, }); @@ -356,12 +360,10 @@ Deno.test({ Deno.test({ name: "Deno.emit() - Unknown media type does not panic", async fn() { - await assertThrowsAsync(async () => { - await Deno.emit("https://example.com/foo", { - sources: { - "https://example.com/foo": `let foo: string = "foo";`, - }, - }); + await Deno.emit("https://example.com/foo", { + sources: { + "https://example.com/foo": `let foo: string = "foo";`, + }, }); }, }); @@ -487,7 +489,7 @@ Deno.test({ code: 900001, start: null, end: null, - messageText: "Unable to find specifier in sources: file:///b.ts", + messageText: 'Cannot load module "file:///b.ts".', messageChain: null, source: null, sourceLine: null, @@ -497,7 +499,7 @@ Deno.test({ ]); assert( Deno.formatDiagnostics(diagnostics).includes( - "Unable to find specifier in sources: file:///b.ts", + 'Cannot load module "file:///b.ts".', ), ); }, diff --git a/cli/tests/testdata/config.ts.out b/cli/tests/testdata/config.ts.out index 9f8a8ddd16..76e357a736 100644 --- a/cli/tests/testdata/config.ts.out +++ b/cli/tests/testdata/config.ts.out @@ -1,6 +1,7 @@ [WILDCARD]Unsupported compiler options in "[WILDCARD]config.tsconfig.json". The following options were ignored: module, target +[WILDCARD] error: TS1219 [ERROR]: Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning. a() { ^ diff --git a/cli/tests/testdata/disallow_http_from_https_js.out b/cli/tests/testdata/disallow_http_from_https_js.out index 3219b7d35e..4280783991 100644 --- a/cli/tests/testdata/disallow_http_from_https_js.out +++ b/cli/tests/testdata/disallow_http_from_https_js.out @@ -1,3 +1,4 @@ error: Modules imported via https are not allowed to import http modules. Importing: http://localhost:4545/001_hello.js - at https://localhost:5545/disallow_http_from_https.js:2:0 + at https://localhost:5545/disallow_http_from_https.js:2:8 + diff --git a/cli/tests/testdata/disallow_http_from_https_ts.out b/cli/tests/testdata/disallow_http_from_https_ts.out index d1ab64394c..bd986cbce1 100644 --- a/cli/tests/testdata/disallow_http_from_https_ts.out +++ b/cli/tests/testdata/disallow_http_from_https_ts.out @@ -1,3 +1,4 @@ error: Modules imported via https are not allowed to import http modules. Importing: http://localhost:4545/001_hello.js - at https://localhost:5545/disallow_http_from_https.ts:2:0 + at https://localhost:5545/disallow_http_from_https.ts:2:8 + diff --git a/cli/tests/testdata/dynamic_import/permissions_blob_local.ts.out b/cli/tests/testdata/dynamic_import/permissions_blob_local.ts.out index 6dfa8e527a..b7b246ba2b 100644 --- a/cli/tests/testdata/dynamic_import/permissions_blob_local.ts.out +++ b/cli/tests/testdata/dynamic_import/permissions_blob_local.ts.out @@ -1,5 +1,5 @@ error: Uncaught (in promise) TypeError: Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag - at blob:null/[WILDCARD]:1:0 + at blob:null/[WILDCARD]:1:8 await import(URL.createObjectURL(blob)); ^ - at async file:///[WILDCARD]/dynamic_import/permissions_blob_local.ts:6:1 + at async file://[WILDCARD]/permissions_blob_local.ts:6:1 diff --git a/cli/tests/testdata/dynamic_import/permissions_blob_remote.ts.out b/cli/tests/testdata/dynamic_import/permissions_blob_remote.ts.out index 60d71ed6d6..a00c02d72f 100644 --- a/cli/tests/testdata/dynamic_import/permissions_blob_remote.ts.out +++ b/cli/tests/testdata/dynamic_import/permissions_blob_remote.ts.out @@ -1,5 +1,5 @@ error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag - at blob:null/[WILDCARD]:1:0 + at blob:null/[WILDCARD]:1:8 await import(URL.createObjectURL(blob)); ^ at async file:///[WILDCARD]/dynamic_import/permissions_blob_remote.ts:4:1 diff --git a/cli/tests/testdata/dynamic_import/permissions_data_local.ts.out b/cli/tests/testdata/dynamic_import/permissions_data_local.ts.out index c9fcb0a173..98c8a73107 100644 --- a/cli/tests/testdata/dynamic_import/permissions_data_local.ts.out +++ b/cli/tests/testdata/dynamic_import/permissions_data_local.ts.out @@ -1,5 +1,5 @@ error: Uncaught (in promise) TypeError: Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag - at data:application/javascript;base64,[WILDCARD]:1:0 + at data:application/javascript;base64,[WILDCARD]:1:8 await import(`data:application/javascript;base64,${btoa(code)}`); ^ at async file:///[WILDCARD]/dynamic_import/permissions_data_local.ts:5:1 diff --git a/cli/tests/testdata/dynamic_import/permissions_data_remote.ts.out b/cli/tests/testdata/dynamic_import/permissions_data_remote.ts.out index 772f0b51e2..cb2a7ccf79 100644 --- a/cli/tests/testdata/dynamic_import/permissions_data_remote.ts.out +++ b/cli/tests/testdata/dynamic_import/permissions_data_remote.ts.out @@ -1,5 +1,5 @@ error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag - at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:0 + at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8 await import(`data:application/javascript;base64,${btoa(code)}`); ^ at async file:///[WILDCARD]/dynamic_import/permissions_data_remote.ts:3:1 diff --git a/cli/tests/testdata/dynamic_import/permissions_remote_remote.ts.out b/cli/tests/testdata/dynamic_import/permissions_remote_remote.ts.out index cd7f58bb94..bd88dd4d98 100644 --- a/cli/tests/testdata/dynamic_import/permissions_remote_remote.ts.out +++ b/cli/tests/testdata/dynamic_import/permissions_remote_remote.ts.out @@ -1,5 +1,5 @@ error: Uncaught (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag - at http://localhost:4545/dynamic_import/static_remote.ts:2:0 + at http://localhost:4545/dynamic_import/static_remote.ts:2:8 await import( ^ at async file:///[WILDCARD]/dynamic_import/permissions_remote_remote.ts:1:1 diff --git a/cli/tests/testdata/error_004_missing_module.ts.out b/cli/tests/testdata/error_004_missing_module.ts.out index 8521f4424b..798ffc007d 100644 --- a/cli/tests/testdata/error_004_missing_module.ts.out +++ b/cli/tests/testdata/error_004_missing_module.ts.out @@ -1,2 +1,2 @@ -[WILDCARD]error: Cannot resolve module "file:///[WILDCARD]/bad-module.ts" from "file:///[WILDCARD]/error_004_missing_module.ts". - at file:///[WILDCARD]/error_004_missing_module.ts:1:0 +[WILDCARD]error: Cannot load module "file:///[WILDCARD]/bad-module.ts". + at file:///[WILDCARD]/error_004_missing_module.ts:1:28 diff --git a/cli/tests/testdata/error_005_missing_dynamic_import.ts.out b/cli/tests/testdata/error_005_missing_dynamic_import.ts.out index e8647a44e9..0ca35bd3b1 100644 --- a/cli/tests/testdata/error_005_missing_dynamic_import.ts.out +++ b/cli/tests/testdata/error_005_missing_dynamic_import.ts.out @@ -1,4 +1,4 @@ -error: Uncaught (in promise) TypeError: Cannot resolve module "[WILDCARD]/bad-module.ts". +error: Uncaught (in promise) TypeError: Cannot load module "[WILDCARD]/bad-module.ts". const _badModule = await import("./bad-module.ts"); ^ at async file://[WILDCARD]/error_005_missing_dynamic_import.ts:2:22 diff --git a/cli/tests/testdata/error_006_import_ext_failure.ts.out b/cli/tests/testdata/error_006_import_ext_failure.ts.out index 24d819e5d3..bffbafc7a4 100644 --- a/cli/tests/testdata/error_006_import_ext_failure.ts.out +++ b/cli/tests/testdata/error_006_import_ext_failure.ts.out @@ -1,2 +1,2 @@ -[WILDCARD]error: Cannot resolve module "[WILDCARD]/non-existent" from "[WILDCARD]/error_006_import_ext_failure.ts". - at file:///[WILDCARD]/error_006_import_ext_failure.ts:1:0 +[WILDCARD]error: Cannot load module "[WILDCARD]/non-existent". + at file:///[WILDCARD]/error_006_import_ext_failure.ts:1:8 diff --git a/cli/tests/testdata/error_011_bad_module_specifier.ts.out b/cli/tests/testdata/error_011_bad_module_specifier.ts.out index 713072191e..9ee9fd4d16 100644 --- a/cli/tests/testdata/error_011_bad_module_specifier.ts.out +++ b/cli/tests/testdata/error_011_bad_module_specifier.ts.out @@ -1 +1,3 @@ [WILDCARD]error: Relative import path "bad-module.ts" not prefixed with / or ./ or ../ from "[WILDCARD]/error_011_bad_module_specifier.ts" + at [WILDCARD]/error_011_bad_module_specifier.ts:1:28 + diff --git a/cli/tests/testdata/error_012_bad_dynamic_import_specifier.ts.out b/cli/tests/testdata/error_012_bad_dynamic_import_specifier.ts.out index 0d0b168a48..7ea32f6829 100644 --- a/cli/tests/testdata/error_012_bad_dynamic_import_specifier.ts.out +++ b/cli/tests/testdata/error_012_bad_dynamic_import_specifier.ts.out @@ -1,5 +1,7 @@ Check [WILDCARD]error_012_bad_dynamic_import_specifier.ts error: Uncaught (in promise) TypeError: Relative import path "bad-module.ts" not prefixed with / or ./ or ../ from "[WILDCARD]/error_012_bad_dynamic_import_specifier.ts" + at [WILDCARD]/error_012_bad_dynamic_import_specifier.ts:2:35 + const _badModule = await import("bad-module.ts"); ^ - at async file:///[WILDCARD]/error_012_bad_dynamic_import_specifier.ts:2:22 + at async [WILDCARD]/error_012_bad_dynamic_import_specifier.ts:2:22 diff --git a/cli/tests/testdata/error_013_missing_script.out b/cli/tests/testdata/error_013_missing_script.out index d1c257bdb7..fdd7aa27ed 100644 --- a/cli/tests/testdata/error_013_missing_script.out +++ b/cli/tests/testdata/error_013_missing_script.out @@ -1 +1 @@ -error: Cannot resolve module "[WILDCARD]missing_file_name". +error: Cannot load module "[WILDCARD]missing_file_name". diff --git a/cli/tests/testdata/error_014_catch_dynamic_import_error.js.out b/cli/tests/testdata/error_014_catch_dynamic_import_error.js.out index 8f2d695f73..b19d875158 100644 --- a/cli/tests/testdata/error_014_catch_dynamic_import_error.js.out +++ b/cli/tests/testdata/error_014_catch_dynamic_import_error.js.out @@ -1,12 +1,16 @@ Caught direct dynamic import error. TypeError: Relative import path "does not exist" not prefixed with / or ./ or ../ from "[WILDCARD]/error_014_catch_dynamic_import_error.js" - at async file:///[WILDCARD]/error_014_catch_dynamic_import_error.js:3:5 + at [WILDCARD]/error_014_catch_dynamic_import_error.js:3:18 + + at async [WILDCARD]/error_014_catch_dynamic_import_error.js:3:5 Caught indirect direct dynamic import error. -TypeError: Relative import path "does not exist either" not prefixed with / or ./ or ../ from "[WILDCARD]/indirect_import_error.js" - at async file:///[WILDCARD]/error_014_catch_dynamic_import_error.js:10:5 +TypeError: Relative import path "does not exist either" not prefixed with / or ./ or ../ from "[WILDCARD]/subdir/indirect_import_error.js" + at [WILDCARD]/subdir/indirect_import_error.js:1:15 + + at async [WILDCARD]/error_014_catch_dynamic_import_error.js:10:5 Caught error thrown by dynamically imported module. Error: An error - at file:///[WILDCARD]/subdir/throws.js:6:7 + at [WILDCARD]/subdir/throws.js:6:7 Caught error thrown indirectly by dynamically imported module. Error: An error - at file:///[WILDCARD]/subdir/throws.js:6:7 + at [WILDCARD]/subdir/throws.js:6:7 diff --git a/cli/tests/testdata/error_015_dynamic_import_permissions.out b/cli/tests/testdata/error_015_dynamic_import_permissions.out index a509cfdab6..42fb0a15d4 100644 --- a/cli/tests/testdata/error_015_dynamic_import_permissions.out +++ b/cli/tests/testdata/error_015_dynamic_import_permissions.out @@ -1,4 +1,5 @@ error: Uncaught (in promise) TypeError: Requires net access to "localhost:4545", run again with the --allow-net flag + at file://[WILDCARD]/error_015_dynamic_import_permissions.js:2:16 await import("http://localhost:4545/subdir/mod4.js"); ^ - at async file:///[WILDCARD]/error_015_dynamic_import_permissions.js:2:3 + at async file://[WILDCARD]/error_015_dynamic_import_permissions.js:2:3 diff --git a/cli/tests/testdata/error_016_dynamic_import_permissions2.out b/cli/tests/testdata/error_016_dynamic_import_permissions2.out index 0b43c5b910..bdfddb9edd 100644 --- a/cli/tests/testdata/error_016_dynamic_import_permissions2.out +++ b/cli/tests/testdata/error_016_dynamic_import_permissions2.out @@ -1,4 +1,8 @@ [WILDCARD] -error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. +error: Uncaught (in promise) TypeError: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. Importing: file:///c:/etc/passwd - at http://localhost:4545/subdir/evil_remote_import.js:3:0 + at http://localhost:4545/subdir/evil_remote_import.js:3:15 + + await import("http://localhost:4545/subdir/evil_remote_import.js"); + ^ + at async file://[WILDCARD]/error_016_dynamic_import_permissions2.js:4:3 diff --git a/cli/tests/testdata/error_local_static_import_from_remote.js.out b/cli/tests/testdata/error_local_static_import_from_remote.js.out index 44b6765327..8904bae296 100644 --- a/cli/tests/testdata/error_local_static_import_from_remote.js.out +++ b/cli/tests/testdata/error_local_static_import_from_remote.js.out @@ -1,4 +1,5 @@ [WILDCARD] -error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. +error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. Importing: file:///some/dir/file.js - at http://localhost:4545/error_local_static_import_from_remote.js:1:0 + at http://localhost:4545/error_local_static_import_from_remote.js:1:8 + diff --git a/cli/tests/testdata/error_local_static_import_from_remote.ts.out b/cli/tests/testdata/error_local_static_import_from_remote.ts.out index f8f409de0e..b06a12454b 100644 --- a/cli/tests/testdata/error_local_static_import_from_remote.ts.out +++ b/cli/tests/testdata/error_local_static_import_from_remote.ts.out @@ -1,4 +1,5 @@ [WILDCARD] -error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. +error: Remote modules are not allowed to import local modules. Consider using a dynamic import instead. Importing: file:///some/dir/file.ts - at http://localhost:4545/error_local_static_import_from_remote.ts:1:0 + at http://localhost:4545/error_local_static_import_from_remote.ts:1:8 + diff --git a/cli/tests/testdata/error_missing_module_named_import.ts.out b/cli/tests/testdata/error_missing_module_named_import.ts.out index 6f09f0c0d5..9569dd9a95 100644 --- a/cli/tests/testdata/error_missing_module_named_import.ts.out +++ b/cli/tests/testdata/error_missing_module_named_import.ts.out @@ -1,2 +1,3 @@ -error: Cannot resolve module "[WILDCARD]/does_not_exist.js" from "[WILDCARD]/error_missing_module_named_import.ts". - at [WILDCARD]/error_missing_module_named_import.ts:1:0 +[WILDCARD] +error: Cannot load module "file://[WILDCARD]/does_not_exist.js". + at file://[WILDCARD]/error_missing_module_named_import.ts:1:19 diff --git a/cli/tests/testdata/error_syntax.js.out b/cli/tests/testdata/error_syntax.js.out index 84f924f8f4..6d3f05b8e7 100644 --- a/cli/tests/testdata/error_syntax.js.out +++ b/cli/tests/testdata/error_syntax.js.out @@ -1 +1 @@ -error: Expected ,, got following at [WILDCARD]/error_syntax.js:3:6 +error: The module's source code could not be parsed: Expected ',', got 'following' at [WILDCARD]/error_syntax.js:3:6 diff --git a/cli/tests/testdata/error_syntax_empty_trailing_line.mjs.out b/cli/tests/testdata/error_syntax_empty_trailing_line.mjs.out index 8b4feeb492..f4bbc6708e 100644 --- a/cli/tests/testdata/error_syntax_empty_trailing_line.mjs.out +++ b/cli/tests/testdata/error_syntax_empty_trailing_line.mjs.out @@ -1 +1 @@ -error: Unexpected eof at [WILDCARD]/error_syntax_empty_trailing_line.mjs:2:22 +error: The module's source code could not be parsed: Unexpected eof at [WILDCARD]/error_syntax_empty_trailing_line.mjs:2:22 diff --git a/cli/tests/testdata/import_blob_url_import_relative.ts.out b/cli/tests/testdata/import_blob_url_import_relative.ts.out index 77f399763b..75e5fe8119 100644 --- a/cli/tests/testdata/import_blob_url_import_relative.ts.out +++ b/cli/tests/testdata/import_blob_url_import_relative.ts.out @@ -1,4 +1,6 @@ error: Uncaught (in promise) TypeError: invalid URL: relative URL with a cannot-be-a-base base + at blob:null/[WILDCARD]:1:19 + const a = await import(url); ^ at async file://[WILDCARD]/import_blob_url_import_relative.ts:6:11 diff --git a/cli/tests/testdata/import_data_url_import_relative.ts.out b/cli/tests/testdata/import_data_url_import_relative.ts.out index 1f4f3e9ef0..ed1a372086 100644 --- a/cli/tests/testdata/import_data_url_import_relative.ts.out +++ b/cli/tests/testdata/import_data_url_import_relative.ts.out @@ -1,4 +1,3 @@ error: invalid URL: relative URL with a cannot-be-a-base base + at data:application/javascript;base64,ZXhwb3J0IHsgYSB9IGZyb20gIi4vYS50cyI7Cg==:1:19 -Caused by: - relative URL with a cannot-be-a-base base diff --git a/cli/tests/testdata/info_missing_module.out b/cli/tests/testdata/info_missing_module.out index 75b0774071..07f893eecc 100644 --- a/cli/tests/testdata/info_missing_module.out +++ b/cli/tests/testdata/info_missing_module.out @@ -1,6 +1,6 @@ local: [WILDCARD]error_009_missing_js_module.js type: JavaScript -dependencies: 1 unique (total 26B) +dependencies: 0 unique (total 26B) file://[WILDCARD]/error_009_missing_js_module.js (26B) -└── file://[WILDCARD]/bad-module.js (error) +└── file://[WILDCARD]/bad-module.js (missing) diff --git a/cli/tests/testdata/lint/expected_json.out b/cli/tests/testdata/lint/expected_json.out index 9af79ce3d3..876aec9e00 100644 --- a/cli/tests/testdata/lint/expected_json.out +++ b/cli/tests/testdata/lint/expected_json.out @@ -58,7 +58,7 @@ "errors": [ { "file_path": "[WILDCARD]malformed.js", - "message": "Expected }, got at [WILDCARD]malformed.js:4:16" + "message": "Expected '}', got '' at [WILDCARD]malformed.js:4:16" } ] } diff --git a/cli/tests/testdata/localhost_unsafe_ssl.ts.out b/cli/tests/testdata/localhost_unsafe_ssl.ts.out index c482bd81ca..b3f895d6e7 100644 --- a/cli/tests/testdata/localhost_unsafe_ssl.ts.out +++ b/cli/tests/testdata/localhost_unsafe_ssl.ts.out @@ -1,3 +1,3 @@ DANGER: TLS certificate validation is disabled for: deno.land error: error sending request for url (https://localhost:5545/subdir/mod2.ts): error trying to connect: invalid certificate: UnknownIssuer - at [WILDCARD]/cafile_url_imports.ts:1:0 + at file://[WILDCARD]/cafile_url_imports.ts:1:28 diff --git a/cli/tests/testdata/lock_check_err_with_bundle.out b/cli/tests/testdata/lock_check_err_with_bundle.out index a47cbc6068..a20e9b3afc 100644 --- a/cli/tests/testdata/lock_check_err_with_bundle.out +++ b/cli/tests/testdata/lock_check_err_with_bundle.out @@ -1,4 +1,4 @@ [WILDCARD] -The source code is invalid, as it does not match the expected hash in the lock file. +error: The source code is invalid, as it does not match the expected hash in the lock file. Specifier: http://127.0.0.1:4545/subdir/subdir2/mod2.ts Lock file: lock_check_err_with_bundle.json diff --git a/cli/tests/testdata/lock_dynamic_imports.out b/cli/tests/testdata/lock_dynamic_imports.out index e5c76c8f06..601d2282b7 100644 --- a/cli/tests/testdata/lock_dynamic_imports.out +++ b/cli/tests/testdata/lock_dynamic_imports.out @@ -1,4 +1,4 @@ [WILDCARD] -The source code is invalid, as it does not match the expected hash in the lock file. +error: The source code is invalid, as it does not match the expected hash in the lock file. Specifier: http://127.0.0.1:4545/subdir/subdir2/mod2.ts Lock file: lock_dynamic_imports.json diff --git a/cli/tests/testdata/swc_syntax_error.ts.out b/cli/tests/testdata/swc_syntax_error.ts.out index 98a7534590..c968db5e92 100644 --- a/cli/tests/testdata/swc_syntax_error.ts.out +++ b/cli/tests/testdata/swc_syntax_error.ts.out @@ -1 +1 @@ -error: Unexpected token `}`. Expected an identifier, void, yield, null, await, break, a string literal, a numeric literal, true, false, `, -, import, this, typeof, {, [, ( at [WILDCARD]syntax_error.ts:4:1 +error: The module's source code could not be parsed: Unexpected token `}`. Expected an identifier, void, yield, null, await, break, a string literal, a numeric literal, true, false, `, -, import, this, typeof, {, [, ( at [WILDCARD]syntax_error.ts:4:1 diff --git a/cli/tests/testdata/ts_decorators_bundle.out b/cli/tests/testdata/ts_decorators_bundle.out index a5b77b7bf3..98f3733bf6 100644 --- a/cli/tests/testdata/ts_decorators_bundle.out +++ b/cli/tests/testdata/ts_decorators_bundle.out @@ -1,5 +1,5 @@ [WILDCARD] function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { [WILDCARD] -new SomeClass().test(); +new SomeClass[WILDCARD].test(); [WILDCARD] \ No newline at end of file diff --git a/cli/tests/testdata/ts_type_only_import.ts.out b/cli/tests/testdata/ts_type_only_import.ts.out index f808ed21ad..9304a987e1 100644 --- a/cli/tests/testdata/ts_type_only_import.ts.out +++ b/cli/tests/testdata/ts_type_only_import.ts.out @@ -1,4 +1,4 @@ -Check [WILDCARD]ts_type_only_import.ts -warning: Compiled module not found "[WILDCARD]ts_type_only_import.d.ts" - From: [WILDCARD]ts_type_only_import.ts - If the source module contains only types, use `import type` and `export type` to import it instead. +Check file://[WILDCARD]/ts_type_only_import.ts +warning: Cannot load module "file://[WILDCARD]/ts_type_only_import.d.ts". + at file://[WILDCARD]/ts_type_only_import.ts:1:15 + If the source module contains only types, use `import type` and `export type` to import it instead. diff --git a/cli/tests/testdata/workers/nonexistent_worker.out b/cli/tests/testdata/workers/nonexistent_worker.out index 99e94bfc9b..5280e22d13 100644 --- a/cli/tests/testdata/workers/nonexistent_worker.out +++ b/cli/tests/testdata/workers/nonexistent_worker.out @@ -1,3 +1,3 @@ -[WILDCARD]error: Uncaught (in worker "") Cannot resolve module "file:///[WILDCARD]/workers/doesnt_exist.js". +[WILDCARD]error: Uncaught (in worker "") Cannot load module "file:///[WILDCARD]/workers/doesnt_exist.js". error: Uncaught (in promise) Error: Unhandled error event in child worker. at Worker.#pollControl ([WILDCARD]) diff --git a/cli/tests/testdata/workers/permissions_blob_local.ts.out b/cli/tests/testdata/workers/permissions_blob_local.ts.out index b35d1d4db5..ee19c7ab56 100644 --- a/cli/tests/testdata/workers/permissions_blob_local.ts.out +++ b/cli/tests/testdata/workers/permissions_blob_local.ts.out @@ -1,4 +1,4 @@ error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag - at blob:null/[WILDCARD]:1:0 + at blob:null/[WILDCARD]:1:8 error: Uncaught (in promise) Error: Unhandled error event in child worker. at Worker.#pollControl ([WILDCARD]) diff --git a/cli/tests/testdata/workers/permissions_blob_remote.ts.out b/cli/tests/testdata/workers/permissions_blob_remote.ts.out index 26936971a6..597e5bf1e8 100644 --- a/cli/tests/testdata/workers/permissions_blob_remote.ts.out +++ b/cli/tests/testdata/workers/permissions_blob_remote.ts.out @@ -1,4 +1,4 @@ error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag - at blob:null/[WILDCARD]:1:0 + at blob:null/[WILDCARD]:1:8 error: Uncaught (in promise) Error: Unhandled error event in child worker. at Worker.#pollControl ([WILDCARD]) diff --git a/cli/tests/testdata/workers/permissions_data_local.ts.out b/cli/tests/testdata/workers/permissions_data_local.ts.out index 1e8c5fffa4..5c9bcf1c14 100644 --- a/cli/tests/testdata/workers/permissions_data_local.ts.out +++ b/cli/tests/testdata/workers/permissions_data_local.ts.out @@ -1,4 +1,4 @@ error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag - at data:application/javascript;base64,[WILDCARD]:1:0 + at data:application/javascript;base64,[WILDCARD]:1:8 error: Uncaught (in promise) Error: Unhandled error event in child worker. at Worker.#pollControl ([WILDCARD]) diff --git a/cli/tests/testdata/workers/permissions_data_remote.ts.out b/cli/tests/testdata/workers/permissions_data_remote.ts.out index 92eadf2842..0273664bdc 100644 --- a/cli/tests/testdata/workers/permissions_data_remote.ts.out +++ b/cli/tests/testdata/workers/permissions_data_remote.ts.out @@ -1,4 +1,4 @@ error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag - at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:0 + at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8 error: Uncaught (in promise) Error: Unhandled error event in child worker. at Worker.#pollControl ([WILDCARD]) diff --git a/cli/tests/testdata/workers/permissions_dynamic_remote.ts.out b/cli/tests/testdata/workers/permissions_dynamic_remote.ts.out index e5015abff4..57a9223916 100644 --- a/cli/tests/testdata/workers/permissions_dynamic_remote.ts.out +++ b/cli/tests/testdata/workers/permissions_dynamic_remote.ts.out @@ -1,4 +1,5 @@ error: Uncaught (in worker "") (in promise) TypeError: Requires net access to "example.com", run again with the --allow-net flag + at http://localhost:4545/workers/dynamic_remote.ts:2:14 await import("https://example.com/some/file.ts"); ^ at async http://localhost:4545/workers/dynamic_remote.ts:2:1 diff --git a/cli/tests/testdata/workers/permissions_remote_remote.ts.out b/cli/tests/testdata/workers/permissions_remote_remote.ts.out index 8998891a39..42602cf715 100644 --- a/cli/tests/testdata/workers/permissions_remote_remote.ts.out +++ b/cli/tests/testdata/workers/permissions_remote_remote.ts.out @@ -1,4 +1,4 @@ error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag - at http://localhost:4545/workers/static_remote.ts:2:0 + at http://localhost:4545/workers/static_remote.ts:2:8 error: Uncaught (in promise) Error: Unhandled error event in child worker. at Worker.#pollControl ([WILDCARD]) diff --git a/cli/tools/coverage.rs b/cli/tools/coverage.rs index 60efe789c2..c34ba22cc2 100644 --- a/cli/tools/coverage.rs +++ b/cli/tools/coverage.rs @@ -1,11 +1,12 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use crate::colors; +use crate::emit; use crate::flags::Flags; use crate::fs_util::collect_files; -use crate::module_graph::TypeLib; use crate::proc_state::ProcState; use crate::source_maps::SourceMapGetter; + use deno_ast::swc::common::Span; use deno_ast::MediaType; use deno_core::error::AnyError; @@ -687,16 +688,15 @@ pub async fn cover_files( let module_specifier = deno_core::resolve_url_or_path(&script_coverage.url)?; ps.prepare_module_load( - module_specifier.clone(), - TypeLib::UnstableDenoWindow, - Permissions::allow_all(), - Permissions::allow_all(), + vec![module_specifier.clone()], false, - ps.maybe_import_map.clone(), + emit::TypeLib::UnstableDenoWindow, + Permissions::allow_all(), + Permissions::allow_all(), ) .await?; - let module_source = ps.load(module_specifier.clone(), None)?; + let module_source = ps.load(module_specifier.clone(), None, false)?; let script_source = &module_source.code; let maybe_source_map = ps.get_source_map(&script_coverage.url); diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs index 1711c5ccdd..aa9d913a01 100644 --- a/cli/tools/doc.rs +++ b/cli/tools/doc.rs @@ -106,7 +106,9 @@ pub async fn print_docs( let source_file_specifier = ModuleSpecifier::parse("deno://lib.deno.d.ts").unwrap(); let graph = create_graph( - source_file_specifier.clone(), + vec![source_file_specifier.clone()], + false, + None, &mut loader, None, None, @@ -142,7 +144,9 @@ pub async fn print_docs( import_map: ps.maybe_import_map.clone(), }; let graph = create_graph( - root_specifier.clone(), + vec![root_specifier.clone()], + false, + None, &mut loader, Some(&resolver), None, diff --git a/cli/tools/test.rs b/cli/tools/test.rs index 8d005b1011..e14b5cc8b1 100644 --- a/cli/tools/test.rs +++ b/cli/tools/test.rs @@ -1,8 +1,11 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use crate::ast::Location; +use crate::cache; +use crate::cache::CacherLoader; use crate::colors; use crate::create_main_worker; +use crate::emit; use crate::file_fetcher::File; use crate::file_watcher; use crate::file_watcher::ResolutionResult; @@ -11,15 +14,13 @@ use crate::fs_util::collect_specifiers; use crate::fs_util::is_supported_test_ext; use crate::fs_util::is_supported_test_path; use crate::located_script_name; -use crate::module_graph; -use crate::module_graph::GraphBuilder; -use crate::module_graph::Module; -use crate::module_graph::TypeLib; +use crate::lockfile; use crate::ops; use crate::proc_state::ProcState; +use crate::resolver::ImportMapResolver; use crate::tokio_util; use crate::tools::coverage::CoverageCollector; -use crate::FetchHandler; + use deno_ast::swc::common::comments::CommentKind; use deno_ast::MediaType; use deno_core::error::generic_error; @@ -28,7 +29,6 @@ use deno_core::futures::future; use deno_core::futures::stream; use deno_core::futures::FutureExt; use deno_core::futures::StreamExt; -use deno_core::parking_lot::Mutex; use deno_core::serde_json::json; use deno_core::JsRuntime; use deno_core::ModuleSpecifier; @@ -500,7 +500,7 @@ async fn check_specifiers( ps: ProcState, permissions: Permissions, specifiers: Vec<(ModuleSpecifier, TestMode)>, - lib: TypeLib, + lib: emit::TypeLib, ) -> Result<(), AnyError> { let inline_files = fetch_inline_files( ps.clone(), @@ -527,12 +527,12 @@ async fn check_specifiers( ps.file_fetcher.insert_cached(file); } - ps.prepare_module_graph( + ps.prepare_module_load( specifiers, + false, lib.clone(), Permissions::allow_all(), permissions.clone(), - ps.maybe_import_map.clone(), ) .await?; } @@ -548,12 +548,12 @@ async fn check_specifiers( }) .collect(); - ps.prepare_module_graph( + ps.prepare_module_load( module_specifiers, + false, lib, Permissions::allow_all(), permissions, - ps.maybe_import_map.clone(), ) .await?; @@ -743,11 +743,14 @@ fn collect_specifiers_with_test_mode( Ok(specifiers_with_mode) } -/// Collects module and document specifiers with test modes via `collect_specifiers_with_test_mode` -/// which are then pre-fetched and adjusted based on the media type. +/// Collects module and document specifiers with test modes via +/// `collect_specifiers_with_test_mode` which are then pre-fetched and adjusted +/// based on the media type. /// -/// Specifiers that do not have a known media type that can be executed as a module are marked as -/// `TestMode::Documentation`. +/// Specifiers that do not have a known media type that can be executed as a +/// module are marked as `TestMode::Documentation`. Type definition files +/// cannot be run, and therefore need to be marked as `TestMode::Documentation` +/// as well. async fn fetch_specifiers_with_test_mode( ps: ProcState, include: Vec, @@ -762,7 +765,9 @@ async fn fetch_specifiers_with_test_mode( .fetch(specifier, &mut Permissions::allow_all()) .await?; - if file.media_type == MediaType::Unknown { + if file.media_type == MediaType::Unknown + || file.media_type == MediaType::Dts + { *mode = TestMode::Documentation } } @@ -798,9 +803,9 @@ pub async fn run_tests( } let lib = if flags.unstable { - TypeLib::UnstableDenoWindow + emit::TypeLib::UnstableDenoWindow } else { - TypeLib::DenoWindow + emit::TypeLib::DenoWindow }; check_specifiers( @@ -845,26 +850,33 @@ pub async fn run_tests_with_watch( let permissions = Permissions::from_options(&flags.clone().into()); let lib = if flags.unstable { - TypeLib::UnstableDenoWindow + emit::TypeLib::UnstableDenoWindow } else { - TypeLib::DenoWindow + emit::TypeLib::DenoWindow }; - let handler = Arc::new(Mutex::new(FetchHandler::new( - &ps, - Permissions::allow_all(), - Permissions::allow_all(), - )?)); - let include = include.unwrap_or_else(|| vec![".".to_string()]); let paths_to_watch: Vec<_> = include.iter().map(PathBuf::from).collect(); let resolver = |changed: Option>| { + let mut cache = cache::FetchCacher::new( + ps.dir.gen_cache.clone(), + ps.file_fetcher.clone(), + Permissions::allow_all(), + Permissions::allow_all(), + ); + let paths_to_watch = paths_to_watch.clone(); let paths_to_watch_clone = paths_to_watch.clone(); - let handler = handler.clone(); - let ps = ps.clone(); + let maybe_resolver = + ps.maybe_import_map.as_ref().map(ImportMapResolver::new); + let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); + let maybe_imports = ps + .maybe_config_file + .as_ref() + .map(|cf| cf.to_maybe_imports()) + .flatten(); let files_changed = changed.is_some(); let include = include.clone(); let ignore = ignore.clone(); @@ -886,61 +898,51 @@ pub async fn run_tests_with_watch( .collect() }; - let mut builder = GraphBuilder::new( - handler, - ps.maybe_import_map.clone(), - ps.lockfile.clone(), - ); - for specifier in test_modules.iter() { - builder.add(specifier, false).await?; - } - builder.analyze_config_file(&ps.maybe_config_file).await?; - let graph = builder.get_graph(); + let graph = deno_graph::create_graph( + test_modules.clone(), + false, + maybe_imports, + cache.as_mut_loader(), + maybe_resolver.as_ref().map(|r| r.as_resolver()), + maybe_locker, + None, + ) + .await; + graph.valid()?; + // TODO(@kitsonk) - This should be totally derivable from the graph. for specifier in test_modules { fn get_dependencies<'a>( - graph: &'a module_graph::Graph, - module: &'a Module, + graph: &'a deno_graph::ModuleGraph, + maybe_module: Option<&'a deno_graph::Module>, // This needs to be accessible to skip getting dependencies if they're already there, // otherwise this will cause a stack overflow with circular dependencies output: &mut HashSet<&'a ModuleSpecifier>, - ) -> Result<(), AnyError> { - for dep in module.dependencies.values() { - if let Some(specifier) = &dep.maybe_code { - if !output.contains(specifier) { - output.insert(specifier); + ) { + if let Some(module) = maybe_module { + for dep in module.dependencies.values() { + if let Some(specifier) = &dep.get_code() { + if !output.contains(specifier) { + output.insert(specifier); - get_dependencies( - graph, - graph.get_specifier(specifier)?, - output, - )?; + get_dependencies(graph, graph.get(specifier), output); + } } - } - if let Some(specifier) = &dep.maybe_type { - if !output.contains(specifier) { - output.insert(specifier); + if let Some(specifier) = &dep.get_type() { + if !output.contains(specifier) { + output.insert(specifier); - get_dependencies( - graph, - graph.get_specifier(specifier)?, - output, - )?; + get_dependencies(graph, graph.get(specifier), output); + } } } } - - Ok(()) } // This test module and all it's dependencies let mut modules = HashSet::new(); modules.insert(&specifier); - get_dependencies( - &graph, - graph.get_specifier(&specifier)?, - &mut modules, - )?; + get_dependencies(&graph, graph.get(&specifier), &mut modules); paths_to_watch.extend( modules diff --git a/cli/tsc.rs b/cli/tsc.rs index bfaaae9717..922bee6d70 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -2,17 +2,14 @@ use crate::config_file::TsConfig; use crate::diagnostics::Diagnostics; -use crate::module_graph::Graph; -use crate::module_graph::Stats; +use crate::emit; use deno_ast::MediaType; use deno_core::error::anyhow; -use deno_core::error::bail; use deno_core::error::AnyError; use deno_core::error::Context; use deno_core::located_script_name; use deno_core::op_sync; -use deno_core::parking_lot::Mutex; use deno_core::resolve_url_or_path; use deno_core::serde::de; use deno_core::serde::Deserialize; @@ -25,6 +22,7 @@ use deno_core::ModuleSpecifier; use deno_core::OpFn; use deno_core::RuntimeOptions; use deno_core::Snapshot; +use deno_graph::ModuleGraph; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; @@ -93,7 +91,7 @@ pub fn get_asset(asset: &str) -> Option<&'static str> { } fn get_maybe_hash( - maybe_source: &Option, + maybe_source: Option<&String>, hash_data: &[Vec], ) -> Option { if let Some(source) = maybe_source { @@ -105,30 +103,15 @@ fn get_maybe_hash( } } -fn hash_data_url( - specifier: &ModuleSpecifier, - media_type: &MediaType, -) -> String { - assert_eq!( - specifier.scheme(), - "data", - "Specifier must be a data: specifier." - ); +/// Hash the URL so it can be sent to `tsc` in a supportable way +fn hash_url(specifier: &ModuleSpecifier, media_type: &MediaType) -> String { let hash = crate::checksum::gen(&[specifier.path().as_bytes()]); - format!("data:///{}{}", hash, media_type.as_ts_extension()) -} - -fn hash_blob_url( - specifier: &ModuleSpecifier, - media_type: &MediaType, -) -> String { - assert_eq!( + format!( + "{}:///{}{}", specifier.scheme(), - "blob", - "Specifier must be a blob: specifier." - ); - let hash = crate::checksum::gen(&[specifier.path().as_bytes()]); - format!("blob:///{}{}", hash, media_type.as_ts_extension()) + hash, + media_type.as_ts_extension() + ) } /// tsc only supports `.ts`, `.tsx`, `.d.ts`, `.js`, or `.jsx` as root modules @@ -180,7 +163,7 @@ pub struct Request { pub config: TsConfig, /// Indicates to the tsc runtime if debug logging should occur. pub debug: bool, - pub graph: Arc>, + pub graph: Arc, pub hash_data: Vec>, pub maybe_config_specifier: Option, pub maybe_tsbuildinfo: Option, @@ -190,7 +173,7 @@ pub struct Request { } #[derive(Debug, Clone, Eq, PartialEq)] -pub struct Response { +pub(crate) struct Response { /// Any diagnostics that have been returned from the checker. pub diagnostics: Diagnostics, /// Any files that were emitted during the check. @@ -198,7 +181,7 @@ pub struct Response { /// If there was any build info associated with the exec request. pub maybe_tsbuildinfo: Option, /// Statistics from the check. - pub stats: Stats, + pub stats: emit::Stats, } #[derive(Debug)] @@ -206,7 +189,7 @@ struct State { data_url_map: HashMap, hash_data: Vec>, emitted_files: Vec, - graph: Arc>, + graph: Arc, maybe_config_specifier: Option, maybe_tsbuildinfo: Option, maybe_response: Option, @@ -215,7 +198,7 @@ struct State { impl State { pub fn new( - graph: Arc>, + graph: Arc, hash_data: Vec>, maybe_config_specifier: Option, maybe_tsbuildinfo: Option, @@ -334,8 +317,7 @@ fn op_exists(state: &mut State, args: ExistsArgs) -> Result { if specifier.scheme() == "asset" || specifier.scheme() == "data" { Ok(true) } else { - let graph = state.graph.lock(); - Ok(graph.contains(&specifier)) + Ok(state.graph.contains(&specifier)) } } else { Ok(false) @@ -366,11 +348,10 @@ fn op_load(state: &mut State, args: Value) -> Result { } else if v.specifier.starts_with("asset:///") { let name = v.specifier.replace("asset:///", ""); let maybe_source = get_asset(&name).map(String::from); - hash = get_maybe_hash(&maybe_source, &state.hash_data); + hash = get_maybe_hash(maybe_source.as_ref(), &state.hash_data); media_type = MediaType::from(&v.specifier); maybe_source } else { - let graph = state.graph.lock(); let specifier = if let Some(data_specifier) = state.data_url_map.get(&v.specifier) { @@ -380,13 +361,14 @@ fn op_load(state: &mut State, args: Value) -> Result { } else { specifier }; - let maybe_source = graph.get_source(&specifier).map(|t| t.to_string()); - media_type = if let Some(media_type) = graph.get_media_type(&specifier) { - media_type + let maybe_source = if let Some(module) = state.graph.get(&specifier) { + media_type = module.media_type; + Some(module.source.as_str().to_string()) } else { - MediaType::Unknown + media_type = MediaType::Unknown; + None }; - hash = get_maybe_hash(&maybe_source, &state.hash_data); + hash = get_maybe_hash(maybe_source.as_ref(), &state.hash_data); maybe_source }; @@ -425,48 +407,31 @@ fn op_resolve(state: &mut State, args: Value) -> Result { MediaType::from(specifier).as_ts_extension().to_string(), )); } else { - let graph = state.graph.lock(); - match graph.resolve(specifier, &referrer, true) { - Ok(resolved_specifier) => { - let media_type = if let Some(media_type) = - graph.get_media_type(&resolved_specifier) - { - media_type - } else { - bail!( - "Unable to resolve media type for specifier: \"{}\"", - resolved_specifier - ) - }; - let resolved_specifier_str = match resolved_specifier.scheme() { - "data" | "blob" => { - let specifier_str = if resolved_specifier.scheme() == "data" { - hash_data_url(&resolved_specifier, &media_type) - } else { - hash_blob_url(&resolved_specifier, &media_type) - }; - state - .data_url_map - .insert(specifier_str.clone(), resolved_specifier); - specifier_str - } - _ => resolved_specifier.to_string(), - }; - resolved.push(( - resolved_specifier_str, - media_type.as_ts_extension().into(), - )); - } - // in certain situations, like certain dynamic imports, we won't have - // the source file in the graph, so we will return a fake module to - // make tsc happy. - Err(_) => { - resolved.push(( + let resolved_dependency = + match state.graph.resolve_dependency(specifier, &referrer, true) { + Some(resolved_specifier) => { + let media_type = state + .graph + .get(resolved_specifier) + .map_or(&MediaType::Unknown, |m| &m.media_type); + let resolved_specifier_str = match resolved_specifier.scheme() { + "data" | "blob" => { + let specifier_str = hash_url(resolved_specifier, media_type); + state + .data_url_map + .insert(specifier_str.clone(), resolved_specifier.clone()); + specifier_str + } + _ => resolved_specifier.to_string(), + }; + (resolved_specifier_str, media_type.as_ts_extension().into()) + } + None => ( "deno:///missing_dependency.d.ts".to_string(), ".d.ts".to_string(), - )); - } - } + ), + }; + resolved.push(resolved_dependency); } } @@ -476,7 +441,7 @@ fn op_resolve(state: &mut State, args: Value) -> Result { #[derive(Debug, Deserialize, Eq, PartialEq)] struct RespondArgs { pub diagnostics: Diagnostics, - pub stats: Stats, + pub stats: emit::Stats, } fn op_respond(state: &mut State, args: Value) -> Result { @@ -489,7 +454,7 @@ fn op_respond(state: &mut State, args: Value) -> Result { /// Execute a request on the supplied snapshot, returning a response which /// contains information, like any emitted files, diagnostics, statistics and /// optionally an updated TypeScript build info. -pub fn exec(request: Request) -> Result { +pub(crate) fn exec(request: Request) -> Result { let mut runtime = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(compiler_snapshot()), ..Default::default() @@ -505,11 +470,7 @@ pub fn exec(request: Request) -> Result { .iter() .map(|(s, mt)| match s.scheme() { "data" | "blob" => { - let specifier_str = if s.scheme() == "data" { - hash_data_url(s, mt) - } else { - hash_blob_url(s, mt) - }; + let specifier_str = hash_url(s, mt); data_url_map.insert(specifier_str.clone(), s.clone()); specifier_str } @@ -530,7 +491,7 @@ pub fn exec(request: Request) -> Result { let op_state = runtime.op_state(); let mut op_state = op_state.borrow_mut(); op_state.put(State::new( - request.graph.clone(), + request.graph, request.hash_data.clone(), request.maybe_config_specifier.clone(), request.maybe_tsbuildinfo.clone(), @@ -589,9 +550,39 @@ mod tests { use crate::config_file::TsConfig; use crate::diagnostics::Diagnostic; use crate::diagnostics::DiagnosticCategory; - use crate::module_graph::tests::MockSpecifierHandler; - use crate::module_graph::GraphBuilder; - use deno_core::parking_lot::Mutex; + use crate::emit::Stats; + use deno_core::futures::future; + use std::fs; + + #[derive(Debug, Default)] + pub(crate) struct MockLoader { + pub fixtures: PathBuf, + } + + impl deno_graph::source::Loader for MockLoader { + fn load( + &mut self, + specifier: &ModuleSpecifier, + _is_dynamic: bool, + ) -> deno_graph::source::LoadFuture { + let specifier_text = specifier + .to_string() + .replace(":///", "_") + .replace("://", "_") + .replace("/", "-"); + let source_path = self.fixtures.join(specifier_text); + let response = fs::read_to_string(&source_path) + .map(|c| { + Some(deno_graph::source::LoadResponse { + specifier: specifier.clone(), + maybe_headers: None, + content: Arc::new(c), + }) + }) + .map_err(|err| err.into()); + Box::pin(future::ready((specifier.clone(), response))) + } + } async fn setup( maybe_specifier: Option, @@ -602,16 +593,19 @@ mod tests { .unwrap_or_else(|| resolve_url_or_path("file:///main.ts").unwrap()); let hash_data = maybe_hash_data.unwrap_or_else(|| vec![b"".to_vec()]); let fixtures = test_util::testdata_path().join("tsc2"); - let handler = Arc::new(Mutex::new(MockSpecifierHandler { - fixtures, - ..MockSpecifierHandler::default() - })); - let mut builder = GraphBuilder::new(handler.clone(), None, None); - builder - .add(&specifier, false) - .await - .expect("module not inserted"); - let graph = Arc::new(Mutex::new(builder.get_graph())); + let mut loader = MockLoader { fixtures }; + let graph = Arc::new( + deno_graph::create_graph( + vec![specifier], + false, + None, + &mut loader, + None, + None, + None, + ) + .await, + ); State::new( graph, hash_data, @@ -627,13 +621,19 @@ mod tests { ) -> Result { let hash_data = vec![b"something".to_vec()]; let fixtures = test_util::testdata_path().join("tsc2"); - let handler = Arc::new(Mutex::new(MockSpecifierHandler { - fixtures, - ..Default::default() - })); - let mut builder = GraphBuilder::new(handler.clone(), None, None); - builder.add(specifier, false).await?; - let graph = Arc::new(Mutex::new(builder.get_graph())); + let mut loader = MockLoader { fixtures }; + let graph = Arc::new( + deno_graph::create_graph( + vec![specifier.clone()], + false, + None, + &mut loader, + None, + None, + None, + ) + .await, + ); let config = TsConfig::new(json!({ "allowJs": true, "checkJs": false, @@ -695,12 +695,12 @@ mod tests { } #[test] - fn test_hash_data_url() { + fn test_hash_url() { let specifier = deno_core::resolve_url( "data:application/javascript,console.log(\"Hello%20Deno\");", ) .unwrap(); - assert_eq!(hash_data_url(&specifier, &MediaType::JavaScript), "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js"); + assert_eq!(hash_url(&specifier, &MediaType::JavaScript), "data:///d300ea0796bd72b08df10348e0b70514c021f2e45bfe59cec24e12e97cd79c58.js"); } #[test]