diff --git a/Cargo.lock b/Cargo.lock index b50c6e97e05e3..4dd7491663b52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "ahash" version = "0.8.3" @@ -9,6 +19,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", ] @@ -48,6 +59,19 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ast_node" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c09c69dffe06d222d072c878c3afe86eee2179806f20503faec97250268b4c24" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.15", +] + [[package]] name = "async-priority-channel" version = "0.1.0" @@ -102,6 +126,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "better_scoped_tls" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794edcc9b3fb07bb4aecaa11f093fd45663b4feadb782d68303a2268bc2701de" +dependencies = [ + "scoped-tls", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -110,9 +143,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.2.1" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "bstr" @@ -355,6 +388,27 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "from_variant" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ec5dc38ee19078d84a692b1c41181ff9f94331c76cee66ff0208c770b5e54f" +dependencies = [ + "pmutil", + "proc-macro2", + "swc_macros_common", + "syn 2.0.15", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -512,7 +566,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786861e84a5793ad5f863d846de5eb064cd23b87e61ad708c8c402608202e7be" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.3.3", "bstr", "gix-path", "libc", @@ -558,7 +612,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.3.3", "bstr", "gix-features", "gix-path", @@ -642,7 +696,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "794520043d5a024dfeac335c6e520cb616f6963e30dab995892382e998c12897" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.3.3", "gix-path", "libc", "windows", @@ -754,6 +808,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "ignore" version = "0.4.20" @@ -829,6 +893,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4467ed1321b310c2625c5aa6c1b1ffc5de4d9e42668cf697a08fb033ee8265e" +dependencies = [ + "Inflector", + "pmutil", + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "itertools" version = "0.10.5" @@ -870,6 +947,79 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7aefb36fd43fef7003334742cbf77b243fcd36418a1d1bdd480d613a67968f6" +dependencies = [ + "lexical-core", +] + +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" +dependencies = [ + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +dependencies = [ + "lexical-util", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.139" @@ -1001,7 +1151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49ac8112fe5998579b22e29903c7b277fc7f91c7860c0236f35792caf8156e18" dependencies = [ "anyhow", - "bitflags 2.2.1", + "bitflags 2.3.3", "ctor", "napi-derive", "napi-sys", @@ -1053,6 +1203,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1120,6 +1276,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1166,6 +1344,11 @@ dependencies = [ "napi-build", "napi-derive", "rayon", + "swc_common", + "swc_ecma_ast", + "swc_ecma_dep_graph", + "swc_ecma_parser", + "swc_ecma_visit", "thiserror", "tokio", "tracing", @@ -1180,9 +1363,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "overload" @@ -1213,13 +1396,19 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + [[package]] name = "phf" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" dependencies = [ - "phf_shared", + "phf_shared 0.11.1", ] [[package]] @@ -1228,8 +1417,18 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.1", + "phf_shared 0.11.1", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand", ] [[package]] @@ -1238,10 +1437,19 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" dependencies = [ - "phf_shared", + "phf_shared 0.11.1", "rand", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "phf_shared" version = "0.11.1" @@ -1263,6 +1471,29 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pmutil" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" version = "2.1.5" @@ -1310,6 +1541,15 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + [[package]] name = "quote" version = "1.0.27" @@ -1335,6 +1575,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -1343,6 +1595,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rayon" @@ -1421,6 +1676,12 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.37.3" @@ -1444,6 +1705,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1461,6 +1728,20 @@ name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] [[package]] name = "sha1_smol" @@ -1507,12 +1788,232 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "string_enum" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fa4d4f81d7c05b9161f8de839975d3326328b8ba2831164b465524cc2f55252" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.15", +] + +[[package]] +name = "swc_atoms" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1370fa1d31e18a999928aaf18f166616b16c5cb7033ced7d50d06858c5fd90" +dependencies = [ + "once_cell", + "rustc-hash", + "serde", + "string_cache", + "string_cache_codegen", + "triomphe", +] + +[[package]] +name = "swc_common" +version = "0.31.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6414bd4e553f5638961d39b07075ffd37a3d63176829592f4a5900260d94ca1" +dependencies = [ + "ahash", + "ast_node", + "better_scoped_tls", + "cfg-if", + "either", + "from_variant", + "new_debug_unreachable", + "num-bigint", + "once_cell", + "rustc-hash", + "serde", + "siphasher", + "string_cache", + "swc_atoms", + "swc_eq_ignore_macros", + "swc_visit", + "tracing", + "unicode-width", + "url", +] + +[[package]] +name = "swc_ecma_ast" +version = "0.107.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5693558188efdd5b664e517b69ba8056a7f64c214ca8cd034e3ae8314566b866" +dependencies = [ + "bitflags 2.3.3", + "is-macro", + "num-bigint", + "scoped-tls", + "string_enum", + "swc_atoms", + "swc_common", + "unicode-id", +] + +[[package]] +name = "swc_ecma_dep_graph" +version = "0.109.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4535c2a8210bf69f78201f93e6dc741a81046d1c04479e41d26849155752d184" +dependencies = [ + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_visit", +] + +[[package]] +name = "swc_ecma_parser" +version = "0.137.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1915adb15d9ca1695e76d41524beb4806e9b603280edb7eedbaebe706a41c" +dependencies = [ + "either", + "lexical", + "num-bigint", + "serde", + "smallvec", + "smartstring", + "stacker", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "tracing", + "typed-arena", +] + +[[package]] +name = "swc_ecma_visit" +version = "0.93.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82bb87ee3345a7049efcbedc419f121933e0e3967457922848d0026fb3b79dac" +dependencies = [ + "num-bigint", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_visit", + "tracing", +] + +[[package]] +name = "swc_eq_ignore_macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a95d367e228d52484c53336991fdcf47b6b553ef835d9159db4ba40efb0ee8" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "swc_macros_common" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a273205ccb09b51fabe88c49f3b34c5a4631c4c00a16ae20e03111d6a42e832" +dependencies = [ + "pmutil", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "swc_visit" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c337fbb2d191bf371173dea6a957f01899adb8f189c6c31b122a6cfc98fc3" +dependencies = [ + "either", + "swc_visit_macros", +] + +[[package]] +name = "swc_visit_macros" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f322730fb82f3930a450ac24de8c98523af7d34ab8cb2f46bcb405839891a99" +dependencies = [ + "Inflector", + "pmutil", + "proc-macro2", + "quote", + "swc_macros_common", + "syn 2.0.15", +] + [[package]] name = "syn" version = "1.0.107" @@ -1625,6 +2126,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.28.2" @@ -1727,18 +2243,55 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "triomphe" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee8098afad3fb0c54a9007aab6804558410503ad676d4633f9c2559a00ac0f" +dependencies = [ + "serde", + "stable_deref_trait", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-bom" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" +[[package]] +name = "unicode-id" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" + [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.10.0" @@ -1751,6 +2304,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/lerna.json b/lerna.json index 4fac41b8c4477..a44bd393fd8e2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["build/packages/*", "build/packages/nx/native-packages/*"], - "version": "16.6.0-beta.3", + "version": "16.6.0-beta.4", "granularPathspec": false, "command": { "publish": { diff --git a/packages/nx/Cargo.toml b/packages/nx/Cargo.toml index 2443f8823382c..286a0e7b450ad 100644 --- a/packages/nx/Cargo.toml +++ b/packages/nx/Cargo.toml @@ -27,6 +27,11 @@ watchexec-filterer-ignore = "1.2.1" watchexec-signals = "1.0.0" xxhash-rust = { version = '0.8.5', features = ['xxh3', 'xxh64'] } +swc_common = "0.31.16" +swc_ecma_parser = { version = "0.137.1", features = ["typescript"] } +swc_ecma_visit = "0.93.0" +swc_ecma_ast = "0.107.0" + [lib] crate-type = ['cdylib'] @@ -35,3 +40,5 @@ napi-build = '2.0.1' [dev-dependencies] assert_fs = "1.0.10" +# This is only used for unit tests +swc_ecma_dep_graph = "0.109.1" diff --git a/packages/nx/src/adapter/angular-json.ts b/packages/nx/src/adapter/angular-json.ts index 57cef283c40a6..19c88d01a4068 100644 --- a/packages/nx/src/adapter/angular-json.ts +++ b/packages/nx/src/adapter/angular-json.ts @@ -28,6 +28,7 @@ export function shouldMergeAngularProjects( export function isAngularPluginInstalled() { try { + // nx-ignore-next-line require.resolve('@nx/angular'); return true; } catch { diff --git a/packages/nx/src/native/index.d.ts b/packages/nx/src/native/index.d.ts index d5445ab9341d4..a57d3c97b3baf 100644 --- a/packages/nx/src/native/index.d.ts +++ b/packages/nx/src/native/index.d.ts @@ -14,6 +14,7 @@ export function hashArray(input: Array): string export function hashFile(file: string): FileData | null export function hashFiles(workspaceRoot: string): Record export function hashFilesMatchingGlobs(directory: string, globPatterns: Array): string | null +export function findImports(projectFileMap: Record>): Array export interface FileData { file: string hash: string @@ -44,6 +45,12 @@ export interface NxWorkspaceFiles { projectConfigurations: Record } export function getWorkspaceFilesNative(workspaceRoot: string, globs: Array, parseConfigurations: (arg0: Array) => Record): NxWorkspaceFiles +export class ImportResult { + file: string + sourceProject: string + dynamicImportExpressions: Array + staticImportExpressions: Array +} export class Watcher { origin: string /** diff --git a/packages/nx/src/native/index.js b/packages/nx/src/native/index.js index a1c22704faa12..abb3085186c94 100644 --- a/packages/nx/src/native/index.js +++ b/packages/nx/src/native/index.js @@ -246,7 +246,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { expandOutputs, remove, copy, hashArray, hashFile, hashFiles, hashFilesMatchingGlobs, EventType, Watcher, WorkspaceErrors, getProjectConfigurations, getWorkspaceFilesNative } = nativeBinding +const { expandOutputs, remove, copy, hashArray, hashFile, hashFiles, hashFilesMatchingGlobs, ImportResult, findImports, EventType, Watcher, WorkspaceErrors, getProjectConfigurations, getWorkspaceFilesNative } = nativeBinding module.exports.expandOutputs = expandOutputs module.exports.remove = remove @@ -255,6 +255,8 @@ module.exports.hashArray = hashArray module.exports.hashFile = hashFile module.exports.hashFiles = hashFiles module.exports.hashFilesMatchingGlobs = hashFilesMatchingGlobs +module.exports.ImportResult = ImportResult +module.exports.findImports = findImports module.exports.EventType = EventType module.exports.Watcher = Watcher module.exports.WorkspaceErrors = WorkspaceErrors diff --git a/packages/nx/src/native/logger/mod.rs b/packages/nx/src/native/logger/mod.rs index a22fa7838744c..d509f71aaf0c4 100644 --- a/packages/nx/src/native/logger/mod.rs +++ b/packages/nx/src/native/logger/mod.rs @@ -63,7 +63,7 @@ where pub(crate) fn enable_logger() { let env_filter = - EnvFilter::try_from_env("NX_NATIVE_LOGGING").unwrap_or_else(|_| EnvFilter::new("INFO")); + EnvFilter::try_from_env("NX_NATIVE_LOGGING").unwrap_or_else(|_| EnvFilter::new("ERROR")); _ = tracing_subscriber::fmt() .with_env_filter(env_filter) .event_format(NxLogFormatter) diff --git a/packages/nx/src/native/mod.rs b/packages/nx/src/native/mod.rs index 8225da804196a..cdaa2e4058783 100644 --- a/packages/nx/src/native/mod.rs +++ b/packages/nx/src/native/mod.rs @@ -1,6 +1,7 @@ pub mod cache; pub mod hasher; mod logger; +pub mod plugins; mod types; mod utils; mod walker; diff --git a/packages/nx/src/native/plugins/js.rs b/packages/nx/src/native/plugins/js.rs new file mode 100644 index 0000000000000..a3a9ee41710b7 --- /dev/null +++ b/packages/nx/src/native/plugins/js.rs @@ -0,0 +1 @@ +mod ts_import_locators; diff --git a/packages/nx/src/native/plugins/js/ts_import_locators.rs b/packages/nx/src/native/plugins/js/ts_import_locators.rs new file mode 100644 index 0000000000000..af1243aa4f09b --- /dev/null +++ b/packages/nx/src/native/plugins/js/ts_import_locators.rs @@ -0,0 +1,1136 @@ +use std::collections::HashMap; +use std::fmt::Debug; +use std::path::Path; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Instant; + +use rayon::prelude::*; +use tracing::debug; +use tracing::trace; + +use swc_common::{BytePos, SourceFile, SourceMap, Spanned}; +use swc_ecma_ast::EsVersion::EsNext; +use swc_ecma_parser::error::Error; +use swc_ecma_parser::lexer::Lexer; +use swc_ecma_parser::token::Keyword::{Class, Default_, Export, Function, Import}; +use swc_ecma_parser::token::Word::{Ident, Keyword}; +use swc_ecma_parser::token::{BinOpToken, Token, TokenAndSpan}; +use swc_ecma_parser::{Syntax, Tokens, TsConfig}; + +use crate::native::logger::enable_logger; + +#[napi] +#[derive(Debug)] +pub struct ImportResult { + pub file: String, + pub source_project: String, + pub dynamic_import_expressions: Vec, + pub static_import_expressions: Vec, +} + +#[derive(Debug)] +enum ImportType { + Static, + Dynamic, +} +#[derive(Debug, Clone, Copy, PartialEq)] +enum BlockType { + Block, + Function, + Class, + Object, + ObjectType, + ArrowFunction, +} + +fn is_identifier(token: &Token) -> bool { + matches!(token, Token::Word(Ident(_))) +} + +struct State<'a> { + lexer: Lexer<'a>, + pub current_token: Option, + pub previous_token: Option, + pub import_type: ImportType, + open_brace_count: i128, + blocks_stack: Vec, + next_block_type: BlockType, +} + +impl<'a> State<'a> { + pub fn new(lexer: Lexer<'a>) -> Self { + State { + lexer, + current_token: None, + previous_token: None, + open_brace_count: 0, + blocks_stack: vec![], + next_block_type: BlockType::Block, + import_type: ImportType::Dynamic, + } + } + + pub fn take_errors(&mut self) -> Vec { + self.lexer.take_errors() + } + + pub fn next(&mut self) -> &Option { + // Keep the current token as the last token before calling next + self.previous_token = self.current_token.clone(); + + let next = self.lexer.next(); + + // Store current token + self.current_token = next; + + if let Some(current) = &self.current_token { + // Keep track of braces/ when blocks begin and end + match ¤t.token { + Token::DollarLBrace => { + self.open_brace_count += 1; + } + Token::LBrace => { + self.open_brace_count += 1; + + // A new block has opened so push the new block type + self.blocks_stack.push(self.next_block_type); + } + Token::RBrace => { + self.open_brace_count -= 1; + + // Reset the next block type + self.next_block_type = BlockType::Block; + + // The block has closed so remove it from the block stack + self.blocks_stack.pop(); + } + _ => {} + } + + // Keep track of when we are in an object declaration because colons mean different things + let in_object_declaration = self.blocks_stack.contains(&BlockType::Object); + + let new_line = self.lexer.had_line_break_before_last(); + + // This is the beginning of a new statement, reset the import type to the default + // Reset import type when there is new line not in braces + if new_line && self.open_brace_count == 0 { + self.import_type = ImportType::Dynamic; + } + + match ¤t.token { + Token::Word(word) => match word { + // Matches something like const a = a as import('a') + // This is a static type import + Ident(i) if i == "as" => { + self.import_type = ImportType::Static; + } + // Matches something like export const = import('a') + // This is a dynamic import + Keyword(keyword) if *keyword == Export => { + self.import_type = ImportType::Dynamic; + } + + // If a function keyword appears, the next open brace will start a function block + Keyword(keyword) if *keyword == Function => { + self.next_block_type = BlockType::Function; + } + // If a class keyword appears, the next open brace will start a class block + Keyword(keyword) if *keyword == Class => { + self.next_block_type = BlockType::Class; + } + _ => {} + }, + Token::AssignOp(_) => { + // When things are assigned, they are dynamic imports + // Ex: const a = import('a'); + self.import_type = ImportType::Dynamic; + + // When assigning things, an open brace means an object + self.next_block_type = BlockType::Object + } + // When we see a (, the next brace is an object passed into a function + // Matches console.log({ a: import('a') }); + Token::LParen => { + if let Some(t) = &self.previous_token { + match t.token { + _ if is_identifier(&t.token) => { + // Function Call + self.next_block_type = BlockType::Object; + } + _ => { + // Arrow Function Declaration + self.next_block_type = BlockType::ArrowFunction; + } + } + } + } + Token::BinOp(op) => match op { + BinOpToken::Lt => { + // Matches things like Foo + // This is a static import + if let Some(t) = &self.previous_token { + match t.token { + _ if is_identifier(&t.token) => { + // Generic + self.import_type = ImportType::Static; + } + _ => {} + } + } + } + + BinOpToken::Div => { + if let Some(t) = &self.previous_token { + match t.token { + // Real division of numbers or identifier + // 2 / 1 + // a / 2 + // a.getLength() / 2 + // a.a / 2 + // "a" / 2 + // `a` / 2 + Token::Num { .. } + | Token::Word(_) + | Token::RParen + | Token::RBrace + | Token::RBracket + | Token::Str { .. } + | Token::BackQuote + | Token::JSXTagStart + | Token::JSXName { .. } => { + return self.next(); + } + // Everything else, is the start of a regex + // The lexer needs to know when there's a regex + _ => { + self.lexer + .set_next_regexp(Some(self.current_token.span_lo())); + + if let Some(_) = self.next() { + self.lexer.set_next_regexp(None); + } + } + } + } + } + + // When there are binary operations, it is dynamic + // Matches things like const a = 3 + (await import('a')) + _ => { + self.import_type = ImportType::Dynamic; + } + }, + // When there is a string literal, ${ begins a dynamic expression + // When functions and methods begin, this starts a dynamic block + Token::DollarLBrace | Token::LBrace => { + self.import_type = ImportType::Dynamic; + } + // When we see a ; A new dynamic statement begins + Token::Semi => { + self.import_type = ImportType::Dynamic; + } + Token::Colon => { + if let Some(t) = &self.previous_token { + match t.token { + // Matches { 'a': import('a') } + Token::Str { .. } if in_object_declaration => { + // Object Property Assignment + self.import_type = ImportType::Dynamic + } + // Matches { [a]: import('a') } + Token::RBracket if in_object_declaration => { + // Object Property Assignment + self.import_type = ImportType::Dynamic + } + // Object Property Assignment + // Matches { a: import('a') } + _ if is_identifier(&t.token) && in_object_declaration => { + self.import_type = ImportType::Dynamic + } + // Matches const a: typeof import('a') + _ => { + // A brace would begin an object type + // Ex: const a: { a: typeof import('a') } + self.next_block_type = BlockType::ObjectType; + // This is a typing and is static + self.import_type = ImportType::Static; + } + } + } + } + _ => {} + } + } + + &self.current_token + } +} + +fn find_specifier_in_import(state: &mut State) -> Option<(String, ImportType)> { + if let Some(next) = state.next() { + // This match is pretty strict on what should follow an import, anything else is skipped + match &next.token { + // This begins a module naming + // Ex: import { a } from 'a'; + Token::LBrace => {} + // This indicates a import function call + // Ex: import('a') + Token::LParen => { + let mut maybe_literal = None; + + while let Some(current) = state.next() { + match ¤t.token { + // If we match a string, then it might be a literal import + Token::Str { value, .. } => { + maybe_literal = Some(value.to_string()); + } + Token::RParen => { + // When the function call is closed, add the import if it exists + if let Some(import) = maybe_literal { + return match &state.import_type { + ImportType::Static => Some((import, ImportType::Static)), + ImportType::Dynamic => Some((import, ImportType::Dynamic)), + }; + } + } + // If we match anything else, continue the outer loop and skip this import + // because it is not a literal import + _ => { + return None; + } + } + } + } + // This is a import star statement + // Ex: import * from 'a'; + Token::BinOp(op) if *op == BinOpToken::Mul => {} + Token::Word(word) => match word { + // This is a import type statement + // Ex: import type { } from 'a'; + Ident(i) if i == "type" => { + if let Some(next) = state.next() { + // What follows a type import is pretty strict, otherwise ignore it + match &next.token { + // Matches import type {} from 'a'; + Token::LBrace => {} + // Matches import type * from 'a'; + Token::BinOp(op) if *op == BinOpToken::Mul => {} + // Matches import type Cat from 'a'; + Token::Word(word) if matches!(word, Ident(_)) => {} + _ => { + return None; + } + } + } + } + _ => {} + }, + // Matches: import 'a'; + Token::Str { value, .. } => { + return Some((value.to_string(), ImportType::Static)); + } + _ => { + return None; + } + } + } + + // This is a static import because it is not a import function call + // import { } from 'a'; + while let Some(current) = state.next() { + if let Token::Str { value, .. } = ¤t.token { + return Some((value.to_string(), ImportType::Static)); + } + } + + None +} + +fn find_specifier_in_export(state: &mut State) -> Option<(String, ImportType)> { + if let Some(next) = state.next() { + // This match is pretty strict about what follows an export keyword + // Everything else is skipped + match &next.token { + // Matches export { } from 'a'; + Token::LBrace => {} + Token::Word(Ident(i)) if i == "type" => { + // Matches an export type + if let Some(next) = state.next() { + // What follows is pretty strict + match next.token { + // Matches export type { a } from 'a'; + Token::LBrace => {} + // Anything else after a type is a definition, not an import + // Matches export type = 'a'; + _ => { + return None; + } + } + } + } + // Matches export * from 'a'; + Token::BinOp(op) if *op == BinOpToken::Mul => {} + _ => { + return None; + } + } + } + + while let Some(current) = state.next() { + match ¤t.token { + // Matches: + // export { A } + // export { A as B } + // export { A } from + // export { A, B } from + Token::RBrace | Token::Word(Ident(_)) | Token::Comma => {} + Token::Word(Keyword(kw)) if *kw == Default_ => {} + // When we find a string, it's a export + Token::Str { value, .. } => return Some((value.to_string(), ImportType::Static)), + _ => { + return None; + } + } + } + + None +} + +fn find_specifier_in_require(state: &mut State) -> Option<(String, ImportType)> { + let mut import = None; + while let Some(current) = state.next() { + match ¤t.token { + // This opens the require call + Token::LParen | + // Matches things like require.resolve + Token::Dot | + Token::Word(Ident(_)) => {} + // This could be a string literal + Token::Str { value, .. } => { + import = Some(value.to_string()); + } + + // When the require call ends, add the require + Token::RParen => { + if let Some(import) = import { + // When all blocks are object blocks, this is a static require + // Matches things like const a = { a: require('a') }; + let static_import = state + .blocks_stack + .iter() + .all(|block_type| matches!(block_type, BlockType::Object)); + + let import_type = if static_import { + ImportType::Static + } else { + ImportType::Dynamic + }; + + return Some((import, import_type)); + } else { + return None; + } + } + // Anything else means this is not a require of a string literal + _ => { + return None; + } + } + } + + None +} + +fn process_file((source_project, file_path): (&String, &String)) -> Option { + let now = Instant::now(); + let cm = Arc::::default() + .load_file(Path::new(file_path)) + .unwrap(); + + let tsx = file_path.ends_with(".tsx") || file_path.ends_with(".jsx"); + let lexer = Lexer::new( + Syntax::Typescript(TsConfig { + tsx, + decorators: false, + dts: file_path.ends_with(".d.ts"), + no_early_errors: false, + disallow_ambiguous_jsx_like: false, + }), + EsNext, + (&*cm).into(), + None, + ); + + // State + let mut state = State::new(lexer); + + let mut static_import_expressions: Vec = vec![]; + let mut dynamic_import_expressions: Vec = vec![]; + + loop { + let current_token = state.next(); + + // This is the end of the file + if current_token.is_none() { + break; + } + + if let Some(current) = ¤t_token { + let word = match ¤t.token { + Token::Word(w) => w, + _ => { + continue; + } + }; + let import = match word { + // This is an import keyword + Keyword(keyword) if *keyword == Import => { + if is_code_ignored(&cm, current.span.lo) { + continue; + } + + find_specifier_in_import(&mut state) + } + Keyword(keyword) if *keyword == Export => { + if is_code_ignored(&cm, current.span.lo) { + continue; + } + + find_specifier_in_export(&mut state) + } + Ident(ident) if ident == "require" => { + if is_code_ignored(&cm, current.span.lo) { + continue; + } + find_specifier_in_require(&mut state) + } + _ => None, + }; + + if let Some((specifier, import_type)) = import { + match import_type { + ImportType::Static => { + static_import_expressions.push(specifier); + } + ImportType::Dynamic => { + dynamic_import_expressions.push(specifier); + } + } + } + } + } + + trace!("finding imports in {} {:.2?}", file_path, now.elapsed()); + + // These are errors from the lexer. They don't always mean something is broken + let mut errs = state.take_errors(); + if !errs.is_empty() { + for err in errs.iter() { + debug!( + "{}:{}:{} {}", + file_path, + cm.lookup_line(err.span_hi()).unwrap() + 1, + (err.span_lo() - cm.line_begin_pos(err.span_lo())).0, + err.kind().msg(), + ); + } + errs.clear(); + } + + if file_path == "nx-dev/ui-markdoc/src/lib/tags/graph.component.tsx" { + trace!("{:?}", static_import_expressions); + trace!("{:?}", dynamic_import_expressions); + } + + Some(ImportResult { + file: file_path.clone(), + source_project: source_project.clone(), + static_import_expressions, + dynamic_import_expressions, + }) +} + +fn is_code_ignored(cm: &Rc, pos: BytePos) -> bool { + let line_with_dep = cm.lookup_line(pos).expect("The dep is on a line"); + + if line_with_dep == 0 { + return false; + } + + if let Some(line_before_dep) = cm.get_line(line_with_dep - 1) { + let trimmed_line = line_before_dep.trim(); + if trimmed_line == "// nx-ignore-next-line" || trimmed_line == "/* nx-ignore-next-line */" { + return true; + } + } + false +} + +#[napi] +fn find_imports(project_file_map: HashMap>) -> Vec { + enable_logger(); + + let files_to_process: Vec<(&String, &String)> = project_file_map + .iter() + .flat_map(|(project_name, files)| files.iter().map(move |file| (project_name, file))) + .collect(); + + files_to_process + .into_par_iter() + .filter_map(process_file) + .collect() +} +#[cfg(test)] +mod find_imports { + use super::*; + use assert_fs::prelude::*; + use assert_fs::TempDir; + use swc_common::comments::NoopComments; + + #[test] + fn should_find_imports() { + let temp_dir = TempDir::new().unwrap(); + temp_dir + .child("test.ts") + .write_str( + r#" + // nx-ignore-next-line + import * as bar from 'ignored'; + /* nx-ignore-next-line */ + import * as bar from 'ignored-by-block-comment'; + + // TODO: comment + + // import { a } from 'commented-import'; + + const a = "import { a } from 'string-import'" + + import 'static-import'; + import a from 'static-default-import-from'; + import { a } from 'static-import-from'; + import { a } from "static-import-from-with-double-quotes"; + import type { c } from 'static-type-import-from'; + import type * as a from 'static-type-import-all'; + import type a from 'static-type-import-default'; + + const a = { + import: a['import-function'] + }; + + const a: Foo + import('dynamic-import-after-line-without-semicolon'); + + const a: typeof import('static-typeof-import'); + const a = import('non-literal-' + 'import'); + const a: import('static-type-import') = import('dynamic-const-import') as import('static-as-type-import'); + + const a: Gen; + + const b = require('static-require'); + const b = require(`require-in-backticks`); + const b = require.resolve('static-require-dot-resolve'); + const b = require(`non-literal-${'require'}`); + + export * from 'static-export'; + export { a } from 'static-partial-export'; + export type { a } from 'static-type-export'; + export default import('dynamic-exported-import'); + export default require('static-default-export-of-a-require'); + export function a() { return 'function-export';} + export const a = 'const-export'; + export type a = 'type-export'; + module.exports = { + a: await import('dynamic-module-export-in-object'), + a: require('static-module-export-require-in-object'), + }; + exports = { + a: await import('dynamic-export-in-object'), + a: require('static-require-in-object'), + }; + + + + console.log(`${import('dynamic-import-in-string-template')}`); + console.log(`${`${import('dynamic-import-in-string-template-in-string-template')}`}`); + + function f(a: import('static-argument-type-import') = import('dynamic-default-argument-import')): import('static-return-type-import') { + require('dynamic-require'); + } + + class Animal { + a: import('static-class-property-type-import') = import('dynamic-class-property-value-import'); + a: { a: import('static-class-property-type-import-in-object') } = { a: import('dynamic-class-property-value-import-in-object') }; + eat(food: import('static-method-argument-type-import')) { + import('dynamic-method-import'); + require('dynamic-method-require'); + } + } + + const obj = { + [import('dynamic-obj-key-import')]: import('dynamic-obj-prop-import'), + [require('static-obj-key-require')]: require('static-obj-prop-require') + }; + + const obj = { + method: function(): import('static-import-in-object-method-return-type') { + import('dynamic-import-in-object-method'); + } + }; + + (a as import('static-import-as-type-cast')).a; + + const a: (a: import('static-function-param-type')) => import('static-function-return-type') = () => { + const a: import('static-import-in-arrow-function') = import('static-import-in-arrow-function'); + } + + (() => { + const a: import('static-import-in-iife') = import('dynamic-import-in-iife'); + })(); + + ((a: { a: import('static-import-in-iife-parameter-type') } = { a: import('static-import-in-iife-parameter-default') }) => { + const a: import('static-import-in-iife') = import('dynamic-import-in-iife'); + })(); + + { + import('dynamic-import-in-block-closure'); + } + + console.log({ + a: import('dynamic-import-in-object-in-function-call') + }); + + const a = 3 + (await import('dynamic-import-in-binary-operation')); + + const arr = [require('static-require-in-arr')]; + + const e = require(name + 'non-literal-require'); + + const foo = import(name + 'non-literal-import2'); + + console.log(`import { c } from 'in-string-literal';`); + + "#, + ) + .unwrap(); + + temp_dir + .child("broken-file.ts") + .write_str( + r#" + impo { a } frm 'broken-import'; + import { a } from + export { } + import { a } from 'import-in-broken-file'; + "#, + ) + .unwrap(); + + let test_file_path = temp_dir.display().to_string() + "/test.ts"; + let broken_file_path = temp_dir.display().to_string() + "/broken-file.ts"; + + let results = find_imports(HashMap::from([( + String::from("a"), + vec![test_file_path.clone(), broken_file_path], + )])); + + let result = results.get(0).unwrap(); + let ast_results: ImportResult = find_imports_with_ast(test_file_path); + + assert_eq!( + result.static_import_expressions, + ast_results.static_import_expressions + ); + assert_eq!( + result.dynamic_import_expressions, + ast_results.dynamic_import_expressions + ); + let result_from_broken_file = results.get(1).unwrap(); + + assert_eq!( + result_from_broken_file.static_import_expressions, + vec![String::from("import-in-broken-file"),] + ); + } + + #[test] + fn should_find_imports_in_all_sorts_of_import_statements() { + let temp_dir = TempDir::new().unwrap(); + temp_dir + .child("test.ts") + .write_str( + r#" + import * as React from "react"; + import { Component } from "react"; + import { + Component + } from "react" + import { + Component + } from "react"; + + import "./app.scss"; + + function inside() { + import('./module.ts') + } + const a = 1; + export class App {} + "#, + ) + .unwrap(); + + let test_file_path = temp_dir.display().to_string() + "/test.ts"; + + let results = find_imports(HashMap::from([( + String::from("a"), + vec![test_file_path.clone()], + )])); + + let result = results.get(0).unwrap(); + let ast_results: ImportResult = find_imports_with_ast(test_file_path); + + assert_eq!( + result.static_import_expressions, + ast_results.static_import_expressions + ); + assert_eq!( + result.dynamic_import_expressions, + ast_results.dynamic_import_expressions + ); + } + + #[test] + fn should_find_imports_in_all_sorts_of_export_statements() { + let temp_dir = TempDir::new().unwrap(); + temp_dir + .child("test.ts") + .write_str( + r#" + export * from './module'; + export { + A + } from './a'; + + export { B } from './b'; + export type { B } from './b'; + + export { C as D } from './c'; + + const a = 1; + export class App {} + + export { a as aa } + + export function f() { + return 'value-after-export'; + } + + export { a as aa }; + + export { a as default } from 'static-default-export'; + + export function f() { + return 'value-after-export'; + } + "#, + ) + .unwrap(); + + let test_file_path = temp_dir.display().to_string() + "/test.ts"; + + let results = find_imports(HashMap::from([( + String::from("a"), + vec![test_file_path.clone()], + )])); + + let result = results.get(0).unwrap(); + let ast_results: ImportResult = find_imports_with_ast(test_file_path); + + assert_eq!( + result.static_import_expressions, + ast_results.static_import_expressions + ); + assert_eq!( + result.dynamic_import_expressions, + ast_results.dynamic_import_expressions + ); + } + + #[test] + fn should_find_imports_in_all_sorts_of_require_statements() { + let temp_dir = TempDir::new().unwrap(); + temp_dir + .child("test.ts") + .write_str( + r#" + require('./a'); + + require('./b'); + const c = require('./c'); + + const a = 1; + export class App {} + "#, + ) + .unwrap(); + + let test_file_path = temp_dir.display().to_string() + "/test.ts"; + + let results = find_imports(HashMap::from([( + String::from("a"), + vec![test_file_path.clone()], + )])); + + let result = results.get(0).unwrap(); + let ast_results: ImportResult = find_imports_with_ast(test_file_path); + + assert_eq!( + result.static_import_expressions, + ast_results.static_import_expressions + ); + assert_eq!( + result.dynamic_import_expressions, + ast_results.dynamic_import_expressions + ); + } + + #[test] + fn should_find_imports_in_tsx_files() { + let temp_dir = TempDir::new().unwrap(); + temp_dir + .child("test.tsx") + .write_str( + r#" + import dynamic from 'next/dynamic'; + import { ReactElement, useEffect, useState } from 'react'; + + + export function A() { + return ( + + ); + } + const NxProjectGraphViz = dynamic( + () => import('dynamic-import-after-jsx').then((m) => m.A), + { + ssr: false, + loading: () => , + } + ); + "#, + ) + .unwrap(); + + let test_file_path = temp_dir.display().to_string() + "/test.tsx"; + + let results = find_imports(HashMap::from([( + String::from("a"), + vec![test_file_path.clone()], + )])); + + let result = results.get(0).unwrap(); + let ast_results: ImportResult = find_imports_with_ast(test_file_path); + + assert_eq!( + result.static_import_expressions, + ast_results.static_import_expressions + ); + assert_eq!( + result.dynamic_import_expressions, + ast_results.dynamic_import_expressions + ); + } + + #[test] + fn should_ignore_lines_with_nx_ignore() { + let temp_dir = TempDir::new().unwrap(); + temp_dir + .child("test.ts") + .write_str( + r#" + + + // nx-ignore-next-line + import * as React from "react"; + + // nx-ignore-next-line + import "./app.scss"; + + // nx-ignore-next-line + import('./module.ts') + + // nx-ignore-next-line + export { B } from './b'; + + // nx-ignore-next-line + const b = require('./b'); + "#, + ) + .unwrap(); + + let test_file_path = temp_dir.display().to_string() + "/test.ts"; + + let results = find_imports(HashMap::from([(String::from("a"), vec![test_file_path])])); + + let result = results.get(0).unwrap(); + + assert!(result.static_import_expressions.is_empty()); + assert!(result.dynamic_import_expressions.is_empty()); + } + + #[test] + fn should_find_imports_around_template_literals() { + let temp_dir = TempDir::new().unwrap(); + temp_dir + .child("test.ts") + .write_str( + r#" + + const b = `a: ${a}` + const c = await import('./c') + + const c = `a: ${a}, b: ${b}` + const d = await import('./d') + const b = unquotedLiteral.replace(/"/g, '\\"') + const c = await import('./c') + const d = require('./d') + const b = `"${unquotedLiteral.replace(/"/g, '\\"')}"` + const c = await import('./c') + const d = require('./d') + const b = `"${1 / 2} ${await import('./b')} ${await require('./c')}"`; + const a = `"${require('./a')}"` + const b = `"${await import('./b')}"` + "#, + ) + .unwrap(); + + let test_file_path = temp_dir.display().to_string() + "/test.ts"; + + let results = find_imports(HashMap::from([( + String::from("a"), + vec![test_file_path.clone()], + )])); + + let result = results.get(0).unwrap(); + let ast_results: ImportResult = find_imports_with_ast(test_file_path); + + assert_eq!( + result.static_import_expressions, + ast_results.static_import_expressions + ); + assert_eq!( + result.dynamic_import_expressions, + ast_results.dynamic_import_expressions + ); + } + + #[test] + fn should_find_imports_after_regexp() { + let temp_dir = TempDir::new().unwrap(); + temp_dir + .child("test.ts") + .write_str( + r#" + + const b = /"/g; const c = await import('dynamic-import-after-regex-assignment'); const d = require('static-require-after-regex-assignment') + + (async () => { + /"/g.test(await import('dynamic-import-after-regex')); + })(); + + + const a = 10 / 1; const c = await import('dynamic-import-after-number-division'); const d = require('static-require-after-number-division') + const a = new RegExp(require('static-require-in-regex')) + const a = 10 / require('static-require-being-divided-by') + + const a = 10; const b = a / 1; const c = await import('dynamic-import-after-identifier-division'); const d = require('static-require-after-identifier-division') + const a = {}; const b = a.a / 1; const c = await import('dynamic-import-after-property-access-division'); const d = require('static-require-after-property-access-division') + const a = {}; const b = a.getLength() / 1; const c = await import('dynamic-import-after-return-value-division'); const d = require('static-require-after-return-value-division') + const a = "a" / 1; const c = await import('dynamic-import-after-string-division'); const d = require('static-require-after-string-division') + const a = `a` / 1; const c = await import('dynamic-import-after-string-template-division'); const d = require('static-require-after-string-template-division') + + + const a = new RegExp(/"/g); const c = await import('dynamic-import-after-new-regexp'); const d = require('static-require-after-new-regexp') + "#, + ) + .unwrap(); + + let test_file_path = temp_dir.display().to_string() + "/test.ts"; + + let results = find_imports(HashMap::from([( + String::from("a"), + vec![test_file_path.clone()], + )])); + + let result = results.get(0).unwrap(); + let ast_results: ImportResult = find_imports_with_ast(test_file_path); + + assert_eq!( + result.static_import_expressions, + ast_results.static_import_expressions + ); + assert_eq!( + result.dynamic_import_expressions, + ast_results.dynamic_import_expressions + ); + } + + // This function finds imports with the ast which verifies that the imports we find are the same as the ones typescript finds + fn find_imports_with_ast(file_path: String) -> ImportResult { + let cm = Arc::::default() + .load_file(Path::new(file_path.as_str())) + .unwrap(); + + let mut errs: Vec = vec![]; + let tsx = file_path.ends_with(".tsx") || file_path.ends_with(".jsx"); + + let module = swc_ecma_parser::parse_file_as_module( + &cm, + Syntax::Typescript(TsConfig { + tsx, + decorators: true, + dts: file_path.ends_with(".d.ts"), + no_early_errors: false, + ..TsConfig::default() + }), + EsNext, + None, + &mut errs, + ) + .unwrap(); + + let comments = NoopComments; + let deps = swc_ecma_dep_graph::analyze_dependencies(&module, &comments); + + let mut static_import_expressions = vec![]; + let mut dynamic_import_expressions = vec![]; + for dep in deps { + let line_with_dep = cm.lookup_line(dep.span.lo).expect("The dep is on a line"); + + if line_with_dep > 0 { + if let Some(line_before_dep) = cm.get_line(line_with_dep - 1) { + let trimmed_line = line_before_dep.trim(); + if trimmed_line == "// nx-ignore-next-line" + || trimmed_line == "/* nx-ignore-next-line */" + { + continue; + } + } + } + + if dep.is_dynamic { + dynamic_import_expressions.push(dep.specifier.to_string()); + } else { + static_import_expressions.push(dep.specifier.to_string()); + } + } + ImportResult { + source_project: String::from("source"), + file: file_path, + static_import_expressions, + dynamic_import_expressions, + } + } +} diff --git a/packages/nx/src/native/plugins/mod.rs b/packages/nx/src/native/plugins/mod.rs new file mode 100644 index 0000000000000..aaf074e6ec734 --- /dev/null +++ b/packages/nx/src/native/plugins/mod.rs @@ -0,0 +1 @@ +mod js; diff --git a/packages/nx/src/native/watch/types.rs b/packages/nx/src/native/watch/types.rs index 2ba55065f23ce..433378d2b7079 100644 --- a/packages/nx/src/native/watch/types.rs +++ b/packages/nx/src/native/watch/types.rs @@ -24,11 +24,11 @@ pub struct WatchEvent { pub r#type: EventType, } -impl From for WatchEvent { - fn from(value: WatchEventInternal) -> Self { +impl From<&WatchEventInternal> for WatchEvent { + fn from(value: &WatchEventInternal) -> Self { let path = value .path - .strip_prefix(&value.origin.expect("origin is available")) + .strip_prefix(value.origin.as_ref().expect("origin is available")) .unwrap_or(&value.path) .display() .to_string(); diff --git a/packages/nx/src/native/watch/watcher.rs b/packages/nx/src/native/watch/watcher.rs index 99c02df84b8ad..86616842cb60c 100644 --- a/packages/nx/src/native/watch/watcher.rs +++ b/packages/nx/src/native/watch/watcher.rs @@ -1,9 +1,10 @@ +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::convert::Infallible; use std::path::MAIN_SEPARATOR; use std::sync::Arc; -use crate::native::watch::types::{WatchEvent, WatchEventInternal}; +use crate::native::watch::types::{EventType, WatchEvent, WatchEventInternal}; use itertools::Itertools; use napi::bindgen_prelude::*; use napi::threadsafe_function::{ @@ -68,27 +69,22 @@ impl Watcher { .with_env_filter(EnvFilter::from_env("NX_NATIVE_LOGGING")) .try_init(); - let mut callback_tsfn: ThreadsafeFunction>> = - callback.create_threadsafe_function( - 0, - |ctx: ThreadSafeCallContext>>| { - let mut watch_events: Vec = vec![]; - trace!(?ctx.value, "Base collection that will be sent"); + let mut callback_tsfn: ThreadsafeFunction> = callback + .create_threadsafe_function( + 0, + |ctx: ThreadSafeCallContext>| { + let mut watch_events: Vec = vec![]; + trace!(?ctx.value, "Base collection that will be sent"); - for (_, value) in ctx.value { - let event = value - .first() - .expect("should always have at least 1 element") - .to_owned(); - - watch_events.push(event.into()); - } + for event in ctx.value.values() { + watch_events.push(event.into()); + } - trace!(?watch_events, "sending to node"); + trace!(?watch_events, "sending to node"); - Ok(vec![watch_events]) - }, - )?; + Ok(vec![watch_events]) + }, + )?; callback_tsfn.unref(&env)?; @@ -150,10 +146,30 @@ impl Watcher { }) .collect::>(); - let group_events = events - .into_iter() - .into_group_map_by(|g| g.path.display().to_string()); - + let mut group_events: HashMap = HashMap::new(); + for g in events.into_iter() { + let path = g.path.display().to_string(); + + // Delete > Create > Modify + match group_events.entry(path) { + // Delete should override anything + Entry::Occupied(mut e) if matches!(g.r#type, EventType::delete) => { + e.insert(g); + } + // Create should override update + Entry::Occupied(mut e) + if matches!(g.r#type, EventType::create) + && matches!(e.get().r#type, EventType::update) => + { + e.insert(g); + } + Entry::Occupied(_) => {} + // If its empty, insert + Entry::Vacant(e) => { + e.insert(g); + } + } + } callback_tsfn.call(Ok(group_events), ThreadsafeFunctionCallMode::NonBlocking); action.outcome(Outcome::Start); diff --git a/packages/nx/src/native/workspace/get_nx_workspace_files.rs b/packages/nx/src/native/workspace/get_nx_workspace_files.rs index 68973833035ad..af90b926ec34c 100644 --- a/packages/nx/src/native/workspace/get_nx_workspace_files.rs +++ b/packages/nx/src/native/workspace/get_nx_workspace_files.rs @@ -1,6 +1,4 @@ -use itertools::Itertools; -use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode}; -use napi::{JsFunction, JsObject, JsUnknown, Status}; +use napi::JsObject; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; @@ -13,7 +11,7 @@ use crate::native::types::FileData; use crate::native::utils::glob::build_glob_set; use crate::native::utils::path::Normalize; use crate::native::walker::nx_walker; -use crate::native::workspace::errors::{InternalWorkspaceErrors, WorkspaceErrors}; +use crate::native::workspace::errors::WorkspaceErrors; use crate::native::workspace::get_config_files::insert_config_file_into_map; use crate::native::workspace::types::FileLocation; diff --git a/packages/nx/src/plugins/js/index.ts b/packages/nx/src/plugins/js/index.ts index 7003b48e1e953..bc2d78223af8b 100644 --- a/packages/nx/src/plugins/js/index.ts +++ b/packages/nx/src/plugins/js/index.ts @@ -18,6 +18,7 @@ import { projectGraphCacheDirectory } from '../../utils/cache-directory'; import { readFileSync, writeFileSync } from 'fs'; import { workspaceRoot } from '../../utils/workspace-root'; import { ensureDirSync } from 'fs-extra'; +import { performance } from 'perf_hooks'; export const processProjectGraph: ProjectGraphProcessor = async ( graph, @@ -41,7 +42,14 @@ export const processProjectGraph: ProjectGraphProcessor = async ( } } + performance.mark('build typescript dependencies - start'); await buildExplicitDependencies(pluginConfig, context, builder); + performance.mark('build typescript dependencies - end'); + performance.measure( + 'build typescript dependencies', + 'build typescript dependencies - start', + 'build typescript dependencies - end' + ); return builder.getUpdatedProjectGraph(); }; diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/build-dependencies.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/build-dependencies.ts index abd82f53a5a9c..3b268be37385f 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/build-dependencies.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/build-dependencies.ts @@ -24,6 +24,8 @@ export function buildExplicitDependencies( // to be able to use at least 2 workers (1 worker per CPU and // 1 CPU for the main thread) if ( + (process.env.NX_NATIVE_TS_DEPS && + process.env.NX_NATIVE_TS_DEPS !== 'false') || jsPluginConfig.analyzeSourceFiles === false || totalNumOfFilesToProcess < 100 || getNumberOfWorkers() <= 2 diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.ts index 385379fb2d8b3..8228ed8484d86 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-project-dependencies.ts @@ -16,11 +16,155 @@ export type ExplicitDependency = { export function buildExplicitTypeScriptDependencies( graph: ProjectGraph, filesToProcess: ProjectFileMap -) { - function isRoot(projectName: string) { - return graph.nodes[projectName]?.data?.root === '.'; +): ExplicitDependency[] { + let results: ExplicitDependency[]; + if ( + process.env.NX_NATIVE_TS_DEPS && + process.env.NX_NATIVE_TS_DEPS !== 'false' + ) { + results = buildExplicitTypeScriptDependenciesWithSwc(filesToProcess, graph); + } else { + results = buildExplicitTypeScriptDependenciesWithTs(filesToProcess, graph); } + if ( + process.env.NX_NATIVE_TS_DEPS && + process.env.NX_NATIVE_TS_DEPS === 'debug' + ) { + const tsResults = buildExplicitTypeScriptDependenciesWithTs( + filesToProcess, + graph + ); + const set = new Set(); + + for (const dep of results) { + set.add( + `+ ${dep.sourceProjectName} -> ${dep.targetProjectName} (${dep.sourceProjectFile})` + ); + } + for (const dep of tsResults) { + set.delete( + `+ ${dep.sourceProjectName} -> ${dep.targetProjectName} (${dep.sourceProjectFile})` + ); + set.add( + `- ${dep.sourceProjectName} -> ${dep.targetProjectName} (${dep.sourceProjectFile})` + ); + } + for (const dep of results) { + set.delete( + `- ${dep.sourceProjectName} -> ${dep.targetProjectName} (${dep.sourceProjectFile})` + ); + } + set.forEach((s) => console.log(s)); + } + return results; +} + +function isRoot(graph: ProjectGraph, projectName: string): boolean { + return graph.nodes[projectName]?.data?.root === '.'; +} + +function convertImportToDependency( + importExpr: string, + file: string, + sourceProject: string, + type: ExplicitDependency['type'], + targetProjectLocator: TargetProjectLocator +): ExplicitDependency { + const target = targetProjectLocator.findProjectWithImport(importExpr, file); + let targetProjectName; + if (target) { + targetProjectName = target; + } else { + // treat all unknowns as npm packages, they can be eiher + // - mistyped local import, which has to be fixed manually + // - node internals, which should still be tracked as a dependency + // - npm packages, which are not yet installed but should be tracked + targetProjectName = `npm:${importExpr}`; + } + + return { + sourceProjectName: sourceProject, + targetProjectName, + sourceProjectFile: file, + type, + }; +} + +function buildExplicitTypeScriptDependenciesWithSwc( + projectFileMap: ProjectFileMap, + graph: ProjectGraph +): ExplicitDependency[] { + const targetProjectLocator = new TargetProjectLocator( + graph.nodes as any, + graph.externalNodes + ); + const res: ExplicitDependency[] = []; + + const filesToProcess: Record = {}; + + const moduleExtensions = ['.ts', '.js', '.tsx', '.jsx', '.mts', '.mjs']; + + for (const [project, fileData] of Object.entries(projectFileMap)) { + filesToProcess[project] ??= []; + for (const { file } of fileData) { + if (moduleExtensions.some((ext) => file.endsWith(ext))) { + filesToProcess[project].push(file); + } + } + } + + const { findImports } = require('../../../../native'); + + const imports = findImports(filesToProcess); + + for (const { + sourceProject, + file, + staticImportExpressions, + dynamicImportExpressions, + } of imports) { + for (const importExpr of staticImportExpressions) { + const dependency = convertImportToDependency( + importExpr, + file, + sourceProject, + DependencyType.static, + targetProjectLocator + ); + // TODO: These edges technically should be allowed but we need to figure out how to separate config files out from root + if ( + isRoot(graph, dependency.sourceProjectName) || + !isRoot(graph, dependency.targetProjectName) + ) { + res.push(dependency); + } + } + for (const importExpr of dynamicImportExpressions) { + const dependency = convertImportToDependency( + importExpr, + file, + sourceProject, + DependencyType.dynamic, + targetProjectLocator + ); + // TODO: These edges technically should be allowed but we need to figure out how to separate config files out from root + if ( + isRoot(graph, dependency.sourceProjectName) || + !isRoot(graph, dependency.targetProjectName) + ) { + res.push(dependency); + } + } + } + + return res; +} + +function buildExplicitTypeScriptDependenciesWithTs( + filesToProcess: ProjectFileMap, + graph: ProjectGraph +): ExplicitDependency[] { const importLocator = new TypeScriptImportLocator(); const targetProjectLocator = new TargetProjectLocator( graph.nodes as any, @@ -42,7 +186,7 @@ export function buildExplicitTypeScriptDependencies( ); let targetProjectName; if (target) { - if (!isRoot(source) && isRoot(target)) { + if (!isRoot(graph, source) && isRoot(graph, target)) { // TODO: These edges technically should be allowed but we need to figure out how to separate config files out from root return; }