diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e463a47c5..7f68e348e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,14 +44,19 @@ jobs: - name: Lint if: contains(matrix.os, 'ubuntu') run: | - cargo clippy --locked --release --all-features --all-targets -- -D clippy::all + cargo clippy --locked --all-features --all-targets -- -D clippy::all deno lint - name: Cargo Build - run: cargo build --locked --release --all-features --all-targets + run: cargo build --locked --all-features --all-targets - name: Cargo Test - run: cargo test --locked --release --all-features --all-targets + run: cargo test --locked --all-features --all-targets + + # ensure we build with no default features, but only bother testing on linux + - name: Cargo Build (no-default-features) + if: contains(matrix.os, 'ubuntu') + run: cargo build --locked --no-default-features - name: Build Wasm run: deno task build diff --git a/Cargo.lock b/Cargo.lock index 7dcafb985..a350f227c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,11 +44,10 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "ast_node" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c09c69dffe06d222d072c878c3afe86eee2179806f20503faec97250268b4c24" +checksum = "c3e3e06ec6ac7d893a0db7127d91063ad7d9da8988f8a1a256f03729e6eec026" dependencies = [ - "pmutil", "proc-macro2", "quote", "swc_macros_common", @@ -87,6 +86,18 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + [[package]] name = "better_scoped_tls" version = "0.1.1" @@ -108,6 +119,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -129,27 +149,86 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.0", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "data-url" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f" +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "deno_ast" -version = "0.31.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba56f7fc8458f75d245b7fb90b9b7df6f6051d70efc206ddb053c638e53f07eb" +checksum = "0d19e5fe5dfe61c87726fdf404babc2369adfec28d5a83adee7ef06f6ce0ec68" dependencies = [ + "anyhow", + "base64 0.13.1", "deno_media_type", "dprint-swc-ext", "serde", "swc_atoms", "swc_common", + "swc_config", + "swc_config_macro", "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_codegen_macros", "swc_ecma_loader", "swc_ecma_parser", "swc_ecma_transforms_base", + "swc_ecma_transforms_classes", + "swc_ecma_transforms_macros", + "swc_ecma_transforms_proposal", + "swc_ecma_transforms_react", + "swc_ecma_transforms_typescript", "swc_ecma_utils", "swc_ecma_visit", "swc_eq_ignore_macros", @@ -232,6 +311,16 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dprint-swc-ext" version = "0.13.0" @@ -287,11 +376,10 @@ dependencies = [ [[package]] name = "from_variant" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ec5dc38ee19078d84a692b1c41181ff9f94331c76cee66ff0208c770b5e54f" +checksum = "3a0b11eeb173ce52f84ebd943d42e58813a2ebb78a6a3ff0a243b71c5199cd7b" dependencies = [ - "pmutil", "proc-macro2", "swc_macros_common", "syn", @@ -386,6 +474,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gimli" version = "0.28.0" @@ -433,6 +531,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "import_map" version = "0.18.0" @@ -532,9 +636,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "miniz_oxide" @@ -785,9 +889,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -797,9 +901,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -808,9 +912,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-demangle" @@ -824,6 +928,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.25" @@ -843,6 +956,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "ryu-js" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4950d85bc52415f8432144c97c4791bd0c4f7954de32a7270ee9cccd3c22b12b" + [[package]] name = "scoped-tls" version = "1.0.1" @@ -855,6 +974,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.186" @@ -898,6 +1032,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -930,6 +1075,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "sourcemap" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4cbf65ca7dc576cf50e21f8d0712d96d4fcfd797389744b7b222a85cdf5bd90" +dependencies = [ + "data-encoding", + "debugid", + "if_chain", + "rustc_version", + "serde", + "serde_json", + "unicode-id", + "url", +] + [[package]] name = "stacker" version = "0.1.15" @@ -951,11 +1112,10 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_enum" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fa4d4f81d7c05b9161f8de839975d3326328b8ba2831164b465524cc2f55252" +checksum = "1b650ea2087d32854a0f20b837fc56ec987a1cb4f758c9757e1171ee9812da63" dependencies = [ - "pmutil", "proc-macro2", "quote", "swc_macros_common", @@ -964,9 +1124,9 @@ dependencies = [ [[package]] name = "swc_atoms" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a9e1b6d97f27b6abe5571f8fe3bdbd2fa987299fc2126450c7cde6214896ef" +checksum = "7d538eaaa6f085161d088a04cf0a3a5a52c5a7f2b3bd9b83f73f058b0ed357c0" dependencies = [ "hstr", "once_cell", @@ -976,9 +1136,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.33.9" +version = "0.33.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ccb656cd57c93614e4e8b33a60e75ca095383565c1a8d2bbe6a1103942831e0" +checksum = "9b3ae36feceded27f0178dc9dabb49399830847ffb7f866af01798844de8f973" dependencies = [ "ast_node", "better_scoped_tls", @@ -991,6 +1151,7 @@ dependencies = [ "rustc-hash", "serde", "siphasher", + "sourcemap", "swc_atoms", "swc_eq_ignore_macros", "swc_visit", @@ -999,11 +1160,35 @@ dependencies = [ "url", ] +[[package]] +name = "swc_config" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112884e66b60e614c0f416138b91b8b82b7fea6ed0ecc5e26bad4726c57a6c99" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_json", + "swc_config_macro", +] + +[[package]] +name = "swc_config_macro" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2574f75082322a27d990116cd2a24de52945fc94172b24ca0b3e9e2a6ceb6b" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn", +] + [[package]] name = "swc_ecma_ast" -version = "0.110.10" +version = "0.110.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3d416121da2d56bcbd1b1623725a68890af4552fef0c6d1e4bfa92776ccd6a" +checksum = "79401a45da704f4fb2552c5bf86ee2198e8636b121cb81f8036848a300edd53b" dependencies = [ "bitflags 2.4.0", "is-macro", @@ -1017,11 +1202,42 @@ dependencies = [ "unicode-id", ] +[[package]] +name = "swc_ecma_codegen" +version = "0.146.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b61ca275e3663238b71c4b5da8e6fb745bde9989ef37d94984dfc81fc6d009" +dependencies = [ + "memchr", + "num-bigint", + "once_cell", + "rustc-hash", + "serde", + "sourcemap", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_codegen_macros", + "tracing", +] + +[[package]] +name = "swc_ecma_codegen_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "394b8239424b339a12012ceb18726ed0244fce6bf6345053cb9320b2791dcaa5" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn", +] + [[package]] name = "swc_ecma_loader" -version = "0.45.10" +version = "0.45.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31cf7549feec3698d0110a0a71ae547f31ae272dc92db3285ce126d6dcbdadf3" +checksum = "c5713ab3429530c10bdf167170ebbde75b046c8003558459e4de5aaec62ce0f1" dependencies = [ "anyhow", "pathdiff", @@ -1032,9 +1248,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.141.23" +version = "0.141.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cc89c175ed17c7f795fb18cf778a5745ecd794ad19c4662f85843d7571957a8" +checksum = "c4d17401dd95048a6a62b777d533c0999dabdd531ef9d667e22f8ae2a2a0d294" dependencies = [ "either", "new_debug_unreachable", @@ -1054,13 +1270,13 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.134.35" +version = "0.135.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8110b783faee399cbd749fb35b832551b2b60d034593f1fbadd7af85cb157c1" +checksum = "6d4ab26ec124b03e47f54d4daade8e9a9dcd66d3a4ca3cd47045f138d267a60e" dependencies = [ "better_scoped_tls", "bitflags 2.4.0", - "indexmap 1.9.3", + "indexmap 2.0.0", "once_cell", "phf", "rustc-hash", @@ -1075,13 +1291,100 @@ dependencies = [ "tracing", ] +[[package]] +name = "swc_ecma_transforms_classes" +version = "0.124.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fe4376c024fa04394cafb8faecafb4623722b92dbbe46532258cc0a6b569d9c" +dependencies = [ + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", +] + +[[package]] +name = "swc_ecma_transforms_macros" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e309b88f337da54ef7fe4c5b99c2c522927071f797ee6c9fb8b6bf2d100481" +dependencies = [ + "proc-macro2", + "quote", + "swc_macros_common", + "syn", +] + +[[package]] +name = "swc_ecma_transforms_proposal" +version = "0.169.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86de99757fc31d8977f47c02a26e5c9a243cb63b03fe8aa8b36d79924b8fa29c" +dependencies = [ + "either", + "rustc-hash", + "serde", + "smallvec", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_transforms_classes", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", +] + +[[package]] +name = "swc_ecma_transforms_react" +version = "0.181.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9918e22caf1ea4a71085f5d818d6c0bf5c19d669cfb9d38f9fdc3da0496abdc7" +dependencies = [ + "base64 0.21.5", + "dashmap", + "indexmap 2.0.0", + "once_cell", + "serde", + "sha-1", + "string_enum", + "swc_atoms", + "swc_common", + "swc_config", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_transforms_base", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", +] + +[[package]] +name = "swc_ecma_transforms_typescript" +version = "0.186.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1495c969ffdc224384f1fb73646b9c1b170779f20fdb984518deb054aa522" +dependencies = [ + "ryu-js", + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_transforms_react", + "swc_ecma_utils", + "swc_ecma_visit", +] + [[package]] name = "swc_ecma_utils" -version = "0.124.29" +version = "0.125.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d9434862c93aadda0b539847a5fdb82624472deed788333b35caf281773931" +checksum = "7cead1083e46b0f072a82938f16d366014468f7510350957765bb4d013496890" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.0.0", "num_cpus", "once_cell", "rustc-hash", @@ -1095,9 +1398,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.96.10" +version = "0.96.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba962f0becf83bab12a17365dface5a4f636c9e1743d479e292b96910a753743" +checksum = "a1d0100c383fb08b6f34911ab6f925950416a5d14404c1cd520d59fb8dfbb3bf" dependencies = [ "num-bigint", "swc_atoms", @@ -1109,11 +1412,10 @@ dependencies = [ [[package]] name = "swc_eq_ignore_macros" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a95d367e228d52484c53336991fdcf47b6b553ef835d9159db4ba40efb0ee8" +checksum = "695a1d8b461033d32429b5befbf0ad4d7a2c4d6ba9cd5ba4e0645c615839e8e4" dependencies = [ - "pmutil", "proc-macro2", "quote", "syn", @@ -1121,11 +1423,10 @@ dependencies = [ [[package]] name = "swc_macros_common" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a273205ccb09b51fabe88c49f3b34c5a4631c4c00a16ae20e03111d6a42e832" +checksum = "50176cfc1cbc8bb22f41c6fe9d1ec53fbe057001219b5954961b8ad0f336fce9" dependencies = [ - "pmutil", "proc-macro2", "quote", "syn", @@ -1133,9 +1434,9 @@ dependencies = [ [[package]] name = "swc_visit" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c337fbb2d191bf371173dea6a957f01899adb8f189c6c31b122a6cfc98fc3" +checksum = "b27078d8571abe23aa52ef608dd1df89096a37d867cf691cbb4f4c392322b7c9" dependencies = [ "either", "swc_visit_macros", @@ -1143,9 +1444,9 @@ dependencies = [ [[package]] name = "swc_visit_macros" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f322730fb82f3930a450ac24de8c98523af7d34ab8cb2f46bcb405839891a99" +checksum = "fa8bb05975506741555ea4d10c3a3bdb0e2357cd58e1a4a4332b8ebb4b44c34d" dependencies = [ "Inflector", "pmutil", @@ -1284,6 +1585,12 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -1329,6 +1636,12 @@ dependencies = [ "serde", ] +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index c99e9a143..e465293aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,13 +19,15 @@ name = "deno_graph" all-features = true [features] +default = ["fast_check"] +fast_check = ["symbols", "deno_ast/transpiling"] symbols = ["deno_ast/transforms", "deno_ast/visit", "deno_ast/utils"] [dependencies] anyhow = "1.0.43" async-trait = "0.1.68" data-url = "0.3.0" -deno_ast = { version = "0.31.4", features = ["dep_analysis", "module_specifier"] } +deno_ast = { version = "1.0.0", features = ["dep_analysis", "module_specifier"] } deno_semver = "0.5.0" futures = "0.3.26" import_map = "0.18.0" @@ -34,7 +36,7 @@ log = "0.4.20" monch = "0.4.3" once_cell = "1.16.0" parking_lot = "0.12.0" -regex = "1.5.4" +regex = "1.10.2" serde = { version = "1.0.130", features = ["derive", "rc"] } serde_json = { version = "1.0.67", features = ["preserve_order"] } thiserror = "1.0.24" diff --git a/deno.json b/deno.json index 01604f013..dfe8a4bc4 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "tasks": { - "build": "cp LICENSE js/LICENSE && deno run --unstable -A --no-check https://deno.land/x/wasmbuild@0.15.1/main.ts --project deno_graph_wasm --out js", + "build": "cp LICENSE js/LICENSE && deno run --unstable -A --no-check https://deno.land/x/wasmbuild@0.15.1/main.ts --no-default-features --project deno_graph_wasm --out js", "build:npm": "deno run -A _build_npm.ts", "test": "deno test --allow-read --allow-net" }, diff --git a/lib/lib.rs b/lib/lib.rs index 1f0ecc96a..b69275de7 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -254,8 +254,10 @@ pub async fn js_create_graph( file_system: Some(&NullFileSystem), npm_resolver: None, module_analyzer: None, + module_parser: None, imports, reporter: None, + workspace_fast_check: false, workspace_members: Vec::new(), }, ) diff --git a/src/ast.rs b/src/ast.rs index 75604e044..a31853859 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -14,6 +14,7 @@ use crate::analyzer::TypeScriptReference; use crate::graph::Position; use crate::module_specifier::ModuleSpecifier; +use deno_ast::MultiThreadedComments; use deno_ast::SourcePos; use deno_ast::SourceRangedForSpanned; @@ -48,48 +49,35 @@ static PATH_REFERENCE_RE: Lazy = static TYPES_REFERENCE_RE: Lazy = Lazy::new(|| Regex::new(r#"(?i)\stypes\s*=\s*["']([^"']*)["']"#).unwrap()); +pub struct ParseOptions<'a> { + pub specifier: &'a ModuleSpecifier, + pub source: Arc, + pub media_type: MediaType, + pub scope_analysis: bool, +} + /// Parses modules to a ParsedSource. pub trait ModuleParser { fn parse_module( &self, - specifier: &ModuleSpecifier, - source: Arc, - media_type: MediaType, + options: ParseOptions, ) -> Result; } #[derive(Default, Clone)] -pub struct DefaultModuleParser { - analysis: bool, -} - -impl DefaultModuleParser { - /// Creates a new default parser that parses using only settings - /// necessary for deno_graph to analyze the modules. - pub fn new() -> Self { - Self::default() - } - - /// Creates a new parser that also collects scope analysis information - /// and captures tokens. - pub fn new_for_analysis() -> Self { - Self { analysis: true } - } -} +pub struct DefaultModuleParser; impl ModuleParser for DefaultModuleParser { fn parse_module( &self, - specifier: &ModuleSpecifier, - source: Arc, - media_type: MediaType, + options: ParseOptions, ) -> Result { deno_ast::parse_module(deno_ast::ParseParams { - specifier: specifier.to_string(), - text_info: SourceTextInfo::new(source), - media_type, - capture_tokens: self.analysis, - scope_analysis: self.analysis, + specifier: options.specifier.to_string(), + text_info: SourceTextInfo::new(options.source), + media_type: options.media_type, + capture_tokens: options.scope_analysis, + scope_analysis: options.scope_analysis, maybe_syntax: None, }) } @@ -111,6 +99,12 @@ pub trait ParsedSourceStore { &self, specifier: &ModuleSpecifier, ) -> Option; + /// Gets a `deno_ast::ParsedSource` from the store, upgrading it + /// to have scope analysis if it doesn't already. + fn get_scope_analysis_parsed_source( + &self, + specifier: &ModuleSpecifier, + ) -> Option; } /// Default store that works on a single thread. @@ -134,6 +128,22 @@ impl ParsedSourceStore for DefaultParsedSourceStore { ) -> Option { self.store.borrow().get(specifier).cloned() } + + fn get_scope_analysis_parsed_source( + &self, + specifier: &ModuleSpecifier, + ) -> Option { + let mut store = self.store.borrow_mut(); + let parsed_source = store.get_mut(specifier)?; + if parsed_source.has_scope_analysis() { + Some(parsed_source.clone()) + } else { + let parsed_source = store.remove(specifier).unwrap(); + let parsed_source = parsed_source.into_with_scope_analysis(); + store.insert(specifier.clone(), parsed_source.clone()); + Some(parsed_source.clone()) + } + } } /// Stores parsed files in the provided store after parsing. @@ -159,13 +169,17 @@ impl<'a> CapturingModuleParser<'a> { fn get_from_store_if_matches( &self, - specifier: &ModuleSpecifier, - source: &str, - media_type: MediaType, + options: &ParseOptions, ) -> Option { - let parsed_source = self.store.get_parsed_source(specifier)?; - if parsed_source.media_type() == media_type - && parsed_source.text_info().text_str() == source + let parsed_source = if options.scope_analysis { + self + .store + .get_scope_analysis_parsed_source(options.specifier)? + } else { + self.store.get_parsed_source(options.specifier)? + }; + if parsed_source.media_type() == options.media_type + && parsed_source.text_info().text_str() == options.source.as_ref() { Some(parsed_source) } else { @@ -177,21 +191,18 @@ impl<'a> CapturingModuleParser<'a> { impl<'a> ModuleParser for CapturingModuleParser<'a> { fn parse_module( &self, - specifier: &ModuleSpecifier, - source: Arc, - media_type: MediaType, + options: ParseOptions, ) -> Result { - if let Some(parsed_source) = - self.get_from_store_if_matches(specifier, &source, media_type) - { + if let Some(parsed_source) = self.get_from_store_if_matches(&options) { Ok(parsed_source) } else { - let default_parser = DefaultModuleParser::default(); + let default_parser = DefaultModuleParser; let parser = self.parser.unwrap_or(&default_parser); - let parsed_source = parser.parse_module(specifier, source, media_type)?; + let specifier = options.specifier.clone(); + let parsed_source = parser.parse_module(options)?; self .store - .set_parsed_source(specifier.clone(), parsed_source.clone()); + .set_parsed_source(specifier, parsed_source.clone()); Ok(parsed_source) } } @@ -213,11 +224,32 @@ impl<'a> DefaultModuleAnalyzer<'a> { /// Gets the module info from a parsed source. pub fn module_info(parsed_source: &ParsedSource) -> ModuleInfo { + let module = match parsed_source.program_ref() { + deno_ast::swc::ast::Program::Module(m) => m, + deno_ast::swc::ast::Program::Script(_) => return ModuleInfo::default(), + }; + Self::module_info_from_swc( + parsed_source.media_type(), + module, + parsed_source.text_info(), + parsed_source.comments(), + ) + } + + pub fn module_info_from_swc( + media_type: MediaType, + module: &deno_ast::swc::ast::Module, + text_info: &SourceTextInfo, + comments: &MultiThreadedComments, + ) -> ModuleInfo { + let start_pos = module.start(); ModuleInfo { - dependencies: analyze_dependencies(parsed_source), - ts_references: analyze_ts_references(parsed_source), - jsx_import_source: analyze_jsx_import_source(parsed_source), - jsdoc_imports: analyze_jsdoc_imports(parsed_source), + dependencies: analyze_dependencies(module, text_info, comments), + ts_references: analyze_ts_references(start_pos, text_info, comments), + jsx_import_source: analyze_jsx_import_source( + media_type, start_pos, text_info, comments, + ), + jsdoc_imports: analyze_jsdoc_imports(media_type, text_info, comments), } } } @@ -229,9 +261,15 @@ impl<'a> ModuleAnalyzer for DefaultModuleAnalyzer<'a> { source: Arc, media_type: MediaType, ) -> Result { - let default_parser = DefaultModuleParser::default(); + let default_parser = DefaultModuleParser; let parser = self.parser.unwrap_or(&default_parser); - let parsed_source = parser.parse_module(specifier, source, media_type)?; + let parsed_source = parser.parse_module(ParseOptions { + specifier, + source, + media_type, + // scope analysis is not necessary for module parsing + scope_analysis: false, + })?; Ok(DefaultModuleAnalyzer::module_info(&parsed_source)) } } @@ -284,12 +322,10 @@ impl ModuleAnalyzer for CapturingModuleAnalyzer { impl ModuleParser for CapturingModuleAnalyzer { fn parse_module( &self, - specifier: &ModuleSpecifier, - source: Arc, - media_type: MediaType, + options: ParseOptions, ) -> Result { let capturing_parser = self.as_capturing_parser(); - capturing_parser.parse_module(specifier, source, media_type) + capturing_parser.parse_module(options) } } @@ -308,13 +344,23 @@ impl ParsedSourceStore for CapturingModuleAnalyzer { ) -> Option { self.store.get_parsed_source(specifier) } + + fn get_scope_analysis_parsed_source( + &self, + specifier: &ModuleSpecifier, + ) -> Option { + self.store.get_scope_analysis_parsed_source(specifier) + } } fn analyze_dependencies( - parsed_source: &ParsedSource, + module: &deno_ast::swc::ast::Module, + text_info: &SourceTextInfo, + comments: &MultiThreadedComments, ) -> Vec { - parsed_source - .analyze_dependencies() + let deps = deno_ast::dep::analyze_module_dependencies(module, comments); + + deps .into_iter() .map(|d| match d { deno_ast::dep::DependencyDescriptor::Static(d) => { @@ -323,16 +369,13 @@ fn analyze_dependencies( leading_comments: d .leading_comments .into_iter() - .map(|c| Comment::from_dep_comment(c, parsed_source.text_info())) + .map(|c| Comment::from_dep_comment(c, text_info)) .collect(), - range: PositionRange::from_source_range( - d.range, - parsed_source.text_info(), - ), + range: PositionRange::from_source_range(d.range, text_info), specifier: d.specifier.to_string(), specifier_range: PositionRange::from_source_range( d.specifier_range, - parsed_source.text_info(), + text_info, ), import_attributes: d.import_attributes, }) @@ -342,12 +385,9 @@ fn analyze_dependencies( leading_comments: d .leading_comments .into_iter() - .map(|c| Comment::from_dep_comment(c, parsed_source.text_info())) + .map(|c| Comment::from_dep_comment(c, text_info)) .collect(), - range: PositionRange::from_source_range( - d.range, - parsed_source.text_info(), - ), + range: PositionRange::from_source_range(d.range, text_info), argument: match d.argument { deno_ast::dep::DynamicArgument::String(text) => { DynamicArgument::String(text.to_string()) @@ -373,7 +413,7 @@ fn analyze_dependencies( }, argument_range: PositionRange::from_source_range( d.argument_range, - parsed_source.text_info(), + text_info, ), import_attributes: d.import_attributes, }) @@ -383,35 +423,40 @@ fn analyze_dependencies( } fn analyze_ts_references( - parsed_source: &ParsedSource, + start_pos: SourcePos, + text_info: &SourceTextInfo, + comments: &MultiThreadedComments, ) -> Vec { let mut references = Vec::new(); - for comment in parsed_source.get_leading_comments().iter() { - if TRIPLE_SLASH_REFERENCE_RE.is_match(&comment.text) { - let comment_start = comment.start(); - if let Some(captures) = PATH_REFERENCE_RE.captures(&comment.text) { - let m = captures.get(1).unwrap(); - references.push(TypeScriptReference::Path(SpecifierWithRange { - text: m.as_str().to_string(), - range: comment_source_to_position_range( - comment_start, - &m, - parsed_source.text_info(), - false, - ), - })); - } else if let Some(captures) = TYPES_REFERENCE_RE.captures(&comment.text) - { - let m = captures.get(1).unwrap(); - references.push(TypeScriptReference::Types(SpecifierWithRange { - text: m.as_str().to_string(), - range: comment_source_to_position_range( - comment_start, - &m, - parsed_source.text_info(), - false, - ), - })); + if let Some(leading_comments) = comments.get_leading(start_pos) { + for comment in leading_comments { + if TRIPLE_SLASH_REFERENCE_RE.is_match(&comment.text) { + let comment_start = comment.start(); + if let Some(captures) = PATH_REFERENCE_RE.captures(&comment.text) { + let m = captures.get(1).unwrap(); + references.push(TypeScriptReference::Path(SpecifierWithRange { + text: m.as_str().to_string(), + range: comment_source_to_position_range( + comment_start, + &m, + text_info, + false, + ), + })); + } else if let Some(captures) = + TYPES_REFERENCE_RE.captures(&comment.text) + { + let m = captures.get(1).unwrap(); + references.push(TypeScriptReference::Types(SpecifierWithRange { + text: m.as_str().to_string(), + range: comment_source_to_position_range( + comment_start, + &m, + text_info, + false, + ), + })); + } } } } @@ -419,24 +464,29 @@ fn analyze_ts_references( } fn analyze_jsx_import_source( - parsed_source: &ParsedSource, + media_type: MediaType, + start_pos: SourcePos, + text_info: &SourceTextInfo, + comments: &MultiThreadedComments, ) -> Option { - match parsed_source.media_type() { + match media_type { MediaType::Jsx | MediaType::Tsx => { - parsed_source.get_leading_comments().iter().find_map(|c| { - if c.kind != CommentKind::Block { - return None; // invalid - } - let captures = JSX_IMPORT_SOURCE_RE.captures(&c.text)?; - let m = captures.get(1)?; - Some(SpecifierWithRange { - text: m.as_str().to_string(), - range: comment_source_to_position_range( - c.start(), - &m, - parsed_source.text_info(), - true, - ), + comments.get_leading(start_pos).and_then(|c| { + c.iter().find_map(|c| { + if c.kind != CommentKind::Block { + return None; // invalid + } + let captures = JSX_IMPORT_SOURCE_RE.captures(&c.text)?; + let m = captures.get(1)?; + Some(SpecifierWithRange { + text: m.as_str().to_string(), + range: comment_source_to_position_range( + c.start(), + &m, + text_info, + true, + ), + }) }) }) } @@ -445,21 +495,23 @@ fn analyze_jsx_import_source( } fn analyze_jsdoc_imports( - parsed_source: &ParsedSource, + media_type: MediaType, + text_info: &SourceTextInfo, + comments: &MultiThreadedComments, ) -> Vec { // Analyze any JSDoc type imports // We only analyze these on JavaScript types of modules, since they are // ignored by TypeScript when type checking anyway and really shouldn't be // there, but some people do strange things. if !matches!( - parsed_source.media_type(), + media_type, MediaType::JavaScript | MediaType::Jsx | MediaType::Mjs | MediaType::Cjs ) { return Vec::new(); } let mut deps = Vec::new(); - for comment in parsed_source.comments().get_vec().iter() { + for comment in comments.iter_unstable() { if comment.kind != CommentKind::Block || !comment.text.starts_with('*') { continue; } @@ -470,13 +522,14 @@ fn analyze_jsdoc_imports( range: comment_source_to_position_range( comment.range().start, &m, - parsed_source.text_info(), + text_info, false, ), }); } } } + deps.sort_by(|a, b| a.range.start.cmp(&b.range.start)); deps } @@ -536,8 +589,13 @@ mod tests { const a = await import("./a.ts"); "#; - let parsed_source = DefaultModuleParser::default() - .parse_module(&specifier, source.into(), MediaType::Tsx) + let parsed_source = DefaultModuleParser + .parse_module(ParseOptions { + specifier: &specifier, + source: source.into(), + media_type: MediaType::Tsx, + scope_analysis: false, + }) .unwrap(); let text_info = parsed_source.text_info(); let module_info = DefaultModuleAnalyzer::module_info(&parsed_source); @@ -605,8 +663,13 @@ mod tests { import type { i } from "./i.d.ts"; export type { j } from "./j.d.ts"; "#; - let parsed_source = DefaultModuleParser::default() - .parse_module(&specifier, source.into(), MediaType::TypeScript) + let parsed_source = DefaultModuleParser + .parse_module(ParseOptions { + specifier: &specifier, + source: source.into(), + media_type: MediaType::TypeScript, + scope_analysis: false, + }) .unwrap(); let module_info = DefaultModuleAnalyzer::module_info(&parsed_source); let text_info = parsed_source.text_info(); @@ -637,8 +700,13 @@ mod tests { await import(\"./b.json\", {{ {keyword}: {{ type: \"json\" }} }}); " ); - let parsed_source = DefaultModuleParser::default() - .parse_module(&specifier, source.into(), MediaType::TypeScript) + let parsed_source = DefaultModuleParser + .parse_module(ParseOptions { + specifier: &specifier, + source: source.into(), + media_type: MediaType::TypeScript, + scope_analysis: false, + }) .unwrap(); let module_info = DefaultModuleAnalyzer::module_info(&parsed_source); let dependencies = module_info.dependencies; @@ -683,8 +751,13 @@ function b(c) { */ const f = new Set(); "#; - let parsed_source = DefaultModuleParser::default() - .parse_module(&specifier, source.into(), MediaType::JavaScript) + let parsed_source = DefaultModuleParser + .parse_module(ParseOptions { + specifier: &specifier, + source: source.into(), + media_type: MediaType::JavaScript, + scope_analysis: false, + }) .unwrap(); let module_info = DefaultModuleAnalyzer::module_info(&parsed_source); let dependencies = module_info.jsdoc_imports; diff --git a/src/fast_check/mod.rs b/src/fast_check/mod.rs new file mode 100644 index 000000000..37462b541 --- /dev/null +++ b/src/fast_check/mod.rs @@ -0,0 +1,205 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::sync::Arc; + +use crate::Range; + +#[cfg(feature = "fast_check")] +mod range_finder; +#[cfg(feature = "fast_check")] +mod swc_helpers; +#[cfg(feature = "fast_check")] +mod transform; + +#[cfg(feature = "fast_check")] +pub use transform::FastCheckModule; +#[cfg(feature = "fast_check")] +pub use transform::TransformOptions; + +fn format_diagnostics(diagnostics: &[FastCheckDiagnostic]) -> String { + diagnostics + .iter() + .map(|diag| format!("{}", diag)) + .collect::>() + .join("\n") +} + +#[derive(Debug, Clone, thiserror::Error)] +pub enum FastCheckDiagnostic { + #[error("Could not resolve '{name}' referenced from '{referrer}'. This may indicate a bug in Deno. Please open an issue to help us improve if so.")] + NotFoundReference { + range: Range, + name: String, + referrer: String, + }, + #[error("Missing explicit type in the public API.")] + MissingExplicitType { range: Range }, + #[error("Missing explicit return type in the public API.")] + MissingExplicitReturnType { range: Range }, + #[error("Global augmentations such as ambient modules are not supported.")] + UnsupportedAmbientModule { range: Range }, + #[error("The reference '{name}' from '{referrer}' was too complex. Extract out the shared type to a type alias.")] + UnsupportedComplexReference { + range: Range, + name: String, + referrer: String, + }, + #[error( + "Default export expression was too complex. Extract it out to a variable and add an explicit type." + )] + UnsupportedDefaultExportExpr { range: Range }, + #[error("Destructuring is not supported in the public API.")] + UnsupportedDestructuring { range: Range }, + #[error("Global augmentations are not supported.")] + UnsupportedGlobalModule { range: Range }, + #[error("Require is not supported in ES modules.")] + UnsupportedRequire { range: Range }, + #[error("Public API members ({referrer}) referencing or transitively referencing a class private member ({name}) are not supported. Extract out the shared type to a type alias.")] + UnsupportedPrivateMemberReference { + range: Range, + name: String, + referrer: String, + }, + #[error( + "Super class expression was too complex. Extract it out to a variable and add an explicit type." + )] + UnsupportedSuperClassExpr { range: Range }, + #[error( + "CommonJS export assignments (`export =`) are not supported in ES modules." + )] + UnsupportedTsExportAssignment { range: Range }, + #[error("Global augmentations such as namespace exports are not supported.")] + UnsupportedTsNamespaceExport { range: Range }, + #[error("Using declarations are not supported in the public API.")] + UnsupportedUsing { range: Range }, + #[error("Failed to emit fast check module: {0:#}")] + Emit(Arc), + #[error("{}", format_diagnostics(.0))] + Multiple(Vec), +} + +impl FastCheckDiagnostic { + pub fn flatten_multiple<'a>( + &'a self, + ) -> Box + 'a> { + match self { + FastCheckDiagnostic::Multiple(diagnostics) => { + Box::new(diagnostics.iter().flat_map(|d| d.flatten_multiple())) + } + _ => Box::new(std::iter::once(self)), + } + } + + pub fn from_vec(mut diagnostics: Vec) -> Option { + match diagnostics.len() { + 0 => None, + 1 => diagnostics.pop(), + _ => Some(FastCheckDiagnostic::Multiple(diagnostics)), + } + } + + pub fn line_and_column_display(&self) -> Option<&Range> { + use FastCheckDiagnostic::*; + match self { + NotFoundReference { range, .. } => Some(range), + MissingExplicitType { range } => Some(range), + MissingExplicitReturnType { range } => Some(range), + UnsupportedAmbientModule { range } => Some(range), + UnsupportedComplexReference { range, .. } => Some(range), + UnsupportedDefaultExportExpr { range } => Some(range), + UnsupportedDestructuring { range } => Some(range), + UnsupportedGlobalModule { range } => Some(range), + UnsupportedPrivateMemberReference { range, .. } => Some(range), + UnsupportedRequire { range } => Some(range), + UnsupportedSuperClassExpr { range } => Some(range), + UnsupportedTsExportAssignment { range } => Some(range), + UnsupportedTsNamespaceExport { range } => Some(range), + UnsupportedUsing { range } => Some(range), + Emit(_) => None, + Multiple(_) => None, + } + } + + pub fn message_with_range(&self) -> String { + match self { + FastCheckDiagnostic::Multiple(errors) => errors + .iter() + .map(|e| e.message_with_range()) + .collect::>() + .join("\n"), + _ => match self.line_and_column_display() { + Some(range) => format!("{}\n at {}", self, range), + None => format!("{}", self), + }, + } + } +} + +#[cfg(feature = "fast_check")] +pub fn build_fast_check_type_graph<'a>( + loader: &'a dyn crate::source::Loader, + graph: &'a crate::ModuleGraph, + root_symbol: &'a crate::symbols::RootSymbol<'a>, + pending_nvs: std::collections::VecDeque, + options: &TransformOptions, +) -> Vec<( + crate::ModuleSpecifier, + Result>, +)> { + let public_modules = range_finder::find_public_ranges( + loader, + graph, + root_symbol, + options.workspace_members, + pending_nvs, + ); + + let mut final_result = Vec::new(); + for (nv, package) in public_modules { + log::debug!("Analyzing '{}' for fast check", nv); + let mut errors = Vec::with_capacity(package.module_ranges.len()); + let mut fast_check_modules = + Vec::with_capacity(package.module_ranges.len()); + for (specifier, mut ranges) in package.module_ranges { + let module_info = root_symbol.module_from_specifier(&specifier).unwrap(); + if let Some(module_info) = module_info.esm() { + let transform_result = + match FastCheckDiagnostic::from_vec(ranges.take_diagnostics()) { + Some(diagnostic) => Err(Box::new(diagnostic)), + None => transform::transform( + &specifier, + &ranges, + module_info.source(), + options, + ), + }; + match transform_result { + Ok(modules) => { + if errors.is_empty() { + fast_check_modules.push((specifier.clone(), Ok(modules))); + } + } + Err(d) => { + errors.push(*d); + } + } + } + } + + if errors.is_empty() { + final_result.extend(fast_check_modules); + } else { + // If there are errors, insert a copy into each entrypoint. + // + // If one entrypoint can't be analyzed then we consider all + // entrypoints are non-analyzable because it's very difficult + // to determine the overlap of internal types between entrypoints. + let combined_errors = FastCheckDiagnostic::Multiple(errors); + for entrypoint in package.entrypoints { + final_result.push((entrypoint, Err(Box::new(combined_errors.clone())))); + } + } + } + + final_result +} diff --git a/src/fast_check/range_finder.rs b/src/fast_check/range_finder.rs new file mode 100644 index 000000000..262a6ef0f --- /dev/null +++ b/src/fast_check/range_finder.rs @@ -0,0 +1,997 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::collections::HashMap; +use std::collections::HashSet; +use std::collections::VecDeque; + +use deno_ast::SourceRange; +use deno_ast::SourceRangedForSpanned; +use deno_semver::package::PackageNv; +use indexmap::IndexMap; +use url::Url; + +use crate::fast_check::swc_helpers::is_expr_leavable; +use crate::fast_check::swc_helpers::source_range_to_range; +use crate::source::Loader; +use crate::symbols::ExportDeclRef; +use crate::symbols::FileDepName; +use crate::symbols::ModuleInfoRef; +use crate::symbols::ResolveDepsMode; +use crate::symbols::RootSymbol; +use crate::symbols::SymbolDeclKind; +use crate::symbols::SymbolId; +use crate::symbols::SymbolNodeDep; +use crate::symbols::SymbolNodeRef; +use crate::ModuleGraph; +use crate::ModuleSpecifier; +use crate::WorkspaceMember; + +use super::FastCheckDiagnostic; + +#[derive(Default, Debug, Clone)] +struct NamedExports(IndexMap); + +impl NamedExports { + pub fn retain_default_or_non_top_level(&mut self) { + // retain non-top level and the default export + self.0.retain(|k, v| k == "default" || !v.is_empty()); + } + + pub fn add(&mut self, export: String) { + self.0.entry(export).or_default(); + } + + pub fn add_qualified(&mut self, export: &str, qualified: &[String]) { + let entry = self.0.entry(export.to_string()).or_default(); + if !qualified.is_empty() { + entry.add_qualified(&qualified[0], &qualified[1..]); + } + } + + pub fn contains(&self, arg: &str) -> bool { + self.0.contains_key(arg) + } + + pub fn extend(&mut self, new_named: NamedExports) -> NamedExports { + let mut difference = NamedExports::default(); + for (key, exports) in new_named.0 { + if let Some(entry) = self.0.get_mut(&key) { + let sub_diff = entry.extend(exports); + if !sub_diff.is_empty() { + difference.add_named(key.clone(), sub_diff); + } + } else { + difference.add_named(key.clone(), exports.clone()); + self.0.insert(key, exports); + } + } + difference + } + + pub fn from_parts(parts: &[String]) -> NamedExports { + let mut exports = NamedExports::default(); + if !parts.is_empty() { + exports.add_qualified(&parts[0], &parts[1..]); + } + exports + } + + pub fn from_many_parts(many_parts: &[Vec]) -> NamedExports { + let mut exports = NamedExports::default(); + for parts in many_parts { + if !parts.is_empty() { + exports.add_qualified(&parts[0], &parts[1..]); + } + } + exports + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn into_separate_parts(self) -> Vec> { + let mut parts = Vec::new(); + for (key, exports) in self.0 { + if exports.is_empty() { + parts.push(vec![key]); + } else { + let mut sub_parts = exports.into_separate_parts(); + for sub_part in &mut sub_parts { + sub_part.insert(0, key.clone()); + } + parts.extend(sub_parts); + } + } + parts + } + + pub fn add_named(&mut self, key: String, exports: NamedExports) { + self.0.entry(key).or_default().extend(exports); + } +} + +#[derive(Debug, Clone)] +struct ImportedExports { + star: bool, + named: NamedExports, +} + +impl ImportedExports { + pub(crate) fn from_file_dep_name(dep_name: &FileDepName) -> Self { + match dep_name { + FileDepName::Star => ImportedExports { + star: true, + named: Default::default(), + }, + FileDepName::Name(value) => { + let mut named_exports = NamedExports::default(); + named_exports.add(value.clone()); + ImportedExports { + star: false, + named: named_exports, + } + } + } + } + + pub fn star_with_default() -> ImportedExports { + Self { + star: true, + named: { + let mut named_exports = NamedExports::default(); + named_exports.add("default".to_string()); + named_exports + }, + } + } + + pub fn star() -> ImportedExports { + ImportedExports { + star: true, + named: Default::default(), + } + } + + pub fn named(named: NamedExports) -> ImportedExports { + ImportedExports { star: false, named } + } + + /// Adds the incoming exports to the existing exports and + /// returns the newly added exports that have not previously + /// been added. + pub(crate) fn add( + &mut self, + mut exports_to_trace: ImportedExports, + ) -> Option { + let difference = if self.star { + // retain named exports in the incoming that are not top level + exports_to_trace.named.retain_default_or_non_top_level(); + let named_difference = self.named.extend(exports_to_trace.named); + ImportedExports { + star: false, + named: named_difference, + } + } else if exports_to_trace.star { + // retain named exports in the existing that are not top level + self.named.retain_default_or_non_top_level(); + let named_difference = self.named.extend(exports_to_trace.named); + self.star = true; + ImportedExports { + star: true, + named: named_difference, + } + } else { + let named_difference = self.named.extend(exports_to_trace.named); + ImportedExports { + star: false, + named: named_difference, + } + }; + + if difference.star || !difference.named.is_empty() { + Some(difference) + } else { + None + } + } +} + +#[derive(Default)] +struct HandledExports(HashMap); + +impl HandledExports { + pub fn add( + &mut self, + dep_specifier: &ModuleSpecifier, + traced_exports: ImportedExports, + ) -> Option { + if let Some(handled_exports) = self.0.get_mut(dep_specifier) { + handled_exports.add(traced_exports) + } else { + self.0.insert(dep_specifier.clone(), traced_exports.clone()); + Some(traced_exports) + } + } +} + +#[derive(Default)] +struct PendingTraces(IndexMap); + +impl PendingTraces { + pub fn add( + &mut self, + package_nv: PackageNv, + dep_specifier: ModuleSpecifier, + exports_to_trace: ImportedExports, + ) { + if let Some((_, current_exports_to_trace)) = self.0.get_mut(&dep_specifier) + { + current_exports_to_trace.add(exports_to_trace); + } else { + self.0.insert(dep_specifier, (package_nv, exports_to_trace)); + } + } + + pub fn pop(&mut self) -> Option { + self + .0 + .pop() + .map(|(specifier, (package_nv, exports_to_trace))| PendingTrace { + package_nv, + specifier, + exports_to_trace, + }) + } +} + +#[derive(Debug)] +struct PendingTrace { + pub package_nv: PackageNv, + pub specifier: ModuleSpecifier, + pub exports_to_trace: ImportedExports, +} + +pub fn find_public_ranges<'a>( + loader: &'a dyn Loader, + graph: &'a ModuleGraph, + root_symbol: &'a RootSymbol<'a>, + workspace_members: &'a [WorkspaceMember], + pending_nvs: VecDeque, +) -> HashMap { + PublicRangeFinder { + seen_nvs: pending_nvs.iter().cloned().collect(), + traced_exports: Default::default(), + pending_nvs, + pending_traces: Default::default(), + public_ranges: Default::default(), + graph, + workspace_members, + root_symbol, + url_converter: RegistryUrlConverter { + loader, + workspace_members, + }, + } + .find() +} + +#[derive(Debug, Default)] +pub struct ModulePublicRanges { + ranges: HashSet, + impl_with_overload_ranges: HashSet, + diagnostics: Vec, +} + +impl ModulePublicRanges { + pub fn contains(&self, range: &SourceRange) -> bool { + self.ranges.contains(range) + } + + pub fn is_impl_with_overloads(&self, range: &SourceRange) -> bool { + self.impl_with_overload_ranges.contains(range) + } + + pub fn take_diagnostics(&mut self) -> Vec { + std::mem::take(&mut self.diagnostics) + } +} + +struct RegistryUrlConverter<'a> { + loader: &'a dyn Loader, + workspace_members: &'a [WorkspaceMember], +} + +impl<'a> RegistryUrlConverter<'a> { + fn registry_package_url(&self, nv: &PackageNv) -> Url { + if let Some(member) = self.workspace_members.iter().find(|m| m.nv == *nv) { + member.base.clone() + } else { + self.loader.registry_package_url(nv) + } + } + + fn registry_package_url_to_nv(&self, url: &Url) -> Option { + if url.scheme() == "file" { + for member in self.workspace_members.iter() { + if url.as_str().starts_with(member.base.as_str()) { + return Some(member.nv.clone()); + } + } + None + } else { + self.loader.registry_package_url_to_nv(url) + } + } +} + +#[derive(Debug, Default)] +pub struct PackagePublicRanges { + pub entrypoints: Vec, + pub module_ranges: HashMap, + pub errors: Vec, +} + +struct PublicRangeFinder<'a> { + url_converter: RegistryUrlConverter<'a>, + graph: &'a ModuleGraph, + workspace_members: &'a [WorkspaceMember], + root_symbol: &'a RootSymbol<'a>, + pending_nvs: VecDeque, + pending_traces: PendingTraces, + traced_exports: HandledExports, + seen_nvs: HashSet, + public_ranges: HashMap, +} + +impl<'a> PublicRangeFinder<'a> { + pub fn find(mut self) -> HashMap { + while let Some(nv) = self.pending_nvs.pop_front() { + let Some(exports) = + self.graph.packages.package_exports(&nv).or_else(|| { + Some(&self.workspace_members.iter().find(|m| m.nv == nv)?.exports) + }) + else { + continue; // should never happen + }; + let base_url = self.url_converter.registry_package_url(&nv); + let mut entrypoints = Vec::with_capacity(exports.len()); + for value in exports.values() { + // if we got this far, then the export must be valid, so we can unwrap + let specifier = base_url.join(value).unwrap(); + self.add_pending_trace( + &nv, + &specifier, + ImportedExports::star_with_default(), + ); + entrypoints.push(specifier); + } + + while let Some(trace) = self.pending_traces.pop() { + self.analyze_trace(&trace); + } + + self.public_ranges.entry(nv).or_default().entrypoints = entrypoints; + } + + self.public_ranges + } + + fn add_pending_trace( + &mut self, + nv: &PackageNv, + specifier: &ModuleSpecifier, + trace: ImportedExports, + ) { + if let Some(trace) = self.traced_exports.add(specifier, trace) { + self + .pending_traces + .add(nv.clone(), specifier.clone(), trace); + } + } + + fn analyze_trace(&mut self, trace: &PendingTrace) { + if let Some(module_info) = + self.root_symbol.module_from_specifier(&trace.specifier) + { + self.analyze_module_info(trace, module_info); + } else { + // should never happen + eprintln!("TEMP: NOT FOUND: {}", trace.specifier); + } + } + + fn analyze_module_info( + &mut self, + trace: &PendingTrace, + module_info: ModuleInfoRef<'a>, + ) -> bool { + #[derive(Debug)] + enum PendingIdTrace { + Id { + symbol_id: SymbolId, + referrer_id: SymbolId, + }, + QualifiedId { + symbol_id: SymbolId, + parts: NamedExports, + referrer_id: SymbolId, + }, + } + + #[derive(Default)] + struct PendingTraces { + traces: VecDeque, + done_id_traces: HashSet<(SymbolId, SymbolId)>, + } + + impl PendingTraces { + fn maybe_add_id_trace( + &mut self, + symbol_id: SymbolId, + referrer_id: SymbolId, + ) { + if self.done_id_traces.insert((symbol_id, referrer_id)) { + self.traces.push_back(PendingIdTrace::Id { + symbol_id, + referrer_id, + }); + } + } + } + + let pkg_nv = &trace.package_nv; + let mut found_ranges = HashSet::new(); + let mut impl_with_overload_ranges = HashSet::new(); + let mut found = false; + let mut diagnostics = Vec::new(); + let mut pending_traces = PendingTraces::default(); + let module_symbol = module_info.module_symbol(); + + if trace.exports_to_trace.star { + for (name, export_symbol_id) in module_info.module_symbol().exports() { + if name == "default" + && !trace.exports_to_trace.named.contains("default") + { + continue; + } + + pending_traces + .maybe_add_id_trace(*export_symbol_id, module_symbol.symbol_id()); + } + + // add all the specifiers to the list of pending specifiers + if let Some(re_export_all_nodes) = module_info.re_export_all_nodes() { + for re_export_all_node in re_export_all_nodes { + found_ranges.insert(re_export_all_node.span.range()); + let specifier_text = re_export_all_node.src.value.as_str(); + if let Some(dep_specifier) = self.graph.resolve_dependency( + specifier_text, + module_info.specifier(), + /* prefer types */ true, + ) { + // only analyze registry specifiers + if let Some(dep_nv) = self + .url_converter + .registry_package_url_to_nv(&dep_specifier) + { + if self.seen_nvs.insert(dep_nv.clone()) { + self.pending_nvs.push_back(dep_nv.clone()); + } + + self.add_pending_trace( + &dep_nv, + &dep_specifier, + ImportedExports::star(), + ); + } + } + } + } + + found = true; + } + + if !trace.exports_to_trace.named.is_empty() { + let mut named_exports = trace.exports_to_trace.named.0.clone(); + let module_exports = module_info.module_symbol().exports(); + for i in (0..named_exports.len()).rev() { + let (export_name, _) = named_exports.get_index(i).unwrap(); + if let Some(export_symbol_id) = module_exports.get(export_name) { + let export_name = export_name.clone(); + let named_exports = named_exports.remove(&export_name).unwrap(); + if named_exports.is_empty() { + pending_traces + .maybe_add_id_trace(*export_symbol_id, module_symbol.symbol_id()); + } else { + pending_traces + .traces + .push_back(PendingIdTrace::QualifiedId { + symbol_id: *export_symbol_id, + parts: named_exports, + referrer_id: module_symbol.symbol_id(), + }); + } + } + } + + if !named_exports.is_empty() { + if let Some(re_export_all_nodes) = module_info.re_export_all_nodes() { + for re_export_all_node in re_export_all_nodes { + if named_exports.is_empty() { + break; // all done + } + let specifier_text = re_export_all_node.src.value.as_str(); + if let Some(dep_specifier) = self.graph.resolve_dependency( + specifier_text, + module_info.specifier(), + /* prefer types */ true, + ) { + if let Some(module_info) = + self.root_symbol.module_from_specifier(&dep_specifier) + { + let module_exports = module_info.exports(self.root_symbol); + + for i in (0..named_exports.len()).rev() { + let (export_name, _) = named_exports.get_index(i).unwrap(); + if let Some(export_path) = + module_exports.resolved.get(export_name) + { + found_ranges.insert(re_export_all_node.span.range()); + let export_name = export_name.clone(); + let named_exports = + named_exports.remove(&export_name).unwrap(); + let module = match export_path { + crate::symbols::ResolvedExportOrReExportAllPath::Export(e) => e.module, + crate::symbols::ResolvedExportOrReExportAllPath::ReExportAllPath(p) => p.referrer_module, + }; + if let Some(nv) = self + .url_converter + .registry_package_url_to_nv(module.specifier()) + { + let mut new_named_exports = NamedExports::default(); + new_named_exports.0.insert(export_name, named_exports); + self.add_pending_trace( + &nv, + module.specifier(), + ImportedExports::named(new_named_exports), + ); + } + } + } + } + } + } + + if !named_exports.is_empty() { + // in this case, include all re_export all ranges because + // we couldn't determine a named export + if let Some(re_export_all_nodes) = module_info.re_export_all_nodes() + { + for re_export_all_node in re_export_all_nodes { + found_ranges.insert(re_export_all_node.span.range()); + } + } + } + } + } + } + + while let Some(trace) = pending_traces.traces.pop_front() { + match trace { + PendingIdTrace::Id { + symbol_id, + referrer_id, + } => { + let symbol = module_info.symbol(symbol_id).unwrap(); + if symbol.is_private_member() { + if Some(referrer_id) != symbol.parent_id() { + diagnostics.push( + FastCheckDiagnostic::UnsupportedPrivateMemberReference { + range: source_range_to_range( + symbol.decls()[0].range, + module_info.specifier(), + module_info.text_info(), + ), + name: module_info + .fully_qualified_symbol_name(symbol) + .unwrap_or_else(|| "".to_string()), + referrer: module_info + .symbol(referrer_id) + .and_then(|symbol| { + module_info.fully_qualified_symbol_name(symbol) + }) + .unwrap_or_else(|| "".to_string()), + }, + ); + } + continue; + } + + for decl in symbol.decls() { + found_ranges.insert(decl.range); + + if decl.has_overloads() && decl.has_body() { + impl_with_overload_ranges.insert(decl.range); + continue; + } + let referrer_id = symbol_id; + match &decl.kind { + SymbolDeclKind::Target(id) => { + found_ranges.insert(decl.range); + if let Some(symbol_id) = + module_info.esm().and_then(|m| m.symbol_id_from_swc(id)) + { + pending_traces.maybe_add_id_trace(symbol_id, referrer_id); + } + } + SymbolDeclKind::QualifiedTarget(id, parts) => { + found_ranges.insert(decl.range); + if let Some(symbol_id) = + module_info.esm().and_then(|m| m.symbol_id_from_swc(id)) + { + pending_traces.traces.push_back( + PendingIdTrace::QualifiedId { + symbol_id, + parts: NamedExports::from_parts(parts), + referrer_id, + }, + ); + } + } + SymbolDeclKind::FileRef(file_dep) => { + if let Some(specifier) = self.graph.resolve_dependency( + &file_dep.specifier, + module_info.specifier(), + /* prefer types */ true, + ) { + if let Some(dep_nv) = + self.url_converter.registry_package_url_to_nv(&specifier) + { + if dep_nv == *pkg_nv { + // just add this specifier + self.add_pending_trace( + &dep_nv, + &specifier, + ImportedExports::from_file_dep_name(&file_dep.name), + ); + } else { + // need to analyze the whole package + if self.seen_nvs.insert(dep_nv.clone()) { + self.pending_nvs.push_back(dep_nv.clone()); + } + } + } + } + } + SymbolDeclKind::Definition(node) => { + if let Some(node) = node.maybe_ref() { + for dep in node.deps(ResolveDepsMode::TypesAndExpressions) { + match dep { + SymbolNodeDep::Id(id) => { + let module_info = module_info.esm().unwrap(); + if let Some(symbol_id) = + module_info.symbol_id_from_swc(&id) + { + pending_traces + .maybe_add_id_trace(symbol_id, referrer_id); + } + } + SymbolNodeDep::QualifiedId(id, parts) => { + let module_info = module_info.esm().unwrap(); + if let Some(symbol_id) = + module_info.symbol_id_from_swc(&id) + { + pending_traces.traces.push_back( + PendingIdTrace::QualifiedId { + symbol_id, + parts: NamedExports::from_parts(&parts), + referrer_id, + }, + ); + } + } + SymbolNodeDep::ImportType(specifier, parts) => { + if let Some(specifier) = self.graph.resolve_dependency( + &specifier, + module_info.specifier(), + /* prefer types */ true, + ) { + if let Some(dep_nv) = self + .url_converter + .registry_package_url_to_nv(&specifier) + { + if dep_nv == *pkg_nv { + // just add this specifier + self.add_pending_trace( + &dep_nv, + &specifier, + if parts.is_empty() { + ImportedExports::star_with_default() + } else { + ImportedExports::named( + NamedExports::from_parts(&parts), + ) + }, + ); + } else { + // need to analyze the whole package + if self.seen_nvs.insert(dep_nv.clone()) { + self.pending_nvs.push_back(dep_nv.clone()); + } + } + } + } + } + } + } + } + } + } + } + + pending_traces.traces.extend( + symbol + .exports() + .values() + .map(|id| (*id, symbol.symbol_id())) + .chain( + symbol.members().iter().map(|id| (*id, symbol.symbol_id())), + ) + .filter(|(symbol_id, referrer_id)| { + !pending_traces + .done_id_traces + .contains(&(*symbol_id, *referrer_id)) + }) + .map(|(symbol_id, referrer_id)| PendingIdTrace::Id { + symbol_id, + referrer_id, + }), + ); + } + PendingIdTrace::QualifiedId { + symbol_id, + parts, + referrer_id, + } => { + let symbol = module_info.symbol(symbol_id).unwrap(); + + let mut handled = false; + for decl in symbol.decls() { + found_ranges.insert(decl.range); + match &decl.kind { + SymbolDeclKind::Target(id) => { + handled = true; + let symbol_id = module_info + .esm() + .and_then(|m| m.symbol_id_from_swc(id)) + .unwrap(); + pending_traces + .traces + .push_back(PendingIdTrace::QualifiedId { + symbol_id, + parts: parts.clone(), + referrer_id, + }); + } + SymbolDeclKind::QualifiedTarget(id, target_parts) => { + handled = true; + let symbol_id = module_info + .esm() + .and_then(|m| m.symbol_id_from_swc(id)) + .unwrap(); + let mut new_parts = NamedExports::default(); + for parts in parts.clone().into_separate_parts() { + let combined_vec = target_parts + .iter() + .cloned() + .chain(parts.into_iter()) + .collect::>(); + new_parts.add_qualified(&combined_vec[0], &combined_vec[1..]); + } + pending_traces + .traces + .push_back(PendingIdTrace::QualifiedId { + symbol_id, + parts: new_parts, + referrer_id, + }); + } + SymbolDeclKind::FileRef(file_dep) => { + handled = true; + if let Some(specifier) = self.graph.resolve_dependency( + &file_dep.specifier, + module_info.specifier(), + /* prefer types */ true, + ) { + if let Some(dep_nv) = + self.url_converter.registry_package_url_to_nv(&specifier) + { + if dep_nv == *pkg_nv { + let named_exports = match &file_dep.name { + FileDepName::Star => parts.clone(), + FileDepName::Name(first_part) => { + let mut separate_parts = + parts.clone().into_separate_parts(); + for parts in &mut separate_parts { + parts.insert(0, first_part.clone()); + } + NamedExports::from_many_parts(&separate_parts) + } + }; + // just add this specifier + self.add_pending_trace( + &dep_nv, + &specifier, + ImportedExports::named(named_exports), + ); + } else { + // need to analyze the whole package + if self.seen_nvs.insert(dep_nv.clone()) { + self.pending_nvs.push_back(dep_nv.clone()); + } + } + } + } + } + SymbolDeclKind::Definition(_) => {} + } + } + + if !handled { + for parts in parts.into_separate_parts() { + if parts[0] == "prototype" + && symbol.decls().iter().any(|d| d.is_class()) + { + if parts.len() > 1 { + let mut member_symbols = symbol + .members() + .iter() + .filter_map(|id| module_info.symbol(*id)); + let member_symbol = member_symbols.find(|s| { + let maybe_name = s.maybe_name(); + maybe_name.as_deref() == Some(parts[1].as_str()) + }); + match member_symbol { + Some(member) => { + if parts.len() > 2 { + diagnostics.push( + FastCheckDiagnostic::UnsupportedComplexReference { + range: source_range_to_range( + symbol.decls()[0].range, + module_info.specifier(), + module_info.text_info(), + ), + name: format!( + "{}.prototype.{}", + module_info + .fully_qualified_symbol_name(symbol) + .unwrap_or_else(|| "".to_string()), + parts[1..].join("."), + ), + referrer: module_info + .symbol(referrer_id) + .and_then(|symbol| { + module_info.fully_qualified_symbol_name(symbol) + }) + .unwrap_or_else(|| "".to_string()), + }, + ); + } else { + pending_traces + .maybe_add_id_trace(member.symbol_id(), referrer_id); + } + } + None => { + diagnostics.push( + FastCheckDiagnostic::NotFoundReference { + range: source_range_to_range( + symbol.decls()[0].range, + module_info.specifier(), + module_info.text_info(), + ), + name: format!( + "{}.prototype.{}", + module_info + .fully_qualified_symbol_name(symbol) + .unwrap_or_else(|| "".to_string()), + parts[1], + ), + referrer: module_info + .symbol(referrer_id) + .and_then(|symbol| { + module_info.fully_qualified_symbol_name(symbol) + }) + .unwrap_or_else(|| "".to_string()), + }, + ); + } + } + } else { + pending_traces.maybe_add_id_trace(symbol_id, referrer_id); + } + } else { + match symbol.export(&parts[0]) { + Some(symbol_id) => { + if parts.len() > 1 { + pending_traces.traces.push_back( + PendingIdTrace::QualifiedId { + symbol_id, + parts: NamedExports::from_parts(&parts[1..]), + referrer_id, + }, + ); + } else { + pending_traces.maybe_add_id_trace(symbol_id, referrer_id); + } + } + None => { + // if the init expression of the variable is leavable, just ignore it + let ignore = + symbol.decls().iter().filter_map(|d| d.maybe_node()).all( + |n| match n { + SymbolNodeRef::ExportDecl( + _, + ExportDeclRef::Var(_, v, _), + ) + | SymbolNodeRef::Var(_, v, _) => match &v.init { + Some(init) => is_expr_leavable(init), + None => false, + }, + SymbolNodeRef::ExportDecl( + _, + ExportDeclRef::TsEnum(_), + ) + | SymbolNodeRef::TsEnum(_) => true, + _ => false, + }, + ); + if !ignore { + diagnostics.push( + FastCheckDiagnostic::NotFoundReference { + range: source_range_to_range( + symbol.decls()[0].range, + module_info.specifier(), + module_info.text_info(), + ), + name: format!( + "{}.{}", + module_info + .fully_qualified_symbol_name(symbol) + .unwrap_or_else(|| "".to_string()), + parts[0], + ), + referrer: module_info + .symbol(referrer_id) + .and_then(|symbol| { + module_info.fully_qualified_symbol_name(symbol) + }) + .unwrap_or_else(|| "".to_string()), + }, + ); + } + } + } + } + } + } + } + } + } + + let ranges = self + .public_ranges + .entry(trace.package_nv.clone()) + .or_default() + .module_ranges + .entry(module_info.specifier().clone()) + .or_default(); + ranges.ranges.extend(found_ranges); + ranges + .impl_with_overload_ranges + .extend(impl_with_overload_ranges); + ranges.diagnostics.extend(diagnostics); + + found + } +} diff --git a/src/fast_check/swc_helpers.rs b/src/fast_check/swc_helpers.rs new file mode 100644 index 000000000..1f298a984 --- /dev/null +++ b/src/fast_check/swc_helpers.rs @@ -0,0 +1,206 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_ast::swc::ast::Expr; +use deno_ast::swc::ast::Ident; +use deno_ast::swc::ast::Lit; +use deno_ast::swc::ast::MemberProp; +use deno_ast::swc::ast::Prop; +use deno_ast::swc::ast::PropName; +use deno_ast::swc::ast::PropOrSpread; +use deno_ast::swc::ast::ReturnStmt; +use deno_ast::swc::ast::Stmt; +use deno_ast::swc::ast::TsKeywordType; +use deno_ast::swc::ast::TsKeywordTypeKind; +use deno_ast::swc::ast::TsType; +use deno_ast::swc::common::DUMMY_SP; +use deno_ast::SourceRange; +use deno_ast::SourceTextInfo; + +use crate::ModuleSpecifier; +use crate::PositionRange; +use crate::Range; + +pub fn ident(name: String) -> Ident { + Ident { + span: DUMMY_SP, + sym: name.clone().into(), + optional: false, + } +} + +pub fn ts_keyword_type(kind: TsKeywordTypeKind) -> TsType { + TsType::TsKeywordType(TsKeywordType { + span: DUMMY_SP, + kind, + }) +} + +pub fn get_return_stmts_with_arg_from_function( + func: &deno_ast::swc::ast::Function, +) -> Vec<&ReturnStmt> { + let Some(body) = func.body.as_ref() else { + return Vec::new(); + }; + let stmts = get_return_stmts_with_arg_from_stmts(&body.stmts); + debug_assert!(stmts.iter().all(|stmt| stmt.arg.is_some())); + stmts +} + +fn get_return_stmts_with_arg_from_stmts(stmts: &[Stmt]) -> Vec<&ReturnStmt> { + let mut result = Vec::new(); + for stmt in stmts { + result.extend(get_return_stmts_with_arg_from_stmt(stmt)) + } + result +} + +fn get_return_stmts_with_arg_from_stmt(stmt: &Stmt) -> Vec<&ReturnStmt> { + match stmt { + Stmt::Block(n) => get_return_stmts_with_arg_from_stmts(&n.stmts), + Stmt::With(n) => get_return_stmts_with_arg_from_stmt(&n.body), + Stmt::Return(n) => { + if n.arg.is_none() { + Vec::new() + } else { + vec![n] + } + } + Stmt::Labeled(n) => get_return_stmts_with_arg_from_stmt(&n.body), + Stmt::If(n) => get_return_stmts_with_arg_from_stmt(&n.cons), + Stmt::Switch(n) => n + .cases + .iter() + .flat_map(|case| get_return_stmts_with_arg_from_stmts(&case.cons)) + .collect(), + Stmt::Try(n) => { + let mut result = Vec::new(); + result.extend(get_return_stmts_with_arg_from_stmts(&n.block.stmts)); + if let Some(n) = &n.handler { + result.extend(get_return_stmts_with_arg_from_stmts(&n.body.stmts)) + } + if let Some(n) = &n.finalizer { + result.extend(get_return_stmts_with_arg_from_stmts(&n.stmts)) + } + result + } + Stmt::While(n) => get_return_stmts_with_arg_from_stmt(&n.body), + Stmt::DoWhile(n) => get_return_stmts_with_arg_from_stmt(&n.body), + Stmt::For(n) => get_return_stmts_with_arg_from_stmt(&n.body), + Stmt::ForIn(n) => get_return_stmts_with_arg_from_stmt(&n.body), + Stmt::ForOf(n) => get_return_stmts_with_arg_from_stmt(&n.body), + Stmt::Break(_) + | Stmt::Continue(_) + | Stmt::Throw(_) + | Stmt::Debugger(_) + | Stmt::Decl(_) + | Stmt::Expr(_) + | Stmt::Empty(_) => Vec::new(), + } +} + +pub fn source_range_to_range( + range: SourceRange, + specifier: &ModuleSpecifier, + text_info: &SourceTextInfo, +) -> Range { + let position_range = PositionRange::from_source_range(range, text_info); + Range::from_position_range(specifier.clone(), position_range) +} + +pub fn is_expr_leavable(expr: &Expr) -> bool { + fn is_member_prop_leavable(n: &MemberProp) -> bool { + match n { + MemberProp::Ident(_) => true, + MemberProp::PrivateName(_) => false, + MemberProp::Computed(n) => is_expr_leavable(&n.expr), + } + } + + match expr { + Expr::This(_) => true, + Expr::Array(n) => n.elems.iter().all(|elem| match elem { + Some(elem) => is_expr_leavable(&elem.expr), + None => true, + }), + Expr::Object(n) => n.props.iter().all(|prop| match prop { + PropOrSpread::Prop(prop) => match &**prop { + Prop::Shorthand(_) => true, + Prop::KeyValue(prop) => match &prop.key { + PropName::Ident(_) => is_expr_leavable(&prop.value), + PropName::Str(_) => is_expr_leavable(&prop.value), + PropName::Num(_) => is_expr_leavable(&prop.value), + PropName::Computed(c) => { + is_expr_leavable(&c.expr) && is_expr_leavable(&prop.value) + } + PropName::BigInt(_) => is_expr_leavable(&prop.value), + }, + Prop::Assign(n) => is_expr_leavable(&n.value), + Prop::Getter(_) | Prop::Setter(_) | Prop::Method(_) => false, + }, + PropOrSpread::Spread(n) => { + is_expr_leavable(&n.expr) + }, + }), + Expr::Unary(n) => is_expr_leavable(&n.arg), + Expr::Update(n) => is_expr_leavable(&n.arg), + Expr::Bin(n) => is_expr_leavable(&n.left) && is_expr_leavable(&n.right), + Expr::Assign(_) | Expr::SuperProp(_) => false, + Expr::Cond(n) => { + is_expr_leavable(&n.test) + && is_expr_leavable(&n.cons) + && is_expr_leavable(&n.alt) + } + Expr::Member(n) => { + is_expr_leavable(&n.obj) && is_member_prop_leavable(&n.prop) + } + Expr::Ident(_) => true, + Expr::Call(_) | Expr::New(_) | Expr::Seq(_) => false, + Expr::Lit(n) => match n { + Lit::Str(_) + | Lit::Bool(_) + | Lit::Null(_) + | Lit::Num(_) + | Lit::BigInt(_) + | Lit::Regex(_) => true, + Lit::JSXText(_) => false, + }, + Expr::Await(n) => is_expr_leavable(&n.arg), + Expr::Paren(n) => is_expr_leavable(&n.expr), + Expr::TsTypeAssertion(_) | Expr::TsAs(_) => false, + Expr::TsConstAssertion(n) => is_expr_leavable(&n.expr), + Expr::TsNonNull(n) => is_expr_leavable(&n.expr), + Expr::Tpl(_) + | Expr::Fn(_) + | Expr::TaggedTpl(_) + | Expr::Arrow(_) + | Expr::Class(_) + | Expr::Yield(_) + | Expr::MetaProp(_) + | Expr::JSXMember(_) + | Expr::JSXNamespacedName(_) + | Expr::JSXEmpty(_) + | Expr::JSXElement(_) + | Expr::JSXFragment(_) + | Expr::TsInstantiation(_) + | Expr::TsSatisfies(_) + | Expr::PrivateName(_) + // todo(dsherret): probably could analyze this more + | Expr::OptChain(_) + | Expr::Invalid(_) => false, + } +} + +pub fn is_void_type(return_type: &TsType) -> bool { + is_keyword_type(return_type, TsKeywordTypeKind::TsVoidKeyword) +} + +pub fn is_never_type(return_type: &TsType) -> bool { + is_keyword_type(return_type, TsKeywordTypeKind::TsNeverKeyword) +} + +fn is_keyword_type(return_type: &TsType, kind: TsKeywordTypeKind) -> bool { + match return_type { + TsType::TsKeywordType(TsKeywordType { kind: k, .. }) => k == &kind, + _ => false, + } +} diff --git a/src/fast_check/transform.rs b/src/fast_check/transform.rs new file mode 100644 index 000000000..ffbdd8cfc --- /dev/null +++ b/src/fast_check/transform.rs @@ -0,0 +1,1481 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// for span methods, which actually make sense to use here in the transforms +#![allow(clippy::disallowed_methods)] +#![allow(clippy::disallowed_types)] + +use std::rc::Rc; +use std::sync::Arc; + +use deno_ast::swc::ast::Accessibility; +use deno_ast::swc::ast::ArrayLit; +use deno_ast::swc::ast::BindingIdent; +use deno_ast::swc::ast::CallExpr; +use deno_ast::swc::ast::Callee; +use deno_ast::swc::ast::Class; +use deno_ast::swc::ast::ClassMember; +use deno_ast::swc::ast::ClassProp; +use deno_ast::swc::ast::Decl; +use deno_ast::swc::ast::DefaultDecl; +use deno_ast::swc::ast::Expr; +use deno_ast::swc::ast::Function; +use deno_ast::swc::ast::Ident; +use deno_ast::swc::ast::Lit; +use deno_ast::swc::ast::MemberProp; +use deno_ast::swc::ast::MethodKind; +use deno_ast::swc::ast::ModuleDecl; +use deno_ast::swc::ast::ModuleItem; +use deno_ast::swc::ast::ObjectLit; +use deno_ast::swc::ast::ObjectPatProp; +use deno_ast::swc::ast::Param; +use deno_ast::swc::ast::ParamOrTsParamProp; +use deno_ast::swc::ast::ParenExpr; +use deno_ast::swc::ast::Pat; +use deno_ast::swc::ast::PrivateName; +use deno_ast::swc::ast::PrivateProp; +use deno_ast::swc::ast::PropName; +use deno_ast::swc::ast::ReturnStmt; +use deno_ast::swc::ast::Stmt; +use deno_ast::swc::ast::TsArrayType; +use deno_ast::swc::ast::TsAsExpr; +use deno_ast::swc::ast::TsEntityName; +use deno_ast::swc::ast::TsIntersectionType; +use deno_ast::swc::ast::TsKeywordType; +use deno_ast::swc::ast::TsKeywordTypeKind; +use deno_ast::swc::ast::TsLit; +use deno_ast::swc::ast::TsModuleDecl; +use deno_ast::swc::ast::TsModuleName; +use deno_ast::swc::ast::TsModuleRef; +use deno_ast::swc::ast::TsNamespaceBody; +use deno_ast::swc::ast::TsOptionalType; +use deno_ast::swc::ast::TsParamPropParam; +use deno_ast::swc::ast::TsParenthesizedType; +use deno_ast::swc::ast::TsRestType; +use deno_ast::swc::ast::TsTupleElement; +use deno_ast::swc::ast::TsTupleType; +use deno_ast::swc::ast::TsType; +use deno_ast::swc::ast::TsTypeAnn; +use deno_ast::swc::ast::TsTypeOperator; +use deno_ast::swc::ast::TsTypeOperatorOp; +use deno_ast::swc::ast::TsTypeParamInstantiation; +use deno_ast::swc::ast::TsTypeRef; +use deno_ast::swc::ast::TsUnionOrIntersectionType; +use deno_ast::swc::ast::TsUnionType; +use deno_ast::swc::ast::VarDecl; +use deno_ast::swc::codegen::text_writer::JsWriter; +use deno_ast::swc::codegen::Node; +use deno_ast::swc::common::comments::CommentKind; +use deno_ast::swc::common::comments::SingleThreadedComments; +use deno_ast::swc::common::comments::SingleThreadedCommentsMapInner; +use deno_ast::swc::common::FileName; +use deno_ast::swc::common::SourceMap; +use deno_ast::swc::common::Spanned; +use deno_ast::swc::common::DUMMY_SP; +use deno_ast::swc_codegen_config; +use deno_ast::ModuleSpecifier; +use deno_ast::MultiThreadedComments; +use deno_ast::ParsedSource; +use deno_ast::SourceRange; +use deno_ast::SourceRangedForSpanned; +use deno_ast::SourceTextInfo; + +use crate::DefaultModuleAnalyzer; +use crate::ModuleInfo; +use crate::Range; +use crate::WorkspaceMember; + +use super::range_finder::ModulePublicRanges; +use super::swc_helpers::get_return_stmts_with_arg_from_function; +use super::swc_helpers::ident; +use super::swc_helpers::is_expr_leavable; +use super::swc_helpers::is_never_type; +use super::swc_helpers::is_void_type; +use super::swc_helpers::source_range_to_range; +use super::swc_helpers::ts_keyword_type; +use super::FastCheckDiagnostic; + +struct CommentsMut { + leading: SingleThreadedCommentsMapInner, + trailing: SingleThreadedCommentsMapInner, +} + +impl CommentsMut { + pub fn new(single_threaded: SingleThreadedComments) -> Self { + fn prune_comments(comments: &mut SingleThreadedCommentsMapInner) { + comments.retain(|_key, value| { + value.retain(|c| { + match c.kind { + // only keep js docs and @deno-types comments + CommentKind::Line => c.text.starts_with("@deno-types"), + CommentKind::Block => c.text.starts_with('*'), + } + }); + !value.is_empty() + }); + } + + let (leading, trailing) = single_threaded.take_all(); + let mut leading = leading.take(); + let mut trailing = trailing.take(); + prune_comments(&mut leading); + prune_comments(&mut trailing); + Self { leading, trailing } + } + + pub fn remove_leading(&mut self, start: deno_ast::SourcePos) { + self.leading.remove(&start.as_byte_pos()); + } + + pub fn into_multi_threaded(self) -> MultiThreadedComments { + MultiThreadedComments::from_leading_and_trailing( + self.leading, + self.trailing, + ) + } +} + +pub struct FastCheckModule { + pub module_info: ModuleInfo, + pub text: String, + pub source_map: Vec, +} + +pub struct TransformOptions<'a> { + pub workspace_members: &'a [WorkspaceMember], + pub should_error_on_first_diagnostic: bool, +} + +pub fn transform( + specifier: &ModuleSpecifier, + public_ranges: &ModulePublicRanges, + parsed_source: &ParsedSource, + options: &TransformOptions, +) -> Result> { + let mut transformer = FastCheckTransformer::new( + specifier, + public_ranges, + parsed_source, + options.should_error_on_first_diagnostic, + ); + let (module, comments) = transformer.transform()?; + if !transformer.diagnostics.is_empty() { + return Err(Box::new(FastCheckDiagnostic::Multiple( + transformer.diagnostics, + ))); + } + let module_info = DefaultModuleAnalyzer::module_info_from_swc( + parsed_source.media_type(), + &module, + parsed_source.text_info(), + &comments, + ); + + // now emit + let comments = comments.into_single_threaded(); + let (text, source_map) = + emit(specifier, &comments, parsed_source.text_info(), &module) + .map_err(|e| FastCheckDiagnostic::Emit(Arc::new(e)))?; + + Ok(FastCheckModule { + module_info, + text, + source_map, + }) +} + +struct FastCheckTransformer<'a> { + specifier: &'a ModuleSpecifier, + public_ranges: &'a ModulePublicRanges, + parsed_source: &'a ParsedSource, + should_error_on_first_diagnostic: bool, + diagnostics: Vec, +} + +impl<'a> FastCheckTransformer<'a> { + pub fn new( + specifier: &'a ModuleSpecifier, + public_ranges: &'a ModulePublicRanges, + parsed_source: &'a ParsedSource, + should_error_on_first_diagnostic: bool, + ) -> Self { + Self { + specifier, + public_ranges, + parsed_source, + should_error_on_first_diagnostic, + diagnostics: Default::default(), + } + } + + pub fn transform( + &mut self, + ) -> Result< + (deno_ast::swc::ast::Module, MultiThreadedComments), + Box, + > { + let mut module = self.parsed_source.module().clone(); + let mut comments = + CommentsMut::new(self.parsed_source.comments().as_single_threaded()); + module.body = self + .transform_module_body(std::mem::take(&mut module.body), &mut comments)?; + Ok((module, comments.into_multi_threaded())) + } + + fn transform_module_body( + &mut self, + body: Vec, + comments: &mut CommentsMut, + ) -> Result, Box> { + let mut final_body = Vec::with_capacity(body.len()); + for mut item in body { + let retain = self.transform_item(&mut item, comments)?; + if retain { + final_body.push(item); + } else { + comments.remove_leading(item.start()); + } + } + Ok(final_body) + } + + fn transform_item( + &mut self, + item: &mut ModuleItem, + comments: &mut CommentsMut, + ) -> Result> { + match item { + ModuleItem::ModuleDecl(decl) => match decl { + ModuleDecl::Import(n) => { + n.specifiers + .retain(|s| self.public_ranges.contains(&s.range())); + Ok(!n.specifiers.is_empty()) + } + ModuleDecl::ExportNamed(n) => { + n.specifiers + .retain(|s| self.public_ranges.contains(&s.range())); + Ok(!n.specifiers.is_empty()) + } + ModuleDecl::ExportAll(n) => Ok(self.public_ranges.contains(&n.range())), + ModuleDecl::ExportDefaultExpr(n) => { + // todo: investigate why both these checks are needed + if !self.public_ranges.contains(&n.range()) + && !self.public_ranges.contains(&n.expr.range()) + { + return Ok(false); + } + + if is_expr_leavable(&n.expr) + || is_expr_ident_or_member_idents(&n.expr) + { + Ok(true) + } else { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedDefaultExportExpr { + range: self.source_range_to_range(n.range()), + }, + )?; + Ok(false) + } + } + ModuleDecl::ExportDefaultDecl(n) => { + if !self.public_ranges.contains(&n.range()) { + return Ok(false); + } + + let node_range = n.range(); + self.transform_default_decl(&mut n.decl, comments, node_range)?; + Ok(true) + } + ModuleDecl::ExportDecl(n) => { + let export_decl_range = n.range(); + self.transform_decl(&mut n.decl, comments, Some(export_decl_range)) + } + ModuleDecl::TsImportEquals(n) => match &n.module_ref { + TsModuleRef::TsEntityName(n) => { + Ok(self.public_ranges.contains(&n.range())) + } + TsModuleRef::TsExternalModuleRef(_) => { + self.mark_diagnostic(FastCheckDiagnostic::UnsupportedRequire { + range: self.source_range_to_range(n.range()), + })?; + Ok(false) + } + }, + ModuleDecl::TsExportAssignment(n) => { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedTsExportAssignment { + range: self.source_range_to_range(n.range()), + }, + )?; + Ok(false) + } + ModuleDecl::TsNamespaceExport(n) => { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedTsNamespaceExport { + range: self.source_range_to_range(n.range()), + }, + )?; + Ok(false) + } + }, + ModuleItem::Stmt(stmt) => match stmt { + Stmt::Block(_) + | Stmt::Empty(_) + | Stmt::Debugger(_) + | Stmt::With(_) + | Stmt::Return(_) + | Stmt::Labeled(_) + | Stmt::Break(_) + | Stmt::Continue(_) + | Stmt::If(_) + | Stmt::Switch(_) + | Stmt::Throw(_) + | Stmt::Try(_) + | Stmt::While(_) + | Stmt::DoWhile(_) + | Stmt::For(_) + | Stmt::ForIn(_) + | Stmt::ForOf(_) + | Stmt::Expr(_) => Ok(false), + Stmt::Decl(n) => self.transform_decl(n, comments, None), + }, + } + } + + fn transform_default_decl( + &mut self, + default_decl: &mut DefaultDecl, + comments: &mut CommentsMut, + parent_range: SourceRange, + ) -> Result<(), Box> { + match default_decl { + DefaultDecl::Class(n) => self.transform_class(&mut n.class, comments), + DefaultDecl::Fn(n) => self.transform_fn( + &mut n.function, + n.ident.as_ref().map(|i| i.range()), + self.public_ranges.is_impl_with_overloads(&parent_range), + /* is setter */ false, + ), + DefaultDecl::TsInterfaceDecl(_) => Ok(()), + } + } + + fn transform_decl( + &mut self, + decl: &mut Decl, + comments: &mut CommentsMut, + parent_range: Option, + ) -> Result> { + let public_range = parent_range.unwrap_or_else(|| decl.range()); + match decl { + Decl::Class(n) => { + if !self.public_ranges.contains(&public_range) { + return Ok(false); + } + self.transform_class(&mut n.class, comments)?; + Ok(true) + } + Decl::Fn(n) => { + if !self.public_ranges.contains(&public_range) { + return Ok(false); + } + let is_overload = + self.public_ranges.is_impl_with_overloads(&public_range); + self.transform_fn( + &mut n.function, + Some(n.ident.range()), + is_overload, + /* is setter */ false, + )?; + Ok(true) + } + Decl::Var(n) => self.transform_var(n), + Decl::TsInterface(_) => Ok(self.public_ranges.contains(&public_range)), + Decl::TsTypeAlias(_) => Ok(self.public_ranges.contains(&public_range)), + Decl::TsEnum(_) => Ok(self.public_ranges.contains(&public_range)), + Decl::TsModule(m) => self.transform_ts_module(m, &public_range, comments), + Decl::Using(n) => { + if self.public_ranges.contains(&public_range) { + self.mark_diagnostic(FastCheckDiagnostic::UnsupportedUsing { + range: self.source_range_to_range( + n.decls + .first() + .map(|n| n.range()) + .unwrap_or_else(|| n.range()), + ), + })?; + } + Ok(false) + } + } + } + + fn transform_class( + &mut self, + n: &mut Class, + comments: &mut CommentsMut, + ) -> Result<(), Box> { + let mut members = Vec::with_capacity(n.body.len()); + let mut had_private = false; + if let Some(super_class) = &n.super_class { + if !is_expr_ident_or_member_idents(super_class) { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedSuperClassExpr { + range: self.source_range_to_range(n.super_class.range()), + }, + )?; + } + } + let mut insert_members = Vec::new(); + for mut member in std::mem::take(&mut n.body) { + had_private = had_private + || matches!( + member, + ClassMember::PrivateMethod(_) | ClassMember::PrivateProp(_) + ); + + let retain = + self.transform_class_member(&mut member, &mut insert_members)?; + if retain { + members.push(member); + } else { + comments.remove_leading(member.start()); + } + } + + if had_private { + insert_members.insert( + 0, + ClassMember::PrivateProp(PrivateProp { + span: DUMMY_SP, + key: PrivateName { + span: DUMMY_SP, + id: ident("private".to_string()), + }, + value: None, + type_ann: Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: Box::new(ts_keyword_type( + TsKeywordTypeKind::TsUnknownKeyword, + )), + })), + is_static: false, + decorators: Default::default(), + accessibility: Default::default(), + is_optional: false, + is_override: false, + readonly: false, + definite: true, + }), + ) + } + + n.body = insert_members.into_iter().chain(members).collect(); + n.decorators.clear(); + Ok(()) + } + + fn transform_class_member( + &mut self, + member: &mut ClassMember, + insert_members: &mut Vec, + ) -> Result> { + match member { + ClassMember::Constructor(n) => { + if let Some(body) = &mut n.body { + body.stmts.retain_mut(|stmt| match stmt { + Stmt::Expr(e) => match &mut *e.expr { + Expr::Call(c) => { + if !matches!(c.callee, Callee::Super(_)) { + return false; + } + for arg in c.args.iter_mut() { + arg.expr = if arg.spread.is_some() { + Box::new(paren_expr(obj_as_any_expr())) + } else { + obj_as_any_expr() + }; + } + true + } + _ => false, + }, + _ => false, + }); + } + + for param in &mut n.params { + let mut is_optional = false; + match param { + ParamOrTsParamProp::Param(_) => { + // ignore + } + ParamOrTsParamProp::TsParamProp(prop) => { + insert_members.push(ClassMember::ClassProp(ClassProp { + span: DUMMY_SP, + key: match &prop.param { + TsParamPropParam::Ident(ident) => { + if ident.optional { + is_optional = true; + } + PropName::Ident(ident.id.clone()) + } + TsParamPropParam::Assign(assign) => match &*assign.left { + Pat::Ident(ident) => PropName::Ident(ident.id.clone()), + Pat::Array(_) + | Pat::Rest(_) + | Pat::Object(_) + | Pat::Assign(_) + | Pat::Invalid(_) + | Pat::Expr(_) => { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedDestructuring { + range: self + .source_range_to_range(assign.left.range()), + }, + )?; + return Ok(false); + } + }, + }, + value: None, + type_ann: if prop.accessibility == Some(Accessibility::Private) + { + Some(unknown_type_ann()) + } else { + match &prop.param { + TsParamPropParam::Ident(ident) => ident.type_ann.clone(), + TsParamPropParam::Assign(assign) => { + let explicit_type_ann = match &*assign.left { + Pat::Ident(binding_ident) => { + binding_ident.type_ann.clone() + } + _ => None, + }; + explicit_type_ann.or_else(|| { + self.maybe_infer_type_from_expr(&assign.right).map( + |type_ann| { + Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: Box::new(type_ann), + }) + }, + ) + }) + } + } + }, + is_static: false, + decorators: Vec::new(), + accessibility: match prop.accessibility { + Some(Accessibility::Public) | None => None, + Some(accessibility) => Some(accessibility), + }, + is_abstract: false, + is_optional: false, + is_override: prop.is_override, + readonly: prop.readonly, + declare: false, + definite: !is_optional, + })); + *param = ParamOrTsParamProp::Param(Param { + span: prop.span, + decorators: vec![], + pat: match prop.param.clone() { + TsParamPropParam::Ident(ident) => Pat::Ident(ident), + TsParamPropParam::Assign(pat) => Pat::Assign(pat), + }, + }); + } + } + } + + if n.accessibility == Some(Accessibility::Private) { + if n.body.is_none() { + return Ok(false); + } + n.params.clear(); + return Ok(true); + } + let is_overload = self.public_ranges.is_impl_with_overloads(&n.range()); + if is_overload { + for (i, param) in n.params.iter_mut().enumerate() { + *param = ParamOrTsParamProp::Param(Param { + span: DUMMY_SP, + decorators: Vec::new(), + pat: Pat::Ident(BindingIdent { + id: Ident { + span: DUMMY_SP, + sym: format!("param{}", i).into(), + optional: true, + }, + type_ann: Some(any_type_ann()), + }), + }); + } + } + + for param in &mut n.params { + match param { + ParamOrTsParamProp::Param(param) => { + self.handle_param_pat(&mut param.pat)?; + param.decorators.clear(); + } + ParamOrTsParamProp::TsParamProp(_) => { + // should have been converted to a param + unreachable!(); + } + } + } + + Ok(true) + } + ClassMember::Method(n) => { + if n.accessibility == Some(Accessibility::Private) { + *member = ClassMember::ClassProp(ClassProp { + span: DUMMY_SP, + key: n.key.clone(), + value: None, + type_ann: Some(unknown_type_ann()), + is_static: n.is_static, + decorators: Vec::new(), + accessibility: Some(Accessibility::Private), + is_abstract: n.is_abstract, + is_optional: n.is_optional, + is_override: n.is_override, + readonly: false, + declare: false, + definite: !n.is_optional && !n.is_static, + }); + return Ok(true); + } + let is_overload = self.public_ranges.is_impl_with_overloads(&n.range()); + self.transform_fn( + &mut n.function, + Some(n.key.range()), + is_overload, + n.kind == MethodKind::Setter, + )?; + Ok(true) + } + ClassMember::ClassProp(n) => { + if n.accessibility == Some(Accessibility::Private) { + n.type_ann = Some(unknown_type_ann()); + if !n.is_optional && !n.is_static { + n.definite = true; + } + return Ok(true); + } + if n.type_ann.is_none() { + let inferred_type = n + .value + .as_ref() + .and_then(|e| self.maybe_infer_type_from_expr(e)); + match inferred_type { + Some(t) => { + n.type_ann = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: Box::new(t), + })); + } + None => { + self.mark_diagnostic( + FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(n.key.range()), + }, + )?; + } + } + } + n.definite = !n.is_optional && !n.is_static && !n.declare; + n.decorators.clear(); + n.value = None; + Ok(true) + } + ClassMember::AutoAccessor(_n) => { + // waiting on https://github.com/swc-project/swc/pull/8436 + // if n.accessibility == Some(Accessibility::Private) { + // n.type_ann = Some(unknown_type_ann()); + // n.definite = true; + // return Ok(true); + // } + // if n.type_ann.is_none() { + // let inferred_type = n + // .value + // .as_ref() + // .and_then(|e| self.maybe_infer_type_from_expr(&*e)); + // match inferred_type { + // Some(t) => { + // n.type_ann = Some(Box::new(TsTypeAnn { + // span: DUMMY_SP, + // type_ann: Box::new(t), + // })); + // } + // None => { + // self.mark_diagnostic( + // FastCheckTransformDiagnostic::MissingExplicitType { + // range: self.source_range_to_range(n.key.range()), + // }, + // )?; + // } + // } + // } + // n.definite = true; + // n.decorators.clear(); + // n.value = None; + // Ok(true) + todo!("Remove auto-accessor for now. Waiting on https://github.com/swc-project/swc/pull/8436") + } + ClassMember::TsIndexSignature(_) => { + // ok, as-is + Ok(true) + } + ClassMember::PrivateMethod(_) + | ClassMember::PrivateProp(_) + | ClassMember::Empty(_) + | ClassMember::StaticBlock(_) => Ok(false), + } + } + + fn transform_fn( + &mut self, + n: &mut Function, + parent_id_range: Option, + is_overload: bool, + is_set_accessor: bool, + ) -> Result<(), Box> { + if is_overload { + for (i, param) in n.params.iter_mut().enumerate() { + *param = Param { + span: DUMMY_SP, + decorators: Vec::new(), + pat: Pat::Ident(BindingIdent { + id: Ident { + span: DUMMY_SP, + sym: format!("param{}", i).into(), + optional: true, + }, + type_ann: Some(any_type_ann()), + }), + }; + } + n.return_type = Some(any_type_ann()); + } + if is_set_accessor { + // suppress any unused param errors + for param in n.params.iter_mut() { + prefix_idents_in_pat(&mut param.pat, "_"); + } + } + + if !is_set_accessor && n.return_type.is_none() { + if n.is_generator { + self.mark_diagnostic( + FastCheckDiagnostic::MissingExplicitReturnType { + range: self.source_range_to_range( + parent_id_range.unwrap_or_else(|| n.range()), + ), + }, + )?; + } + + let return_stmts = get_return_stmts_with_arg_from_function(n); + if return_stmts.is_empty() { + let void_type = + Box::new(ts_keyword_type(TsKeywordTypeKind::TsVoidKeyword)); + n.return_type = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: if n.is_async { + Box::new(TsType::TsTypeRef(TsTypeRef { + span: DUMMY_SP, + type_name: TsEntityName::Ident(Ident::new( + "Promise".into(), + DUMMY_SP, + )), + type_params: Some(Box::new(TsTypeParamInstantiation { + span: DUMMY_SP, + params: vec![void_type], + })), + })) + } else { + void_type + }, + })); + } else { + self.mark_diagnostic( + FastCheckDiagnostic::MissingExplicitReturnType { + range: self.source_range_to_range( + parent_id_range.unwrap_or_else(|| n.range()), + ), + }, + )?; + } + } + + if let Some(body) = &mut n.body { + body.stmts.clear(); + + // push a return stmt to suppress type errors + let has_void_return_type = n + .return_type + .as_ref() + .map(|t| is_void_type(&t.type_ann)) + .unwrap_or(true); + if !has_void_return_type { + let has_never_return_type = n + .return_type + .as_ref() + .map(|t| is_never_type(&t.type_ann)) + .unwrap_or(false); + body.stmts.push(Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(if has_never_return_type { + obj_as_never_expr() + } else { + obj_as_any_expr() + }), + })); + } + } + + for param in &mut n.params { + self.handle_param_pat(&mut param.pat)?; + param.decorators.clear(); + } + + n.is_async = false; + n.is_generator = false; + n.decorators.clear(); + + Ok(()) + } + + fn handle_param_pat( + &mut self, + pat: &mut Pat, + ) -> Result<(), Box> { + match pat { + Pat::Ident(ident) => { + if ident.type_ann.is_none() { + self.mark_diagnostic(FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(pat.range()), + })?; + } + } + Pat::Assign(assign) => match &mut *assign.left { + Pat::Ident(ident) => { + if ident.type_ann.is_none() { + let inferred_type = self.maybe_infer_type_from_expr(&assign.right); + match inferred_type { + Some(t) => { + ident.type_ann = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: Box::new(t), + })); + ident.id.optional = true; + *pat = Pat::Ident((*ident).clone()); + } + None => { + if !is_expr_leavable(&assign.right) { + self.mark_diagnostic( + FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(ident.range()), + }, + )?; + } + } + } + } else { + ident.id.optional = true; + *pat = Pat::Ident((*ident).clone()); + } + } + Pat::Array(p) => { + if p.type_ann.is_none() { + if !is_expr_leavable(&assign.right) { + self.mark_diagnostic( + FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(p.range()), + }, + )?; + } + } else { + assign.right = array_as_any_expr(); + } + p.elems.clear(); + } + Pat::Object(p) => { + if p.type_ann.is_none() { + if !is_expr_leavable(&assign.right) { + self.mark_diagnostic( + FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(p.range()), + }, + )?; + } + } else { + assign.right = obj_as_any_expr(); + } + p.props.clear(); + } + Pat::Assign(_) | Pat::Invalid(_) | Pat::Rest(_) | Pat::Expr(_) => { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedDestructuring { + range: self.source_range_to_range(pat.range()), + }, + )?; + } + }, + Pat::Rest(p) => { + if p.type_ann.is_none() { + self.mark_diagnostic(FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(p.range()), + })?; + } + } + Pat::Array(p) => { + if p.type_ann.is_none() { + self.mark_diagnostic(FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(p.range()), + })?; + } + p.elems.clear(); + } + Pat::Object(p) => { + if p.type_ann.is_none() { + self.mark_diagnostic(FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(p.range()), + })?; + } + p.props.clear(); + } + Pat::Invalid(_) | Pat::Expr(_) => { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedDestructuring { + range: self.source_range_to_range(pat.range()), + }, + )?; + } + } + Ok(()) + } + + fn transform_var( + &mut self, + n: &mut VarDecl, + ) -> Result> { + n.decls.retain(|n| self.public_ranges.contains(&n.range())); + + for decl in &mut n.decls { + match &mut decl.name { + Pat::Ident(ident) => { + if ident.type_ann.is_none() { + let inferred_type = decl + .init + .as_ref() + .and_then(|e| self.maybe_infer_type_from_expr(e)); + match inferred_type { + Some(t) => { + ident.type_ann = Some(Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: Box::new(t), + })); + decl.init = Some(obj_as_any_expr()); + } + None => { + let is_init_leavable = decl + .init + .as_ref() + .map(|i| is_expr_leavable(i)) + .unwrap_or(false); + if !is_init_leavable { + self.mark_diagnostic( + FastCheckDiagnostic::MissingExplicitType { + range: self.source_range_to_range(ident.range()), + }, + )?; + } + } + } + } else { + decl.init = Some(obj_as_any_expr()); + } + } + Pat::Array(_) + | Pat::Rest(_) + | Pat::Object(_) + | Pat::Assign(_) + | Pat::Invalid(_) + | Pat::Expr(_) => { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedDestructuring { + range: self.source_range_to_range(decl.name.range()), + }, + )?; + } + } + } + + Ok(!n.decls.is_empty()) + } + + fn transform_ts_module( + &mut self, + n: &mut TsModuleDecl, + public_range: &SourceRange, + comments: &mut CommentsMut, + ) -> Result> { + if n.global { + self.mark_diagnostic(FastCheckDiagnostic::UnsupportedGlobalModule { + range: self.source_range_to_range(n.range()), + })?; + return Ok(false); + } + + match &n.id { + TsModuleName::Ident(_) => { + // ok + } + TsModuleName::Str(_) => { + // not ok + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedAmbientModule { + range: self.source_range_to_range(n.id.range()), + }, + )?; + return Ok(false); + } + } + + let ts_module_block = match &mut n.body { + Some(body) => match body { + TsNamespaceBody::TsModuleBlock(block) => block, + TsNamespaceBody::TsNamespaceDecl(decl) => { + let mut body = &mut *decl.body; + loop { + match body { + TsNamespaceBody::TsModuleBlock(block) => { + break block; + } + TsNamespaceBody::TsNamespaceDecl(decl) => { + body = &mut decl.body; + } + } + } + } + }, + None => { + self.mark_diagnostic( + FastCheckDiagnostic::UnsupportedAmbientModule { + range: self.source_range_to_range(n.id.range()), + }, + )?; + return Ok(false); + } + }; + + // allow the above diagnostics to error before checking for a public range + if !self.public_ranges.contains(public_range) { + return Ok(false); + } + + let body = std::mem::take(&mut ts_module_block.body); + ts_module_block.body = self.transform_module_body(body, comments)?; + + Ok(true) + } + + fn mark_diagnostic( + &mut self, + diagnostic: FastCheckDiagnostic, + ) -> Result<(), Box> { + if self.should_error_on_first_diagnostic { + Err(Box::new(diagnostic)) + } else { + self.diagnostics.push(diagnostic); + Ok(()) + } + } + + fn source_range_to_range(&self, range: SourceRange) -> Range { + let text_info = self.parsed_source.text_info(); + source_range_to_range(range, self.specifier, text_info) + } + + fn maybe_infer_type_from_expr(&self, expr: &Expr) -> Option { + match expr { + Expr::TsTypeAssertion(n) => infer_simple_type_from_type(&n.type_ann), + Expr::TsAs(n) => infer_simple_type_from_type(&n.type_ann), + Expr::Lit(lit) => match lit { + Lit::Str(_) => { + Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)) + } + Lit::Bool(_) => { + Some(ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword)) + } + Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)), + Lit::Num(_) => { + Some(ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword)) + } + Lit::BigInt(_) => { + Some(ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword)) + } + Lit::Regex(_) => Some(TsType::TsTypeRef(TsTypeRef { + span: DUMMY_SP, + type_name: TsEntityName::Ident(Ident::new("RegExp".into(), DUMMY_SP)), + type_params: None, + })), + Lit::JSXText(_) => None, + }, + Expr::Call(call_expr) => { + if self.is_call_expr_symbol_create(call_expr) { + Some(TsType::TsTypeOperator(TsTypeOperator { + span: DUMMY_SP, + op: TsTypeOperatorOp::Unique, + type_ann: Box::new(TsType::TsTypeRef(TsTypeRef { + span: DUMMY_SP, + type_name: TsEntityName::Ident(ident("symbol".to_string())), + type_params: None, + })), + })) + } else { + None + } + } + Expr::This(_) + | Expr::Array(_) + | Expr::Object(_) + | Expr::Fn(_) + | Expr::Unary(_) + | Expr::Update(_) + | Expr::Bin(_) + | Expr::Assign(_) + | Expr::Member(_) + | Expr::SuperProp(_) + | Expr::Cond(_) + | Expr::New(_) + | Expr::Seq(_) + | Expr::Ident(_) + | Expr::Tpl(_) + | Expr::TaggedTpl(_) + | Expr::Arrow(_) + | Expr::Class(_) + | Expr::Yield(_) + | Expr::MetaProp(_) + | Expr::Await(_) + | Expr::Paren(_) + | Expr::JSXMember(_) + | Expr::JSXNamespacedName(_) + | Expr::JSXEmpty(_) + | Expr::JSXElement(_) + | Expr::JSXFragment(_) + | Expr::TsConstAssertion(_) + | Expr::TsNonNull(_) + | Expr::TsInstantiation(_) + | Expr::TsSatisfies(_) + | Expr::PrivateName(_) + | Expr::OptChain(_) + | Expr::Invalid(_) => None, + } + } + + /// Looks if the call expr is `Symbol("example")` or `Symbol.for("example")` + fn is_call_expr_symbol_create(&self, call_expr: &CallExpr) -> bool { + let Some(expr) = call_expr.callee.as_expr() else { + return false; + }; + let expr_ident = match &**expr { + Expr::Ident(ident) => ident, + Expr::Member(member_expr) => { + let Some(ident) = member_expr.obj.as_ident() else { + return false; + }; + let Some(prop_ident) = member_expr.prop.as_ident() else { + return false; + }; + if prop_ident.sym != "for" { + return false; + } + ident + } + _ => return false, + }; + + let is_symbol_global = expr_ident.sym == "Symbol" + && expr_ident.to_id().1 == self.parsed_source.unresolved_context(); + if !is_symbol_global || call_expr.args.len() != 1 { + return false; + } + let Some(arg_lit) = call_expr.args.first().and_then(|a| a.expr.as_lit()) + else { + return false; + }; + matches!(arg_lit, Lit::Str(_)) + } +} + +fn prefix_ident(ident: &mut Ident, prefix: &str) { + ident.sym = format!("{}{}", prefix, ident.sym).into(); +} + +fn prefix_idents_in_pat(pat: &mut Pat, prefix: &str) { + match pat { + Pat::Ident(ident) => { + prefix_ident(&mut ident.id, prefix); + } + Pat::Array(array) => { + for pat in array.elems.iter_mut().flatten() { + prefix_idents_in_pat(pat, prefix); + } + } + Pat::Rest(rest) => { + prefix_idents_in_pat(&mut rest.arg, prefix); + } + Pat::Object(o) => { + for prop in &mut o.props { + match prop { + ObjectPatProp::KeyValue(n) => match &mut n.key { + PropName::Ident(ident) => { + prefix_ident(ident, prefix); + } + PropName::Str(str) => { + str.value = format!("{}{}", prefix, str.value).into(); + } + PropName::Num(_) | PropName::Computed(_) | PropName::BigInt(_) => { + // ignore + } + }, + ObjectPatProp::Assign(n) => prefix_ident(&mut n.key, prefix), + ObjectPatProp::Rest(n) => prefix_idents_in_pat(&mut n.arg, prefix), + } + } + } + Pat::Assign(a) => prefix_idents_in_pat(&mut a.left, prefix), + Pat::Expr(_) | Pat::Invalid(_) => { + // ignore + } + } +} + +fn emit( + specifier: &ModuleSpecifier, + comments: &SingleThreadedComments, + text_info: &SourceTextInfo, + module: &deno_ast::swc::ast::Module, +) -> Result<(String, Vec), anyhow::Error> { + let source_map = Rc::new(SourceMap::default()); + let file_name = FileName::Url(specifier.clone()); + source_map.new_source_file(file_name, text_info.text_str().to_string()); + + let mut src_map_buf = vec![]; + let mut buf = vec![]; + { + let mut writer = Box::new(JsWriter::new( + source_map.clone(), + "\n", + &mut buf, + Some(&mut src_map_buf), + )); + writer.set_indent_str(" "); // two spaces + + let mut emitter = deno_ast::swc::codegen::Emitter { + cfg: swc_codegen_config(), + comments: Some(comments), + cm: source_map.clone(), + wr: writer, + }; + module.emit_with(&mut emitter)?; + } + let src = String::from_utf8(buf)?; + let map = { + let mut buf = Vec::new(); + source_map + .build_source_map_from(&src_map_buf, None) + .to_writer(&mut buf)?; + buf + }; + + Ok((src, map)) +} + +fn infer_simple_type_from_type(t: &TsType) -> Option { + match t { + TsType::TsKeywordType(_) => Some(t.clone()), + TsType::TsThisType(_) => Some(t.clone()), + TsType::TsFnOrConstructorType(_) => None, + TsType::TsTypeRef(_) => None, + TsType::TsTypeQuery(_) => None, + TsType::TsTypeLit(_) => None, + TsType::TsTupleType(t) => { + let mut elems = Vec::with_capacity(t.elem_types.len()); + for elem_type in &t.elem_types { + let inferred_type = infer_simple_type_from_type(&elem_type.ty)?; + elems.push(TsTupleElement { + span: elem_type.span, + label: elem_type.label.clone(), + ty: Box::new(inferred_type), + }); + } + Some(TsType::TsTupleType(TsTupleType { + span: t.span(), + elem_types: elems, + })) + } + TsType::TsArrayType(t) => { + infer_simple_type_from_type(&t.elem_type).map(|inner| { + TsType::TsArrayType(TsArrayType { + span: t.span(), + elem_type: Box::new(inner), + }) + }) + } + TsType::TsOptionalType(t) => { + infer_simple_type_from_type(&t.type_ann).map(|inner| { + TsType::TsOptionalType(TsOptionalType { + span: t.span(), + type_ann: Box::new(inner), + }) + }) + } + TsType::TsRestType(t) => { + infer_simple_type_from_type(&t.type_ann).map(|inner| { + TsType::TsRestType(TsRestType { + span: t.span(), + type_ann: Box::new(inner), + }) + }) + } + TsType::TsUnionOrIntersectionType(t) => match t { + TsUnionOrIntersectionType::TsUnionType(t) => { + let mut types = Vec::with_capacity(t.types.len()); + for ty in &t.types { + let inferred_type = infer_simple_type_from_type(ty)?; + types.push(Box::new(inferred_type)); + } + Some(TsType::TsUnionOrIntersectionType( + TsUnionOrIntersectionType::TsUnionType(TsUnionType { + span: t.span(), + types, + }), + )) + } + TsUnionOrIntersectionType::TsIntersectionType(t) => { + let mut types = Vec::with_capacity(t.types.len()); + for ty in &t.types { + let inferred_type = infer_simple_type_from_type(ty)?; + types.push(Box::new(inferred_type)); + } + Some(TsType::TsUnionOrIntersectionType( + TsUnionOrIntersectionType::TsIntersectionType(TsIntersectionType { + span: t.span(), + types, + }), + )) + } + }, + TsType::TsConditionalType(_) => None, + TsType::TsInferType(_) => None, + TsType::TsParenthesizedType(t) => infer_simple_type_from_type(&t.type_ann) + .map(|inner| { + TsType::TsParenthesizedType(TsParenthesizedType { + span: t.span(), + type_ann: Box::new(inner), + }) + }), + TsType::TsTypeOperator(t) => { + infer_simple_type_from_type(&t.type_ann).map(|inner| { + TsType::TsTypeOperator(TsTypeOperator { + span: t.span(), + op: t.op, + type_ann: Box::new(inner), + }) + }) + } + TsType::TsIndexedAccessType(_) => None, + TsType::TsMappedType(_) => None, + TsType::TsLitType(t) => match &t.lit { + TsLit::Number(_) | TsLit::Str(_) | TsLit::Bool(_) | TsLit::BigInt(_) => { + Some(TsType::TsLitType(t.clone())) + } + TsLit::Tpl(_) => None, + }, + TsType::TsTypePredicate(_) | TsType::TsImportType(_) => None, + } +} + +fn is_expr_ident_or_member_idents(expr: &Expr) -> bool { + match expr { + Expr::Ident(_) => true, + Expr::Member(n) => { + is_expr_ident_or_member_idents(&n.obj) + && match &n.prop { + MemberProp::Ident(_) => true, + MemberProp::PrivateName(_) => false, + MemberProp::Computed(p) => is_expr_ident_or_member_idents(&p.expr), + } + } + _ => false, + } +} + +fn array_as_any_expr() -> Box { + expr_as_keyword_expr( + Expr::Array(ArrayLit { + span: DUMMY_SP, + elems: Default::default(), + }), + TsKeywordTypeKind::TsAnyKeyword, + ) +} + +fn obj_as_any_expr() -> Box { + expr_as_keyword_expr( + Expr::Object(ObjectLit { + span: DUMMY_SP, + props: Default::default(), + }), + TsKeywordTypeKind::TsAnyKeyword, + ) +} + +fn obj_as_never_expr() -> Box { + expr_as_keyword_expr( + Expr::Object(ObjectLit { + span: DUMMY_SP, + props: Default::default(), + }), + TsKeywordTypeKind::TsNeverKeyword, + ) +} + +fn expr_as_keyword_expr(expr: Expr, keyword: TsKeywordTypeKind) -> Box { + Box::new(Expr::TsAs(TsAsExpr { + span: DUMMY_SP, + expr: Box::new(expr), + type_ann: Box::new(TsType::TsKeywordType(TsKeywordType { + span: DUMMY_SP, + kind: keyword, + })), + })) +} + +fn paren_expr(expr: Box) -> Expr { + Expr::Paren(ParenExpr { + span: DUMMY_SP, + expr, + }) +} + +fn any_type_ann() -> Box { + Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: Box::new(ts_keyword_type(TsKeywordTypeKind::TsAnyKeyword)), + }) +} + +fn unknown_type_ann() -> Box { + Box::new(TsTypeAnn { + span: DUMMY_SP, + type_ann: Box::new(ts_keyword_type(TsKeywordTypeKind::TsUnknownKeyword)), + }) +} diff --git a/src/graph.rs b/src/graph.rs index 27fe797a6..c0fccecad 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -9,9 +9,11 @@ use crate::analyzer::ModuleInfo; use crate::analyzer::PositionRange; use crate::analyzer::SpecifierWithRange; use crate::analyzer::TypeScriptReference; -use crate::DefaultModuleAnalyzer; +use crate::CapturingModuleAnalyzer; +use crate::ModuleParser; use crate::ReferrerImports; +use crate::fast_check::FastCheckDiagnostic; use crate::module_specifier::is_fs_root_specifier; use crate::module_specifier::resolve_import; use crate::module_specifier::ModuleSpecifier; @@ -53,6 +55,7 @@ use std::borrow::Cow; use std::cell::RefCell; use std::cmp::Ordering; use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; @@ -797,6 +800,19 @@ impl JsonModule { } } +#[derive(Debug, Clone)] +pub enum FastCheckTypeModuleSlot { + Module(Box), + Error(Box), +} + +#[derive(Debug, Clone)] +pub struct FastCheckTypeModule { + pub dependencies: IndexMap, + pub source: Arc, + pub source_map: Arc<[u8]>, +} + #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct EsmModule { @@ -814,6 +830,8 @@ pub struct EsmModule { #[serde(skip_serializing_if = "is_media_type_unknown")] pub media_type: MediaType, pub specifier: ModuleSpecifier, + #[serde(skip_serializing)] + pub fast_check: Option, } impl EsmModule { @@ -825,6 +843,7 @@ impl EsmModule { maybe_types_dependency: None, media_type: MediaType::Unknown, specifier, + fast_check: None, } } @@ -832,6 +851,31 @@ impl EsmModule { pub fn size(&self) -> usize { self.source.as_bytes().len() } + + pub fn fast_check_diagnostic(&self) -> Option<&FastCheckDiagnostic> { + let module_slot = self.fast_check.as_ref()?; + match module_slot { + FastCheckTypeModuleSlot::Module(_) => None, + FastCheckTypeModuleSlot::Error(d) => Some(d), + } + } + + pub fn fast_check_module(&self) -> Option<&FastCheckTypeModule> { + let module_slot = self.fast_check.as_ref()?; + match module_slot { + FastCheckTypeModuleSlot::Module(m) => Some(m), + FastCheckTypeModuleSlot::Error(_) => None, + } + } + + pub fn dependencies_prefer_fast_check( + &self, + ) -> &IndexMap { + match self.fast_check_module() { + Some(fast_check) => &fast_check.dependencies, + None => &self.dependencies, + } + } } #[allow(clippy::large_enum_variant)] @@ -933,7 +977,10 @@ pub struct BuildOptions<'a> { pub resolver: Option<&'a dyn Resolver>, pub npm_resolver: Option<&'a dyn NpmResolver>, pub module_analyzer: Option<&'a dyn ModuleAnalyzer>, + pub module_parser: Option<&'a dyn ModuleParser>, pub reporter: Option<&'a dyn Reporter>, + /// Whether to fill workspace members with fast check TypeScript data. + pub workspace_fast_check: bool, pub workspace_members: Vec, } @@ -1068,7 +1115,12 @@ impl<'a> Iterator for ModuleEntryIterator<'a> { } } } - for dep in module.dependencies.values().rev() { + let module_deps = if check_types { + module.dependencies_prefer_fast_check() + } else { + &module.dependencies + }; + for dep in module_deps.values().rev() { if !dep.is_dynamic || self.follow_dynamic { let mut resolutions = Vec::with_capacity(2); resolutions.push(&dep.maybe_code); @@ -1247,7 +1299,12 @@ impl<'a> Iterator for ModuleGraphErrorIterator<'a> { } } } - for (specifier_text, dep) in &module.dependencies { + let module_deps = if follow_type_only { + module.dependencies_prefer_fast_check() + } else { + &module.dependencies + }; + for (specifier_text, dep) in module_deps { if follow_dynamic || !dep.is_dynamic { if let Some(err) = self.check_resolution( module, @@ -1344,7 +1401,7 @@ impl ModuleGraph { loader: &mut dyn Loader, options: BuildOptions<'a>, ) -> Vec { - let default_analyzer = DefaultModuleAnalyzer::default(); + let default_module_parser = CapturingModuleAnalyzer::default(); #[cfg(not(target_arch = "wasm32"))] let file_system = RealFileSystem; #[cfg(target_arch = "wasm32")] @@ -1358,8 +1415,10 @@ impl ModuleGraph { options.resolver, options.npm_resolver, loader, - options.module_analyzer.unwrap_or(&default_analyzer), + options.module_analyzer.unwrap_or(&default_module_parser), + options.module_parser.unwrap_or(&default_module_parser), options.reporter, + options.workspace_fast_check, options.workspace_members, ) .await @@ -2101,7 +2160,30 @@ pub(crate) fn parse_esm_module_from_module_info( } // Analyze ES dependencies - for desc in module_info.dependencies { + fill_module_dependencies( + graph_kind, + module_info.dependencies, + &module.specifier, + &mut module.dependencies, + file_system, + maybe_resolver, + maybe_npm_resolver, + ); + + // Return the module as a valid module + module +} + +fn fill_module_dependencies( + graph_kind: GraphKind, + dependencies: Vec, + module_specifier: &ModuleSpecifier, + module_dependencies: &mut IndexMap, + file_system: &dyn FileSystem, + maybe_resolver: Option<&dyn Resolver>, + maybe_npm_resolver: Option<&dyn NpmResolver>, +) { + for desc in dependencies { let (imports, leading_comments) = match desc { DependencyDescriptor::Static(desc) => { let is_import_or_export_type = matches!( @@ -2112,7 +2194,7 @@ pub(crate) fn parse_esm_module_from_module_info( continue; // skip } let range = Range::from_position_range( - module.specifier.clone(), + module_specifier.clone(), desc.specifier_range.clone(), ); @@ -2136,10 +2218,12 @@ pub(crate) fn parse_esm_module_from_module_info( DynamicArgument::String(text) => { vec![text] } - DynamicArgument::Template(parts) if specifier.scheme() == "file" => { + DynamicArgument::Template(parts) + if module_specifier.scheme() == "file" => + { let mut parts = analyze_dynamic_arg_template_parts( &parts, - specifier, + module_specifier, &desc.argument_range, &import_attributes, file_system, @@ -2152,7 +2236,7 @@ pub(crate) fn parse_esm_module_from_module_info( _ => continue, }; let range = Range::from_position_range( - module.specifier.clone(), + module_specifier.clone(), desc.argument_range.clone(), ); ( @@ -2172,8 +2256,7 @@ pub(crate) fn parse_esm_module_from_module_info( }; for import in imports { - let dep = module - .dependencies + let dep = module_dependencies .entry(import.specifier.clone()) .or_default(); // TODO(nayeemrmn): Import attributes should be visited and checked for @@ -2211,7 +2294,7 @@ pub(crate) fn parse_esm_module_from_module_info( dep.is_dynamic = dep.is_dynamic && import.is_dynamic; } if graph_kind.include_types() && dep.maybe_type.is_none() { - let specifier = module.specifier.clone(); + let specifier = module_specifier.clone(); let maybe_type = if let Some(pragma) = analyze_deno_types(&leading_comments) { resolve( @@ -2250,9 +2333,6 @@ pub(crate) fn parse_esm_module_from_module_info( dep.imports.push(import); } } - - // Return the module as a valid module - module } fn analyze_dynamic_arg_template_parts( @@ -2501,7 +2581,7 @@ impl From for PendingInfoResponse { } #[derive(Clone)] -struct DenoPackageVersionInfoExt { +struct JsrPackageVersionInfoExt { base_url: Url, inner: Arc, } @@ -2510,7 +2590,7 @@ struct PendingInfo { specifier: ModuleSpecifier, maybe_range: Option, result: Result, anyhow::Error>, - maybe_version_info: Option, + maybe_version_info: Option, } type PendingInfoFuture = LocalBoxFuture<'static, PendingInfo>; @@ -2562,6 +2642,7 @@ struct PendingJsrState { pending_resolutions: Vec, pending_content_loads: FuturesUnordered>, + top_level_nvs: BTreeSet, } #[derive(Default)] @@ -2647,10 +2728,14 @@ struct Builder<'a, 'graph> { resolver: Option<&'a dyn Resolver>, npm_resolver: Option<&'a dyn NpmResolver>, module_analyzer: &'a dyn ModuleAnalyzer, + #[cfg_attr(not(feature = "fast_check"), allow(dead_code))] + module_parser: &'a dyn ModuleParser, reporter: Option<&'a dyn Reporter>, graph: &'graph mut ModuleGraph, state: PendingState, fill_pass_mode: FillPassMode, + #[cfg_attr(not(feature = "fast_check"), allow(dead_code))] + workspace_fast_check: bool, workspace_members: Vec, diagnostics: Vec, } @@ -2667,7 +2752,10 @@ impl<'a, 'graph> Builder<'a, 'graph> { npm_resolver: Option<&'a dyn NpmResolver>, loader: &'a mut dyn Loader, module_analyzer: &'a dyn ModuleAnalyzer, + #[cfg_attr(not(feature = "fast_check"), allow(dead_code))] + module_parser: &'a dyn ModuleParser, reporter: Option<&'a dyn Reporter>, + workspace_fast_check: bool, workspace_members: Vec, ) -> Vec { let fill_pass_mode = match graph.roots.is_empty() { @@ -2681,10 +2769,12 @@ impl<'a, 'graph> Builder<'a, 'graph> { resolver, npm_resolver, module_analyzer, + module_parser, reporter, graph, state: PendingState::default(), fill_pass_mode, + workspace_fast_check, workspace_members, diagnostics: Vec::new(), }; @@ -2797,6 +2887,10 @@ impl<'a, 'graph> Builder<'a, 'graph> { // resolve any npm package requirements NpmSpecifierResolver::fill_builder(self).await; + + // create the fast check type graph + #[cfg(feature = "fast_check")] + self.create_fast_check_type_graph(); } fn handle_provided_imports(&mut self, imports: Vec) { @@ -2826,6 +2920,8 @@ impl<'a, 'graph> Builder<'a, 'graph> { std::mem::take(&mut self.state.jsr.pending_resolutions); let mut pending_version_resolutions = Vec::with_capacity(pending_resolutions.len()); + let should_collect_top_level_nvs = self.state.jsr.top_level_nvs.is_empty() + && self.graph.graph_kind.include_types(); for pending_resolution in pending_resolutions { let package_name = &pending_resolution.package_ref.req().name; let fut = self @@ -2849,7 +2945,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { self .graph .packages - .add(package_req.clone(), package_nv.clone()); + .add_nv(package_req.clone(), package_nv.clone()); self.queue_load_package_version_info(&package_nv); pending_version_resolutions @@ -2904,25 +3000,31 @@ impl<'a, 'graph> Builder<'a, 'graph> { .unwrap() .clone() .await - .map(|info| { - (info, resolution_item.package_ref.sub_path().unwrap_or(".")) - }); + .map(|info| (info, resolution_item.package_ref.sub_path())); match version_info_result { Ok((version_info, sub_path)) => { - let base_url = self - .loader - .registry_url() - .join(&format!("{}/{}/", nv.name, nv.version)) - .unwrap(); + let base_url = self.loader.registry_package_url(&nv); let export_name = normalize_export_name(sub_path); match version_info.export(&export_name) { Some(export_value) => { + self.graph.packages.add_export( + nv.clone(), + ( + normalize_export_name(resolution_item.package_ref.sub_path()) + .to_string(), + export_value.to_string(), + ), + ); + if should_collect_top_level_nvs { + self.state.jsr.top_level_nvs.insert(nv.clone()); + } + let specifier = base_url.join(export_value).unwrap(); self .graph .redirects .insert(resolution_item.specifier, specifier.clone()); - let version_info = DenoPackageVersionInfoExt { + let version_info = JsrPackageVersionInfoExt { base_url, inner: version_info, }; @@ -3163,7 +3265,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { maybe_range: Option<&Range>, is_dynamic: bool, maybe_assert_type: Option, - maybe_version_info: Option<&DenoPackageVersionInfoExt>, + maybe_version_info: Option<&JsrPackageVersionInfoExt>, ) { let specifier = self.graph.redirects.get(specifier).unwrap_or(specifier); self @@ -3362,8 +3464,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { })); } - let export_name = - normalize_export_name(package_ref.sub_path().unwrap_or(".")); + let export_name = normalize_export_name(package_ref.sub_path()); if let Some(sub_path) = workspace_member.exports.get(export_name.as_ref()) { match workspace_member.base.join(sub_path) { Ok(load_specifier) => Ok(load_specifier), @@ -3561,7 +3662,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { response: &PendingInfoResponse, maybe_assert_type: Option, maybe_referrer: Option, - maybe_version_info: Option<&DenoPackageVersionInfoExt>, + maybe_version_info: Option<&JsrPackageVersionInfoExt>, ) { let (specifier, module_slot) = match response { PendingInfoResponse::External { specifier } => { @@ -3605,7 +3706,7 @@ impl<'a, 'graph> Builder<'a, 'graph> { content_or_module_info: ContentOrModuleInfo, maybe_assert_type: Option, maybe_referrer: Option, - maybe_version_info: Option<&DenoPackageVersionInfoExt>, + maybe_version_info: Option<&JsrPackageVersionInfoExt>, ) -> ModuleSlot { struct ProvidedModuleAnalyzer(RefCell>); @@ -3767,9 +3868,79 @@ impl<'a, 'graph> Builder<'a, 'graph> { } module_slot } + + #[cfg(feature = "fast_check")] + fn create_fast_check_type_graph(&mut self) { + if !self.graph.graph_kind().include_types() { + return; + } + + let mut pending_nvs = std::mem::take(&mut self.state.jsr.top_level_nvs) + .into_iter() + .collect::>(); + if self.workspace_fast_check { + pending_nvs.extend(self.workspace_members.iter().map(|n| n.nv.clone())); + } + if pending_nvs.is_empty() { + return; + } + + let root_symbol = + crate::symbols::RootSymbol::new(self.graph, self.module_parser); + + let modules = crate::fast_check::build_fast_check_type_graph( + self.loader, + self.graph, + &root_symbol, + pending_nvs, + &crate::fast_check::TransformOptions { + workspace_members: if self.workspace_fast_check { + &self.workspace_members + } else { + &[] + }, + should_error_on_first_diagnostic: !self.workspace_fast_check, + }, + ); + for (specifier, fast_check_module_result) in modules { + let module_slot = self.graph.module_slots.get_mut(&specifier).unwrap(); + let module = match module_slot { + ModuleSlot::Module(m) => match m { + Module::Esm(m) => m, + _ => unreachable!(), + }, + ModuleSlot::Err(_) | ModuleSlot::Pending => unreachable!(), + }; + module.fast_check = Some(match fast_check_module_result { + Ok(fast_check_module) => { + let mut dependencies: IndexMap = + Default::default(); + fill_module_dependencies( + GraphKind::TypesOnly, + fast_check_module.module_info.dependencies, + &module.specifier, + &mut dependencies, + // no need to resolve dynamic imports + &NullFileSystem, + self.resolver, + self.npm_resolver, + ); + FastCheckTypeModuleSlot::Module(Box::new(FastCheckTypeModule { + dependencies, + source: fast_check_module.text.into(), + source_map: fast_check_module.source_map.into(), + })) + } + Err(diagnostic) => FastCheckTypeModuleSlot::Error(diagnostic), + }); + } + } } -fn normalize_export_name(sub_path: &str) -> Cow { +fn normalize_export_name(sub_path: Option<&str>) -> Cow { + let Some(sub_path) = sub_path else { + return Cow::Borrowed("."); + }; if sub_path.is_empty() || matches!(sub_path, "/" | ".") { Cow::Borrowed(".") } else { diff --git a/src/lib.rs b/src/lib.rs index f5be8fa05..f2c321e72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ mod module_specifier; #[cfg(feature = "symbols")] pub mod symbols; +mod fast_check; pub mod packages; pub mod source; mod text_encoding; @@ -38,13 +39,20 @@ pub use ast::DefaultModuleAnalyzer; pub use ast::DefaultModuleParser; pub use ast::DefaultParsedSourceStore; pub use ast::ModuleParser; +pub use ast::ParseOptions; pub use ast::ParsedSourceStore; pub use deno_ast::MediaType; +#[cfg(feature = "fast_check")] +pub use fast_check::FastCheckDiagnostic; +#[cfg(feature = "fast_check")] +pub use fast_check::FastCheckModule; pub use graph::BuildDiagnostic; pub use graph::BuildOptions; pub use graph::Dependency; pub use graph::EsmModule; pub use graph::ExternalModule; +pub use graph::FastCheckTypeModule; +pub use graph::FastCheckTypeModuleSlot; pub use graph::GraphImport; pub use graph::GraphKind; pub use graph::JsonModule; diff --git a/src/packages.rs b/src/packages.rs index 52d96bd16..d3d80ad23 100644 --- a/src/packages.rs +++ b/src/packages.rs @@ -7,6 +7,7 @@ use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use deno_semver::Version; use deno_semver::VersionReq; +use indexmap::IndexMap; use serde::Deserialize; use serde::Serialize; @@ -77,12 +78,20 @@ impl JsrPackageVersionInfo { } } +#[derive(Default, Debug, Clone)] +struct PackageNvInfo { + /// Collection of exports used. + exports: IndexMap, +} + #[derive(Debug, Clone, Default, Serialize)] pub struct PackageSpecifiers { #[serde(flatten)] package_reqs: BTreeMap, #[serde(skip_serializing)] packages_by_name: HashMap>, + #[serde(skip_serializing)] + packages: BTreeMap, } impl PackageSpecifiers { @@ -90,7 +99,7 @@ impl PackageSpecifiers { self.package_reqs.is_empty() } - pub fn add(&mut self, package_req: PackageReq, nv: PackageNv) { + pub fn add_nv(&mut self, package_req: PackageReq, nv: PackageNv) { let nvs = self .packages_by_name .entry(package_req.name.clone()) @@ -101,6 +110,22 @@ impl PackageSpecifiers { self.package_reqs.insert(package_req, nv); } + pub(crate) fn add_export(&mut self, nv: PackageNv, export: (String, String)) { + self + .packages + .entry(nv.clone()) + .or_default() + .exports + .insert(export.0, export.1); + } + + pub fn package_exports( + &self, + nv: &PackageNv, + ) -> Option<&IndexMap> { + self.packages.get(nv).map(|p| &p.exports) + } + pub fn versions_by_name(&self, name: &str) -> Option<&Vec> { self.packages_by_name.get(name) } diff --git a/src/source/mod.rs b/src/source/mod.rs index 7abafe195..a1723c12a 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -115,6 +115,26 @@ pub trait Loader { &DEFAULT_DENO_REGISTRY_URL } + fn registry_package_url(&self, nv: &PackageNv) -> Url { + self + .registry_url() + .join(&format!("{}/{}/", nv.name, nv.version)) + .unwrap() + } + + fn registry_package_url_to_nv(&self, url: &Url) -> Option { + let path = url.as_str().strip_prefix(self.registry_url().as_str())?; + let path = path.strip_prefix('/').unwrap_or(path); + let parts = path.split('/').take(3).collect::>(); + if parts.len() != 3 { + return None; + } + Some(PackageNv { + name: format!("{}/{}", parts[0], parts[1]), + version: deno_semver::Version::parse_standard(parts[2]).ok()?, + }) + } + /// An optional method which returns cache info for a module specifier. fn get_cache_info(&self, _specifier: &ModuleSpecifier) -> Option { None diff --git a/src/symbols/analyzer.rs b/src/symbols/analyzer.rs index 632f795d8..329d1778e 100644 --- a/src/symbols/analyzer.rs +++ b/src/symbols/analyzer.rs @@ -9,7 +9,6 @@ use std::hash::Hash; use anyhow::Result; use deno_ast::swc::ast::*; use deno_ast::swc::utils::find_pat_ids; -use deno_ast::LineAndColumnDisplay; use deno_ast::ModuleSpecifier; use deno_ast::ParsedSource; use deno_ast::SourceRange; @@ -17,14 +16,12 @@ use deno_ast::SourceRangedForSpanned; use deno_ast::SourceTextInfo; use indexmap::IndexMap; use indexmap::IndexSet; -use serde::Deserialize; -use serde::Serialize; -use crate::CapturingModuleParser; use crate::EsmModule; use crate::JsonModule; use crate::ModuleGraph; use crate::ModuleParser; +use crate::ParseOptions; use super::collections::AdditiveOnlyIndexMap; use super::collections::AdditiveOnlyIndexMapForCopyValues; @@ -34,7 +31,8 @@ use super::cross_module; use super::cross_module::Definition; use super::cross_module::DefinitionOrUnresolved; use super::cross_module::DefinitionPath; -use super::cross_module::ResolvedExports; +use super::cross_module::ModuleExports; +use super::dep_analyzer::ResolveDepsMode; use super::swc_helpers::ts_entity_name_to_parts; use super::ResolvedSymbolDepEntry; use super::SymbolNodeDep; @@ -44,23 +42,21 @@ use super::SymbolNodeDep; /// Building the symbols for modules is lazy. pub struct RootSymbol<'a> { module_graph: &'a ModuleGraph, - parser: CapturingModuleParser<'a>, + parser: &'a dyn ModuleParser, specifiers_to_ids: AdditiveOnlyMapForCopyValues, ids_to_modules: AdditiveOnlyMap, - diagnostics: RefCell>, } impl<'a> RootSymbol<'a> { pub fn new( module_graph: &'a ModuleGraph, - parser: CapturingModuleParser<'a>, + parser: &'a dyn ModuleParser, ) -> Self { Self { module_graph, parser, specifiers_to_ids: Default::default(), ids_to_modules: Default::default(), - diagnostics: Default::default(), } } @@ -97,16 +93,6 @@ impl<'a> RootSymbol<'a> { } } - pub fn resolve_types_dependency( - &self, - specifier: &str, - referrer: &ModuleSpecifier, - ) -> Option { - self - .module_graph - .resolve_dependency(specifier, referrer, /* prefer_types */ true) - } - pub fn module_from_id(&self, module_id: ModuleId) -> Option { self.ids_to_modules.get(&module_id).map(|s| s.as_ref()) } @@ -179,15 +165,9 @@ impl<'a> RootSymbol<'a> { let builder = ModuleBuilder::new(module_id); let filler = SymbolFiller { source: &source, - specifier, - diagnostics: RefCell::new(Vec::new()), builder: &builder, }; filler.fill(module); - let diagnostics = filler.diagnostics.take(); - if !diagnostics.is_empty() { - self.diagnostics.borrow_mut().extend(diagnostics); - } let module_symbol = EsmModuleInfo { specifier: specifier.clone(), module_id, @@ -265,15 +245,12 @@ impl<'a> RootSymbol<'a> { &self, graph_module: &EsmModule, ) -> Result { - self.parser.parse_module( - &graph_module.specifier, - graph_module.source.clone(), - graph_module.media_type, - ) - } - - pub fn take_diagnostics(&self) -> Vec { - std::mem::take(&mut *self.diagnostics.borrow_mut()) + self.parser.parse_module(ParseOptions { + specifier: &graph_module.specifier, + source: graph_module.source.clone(), + media_type: graph_module.media_type, + scope_analysis: true, + }) } } @@ -373,7 +350,7 @@ impl std::fmt::Debug for SymbolNode { SymbolNodeInner::ExportDefaultDecl(d) => { d.value().text_fast(d.source.text_info()).to_string() } - SymbolNodeInner::ExportDefaultExprLit(d, _) => { + SymbolNodeInner::ExportDefaultExpr(d) => { d.value().text_fast(d.source.text_info()).to_string() } SymbolNodeInner::FnDecl(d) => { @@ -407,6 +384,9 @@ impl std::fmt::Debug for SymbolNode { SymbolNodeInner::ClassProp(d) => { d.value().text_fast(d.source.text_info()).to_string() } + SymbolNodeInner::ClassParamProp(d) => { + d.value().text_fast(d.source.text_info()).to_string() + } SymbolNodeInner::Constructor(d) => { d.value().text_fast(d.source.text_info()).to_string() } @@ -484,10 +464,9 @@ impl SymbolNode { SymbolNodeInner::ExportDefaultDecl(n) => { Some((SymbolNodeRef::ExportDefaultDecl(n.value()), n.source())) } - SymbolNodeInner::ExportDefaultExprLit(n, lit) => Some(( - SymbolNodeRef::ExportDefaultExprLit(n.value(), lit.value()), - n.source(), - )), + SymbolNodeInner::ExportDefaultExpr(n) => { + Some((SymbolNodeRef::ExportDefaultExpr(n.value()), n.source())) + } SymbolNodeInner::FnDecl(n) => { Some((SymbolNodeRef::FnDecl(n.value()), n.source())) } @@ -516,6 +495,9 @@ impl SymbolNode { SymbolNodeInner::ClassProp(n) => { Some((SymbolNodeRef::ClassProp(n.value()), n.source())) } + SymbolNodeInner::ClassParamProp(n) => { + Some((SymbolNodeRef::ClassParamProp(n.value()), n.source())) + } SymbolNodeInner::Constructor(n) => { Some((SymbolNodeRef::Constructor(n.value()), n.source())) } @@ -563,7 +545,7 @@ enum SymbolNodeInner { ClassDecl(NodeRefBox), ExportDecl(NodeRefBox, SymbolNodeInnerExportDecl), ExportDefaultDecl(NodeRefBox), - ExportDefaultExprLit(NodeRefBox, NodeRefBox), + ExportDefaultExpr(NodeRefBox), FnDecl(NodeRefBox), TsEnum(NodeRefBox), TsNamespace(NodeRefBox), @@ -573,6 +555,7 @@ enum SymbolNodeInner { AutoAccessor(NodeRefBox), ClassMethod(NodeRefBox), ClassProp(NodeRefBox), + ClassParamProp(NodeRefBox), Constructor(NodeRefBox), TsIndexSignature(NodeRefBox), TsCallSignatureDecl(NodeRefBox), @@ -599,7 +582,7 @@ pub enum SymbolNodeRef<'a> { Module(&'a Module), ExportDecl(&'a ExportDecl, ExportDeclRef<'a>), ExportDefaultDecl(&'a ExportDefaultDecl), - ExportDefaultExprLit(&'a ExportDefaultExpr, &'a Lit), + ExportDefaultExpr(&'a ExportDefaultExpr), ClassDecl(&'a ClassDecl), FnDecl(&'a FnDecl), TsEnum(&'a TsEnumDecl), @@ -611,6 +594,7 @@ pub enum SymbolNodeRef<'a> { AutoAccessor(&'a AutoAccessor), ClassMethod(&'a ClassMethod), ClassProp(&'a ClassProp), + ClassParamProp(&'a TsParamProp), Constructor(&'a Constructor), TsIndexSignature(&'a TsIndexSignature), TsCallSignatureDecl(&'a TsCallSignatureDecl), @@ -648,6 +632,21 @@ impl<'a> SymbolNodeRef<'a> { } } + fn maybe_param_prop_name(param: &TsParamPropParam) -> Option> { + match param { + TsParamPropParam::Ident(ident) => Some(Cow::Borrowed(&ident.sym)), + TsParamPropParam::Assign(assign_pat) => match &*assign_pat.left { + Pat::Ident(ident) => Some(Cow::Borrowed(&ident.sym)), + Pat::Array(_) + | Pat::Rest(_) + | Pat::Object(_) + | Pat::Assign(_) + | Pat::Invalid(_) + | Pat::Expr(_) => unreachable!(), + }, + } + } + fn maybe_expr(expr: &Expr) -> Option> { match expr { Expr::Ident(n) => Some(Cow::Borrowed(&n.sym)), @@ -680,7 +679,7 @@ impl<'a> SymbolNodeRef<'a> { DefaultDecl::Fn(n) => Some(Cow::Borrowed(&n.ident.as_ref()?.sym)), DefaultDecl::TsInterfaceDecl(n) => Some(Cow::Borrowed(&n.id.sym)), }, - Self::ExportDefaultExprLit(_, _) => None, + Self::ExportDefaultExpr(_) => None, Self::FnDecl(n) => Some(Cow::Borrowed(&n.ident.sym)), Self::TsEnum(n) => Some(Cow::Borrowed(&n.id.sym)), Self::TsInterface(n) => Some(Cow::Borrowed(&n.id.sym)), @@ -692,6 +691,7 @@ impl<'a> SymbolNodeRef<'a> { Self::AutoAccessor(n) => maybe_key_name(&n.key), Self::ClassMethod(n) => maybe_prop_name(&n.key), Self::ClassProp(n) => maybe_prop_name(&n.key), + Self::ClassParamProp(n) => maybe_param_prop_name(&n.param), Self::TsPropertySignature(n) => maybe_expr(&n.key), Self::TsGetterSignature(n) => maybe_expr(&n.key), Self::TsSetterSignature(n) => maybe_expr(&n.key), @@ -754,9 +754,9 @@ impl<'a> SymbolNodeRef<'a> { ) } - /// If the node is a method. - pub fn is_class_method(&self) -> bool { - matches!(self, Self::ClassMethod(_)) + /// If the node is a contructor. + pub fn is_ctor(&self) -> bool { + matches!(self, Self::Constructor(_)) } /// If the node has a body. @@ -784,9 +784,10 @@ impl<'a> SymbolNodeRef<'a> { | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) => true, SymbolNodeRef::TsTypeAlias(_) - | SymbolNodeRef::ExportDefaultExprLit(_, _) + | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::Var(_, _, _) | SymbolNodeRef::ClassProp(_) + | SymbolNodeRef::ClassParamProp(_) | SymbolNodeRef::TsIndexSignature(_) | SymbolNodeRef::TsCallSignatureDecl(_) | SymbolNodeRef::TsConstructSignatureDecl(_) @@ -809,7 +810,7 @@ impl<'a> SymbolNodeRef<'a> { SymbolNodeRef::ClassDecl(_) | SymbolNodeRef::ExportDecl(_, _) | SymbolNodeRef::ExportDefaultDecl(_) - | SymbolNodeRef::ExportDefaultExprLit(_, _) + | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::FnDecl(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) @@ -819,6 +820,7 @@ impl<'a> SymbolNodeRef<'a> { SymbolNodeRef::AutoAccessor(_) | SymbolNodeRef::ClassMethod(_) | SymbolNodeRef::ClassProp(_) + | SymbolNodeRef::ClassParamProp(_) | SymbolNodeRef::Constructor(_) | SymbolNodeRef::TsIndexSignature(_) | SymbolNodeRef::TsCallSignatureDecl(_) @@ -837,7 +839,7 @@ impl<'a> SymbolNodeRef<'a> { | SymbolNodeRef::ClassDecl(_) | SymbolNodeRef::ExportDecl(_, _) | SymbolNodeRef::ExportDefaultDecl(_) - | SymbolNodeRef::ExportDefaultExprLit(_, _) + | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::FnDecl(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) @@ -847,6 +849,7 @@ impl<'a> SymbolNodeRef<'a> { SymbolNodeRef::AutoAccessor(_) | SymbolNodeRef::ClassMethod(_) | SymbolNodeRef::ClassProp(_) + | SymbolNodeRef::ClassParamProp(_) | SymbolNodeRef::Constructor(_) | SymbolNodeRef::TsIndexSignature(_) | SymbolNodeRef::TsCallSignatureDecl(_) @@ -865,23 +868,29 @@ impl<'a> SymbolNodeRef<'a> { | SymbolNodeRef::ClassDecl(_) | SymbolNodeRef::ExportDecl(_, _) | SymbolNodeRef::ExportDefaultDecl(_) - | SymbolNodeRef::ExportDefaultExprLit(_, _) + | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::FnDecl(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) | SymbolNodeRef::TsNamespace(_) | SymbolNodeRef::TsTypeAlias(_) | SymbolNodeRef::Var(_, _, _) => false, - SymbolNodeRef::AutoAccessor(n) => match &n.key { - Key::Private(_) => true, - Key::Public(_) => false, - }, + SymbolNodeRef::AutoAccessor(n) => { + n.accessibility == Some(Accessibility::Private) + || match &n.key { + Key::Private(_) => true, + Key::Public(_) => false, + } + } SymbolNodeRef::ClassMethod(n) => { n.accessibility == Some(Accessibility::Private) } SymbolNodeRef::ClassProp(n) => { n.accessibility == Some(Accessibility::Private) } + SymbolNodeRef::ClassParamProp(n) => { + n.accessibility == Some(Accessibility::Private) + } SymbolNodeRef::Constructor(n) => { n.accessibility == Some(Accessibility::Private) } @@ -895,8 +904,8 @@ impl<'a> SymbolNodeRef<'a> { } } - pub fn deps(&self) -> Vec { - super::dep_analyzer::resolve_deps(*self) + pub fn deps(&self, mode: ResolveDepsMode) -> Vec { + super::dep_analyzer::resolve_deps(*self, mode) } } @@ -980,9 +989,9 @@ impl SymbolDecl { self.maybe_node().and_then(|n| n.maybe_name()) } - pub fn deps(&self) -> Vec { + pub fn deps(&self, mode: ResolveDepsMode) -> Vec { match self.maybe_node() { - Some(node) => node.deps(), + Some(node) => node.deps(mode), None => Vec::new(), } } @@ -999,11 +1008,8 @@ impl SymbolDecl { self.maybe_node().map(|n| n.is_var()).unwrap_or(false) } - pub fn is_class_method(&self) -> bool { - self - .maybe_node() - .map(|n| n.is_class_method()) - .unwrap_or(false) + pub fn is_ctor(&self) -> bool { + self.maybe_node().map(|n| n.is_ctor()).unwrap_or(false) } pub fn is_function(&self) -> bool { @@ -1071,6 +1077,13 @@ impl Symbol { self.parent_id } + /// Gets the symbol's parent's unique symbol id. + pub fn parent_unique_id(&self) -> Option { + self + .parent_id + .map(|id| UniqueSymbolId::new(self.module_id, id)) + } + /// The local name of the symbol if it has one. pub fn maybe_name(&self) -> Option> { self.decls.first().and_then(|d| d.maybe_name()) @@ -1240,7 +1253,7 @@ impl<'a> ModuleInfoRef<'a> { } } - pub fn exports(&self, root_symbol: &'a RootSymbol) -> ResolvedExports<'a> { + pub fn exports(&self, root_symbol: &'a RootSymbol) -> ModuleExports<'a> { cross_module::exports_and_re_exports( root_symbol.module_graph, *self, @@ -1248,10 +1261,64 @@ impl<'a> ModuleInfoRef<'a> { ) } - pub(crate) fn re_export_all_specifiers(&self) -> Option<&'a Vec> { + pub fn re_export_all_nodes( + &self, + ) -> Option> { match self { Self::Json(_) => None, - Self::Esm(m) => Some(&m.re_exports), + Self::Esm(m) => Some(m.re_exports.iter().map(|n| n.value())), + } + } + + pub(crate) fn re_export_all_specifiers( + &self, + ) -> Option> { + match self { + Self::Json(_) => None, + Self::Esm(m) => { + Some(m.re_exports.iter().map(|e| e.value().src.value.as_str())) + } + } + } + + pub fn fully_qualified_symbol_name(&self, symbol: &Symbol) -> Option { + debug_assert_eq!(symbol.module_id(), self.module_id()); + let mut text = String::new(); + let mut last: Option<&Symbol> = None; + let mut next = Some(symbol); + while let Some(symbol) = next { + if symbol.parent_id().is_none() { + break; // ignore the source file + } + if !text.is_empty() { + let prop_was_member = last + .map(|l| symbol.members().contains(&l.symbol_id())) + .unwrap_or(false); + let part_name = symbol.maybe_name()?; + let prop_was_class_member = prop_was_member + && symbol + .decls() + .first() + .map(|d| d.is_class()) + .unwrap_or(false); + text = if prop_was_class_member { + format!("{}.prototype.{}", part_name, text) + } else if prop_was_member { + // not the best, but good enough + format!("{}[\"{}\"]", part_name, text.replace('"', "\\\"")) + } else { + format!("{}.{}", part_name, text) + }; + } else { + text = symbol.maybe_name()?.to_string(); + } + last = next; + next = symbol.parent_id().and_then(|id| self.symbol(id)); + } + if text.is_empty() { + None + } else { + Some(text) } } } @@ -1360,7 +1427,7 @@ pub struct EsmModuleInfo { specifier: ModuleSpecifier, source: ParsedSource, /// The re-export specifiers. - re_exports: Vec, + re_exports: Vec>, // note: not all symbol ids have an swc id. For example, default exports swc_id_to_symbol_id: IndexMap, symbols: IndexMap, @@ -1371,7 +1438,14 @@ impl std::fmt::Debug for EsmModuleInfo { f.debug_struct("EsmModuleInfo") .field("module_id", &self.module_id) .field("specifier", &self.specifier.as_str()) - .field("re_exports", &self.re_exports) + .field( + "re_exports", + &self + .re_exports + .iter() + .map(|e| e.value().src.value.as_str()) + .collect::>(), + ) .field("swc_id_to_symbol_id", &self.swc_id_to_symbol_id) .field("symbols", &self.symbols) .finish() @@ -1398,7 +1472,7 @@ impl EsmModuleInfo { pub fn exports<'a>( &'a self, root_symbol: &'a RootSymbol, - ) -> ResolvedExports<'a> { + ) -> ModuleExports<'a> { self.as_ref().exports(root_symbol) } @@ -1424,18 +1498,6 @@ impl EsmModuleInfo { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum SymbolFillDiagnosticKind { - UnsupportedDefaultExpr, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct SymbolFillDiagnostic { - pub kind: SymbolFillDiagnosticKind, - pub specifier: ModuleSpecifier, - pub line_and_column: Option, -} - struct SymbolMut(RefCell); impl SymbolMut { @@ -1444,15 +1506,36 @@ impl SymbolMut { } pub fn add_decl(&self, mut symbol_decl: SymbolDecl) { + fn is_class_method(symbol_decl: &SymbolDecl) -> bool { + symbol_decl + .maybe_node() + .map(|node| { + matches!( + node, + SymbolNodeRef::ClassMethod(ClassMethod { + kind: MethodKind::Method, + .. + }) + ) + }) + .unwrap_or(false) + } + let mut inner = self.0.borrow_mut(); // check if this is an implementation with overloads if !inner.decls.is_empty() - && (symbol_decl.is_function() || symbol_decl.is_class_method()) + && (symbol_decl.is_function() + || is_class_method(&symbol_decl) + || symbol_decl.is_ctor()) && symbol_decl.has_body() && inner .decls .last() - .map(|d| d.is_function() || d.is_class_method()) + .map(|d| { + d.is_function() + || is_class_method(&symbol_decl) + || symbol_decl.is_ctor() + }) .unwrap_or(false) { symbol_decl.flags.set_has_overloads(); @@ -1493,7 +1576,7 @@ struct ModuleBuilder { // todo(dsherret): make this not an IndexMap symbols: AdditiveOnlyIndexMap, next_symbol_id: Cell, - re_exports: RefCell>, + re_exports: RefCell>>, } impl ModuleBuilder { @@ -1600,15 +1683,13 @@ impl ModuleBuilder { symbol_id } - fn add_re_export(&self, value: String) { - self.re_exports.borrow_mut().push(value) + fn add_re_export(&self, export_all: NodeRefBox) { + self.re_exports.borrow_mut().push(export_all) } } struct SymbolFiller<'a> { - specifier: &'a ModuleSpecifier, source: &'a ParsedSource, - diagnostics: RefCell>, builder: &'a ModuleBuilder, } @@ -1913,7 +1994,7 @@ impl<'a> SymbolFiller<'a> { .create_new_symbol(module_symbol.symbol_id()); orig_symbol.add_decl(SymbolDecl::new( SymbolDeclKind::Target(orig_ident.to_id()), - n.range(), + named.range(), )); module_symbol .add_export(exported_name, orig_symbol.symbol_id()); @@ -1929,7 +2010,7 @@ impl<'a> SymbolFiller<'a> { }), None => SymbolDeclKind::Target(orig_ident.to_id()), }, - n.range(), + named.range(), )); module_symbol.add_export( orig_ident.sym.to_string(), @@ -2030,7 +2111,9 @@ impl<'a> SymbolFiller<'a> { ); } ModuleDecl::ExportAll(n) => { - self.builder.add_re_export(n.src.value.to_string()); + self + .builder + .add_re_export(NodeRefBox::unsafe_new(self.source, n)); } ModuleDecl::TsImportEquals(import_equals) => { let symbol_id = @@ -2255,8 +2338,7 @@ impl<'a> SymbolFiller<'a> { SymbolDecl::new(SymbolDeclKind::Target(ident.to_id()), ident.range()), ); } - Expr::Lit(lit) => { - debug_assert!(maybe_parent.is_some()); + _ => { let Some(parent) = maybe_parent else { return; }; @@ -2264,26 +2346,15 @@ impl<'a> SymbolFiller<'a> { module_symbol, SymbolDecl::new( SymbolDeclKind::Definition(SymbolNode( - SymbolNodeInner::ExportDefaultExprLit( - NodeRefBox::unsafe_new(self.source, parent), - NodeRefBox::unsafe_new(self.source, lit), - ), + SymbolNodeInner::ExportDefaultExpr(NodeRefBox::unsafe_new( + self.source, + parent, + )), )), default_export_range, ), ); } - _ => { - let line_and_column = self - .source - .text_info() - .line_and_column_display(default_export_range.start); - self.add_diagnostic(SymbolFillDiagnostic { - kind: SymbolFillDiagnosticKind::UnsupportedDefaultExpr, - specifier: self.specifier.clone(), - line_and_column: Some(line_and_column), - }); - } } } @@ -2445,6 +2516,14 @@ impl<'a> SymbolFiller<'a> { symbol, SymbolNodeRef::Constructor(ctor), ); + for param in &ctor.params { + if let ParamOrTsParamProp::TsParamProp(prop) = param { + self.create_symbol_member_or_export( + symbol, + SymbolNodeRef::ClassParamProp(prop), + ); + } + } } ClassMember::Method(method) => { self.create_symbol_member_or_export( @@ -2507,6 +2586,12 @@ impl<'a> SymbolFiller<'a> { n.range(), ), + SymbolNodeRef::ClassParamProp(n) => ( + SymbolNodeInner::ClassParamProp(NodeRefBox::unsafe_new(self.source, n)), + /* is_static */ false, + n.range(), + ), + SymbolNodeRef::Constructor(n) => ( SymbolNodeInner::Constructor(NodeRefBox::unsafe_new(self.source, n)), true, @@ -2578,7 +2663,7 @@ impl<'a> SymbolFiller<'a> { | SymbolNodeRef::ClassDecl(_) | SymbolNodeRef::ExportDecl(_, _) | SymbolNodeRef::ExportDefaultDecl(_) - | SymbolNodeRef::ExportDefaultExprLit(_, _) + | SymbolNodeRef::ExportDefaultExpr(_) | SymbolNodeRef::FnDecl(_) | SymbolNodeRef::TsEnum(_) | SymbolNodeRef::TsInterface(_) @@ -2674,8 +2759,4 @@ impl<'a> SymbolFiller<'a> { } None } - - fn add_diagnostic(&self, diagnostic: SymbolFillDiagnostic) { - self.diagnostics.borrow_mut().push(diagnostic); - } } diff --git a/src/symbols/cross_module.rs b/src/symbols/cross_module.rs index 369e40392..07ce3bad8 100644 --- a/src/symbols/cross_module.rs +++ b/src/symbols/cross_module.rs @@ -321,10 +321,10 @@ fn go_to_file_export<'a>( ), None => { // maybe it's in a re-export - if let Some(re_export_all_specifier) = + if let Some(re_export_all_specifiers) = dep_module.re_export_all_specifiers() { - for re_export_specifier in re_export_all_specifier.iter() { + for re_export_specifier in re_export_all_specifiers { let maybe_specifier = module_graph.resolve_dependency( re_export_specifier, dep_module.specifier(), @@ -715,7 +715,7 @@ fn resolve_qualified_name_internal<'a>( } #[derive(Debug, Default, Clone)] -pub struct ResolvedExports<'a> { +pub struct ModuleExports<'a> { pub resolved: IndexMap>, pub unresolved_specifiers: Vec>, } @@ -740,7 +740,7 @@ pub struct ResolvedReExportAllPath<'a> { /// Module that contains this re-export. pub referrer_module: ModuleInfoRef<'a>, /// Specifier from the referrer that led to the resolved module. - pub specifier: &'a String, + pub specifier: &'a str, /// Holds the next resolved export or re-export. pub next: Box>, } @@ -785,14 +785,14 @@ impl<'a> ResolvedExportOrReExportAllPath<'a> { #[derive(Debug, Clone)] pub struct UnresolvedSpecifier<'a> { pub referrer: ModuleInfoRef<'a>, - pub specifier: &'a String, + pub specifier: &'a str, } pub fn exports_and_re_exports<'a>( module_graph: &'a ModuleGraph, module: ModuleInfoRef<'a>, specifier_to_module: &impl Fn(&ModuleSpecifier) -> Option>, -) -> ResolvedExports<'a> { +) -> ModuleExports<'a> { exports_and_re_exports_inner( module_graph, module, @@ -806,9 +806,9 @@ fn exports_and_re_exports_inner<'a>( module: ModuleInfoRef<'a>, specifier_to_module: &impl Fn(&ModuleSpecifier) -> Option>, visited: &mut HashSet<&'a ModuleSpecifier>, -) -> ResolvedExports<'a> { +) -> ModuleExports<'a> { if !visited.insert(module.specifier()) { - return ResolvedExports::default(); + return ModuleExports::default(); } let mut unresolved_specifiers = Vec::new(); @@ -824,7 +824,7 @@ fn exports_and_re_exports_inner<'a>( } if let Some(re_export_all_specifier) = module.re_export_all_specifiers() { let referrer_module = module; - for re_export_specifier in re_export_all_specifier.iter() { + for re_export_specifier in re_export_all_specifier { let maybe_specifier = module_graph.resolve_dependency( re_export_specifier, module.specifier(), @@ -861,7 +861,7 @@ fn exports_and_re_exports_inner<'a>( } } } - ResolvedExports { + ModuleExports { resolved, unresolved_specifiers, } diff --git a/src/symbols/dep_analyzer.rs b/src/symbols/dep_analyzer.rs index 4bbc2c9ee..87d25ad5f 100644 --- a/src/symbols/dep_analyzer.rs +++ b/src/symbols/dep_analyzer.rs @@ -1,5 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_ast::swc::ast::BindingIdent; use deno_ast::swc::ast::Class; use deno_ast::swc::ast::DefaultDecl; use deno_ast::swc::ast::Expr; @@ -7,20 +8,27 @@ use deno_ast::swc::ast::Function; use deno_ast::swc::ast::Id; use deno_ast::swc::ast::Ident; use deno_ast::swc::ast::Lit; +use deno_ast::swc::ast::MemberExpr; use deno_ast::swc::ast::MemberProp; use deno_ast::swc::ast::Param; use deno_ast::swc::ast::ParamOrTsParamProp; use deno_ast::swc::ast::Pat; use deno_ast::swc::ast::PropName; +use deno_ast::swc::ast::TsCallSignatureDecl; +use deno_ast::swc::ast::TsConstructSignatureDecl; use deno_ast::swc::ast::TsEnumDecl; use deno_ast::swc::ast::TsExprWithTypeArgs; -use deno_ast::swc::ast::TsFnParam; +use deno_ast::swc::ast::TsGetterSignature; use deno_ast::swc::ast::TsImportType; +use deno_ast::swc::ast::TsIndexSignature; use deno_ast::swc::ast::TsInterfaceDecl; +use deno_ast::swc::ast::TsMethodSignature; use deno_ast::swc::ast::TsParamProp; use deno_ast::swc::ast::TsParamPropParam; +use deno_ast::swc::ast::TsPropertySignature; use deno_ast::swc::ast::TsQualifiedName; -use deno_ast::swc::ast::TsType; +use deno_ast::swc::ast::TsSetterSignature; +use deno_ast::swc::ast::TsTupleElement; use deno_ast::swc::ast::TsTypeAliasDecl; use deno_ast::swc::ast::TsTypeAnn; use deno_ast::swc::ast::TsTypeParam; @@ -48,368 +56,429 @@ impl From for SymbolNodeDep { } } -pub fn resolve_deps(node_ref: SymbolNodeRef) -> Vec { - let mut result = Vec::new(); - let deps = &mut result; - match node_ref { - SymbolNodeRef::Module(_) | SymbolNodeRef::TsNamespace(_) => { - // no deps, as this has children - } - SymbolNodeRef::ClassDecl(n) => { - fill_class(deps, &n.class); - } - SymbolNodeRef::ExportDecl(_, n) => match n { - ExportDeclRef::Class(n) => fill_class(deps, &n.class), - ExportDeclRef::Fn(n) => fill_function_decl(deps, &n.function), - ExportDeclRef::Var(_, n, _) => { - fill_var_declarator(deps, n); - } - ExportDeclRef::TsEnum(n) => fill_enum(deps, n), - ExportDeclRef::TsInterface(n) => fill_interface(deps, n), - ExportDeclRef::TsModule(_) => { +#[derive(Debug, Copy, Clone)] +pub enum ResolveDepsMode { + /// Resolve dependencies of types only (used for deno doc). + TypesOnly, + /// Resolve dependencies of types and expressions (used for fast check). + TypesAndExpressions, +} + +impl ResolveDepsMode { + pub fn visit_exprs(&self) -> bool { + match self { + ResolveDepsMode::TypesOnly => false, + ResolveDepsMode::TypesAndExpressions => true, + } + } +} + +pub fn resolve_deps( + node_ref: SymbolNodeRef, + mode: ResolveDepsMode, +) -> Vec { + let mut filler = DepsFiller { + deps: Vec::new(), + mode, + }; + filler.fill(node_ref); + filler.deps +} + +struct DepsFiller { + deps: Vec, + mode: ResolveDepsMode, +} + +impl DepsFiller { + fn fill(&mut self, node_ref: SymbolNodeRef<'_>) { + match node_ref { + SymbolNodeRef::Module(_) | SymbolNodeRef::TsNamespace(_) => { // no deps, as this has children } - ExportDeclRef::TsTypeAlias(n) => fill_type_alias(deps, n), - }, - SymbolNodeRef::ExportDefaultDecl(n) => match &n.decl { - DefaultDecl::Class(n) => fill_class(deps, &n.class), - DefaultDecl::Fn(n) => { - fill_function_decl(deps, &n.function); + SymbolNodeRef::ClassDecl(n) => { + self.visit_class(&n.class); } - DefaultDecl::TsInterfaceDecl(n) => { - fill_interface(deps, n); + SymbolNodeRef::ExportDecl(_, n) => match n { + ExportDeclRef::Class(n) => self.visit_class(&n.class), + ExportDeclRef::Fn(n) => self.visit_function(&n.function), + ExportDeclRef::Var(_, n, _) => { + self.visit_var_declarator(n); + } + ExportDeclRef::TsEnum(n) => self.visit_ts_enum_decl(n), + ExportDeclRef::TsInterface(n) => self.visit_ts_interface_decl(n), + ExportDeclRef::TsModule(_) => { + // no deps, as this has children + } + ExportDeclRef::TsTypeAlias(n) => self.visit_ts_type_alias_decl(n), + }, + SymbolNodeRef::ExportDefaultDecl(n) => match &n.decl { + DefaultDecl::Class(n) => self.visit_class(&n.class), + DefaultDecl::Fn(n) => { + self.visit_function(&n.function); + } + DefaultDecl::TsInterfaceDecl(n) => { + self.visit_ts_interface_decl(n); + } + }, + SymbolNodeRef::ExportDefaultExpr(n) => { + self.visit_expr(&n.expr); } - }, - SymbolNodeRef::ExportDefaultExprLit(n, _) => { - fill_expr(deps, &n.expr); - } - SymbolNodeRef::FnDecl(n) => fill_function_decl(deps, &n.function), - SymbolNodeRef::TsEnum(n) => { - fill_enum(deps, n); - } - SymbolNodeRef::TsInterface(n) => fill_interface(deps, n), - SymbolNodeRef::TsTypeAlias(n) => { - fill_type_alias(deps, n); - } - SymbolNodeRef::Var(_, n, _) => { - fill_var_declarator(deps, n); - } - SymbolNodeRef::AutoAccessor(n) => { - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann) + SymbolNodeRef::FnDecl(n) => self.visit_function(&n.function), + SymbolNodeRef::TsEnum(n) => { + self.visit_ts_enum_decl(n); } - } - SymbolNodeRef::ClassMethod(n) => { - fill_prop_name(deps, &n.key); - if let Some(type_params) = &n.function.type_params { - fill_ts_type_param_decl(deps, type_params) + SymbolNodeRef::TsInterface(n) => self.visit_ts_interface_decl(n), + SymbolNodeRef::TsTypeAlias(n) => { + self.visit_ts_type_alias_decl(n); } - for param in &n.function.params { - fill_param(deps, param) + SymbolNodeRef::Var(_, n, _) => { + self.visit_var_declarator(n); } - if let Some(return_type) = &n.function.return_type { - fill_ts_type_ann(deps, return_type) + SymbolNodeRef::AutoAccessor(n) => { + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann) + } } - } - SymbolNodeRef::ClassProp(n) => { - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann) + SymbolNodeRef::ClassMethod(n) => { + if self.mode.visit_exprs() { + self.visit_prop_name(&n.key); + } + + if let Some(type_params) = &n.function.type_params { + self.visit_ts_type_param_decl(type_params) + } + for param in &n.function.params { + self.visit_param(param) + } + if let Some(return_type) = &n.function.return_type { + self.visit_ts_type_ann(return_type) + } } - } - SymbolNodeRef::Constructor(n) => { - for param in &n.params { - match param { - ParamOrTsParamProp::TsParamProp(param) => { - fill_ts_param_prop(deps, param) - } - ParamOrTsParamProp::Param(param) => fill_param(deps, param), + SymbolNodeRef::ClassProp(n) => { + if self.mode.visit_exprs() { + self.visit_prop_name(&n.key); + } + + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann) } } - } - SymbolNodeRef::TsIndexSignature(n) => { - for param in &n.params { - fill_ts_fn_param(deps, param) + SymbolNodeRef::ClassParamProp(n) => self.visit_ts_param_prop(n), + SymbolNodeRef::Constructor(n) => { + for param in &n.params { + match param { + ParamOrTsParamProp::TsParamProp(param) => { + self.visit_ts_param_prop(param) + } + ParamOrTsParamProp::Param(param) => self.visit_param(param), + } + } } - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann) + SymbolNodeRef::TsIndexSignature(n) => { + self.visit_ts_index_signature(n); } - } - SymbolNodeRef::TsCallSignatureDecl(n) => { - if let Some(type_params) = &n.type_params { - fill_ts_type_param_decl(deps, type_params); + SymbolNodeRef::TsCallSignatureDecl(n) => { + self.visit_ts_call_signature_decl(n); } - for param in &n.params { - fill_ts_fn_param(deps, param); + SymbolNodeRef::TsConstructSignatureDecl(n) => { + self.visit_ts_construct_signature_decl(n); } - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann) + SymbolNodeRef::TsPropertySignature(n) => { + self.visit_ts_property_signature(n); } - } - SymbolNodeRef::TsConstructSignatureDecl(n) => { - if let Some(type_params) = &n.type_params { - fill_ts_type_param_decl(deps, type_params); + SymbolNodeRef::TsGetterSignature(n) => { + self.visit_ts_getter_signature(n); } - for param in &n.params { - fill_ts_fn_param(deps, param); + SymbolNodeRef::TsSetterSignature(n) => { + self.visit_ts_setter_signature(n); } - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann) + SymbolNodeRef::TsMethodSignature(n) => { + self.visit_ts_method_signature(n); } } - SymbolNodeRef::TsPropertySignature(n) => { - if let Some(init) = &n.init { - fill_expr(deps, init); - } - if let Some(type_params) = &n.type_params { - fill_ts_type_param_decl(deps, type_params); - } - for param in &n.params { - fill_ts_fn_param(deps, param); - } - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann) - } + } +} + +impl Visit for DepsFiller { + fn visit_ts_index_signature(&mut self, n: &TsIndexSignature) { + for param in &n.params { + self.visit_ts_fn_param(param) } - SymbolNodeRef::TsGetterSignature(n) => { - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann) - } + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann) } - SymbolNodeRef::TsSetterSignature(n) => { - fill_ts_fn_param(deps, &n.param); + } + + fn visit_ts_call_signature_decl(&mut self, n: &TsCallSignatureDecl) { + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); } - SymbolNodeRef::TsMethodSignature(n) => { - if let Some(type_params) = &n.type_params { - fill_ts_type_param_decl(deps, type_params); - } - for param in &n.params { - fill_ts_fn_param(deps, param) - } - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann) - } + for param in &n.params { + self.visit_ts_fn_param(param); + } + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann) } } - result -} -fn fill_class(deps: &mut Vec, n: &Class) { - if let Some(type_params) = &n.type_params { - fill_ts_type_param_decl(deps, type_params); - } - if let Some(expr) = &n.super_class { - fill_expr(deps, expr); - } - if let Some(type_params) = &n.super_type_params { - fill_ts_type_param_instantiation(deps, type_params) - } - for expr in &n.implements { - fill_ts_expr_with_type_args(deps, expr); + fn visit_ts_construct_signature_decl( + &mut self, + n: &TsConstructSignatureDecl, + ) { + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); + } + for param in &n.params { + self.visit_ts_fn_param(param); + } + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann) + } } -} -fn fill_enum(deps: &mut Vec, n: &TsEnumDecl) { - for member in &n.members { - if let Some(init) = &member.init { - fill_expr(deps, init); + fn visit_ts_property_signature(&mut self, n: &TsPropertySignature) { + if let Some(init) = &n.init { + self.visit_expr(init); + } + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); + } + for param in &n.params { + self.visit_ts_fn_param(param); + } + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann) } } -} -fn fill_function_decl(deps: &mut Vec, n: &Function) { - if let Some(type_params) = &n.type_params { - fill_ts_type_param_decl(deps, type_params); - } - for param in &n.params { - fill_param(deps, param); - } - if let Some(return_type) = &n.return_type { - fill_ts_type_ann(deps, return_type); + fn visit_ts_getter_signature(&mut self, n: &TsGetterSignature) { + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann) + } } -} -fn fill_interface(deps: &mut Vec, n: &TsInterfaceDecl) { - if let Some(type_params) = &n.type_params { - fill_ts_type_param_decl(deps, type_params); + fn visit_ts_setter_signature(&mut self, n: &TsSetterSignature) { + self.visit_ts_fn_param(&n.param); } - for extends in &n.extends { - fill_ts_expr_with_type_args(deps, extends); - } -} -fn fill_type_alias(deps: &mut Vec, n: &TsTypeAliasDecl) { - if let Some(type_params) = &n.type_params { - fill_ts_type_param_decl(deps, type_params); + fn visit_ts_method_signature(&mut self, n: &TsMethodSignature) { + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); + } + for param in &n.params { + self.visit_ts_fn_param(param) + } + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann) + } } - fill_ts_type(deps, &n.type_ann) -} - -fn fill_var_declarator(deps: &mut Vec, n: &VarDeclarator) { - fill_pat(deps, &n.name); -} -fn fill_prop_name(deps: &mut Vec, key: &PropName) { - match key { - PropName::Computed(name) => { - fill_expr(deps, &name.expr); + fn visit_class(&mut self, n: &Class) { + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); } - PropName::Ident(_) - | PropName::Str(_) - | PropName::Num(_) - | PropName::BigInt(_) => { - // ignore + if let Some(expr) = &n.super_class { + self.visit_expr(expr); + } + if let Some(type_params) = &n.super_type_params { + self.visit_ts_type_param_instantiation(type_params) + } + for expr in &n.implements { + self.visit_ts_expr_with_type_args(expr); } } -} -fn fill_ts_expr_with_type_args( - deps: &mut Vec, - n: &TsExprWithTypeArgs, -) { - if let Some(type_args) = &n.type_args { - fill_ts_type_param_instantiation(deps, type_args); + fn visit_ts_enum_decl(&mut self, n: &TsEnumDecl) { + for member in &n.members { + if let Some(init) = &member.init { + self.visit_expr(init); + } + } } - fill_expr(deps, &n.expr); -} -fn fill_ts_fn_param(deps: &mut Vec, param: &TsFnParam) { - let mut visitor = SymbolDepFillVisitor { deps }; - param.visit_with(&mut visitor); -} - -fn fill_ts_type_param_decl( - deps: &mut Vec, - type_params: &TsTypeParamDecl, -) { - for param in &type_params.params { - fill_ts_type_param(deps, param); + fn visit_function(&mut self, n: &Function) { + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); + } + for param in &n.params { + self.visit_param(param); + } + if let Some(return_type) = &n.return_type { + self.visit_ts_type_ann(return_type); + } } -} -fn fill_ts_type_param(deps: &mut Vec, param: &TsTypeParam) { - if let Some(constraint) = ¶m.constraint { - fill_ts_type(deps, constraint); - } - if let Some(default) = ¶m.default { - fill_ts_type(deps, default); + fn visit_ts_interface_decl(&mut self, n: &TsInterfaceDecl) { + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); + } + for extends in &n.extends { + self.visit_ts_expr_with_type_args(extends); + } } -} -fn fill_ts_type_param_instantiation( - deps: &mut Vec, - type_params: &TsTypeParamInstantiation, -) { - for param in &type_params.params { - fill_ts_type(deps, param); + fn visit_ts_type_alias_decl(&mut self, n: &TsTypeAliasDecl) { + if let Some(type_params) = &n.type_params { + self.visit_ts_type_param_decl(type_params); + } + self.visit_ts_type(&n.type_ann) } -} -fn fill_ts_param_prop(deps: &mut Vec, param: &TsParamProp) { - match ¶m.param { - TsParamPropParam::Ident(ident) => { - if let Some(type_ann) = &ident.type_ann { - fill_ts_type_ann(deps, type_ann) + fn visit_var_declarator(&mut self, n: &VarDeclarator) { + self.visit_pat(&n.name); + if self.mode.visit_exprs() && !pat_has_type_ann(&n.name) { + if let Some(init) = &n.init { + self.visit_expr(init); } } - TsParamPropParam::Assign(_) => { - // nothing to fill - } } -} - -fn fill_param(deps: &mut Vec, param: &Param) { - fill_pat(deps, ¶m.pat); -} -fn fill_pat(deps: &mut Vec, pat: &Pat) { - match pat { - Pat::Ident(n) => { - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann); - } - } - Pat::Array(n) => { - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann); + fn visit_prop_name(&mut self, key: &PropName) { + match key { + PropName::Computed(computed) => { + self.visit_expr(&computed.expr); } - } - Pat::Rest(n) => { - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann); + // property name idents aren't a dep + PropName::Ident(_) + | PropName::Str(_) + | PropName::Num(_) + | PropName::BigInt(_) => { + // ignore } } - Pat::Object(n) => { - if let Some(type_ann) = &n.type_ann { - fill_ts_type_ann(deps, type_ann); - } + } + + fn visit_ts_expr_with_type_args(&mut self, n: &TsExprWithTypeArgs) { + if let Some(type_args) = &n.type_args { + self.visit_ts_type_param_instantiation(type_args); } - Pat::Assign(n) => { - fill_pat(deps, &n.left); + // visit this expr unconditionally because it's in a TsExprWithTypeArgs + self.visit_expr(&n.expr); + } + + fn visit_ts_type_param_decl(&mut self, type_params: &TsTypeParamDecl) { + for param in &type_params.params { + self.visit_ts_type_param(param); } - Pat::Invalid(_) => { - // ignore + } + + fn visit_ts_type_param(&mut self, param: &TsTypeParam) { + if let Some(constraint) = ¶m.constraint { + self.visit_ts_type(constraint); } - Pat::Expr(expr) => { - fill_expr(deps, expr); + if let Some(default) = ¶m.default { + self.visit_ts_type(default); } } -} - -fn fill_ts_type_ann(deps: &mut Vec, type_ann: &TsTypeAnn) { - fill_ts_type(deps, &type_ann.type_ann) -} -fn fill_ts_type(deps: &mut Vec, n: &TsType) { - let mut visitor = SymbolDepFillVisitor { deps }; - n.visit_with(&mut visitor); -} + fn visit_ts_type_param_instantiation( + &mut self, + type_params: &TsTypeParamInstantiation, + ) { + for param in &type_params.params { + self.visit_ts_type(param); + } + } -fn fill_expr(deps: &mut Vec, n: &Expr) { - fn member_prop_to_str(member_prop: &MemberProp) -> Option { - match member_prop { - MemberProp::Ident(ident) => Some(ident.sym.to_string()), - MemberProp::PrivateName(n) => Some(format!("#{}", n.id.sym)), - MemberProp::Computed(n) => match &*n.expr { - Expr::Lit(Lit::Str(str)) => Some(str.value.to_string()), - _ => None, + fn visit_ts_param_prop(&mut self, param: &TsParamProp) { + match ¶m.param { + TsParamPropParam::Ident(ident) => { + if let Some(type_ann) = &ident.type_ann { + self.visit_ts_type_ann(type_ann) + } + } + TsParamPropParam::Assign(assign) => match &*assign.left { + Pat::Ident(ident) => { + if let Some(type_ann) = &ident.type_ann { + self.visit_ts_type_ann(type_ann) + } + } + _ => { + unreachable!(); + } }, } } - fn expr_into_id_and_parts(expr: &Expr) -> Option<(Id, Vec)> { - match expr { - Expr::Member(member) => { - let (id, mut parts) = expr_into_id_and_parts(&member.obj)?; - parts.push(member_prop_to_str(&member.prop)?); - Some((id, parts)) + fn visit_param(&mut self, param: &Param) { + self.visit_pat(¶m.pat); + } + + fn visit_pat(&mut self, pat: &Pat) { + match pat { + Pat::Ident(n) => { + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann); + } + } + Pat::Array(n) => { + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann); + } + } + Pat::Rest(n) => { + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann); + } + } + Pat::Object(n) => { + if let Some(type_ann) = &n.type_ann { + self.visit_ts_type_ann(type_ann); + } + } + Pat::Assign(n) => { + self.visit_pat(&n.left); + let has_type_ann = pat_has_type_ann(&n.left); + if self.mode.visit_exprs() && !has_type_ann { + self.visit_expr(&n.right); + } + } + Pat::Invalid(_) => { + // ignore + } + Pat::Expr(expr) => { + if self.mode.visit_exprs() { + self.visit_expr(expr); + } } - Expr::Ident(ident) => Some((ident.to_id(), vec![])), - _ => None, } } - if let Some((id, parts)) = expr_into_id_and_parts(n) { - if parts.is_empty() { - deps.push(SymbolNodeDep::Id(id)) + fn visit_expr(&mut self, n: &Expr) { + if let Some((id, parts)) = expr_into_id_and_parts(n) { + if parts.is_empty() { + self.deps.push(SymbolNodeDep::Id(id)) + } else { + self.deps.push(SymbolNodeDep::QualifiedId(id, parts)) + } } else { - deps.push(SymbolNodeDep::QualifiedId(id, parts)) + n.visit_children_with(self); } - } else { - let mut visitor = SymbolDepFillVisitor { deps }; - n.visit_with(&mut visitor); } -} - -struct SymbolDepFillVisitor<'a> { - deps: &'a mut Vec, -} -impl<'a> Visit for SymbolDepFillVisitor<'a> { fn visit_ident(&mut self, n: &Ident) { let id = n.to_id(); self.deps.push(id.into()); } + fn visit_binding_ident(&mut self, n: &BindingIdent) { + // skip over the ident because it's not a dep + n.type_ann.visit_with(self); + } + + fn visit_member_expr(&mut self, n: &MemberExpr) { + if let Some((id, parts)) = member_expr_into_id_and_parts(n) { + self.deps.push(SymbolNodeDep::QualifiedId(id, parts)) + } else { + n.visit_children_with(self); + } + } + + fn visit_ts_tuple_element(&mut self, n: &TsTupleElement) { + n.ty.visit_with(self); + } + fn visit_ts_import_type(&mut self, n: &TsImportType) { let parts = match &n.qualifier { Some(qualifier) => { @@ -429,4 +498,47 @@ impl<'a> Visit for SymbolDepFillVisitor<'a> { let (id, parts) = ts_qualified_name_parts(n); self.deps.push(SymbolNodeDep::QualifiedId(id, parts)); } + + fn visit_ts_type_ann(&mut self, type_ann: &TsTypeAnn) { + self.visit_ts_type(&type_ann.type_ann) + } +} + +fn pat_has_type_ann(n: &Pat) -> bool { + match n { + Pat::Ident(n) => n.type_ann.is_some(), + Pat::Array(n) => n.type_ann.is_some(), + Pat::Rest(n) => n.type_ann.is_some(), + Pat::Object(n) => n.type_ann.is_some(), + Pat::Assign(n) => pat_has_type_ann(&n.left), + Pat::Invalid(_) => false, + Pat::Expr(_) => false, + } +} + +fn expr_into_id_and_parts(expr: &Expr) -> Option<(Id, Vec)> { + match expr { + Expr::Member(member) => member_expr_into_id_and_parts(member), + Expr::Ident(ident) => Some((ident.to_id(), vec![])), + _ => None, + } +} + +fn member_expr_into_id_and_parts( + member: &MemberExpr, +) -> Option<(Id, Vec)> { + fn member_prop_to_str(member_prop: &MemberProp) -> Option { + match member_prop { + MemberProp::Ident(ident) => Some(ident.sym.to_string()), + MemberProp::PrivateName(n) => Some(format!("#{}", n.id.sym)), + MemberProp::Computed(n) => match &*n.expr { + Expr::Lit(Lit::Str(str)) => Some(str.value.to_string()), + _ => None, + }, + } + } + + let (id, mut parts) = expr_into_id_and_parts(&member.obj)?; + parts.push(member_prop_to_str(&member.prop)?); + Some((id, parts)) } diff --git a/src/symbols/mod.rs b/src/symbols/mod.rs index 21bcb8500..f74e75d67 100644 --- a/src/symbols/mod.rs +++ b/src/symbols/mod.rs @@ -12,8 +12,6 @@ pub use self::analyzer::RootSymbol; pub use self::analyzer::Symbol; pub use self::analyzer::SymbolDecl; pub use self::analyzer::SymbolDeclKind; -pub use self::analyzer::SymbolFillDiagnostic; -pub use self::analyzer::SymbolFillDiagnosticKind; pub use self::analyzer::SymbolId; pub use self::analyzer::SymbolNodeRef; pub use self::analyzer::UniqueSymbolId; @@ -21,12 +19,13 @@ pub use self::cross_module::Definition; pub use self::cross_module::DefinitionKind; pub use self::cross_module::DefinitionOrUnresolved; pub use self::cross_module::DefinitionPath; +pub use self::cross_module::ModuleExports; pub use self::cross_module::ResolvedExport; pub use self::cross_module::ResolvedExportOrReExportAllPath; -pub use self::cross_module::ResolvedExports; pub use self::cross_module::ResolvedReExportAllPath; pub use self::cross_module::ResolvedSymbolDepEntry; pub use self::cross_module::UnresolvedSpecifier; +pub use self::dep_analyzer::ResolveDepsMode; pub use self::dep_analyzer::SymbolNodeDep; mod analyzer; diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index 1d574530d..1b8ff3319 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -8,10 +8,19 @@ mod test_builder; use deno_graph::WorkspaceMember; use indexmap::IndexMap; use serde::de::DeserializeOwned; +use serde::Deserialize; +use serde::Serialize; pub use test_builder::*; use url::Url; +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SpecOptions { + pub workspace_fast_check: bool, +} + pub struct Spec { + pub options: Option, pub files: Vec, pub output_file: SpecFile, pub diagnostics: Vec, @@ -21,6 +30,12 @@ pub struct Spec { impl Spec { pub fn emit(&self) -> String { let mut text = String::new(); + if let Some(options) = &self.options { + text.push_str("~~ "); + text.push_str(&serde_json::to_string(options).unwrap()); + text.push_str(" ~~"); + text.push('\n'); + } if !self.workspace_members.is_empty() { text.push_str("# workspace_members\n"); text.push_str( @@ -84,7 +99,7 @@ impl SpecFile { pub fn get_specs_in_dir(path: &Path) -> Vec<(PathBuf, Spec)> { let files = collect_files_in_dir_recursive(path); - let files = if files + let files: Vec<_> = if files .iter() .any(|file| file.path.to_string_lossy().to_lowercase().contains("_only")) { @@ -96,6 +111,11 @@ pub fn get_specs_in_dir(path: &Path) -> Vec<(PathBuf, Spec)> { .collect() } else { files + .into_iter() + .filter(|file| { + !file.path.to_string_lossy().to_lowercase().contains("_skip") + }) + .collect() }; files .into_iter() @@ -106,7 +126,13 @@ pub fn get_specs_in_dir(path: &Path) -> Vec<(PathBuf, Spec)> { fn parse_spec(text: String) -> Spec { let mut files = Vec::new(); let mut current_file = None; - for line in text.split('\n') { + let mut options: Option = None; + for (i, line) in text.split('\n').enumerate() { + if i == 0 && line.starts_with("~~ ") { + let line = line.replace("~~", "").trim().to_string(); // not ideal, being lazy + options = Some(serde_json::from_str(&line).unwrap()); + continue; + } if let Some(specifier) = line.strip_prefix("# ") { if let Some(file) = current_file.take() { files.push(file); @@ -133,6 +159,7 @@ fn parse_spec(text: String) -> Spec { let diagnostics = take_vec_file(&mut files, "diagnostics"); let workspace_members = take_vec_file(&mut files, "workspace_members"); Spec { + options, files, output_file, diagnostics, diff --git a/tests/helpers/test_builder.rs b/tests/helpers/test_builder.rs index 3a847413a..140bc0d79 100644 --- a/tests/helpers/test_builder.rs +++ b/tests/helpers/test_builder.rs @@ -46,29 +46,23 @@ impl Loader for TestLoader { #[cfg(feature = "symbols")] pub mod symbols { - use deno_graph::symbols::RootSymbol; - use deno_graph::symbols::SymbolFillDiagnostic; - pub struct SymbolsResult { pub output: String, - pub diagnostics: Vec, - } - - pub struct SymbolsBuildResult { - pub graph: deno_graph::ModuleGraph, - pub analyzer: deno_graph::CapturingModuleAnalyzer, - } - - impl SymbolsBuildResult { - pub fn root_symbol(&self) -> RootSymbol { - RootSymbol::new(&self.graph, self.analyzer.as_capturing_parser()) - } } } pub struct BuildResult { pub graph: ModuleGraph, pub diagnostics: Vec, + pub analyzer: deno_graph::CapturingModuleAnalyzer, +} + +#[cfg(feature = "symbols")] +impl BuildResult { + pub fn root_symbol(&self) -> deno_graph::symbols::RootSymbol { + self.graph.valid().unwrap(); // assert valid + deno_graph::symbols::RootSymbol::new(&self.graph, &self.analyzer) + } } pub struct TestBuilder { @@ -76,6 +70,7 @@ pub struct TestBuilder { entry_point: String, entry_point_types: String, workspace_members: Vec, + workspace_fast_check: bool, } impl TestBuilder { @@ -85,6 +80,7 @@ impl TestBuilder { entry_point: "file:///mod.ts".to_string(), entry_point_types: "file:///mod.ts".to_string(), workspace_members: Default::default(), + workspace_fast_check: false, } } @@ -116,46 +112,32 @@ impl TestBuilder { self } + pub fn workspace_fast_check(&mut self, value: bool) -> &mut Self { + self.workspace_fast_check = value; + self + } + pub async fn build(&mut self) -> BuildResult { let mut graph = deno_graph::ModuleGraph::new(GraphKind::All); let entry_point_url = ModuleSpecifier::parse(&self.entry_point).unwrap(); let roots = vec![entry_point_url.clone()]; + let capturing_analyzer = deno_graph::CapturingModuleAnalyzer::default(); let diagnostics = graph .build( roots.clone(), &mut self.loader, deno_graph::BuildOptions { workspace_members: self.workspace_members.clone(), - ..Default::default() - }, - ) - .await; - BuildResult { graph, diagnostics } - } - - #[cfg(feature = "symbols")] - pub async fn build_for_symbols(&mut self) -> symbols::SymbolsBuildResult { - let mut graph = deno_graph::ModuleGraph::new(GraphKind::All); - let entry_point_url = ModuleSpecifier::parse(&self.entry_point).unwrap(); - let roots = vec![entry_point_url.clone()]; - let source_parser = deno_graph::DefaultModuleParser::new_for_analysis(); - let capturing_analyzer = deno_graph::CapturingModuleAnalyzer::new( - Some(Box::new(source_parser)), - None, - ); - graph - .build( - roots.clone(), - &mut self.loader, - deno_graph::BuildOptions { module_analyzer: Some(&capturing_analyzer), + module_parser: Some(&capturing_analyzer), + workspace_fast_check: self.workspace_fast_check, ..Default::default() }, ) .await; - graph.valid().unwrap(); // assert valid - symbols::SymbolsBuildResult { + BuildResult { graph, + diagnostics, analyzer: capturing_analyzer, } } @@ -270,8 +252,9 @@ impl TestBuilder { use deno_graph::symbols::DefinitionOrUnresolved; use deno_graph::symbols::ModuleInfoRef; + use deno_graph::symbols::ResolveDepsMode; - let build_result = self.build_for_symbols().await; + let build_result = self.build().await; let graph = &build_result.graph; let entry_point_types_url = ModuleSpecifier::parse(&self.entry_point_types).unwrap(); @@ -300,25 +283,48 @@ impl TestBuilder { ); output_text.push_str(&module_output_text); - let mut symbol_deps_text = String::new(); - for symbol in module.symbols() { - for decl in symbol.decls() { - if let Some((node, source)) = decl.maybe_node_and_source() { - let deps = node.deps(); - if !deps.is_empty() { - symbol_deps_text.push_str(&format!( - "{:?}:{:?} {:?}\n", - symbol.symbol_id(), - decl.range.as_byte_range(source.text_info().range().start), - deps - )); + fn get_symbol_deps_text_for_mode( + module: ModuleInfoRef<'_>, + resolve_mode: ResolveDepsMode, + ) -> String { + let mut symbol_deps_text = String::new(); + for symbol in module.symbols() { + for decl in symbol.decls() { + if let Some((node, source)) = decl.maybe_node_and_source() { + let deps = node.deps(resolve_mode); + if !deps.is_empty() { + symbol_deps_text.push_str(&format!( + "{:?}:{:?} {:?}\n", + symbol.symbol_id(), + decl + .range + .as_byte_range(source.text_info().range().start), + deps + )); + } } } } + symbol_deps_text + } + + let symbol_deps_text = get_symbol_deps_text_for_mode( + module, + ResolveDepsMode::TypesAndExpressions, + ); + if !symbol_deps_text.is_empty() { + output_text.push_str(&format!( + "== symbol deps (types and exprs) ==\n{}\n", + symbol_deps_text + )); } + let symbol_deps_text = + get_symbol_deps_text_for_mode(module, ResolveDepsMode::TypesOnly); if !symbol_deps_text.is_empty() { - output_text - .push_str(&format!("== symbol deps ==\n{}\n", symbol_deps_text)); + output_text.push_str(&format!( + "== symbol deps (types only) ==\n{}\n", + symbol_deps_text + )); } // analyze the module graph for any problems @@ -396,7 +402,6 @@ impl TestBuilder { } output_text }, - diagnostics: root_symbol.take_diagnostics(), }) } } diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 772678392..415460adb 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -40,6 +40,11 @@ async fn test_graph_specs() { for (test_file_path, spec) in get_specs_in_dir(&PathBuf::from("./tests/specs/graph")) { + if !cfg!(feature = "fast_check") + && spec.output_file.text.contains("Fast check ") + { + continue; + } eprintln!("Running {}", test_file_path.display()); let mut builder = TestBuilder::new(); builder.with_loader(|loader| { @@ -58,10 +63,43 @@ async fn test_graph_specs() { }); builder.workspace_members(spec.workspace_members.clone()); + if let Some(options) = &spec.options { + builder.workspace_fast_check(options.workspace_fast_check); + } + let result = builder.build().await; let update_var = std::env::var("UPDATE"); let mut output_text = serde_json::to_string_pretty(&result.graph).unwrap(); output_text.push('\n'); + let fast_check_modules = result.graph.modules().filter_map(|module| { + let module = module.esm()?; + let fast_check = module.fast_check.as_ref()?; + Some((module, fast_check)) + }); + for (module, fast_check) in fast_check_modules { + output_text.push_str(&format!("\nFast check {}:\n", module.specifier,)); + match fast_check { + deno_graph::FastCheckTypeModuleSlot::Module(fast_check) => { + output_text.push_str(&format!( + "{}\n{}", + indent( + &serde_json::to_string_pretty(&fast_check.dependencies).unwrap() + ), + if fast_check.source.is_empty() { + " ".to_string() + } else { + indent(&fast_check.source) + }, + )); + } + deno_graph::FastCheckTypeModuleSlot::Error(diagnostic) => { + output_text.push_str(&indent(&diagnostic.message_with_range())); + } + } + } + if !output_text.ends_with('\n') { + output_text.push('\n'); + } let diagnostics = result .diagnostics .iter() @@ -91,6 +129,14 @@ async fn test_graph_specs() { } } +fn indent(text: &str) -> String { + text + .split('\n') + .map(|l| format!(" {}", l).trim_end().to_string()) + .collect::>() + .join("\n") +} + #[cfg(feature = "symbols")] #[tokio::test] async fn test_symbols_specs() { @@ -106,6 +152,10 @@ async fn test_symbols_specs() { builder.entry_point_types("file:///mod.d.ts"); } + if let Some(options) = &spec.options { + builder.workspace_fast_check(options.workspace_fast_check); + } + builder.with_loader(|loader| { for file in &spec.files { let source = Source::Module { @@ -123,15 +173,9 @@ async fn test_symbols_specs() { let result = builder.symbols().await.unwrap(); let update_var = std::env::var("UPDATE"); - let diagnostics = result - .diagnostics - .iter() - .map(|d| serde_json::to_value(d.clone()).unwrap()) - .collect::>(); let spec = if update_var.as_ref().map(|v| v.as_str()) == Ok("1") { let mut spec = spec; spec.output_file.text = result.output.clone(); - spec.diagnostics = diagnostics.clone(); std::fs::write(&test_file_path, spec.emit()).unwrap(); spec } else { @@ -143,12 +187,6 @@ async fn test_symbols_specs() { "Should be same for {}", test_file_path.display() ); - assert_eq!( - diagnostics, - spec.diagnostics, - "Should be same for {}", - test_file_path.display() - ); } } @@ -174,7 +212,7 @@ export class MyClass { "#, ); }) - .build_for_symbols() + .build() .await; let root_symbol = result.root_symbol(); @@ -191,7 +229,9 @@ export class MyClass { .decls() .iter() .filter_map(|d| d.maybe_node()) - .flat_map(|s| s.deps()) + .flat_map(|s| { + s.deps(deno_graph::symbols::ResolveDepsMode::TypesAndExpressions) + }) .collect::>(); assert_eq!(deps.len(), 1); let mut resolved_deps = root_symbol.resolve_symbol_dep( @@ -241,7 +281,7 @@ async fn test_symbols_re_export_external() { ); loader.remote.add_external_source("npm:example"); }) - .build_for_symbols() + .build() .await; let root_symbol = result.root_symbol(); @@ -253,7 +293,7 @@ async fn test_symbols_re_export_external() { exports .unresolved_specifiers .into_iter() - .map(|s| s.specifier.as_str()) + .map(|s| s.specifier) .collect::>(), vec!["npm:example"] ); diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck.txt b/tests/specs/graph/JsrSpecifiers_FastCheck.txt new file mode 100644 index 000000000..9a349cbb8 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck.txt @@ -0,0 +1,256 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/a.ts +export class A1 {} +export class A2 {} +export class A3 {} + +export { A4, A5 } from "./a4.ts"; +export * from "./a6.ts"; + +# https://jsr.io/@scope/a/1.0.0/a4.ts +export class A4 {} +export class A5 {} + +# https://jsr.io/@scope/a/1.0.0/a6.ts +export class A6 {} +export class A6Private {} + +# https://jsr.io/@scope/a/1.0.0/mod.ts +import { A1, A2, A3 as RenamedA3, A4, A6 } from "./a.ts"; +import { A3 } from "./a.ts"; + +@dec +export class Test { + /** Testing */ + @dec + noReturnTypeVoid(@dec param = 5, param2 = "test") { + // should remove this comment + console.log(1); + } + + async noReturnTypeAsync() { + } + + *returnTypeGenerator(): Generator { + yield 5; + } + + hasReturnType(): A1 & RenamedA3 | A4 | A6 {} + + private inner(param) {} +} + +class Private { + wontCareAboutThis(param) { + return Math.random(); + } + + other(): A3 { + } +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./a4.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a4.ts", + "span": { + "start": { + "line": 4, + "character": 23 + }, + "end": { + "line": 4, + "character": 32 + } + } + } + }, + { + "specifier": "./a6.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a6.ts", + "span": { + "start": { + "line": 5, + "character": 14 + }, + "end": { + "line": 5, + "character": 23 + } + } + } + } + ], + "size": 117, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts" + }, + { + "kind": "esm", + "size": 38, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/a4.ts" + }, + { + "kind": "esm", + "size": 45, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/a6.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./a.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 48 + }, + "end": { + "line": 0, + "character": 56 + } + } + } + } + ], + "size": 535, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/a.ts: + { + "./a4.ts": { + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a4.ts", + "span": { + "start": { + "line": 4, + "character": 23 + }, + "end": { + "line": 4, + "character": 32 + } + } + } + }, + "./a6.ts": { + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a6.ts", + "span": { + "start": { + "line": 5, + "character": 14 + }, + "end": { + "line": 5, + "character": 23 + } + } + } + } + } + export class A1 { + } + export class A3 { + } + export { A4 } from "./a4.ts"; + export * from "./a6.ts"; + +Fast check https://jsr.io/@scope/a/1.0.0/a4.ts: + {} + export class A4 { + } + +Fast check https://jsr.io/@scope/a/1.0.0/a6.ts: + {} + export class A6 { + } + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + { + "./a.ts": { + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 48 + }, + "end": { + "line": 0, + "character": 56 + } + } + } + } + } + import { A1, A3 as RenamedA3, A4, A6 } from "./a.ts"; + export class Test { + /** Testing */ noReturnTypeVoid(param?: number, param2?: string): void {} + noReturnTypeAsync(): Promise { + return {} as any; + } + returnTypeGenerator(): Generator { + return {} as any; + } + hasReturnType(): A1 & RenamedA3 | A4 | A6 { + return {} as any; + } + private inner!: unknown; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassCtors.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassCtors.txt new file mode 100644 index 000000000..df6014dbf --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassCtors.txt @@ -0,0 +1,161 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class ClassBasic { + constructor(prop: string, other = 5) { + } +} +export class ClassPrivateCtor { + private constructor(prop: string, other: Private1) { + } +} +export class ClassPrivateCtorWithOverloads { + private constructor(prop: string, other: Private1); + private constructor(prop: string, other: Private1) { + } +} +export class ClassProtectedCtor { + protected constructor(prop: string, other = 5) { + } +} +export class ClassProtectedCtorWithOverloads { + protected constructor(prop: Public1); + protected constructor(prop: Private1) { + } +} +export class ClassCtorWithOverloads { + constructor(prop: Public1); + constructor(prop: Private1) { + } +} +export class ClassPrivateCtorPublicParamProp { + private constructor(public param1: Public2, private param2: Private1) { + } +} +export class ClassCtorPublicParamProp { + constructor( + public param1: Public3, + private param2: Public4, + private param3: Public5 = new Public5(), + ) { + } +} +export class ClassCtorPublicParamPropInit { + constructor(public param1: Public3 = new Private1()) { + } +} +export class ClassCtorPublicParamPropOptional { + constructor(public param1?: string) { + } +} + +class Public1 {} +class Public2 {} +class Public3 {} +class Public4 {} +class Public5 {} +class Private1 {} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 1259, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export class ClassBasic { + constructor(prop: string, other?: number){} + } + export class ClassPrivateCtor { + private constructor(){} + } + export class ClassPrivateCtorWithOverloads { + private constructor(){} + } + export class ClassProtectedCtor { + protected constructor(prop: string, other?: number){} + } + export class ClassProtectedCtorWithOverloads { + protected constructor(prop: Public1); + protected constructor(param0?: any){} + } + export class ClassCtorWithOverloads { + constructor(prop: Public1); + constructor(param0?: any){} + } + export class ClassPrivateCtorPublicParamProp { + param1!: Public2; + private param2!: unknown; + private constructor(){} + } + export class ClassCtorPublicParamProp { + param1!: Public3; + private param2!: unknown; + private param3!: unknown; + constructor(param1: Public3, param2: Public4, param3?: Public5){} + } + export class ClassCtorPublicParamPropInit { + param1!: Public3; + constructor(param1?: Public3){} + } + export class ClassCtorPublicParamPropOptional { + param1?: string; + constructor(param1?: string){} + } + class Public1 { + } + class Public2 { + } + class Public3 { + } + class Public4 { + } + class Public5 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassGettersAndSetters.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassGettersAndSetters.txt new file mode 100644 index 000000000..d4dbbf05c --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassGettersAndSetters.txt @@ -0,0 +1,73 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class MyClass { + get value(): string { + return ""; + } + + set value(value: string) { + console.log(value); + } +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 126, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export class MyClass { + get value(): string { + return {} as any; + } + set value(_value: string) {} + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefAdditionalPropOnMember.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefAdditionalPropOnMember.txt new file mode 100644 index 000000000..564d1c73e --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefAdditionalPropOnMember.txt @@ -0,0 +1,66 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class Export { + prop: MyClass.prototype.member.additional; +} + +class MyClass { + member: string; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 106, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + The reference 'MyClass.prototype.member.additional' from 'Export.prototype.prop' was too complex. Extract out the shared type to a type alias. + at https://jsr.io/@scope/a/1.0.0/mod.ts:5:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefFound.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefFound.txt new file mode 100644 index 000000000..ffee52165 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefFound.txt @@ -0,0 +1,70 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class Export { + prop: typeof Public1.prototype.prop; +} +class Public1 { + prop: string; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 97, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export class Export { + prop!: typeof Public1.prototype.prop; + } + class Public1 { + prop!: string; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefNotFound.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefNotFound.txt new file mode 100644 index 000000000..52c2d595c --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassMemberRefNotFound.txt @@ -0,0 +1,65 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class Export { + prop: typeof Public1.prototype.nonExistent; +} +class Public1 { + prop: string; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 104, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Could not resolve 'Public1.prototype.nonExistent' referenced from 'Export.prototype.prop'. This may indicate a bug in Deno. Please open an issue to help us improve if so. + at https://jsr.io/@scope/a/1.0.0/mod.ts:4:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassProperties.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassProperties.txt new file mode 100644 index 000000000..c67fb5f50 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassProperties.txt @@ -0,0 +1,107 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class RedBlackNode extends BinarySearchNode { + declare parent: RedBlackNode | null; + declare left: RedBlackNode | null; + declare right: RedBlackNode | null; + red: boolean; + + constructor(parent: RedBlackNode | null, value: T) { + super(parent, value); + this.red = true; + } + + static override from(node: RedBlackNode): RedBlackNode { + const copy: RedBlackNode = new RedBlackNode(node.parent, node.value); + copy.left = node.left; + copy.right = node.right; + copy.red = node.red; + return copy; + } +} + +const isSecure: Symbol = Symbol("#secure"); +const public2: Symbol = Symbol("#public"); + +export class CookieMapBase { + [isSecure] = false; + [public2](): number { + return 5; + } +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 742, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export class RedBlackNode extends BinarySearchNode { + declare parent: RedBlackNode | null; + declare left: RedBlackNode | null; + declare right: RedBlackNode | null; + red!: boolean; + constructor(parent: RedBlackNode | null, value: T){ + super({} as any, {} as any); + } + static override from(node: RedBlackNode): RedBlackNode { + return {} as any; + } + } + const isSecure: Symbol = {} as any; + const public2: Symbol = {} as any; + export class CookieMapBase { + [isSecure]!: boolean; + [public2](): number { + return {} as any; + } + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefFound.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefFound.txt new file mode 100644 index 000000000..9bf14ef7c --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefFound.txt @@ -0,0 +1,70 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class Export { + prop: typeof Public1.prop; +} +class Public1 { + static prop: string; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 94, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export class Export { + prop!: typeof Public1.prop; + } + class Public1 { + static prop: string; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefNotFound.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefNotFound.txt new file mode 100644 index 000000000..b4fb93e83 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassStaticMemberRefNotFound.txt @@ -0,0 +1,65 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class Export { + prop: typeof Public1.nonExistent; +} +class Public1 { + static prop: string; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 101, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Could not resolve 'Public1.nonExistent' referenced from 'Export.prototype.prop'. This may indicate a bug in Deno. Please open an issue to help us improve if so. + at https://jsr.io/@scope/a/1.0.0/mod.ts:4:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuper.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuper.txt new file mode 100644 index 000000000..58754e999 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuper.txt @@ -0,0 +1,92 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +class Base { + constructor(a: number, b: number) { + } +} +export class Child extends Base { + constructor() { + super(1, 2); + } +} +class SpreadBase { + constructor(...params: string[]) { + } +} +export class SpreadChild extends SpreadBase { + constructor() { + super(...[1, 2]); + } +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 286, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + class Base { + constructor(a: number, b: number){} + } + export class Child extends Base { + constructor(){ + super({} as any, {} as any); + } + } + class SpreadBase { + constructor(...params: string[]){} + } + export class SpreadChild extends SpreadBase { + constructor(){ + super(...({} as any)); + } + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuperUnsupported.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuperUnsupported.txt new file mode 100644 index 000000000..d2d1d554f --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ClassSuperUnsupported.txt @@ -0,0 +1,64 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +class Base {} +function MyFunction() {} +// perhaps it could figure out this scenario though... +export class Child extends MyFunction(Base) { +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 142, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Super class expression was too complex. Extract it out to a variable and add an explicit type. + at https://jsr.io/@scope/a/1.0.0/mod.ts:4:28 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefAliased.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefAliased.txt new file mode 100644 index 000000000..dc1c67f07 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefAliased.txt @@ -0,0 +1,185 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/a.ts +export class A { + member: string; +} +export class Private2 {} + +# https://jsr.io/@scope/a/1.0.0/b.ts +export { A as B } from "./a.ts"; + +export class Private1 {} + +# https://jsr.io/@scope/a/1.0.0/mod.ts +import { B } from "./b.ts"; +import { Private2 } from "./a.ts"; + +export class Export { + prop: typeof B.prototype.member; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 62, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./a.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 23 + }, + "end": { + "line": 0, + "character": 31 + } + } + } + } + ], + "size": 59, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/b.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./b.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/b.ts", + "span": { + "start": { + "line": 0, + "character": 18 + }, + "end": { + "line": 0, + "character": 26 + } + } + } + }, + { + "specifier": "./a.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 1, + "character": 25 + }, + "end": { + "line": 1, + "character": 33 + } + } + } + } + ], + "size": 123, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/a.ts: + {} + export class A { + member!: string; + } + +Fast check https://jsr.io/@scope/a/1.0.0/b.ts: + { + "./a.ts": { + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 23 + }, + "end": { + "line": 0, + "character": 31 + } + } + } + } + } + export { A as B } from "./a.ts"; + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + { + "./b.ts": { + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/b.ts", + "span": { + "start": { + "line": 0, + "character": 18 + }, + "end": { + "line": 0, + "character": 26 + } + } + } + } + } + import { B } from "./b.ts"; + export class Export { + prop!: typeof B.prototype.member; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportType.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportType.txt new file mode 100644 index 000000000..208d78738 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportType.txt @@ -0,0 +1,111 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/a.ts +export default class Test {} +export class A {} + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export type Example = import("./a.ts"); + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 47, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./a.ts", + "type": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 29 + }, + "end": { + "line": 0, + "character": 37 + } + } + } + } + ], + "size": 40, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/a.ts: + {} + export default class Test { + } + export class A { + } + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + { + "./a.ts": { + "type": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 29 + }, + "end": { + "line": 0, + "character": 37 + } + } + } + } + } + export type Example = import("./a.ts"); diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportTypeQualified.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportTypeQualified.txt new file mode 100644 index 000000000..f9ac82eb9 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefImportTypeQualified.txt @@ -0,0 +1,109 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/a.ts +export default class Test {} +export class A {} + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export type Example = import("./a.ts").default; + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 47, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./a.ts", + "type": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 29 + }, + "end": { + "line": 0, + "character": 37 + } + } + } + } + ], + "size": 48, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/a.ts: + {} + export default class Test { + } + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + { + "./a.ts": { + "type": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 29 + }, + "end": { + "line": 0, + "character": 37 + } + } + } + } + } + export type Example = import("./a.ts").default; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModule.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModule.txt new file mode 100644 index 000000000..b9e5a9cae --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModule.txt @@ -0,0 +1,189 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/a.ts +export class A { + member: string; + member2: string; +} +export class Private2 {} + +# https://jsr.io/@scope/a/1.0.0/b.ts +export { A as B } from "./a.ts"; + +export class Private1 {} + +# https://jsr.io/@scope/a/1.0.0/mod.ts +import * as mod from "./b.ts"; +import { Private2 } from "./a.ts"; + +export class Export { + prop: typeof mod.B.prototype.member; + prop2: typeof mod.B.prototype.member2; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 81, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./a.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 23 + }, + "end": { + "line": 0, + "character": 31 + } + } + } + } + ], + "size": 59, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/b.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./b.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/b.ts", + "span": { + "start": { + "line": 0, + "character": 21 + }, + "end": { + "line": 0, + "character": 29 + } + } + } + }, + { + "specifier": "./a.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 1, + "character": 25 + }, + "end": { + "line": 1, + "character": 33 + } + } + } + } + ], + "size": 171, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/a.ts: + {} + export class A { + member!: string; + member2!: string; + } + +Fast check https://jsr.io/@scope/a/1.0.0/b.ts: + { + "./a.ts": { + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 23 + }, + "end": { + "line": 0, + "character": 31 + } + } + } + } + } + export { A as B } from "./a.ts"; + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + { + "./b.ts": { + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/b.ts", + "span": { + "start": { + "line": 0, + "character": 21 + }, + "end": { + "line": 0, + "character": 29 + } + } + } + } + } + import * as mod from "./b.ts"; + export class Export { + prop!: typeof mod.B.prototype.member; + prop2!: typeof mod.B.prototype.member2; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModuleNotFound.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModuleNotFound.txt new file mode 100644 index 000000000..54071eda7 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_CrossFileRefModuleNotFound.txt @@ -0,0 +1,142 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/a.ts +export class A { + member: string; + member2: string; +} +export class Private2 {} + +# https://jsr.io/@scope/a/1.0.0/b.ts +export { A as B } from "./a.ts"; + +export class Public1 {} + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export * from "./b.ts"; +import * as mod from "./b.ts"; +import { Private2 } from "./a.ts"; + +export class Export { + prop: typeof mod.B.prototype.notExistent; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 81, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./a.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 0, + "character": 23 + }, + "end": { + "line": 0, + "character": 31 + } + } + } + } + ], + "size": 58, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/b.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./b.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/b.ts", + "span": { + "start": { + "line": 0, + "character": 14 + }, + "end": { + "line": 0, + "character": 22 + } + } + } + }, + { + "specifier": "./a.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/a.ts", + "span": { + "start": { + "line": 2, + "character": 25 + }, + "end": { + "line": 2, + "character": 33 + } + } + } + } + ], + "size": 159, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Could not resolve 'A.prototype.notExistent' referenced from ''. This may indicate a bug in Deno. Please open an issue to help us improve if so. + at https://jsr.io/@scope/a/1.0.0/a.ts:1:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprLeavable.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprLeavable.txt new file mode 100644 index 000000000..c6b9a7899 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprLeavable.txt @@ -0,0 +1,79 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export default { + "application/1d-interleaved-parityfec": { + "source": "iana" + }, + "application/3gpdash-qoe-report+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + }, +} as const; + + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 218, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export default { + "application/1d-interleaved-parityfec": { + "source": "iana" + }, + "application/3gpdash-qoe-report+xml": { + "source": "iana", + "charset": "UTF-8", + "compressible": true + } + } as const; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprVar.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprVar.txt new file mode 100644 index 000000000..825b560ab --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_DefaultExportExprVar.txt @@ -0,0 +1,71 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export interface $Type { + prop: boolean; +} + +export const $: $Type = build$FromState(buildInitial$State({ + isGlobal: true, +})); +export default $; + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 147, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export interface $Type { + prop: boolean; + } + export const $: $Type = {} as any; + export default $; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Enums.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Enums.txt new file mode 100644 index 000000000..62e19a3c2 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Enums.txt @@ -0,0 +1,110 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export enum EnumWithNoInits { + Value1, + Value2, +} + +export enum EnumWithNumInits { + Value1 = 1, + Value2 = 2, +} + +export enum EnumWithStringInits { + Value1 = "a", + Value2 = "b", +} + +const value = 10; + +export enum EnumWithNonConstInits { + Value1 = new Public1().test, + Value2 = new Public2().asdf * value + NonExportedEnum.Value, +} + +class Public1 {} +class Public2 {} +class Private1 {} +enum NonExportedEnum { + Value +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 421, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export enum EnumWithNoInits { + Value1, + Value2 + } + export enum EnumWithNumInits { + Value1 = 1, + Value2 = 2 + } + export enum EnumWithStringInits { + Value1 = "a", + Value2 = "b" + } + const value: number = {} as any; + export enum EnumWithNonConstInits { + Value1 = new Public1().test, + Value2 = new Public2().asdf * value + NonExportedEnum.Value + } + class Public1 { + } + class Public2 { + } + enum NonExportedEnum { + Value + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_ExportEquals.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_ExportEquals.txt new file mode 100644 index 000000000..5a55c5cb9 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_ExportEquals.txt @@ -0,0 +1,60 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export = 5; + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 12, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + CommonJS export assignments (`export =`) are not supported in ES modules. + at https://jsr.io/@scope/a/1.0.0/mod.ts:1:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Functions.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Functions.txt new file mode 100644 index 000000000..c50700d46 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Functions.txt @@ -0,0 +1,167 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export function test1() {} +export function test2(): number { + return 2; +} +export function test3(param: number): number { +} +export function test4(param = 2): number { + return 1; +} +export function test5(param = 2): T { + return new PublicOther(); +} +export function test6(param: number = 2): T { + return new PublicOther(); +} +export function test7(param?: number): number; +export function test7(param?: number, param2?: PublicOther2): number; +export function test7(param: PrivateOther = new PrivateOther, param2?: string): PrivateOther { + return new PublicOther(); +} + +function test8(param: number): number; +function test8(param: string): string; +function test8(param: string): string { +} + +export { test8 }; + +export default function test9(param: number): number; +export default function test9(param: string): string; +export default function test9(param: string): string { +} + +export function test10(...params: string[]): string[] { + return params; +} + +// should support leaving this expr as-is +export function test11(options = { copy: true }) { +} + +export interface GlobOptions { +} +const privateBool = false; +export function test12({ globstar = privateBool }: GlobOptions = {}) { +} +const private1 = 1; +export function test13([test = private1]: number[] = [1 , 2]) { +} + +export function test14(): never { + throw new Error("test"); +} + +class PublicOther { +} +class PublicOther2 { +} + +class PrivateOther {} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 1438, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export function test1(): void {} + export function test2(): number { + return {} as any; + } + export function test3(param: number): number { + return {} as any; + } + export function test4(param?: number): number { + return {} as any; + } + export function test5(param?: number): T { + return {} as any; + } + export function test6(param?: number): T { + return {} as any; + } + export function test7(param?: number): number; + export function test7(param?: number, param2?: PublicOther2): number; + export function test7(param0?: any, param1?: any): any { + return {} as any; + } + function test8(param: number): number; + function test8(param: string): string; + function test8(param0?: any): any { + return {} as any; + } + export { test8 }; + export default function test9(param: number): number; + export default function test9(param: string): string; + export default function test9(param0?: any): any { + return {} as any; + } + export function test10(...params: string[]): string[] { + return {} as any; + } + export function test11(options = { + copy: true + }): void {} + export interface GlobOptions { + } + export function test12({}: GlobOptions = {} as any): void {} + export function test13([]: number[] = [] as any): void {} + export function test14(): never { + return {} as never; + } + class PublicOther { + } + class PublicOther2 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_GlobalModule.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_GlobalModule.txt new file mode 100644 index 000000000..f1c76c260 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_GlobalModule.txt @@ -0,0 +1,62 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +global { + declare var Test: 5; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 34, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Global augmentations are not supported. + at https://jsr.io/@scope/a/1.0.0/mod.ts:1:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols.txt new file mode 100644 index 000000000..8b7fe1367 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols.txt @@ -0,0 +1,71 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +const key = Symbol("#keys"); +const key2 = Symbol.for("#keys"); + +export class MyClass { + [key] = 5; + [key2] = ""; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 117, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + const key: unique symbol = {} as any; + const key2: unique symbol = {} as any; + export class MyClass { + [key]!: number; + [key2]!: string; + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols_NotGlobal.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols_NotGlobal.txt new file mode 100644 index 000000000..9a7f709c6 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_InferredUniqueSymbols_NotGlobal.txt @@ -0,0 +1,69 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +// this will fail because Symbol is local +function Symbol() {} + +const key = Symbol("#keys"); +const key2 = Symbol.for("#keys"); + +export class MyClass { + [key] = 5; + [key2] = ""; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 181, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Could not resolve 'Symbol.for' referenced from 'key2'. This may indicate a bug in Deno. Please open an issue to help us improve if so. + at https://jsr.io/@scope/a/1.0.0/mod.ts:2:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_InitIdentsAndMembers.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_InitIdentsAndMembers.txt new file mode 100644 index 000000000..daaa37700 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_InitIdentsAndMembers.txt @@ -0,0 +1,147 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +class BaseHandler { +} + +class Public { +} + +class Private {} + +namespace Test { + export class A {} + // should not include this + export class B {} +} + +export const handlers = { + BaseHandler, + Private: Public, + C: Test.A, +}; + +# https://jsr.io/@scope/b/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/b/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/b/1.0.0/mod.ts +class BaseHandler { +} + +namespace Test { + export class A {} + // should not include this + export class B {} +} + +export function test( + a = BaseHandler, + C = Test.A, +) { +} + +# mod.ts +import 'jsr:@scope/a' +import 'jsr:@scope/b' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + }, + { + "specifier": "jsr:@scope/b", + "code": { + "specifier": "jsr:@scope/b", + "span": { + "start": { + "line": 1, + "character": 7 + }, + "end": { + "line": 1, + "character": 21 + } + } + } + } + ], + "size": 44, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 224, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + { + "kind": "esm", + "size": 173, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/b/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts", + "jsr:@scope/b": "https://jsr.io/@scope/b/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0", + "@scope/b": "@scope/b@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + class BaseHandler { + } + class Public { + } + module Test { + export class A { + } + } + export const handlers = { + BaseHandler, + Private: Public, + C: Test.A + }; + +Fast check https://jsr.io/@scope/b/1.0.0/mod.ts: + {} + class BaseHandler { + } + module Test { + export class A { + } + } + export function test(a = BaseHandler, C = Test.A): void {} diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Methods.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Methods.txt new file mode 100644 index 000000000..aa1f0cb61 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Methods.txt @@ -0,0 +1,142 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class Test { + test1() {} + test2(): number { + return 2; + } + test3(param: number): number { + } + test4(param = 2): number { + return 1; + } + test5(param = 2): T { + return new PublicOther(); + } + test6(param: number = 2): T { + return new PublicOther(); + } + + test7(param?: number): number; + test7(param?: number, param2?: PublicOther2): number; + test7(param: PrivateOther = new PrivateOther, param2?: string): PrivateOther { + return new PublicOther(); + } + + test8(param: number): number; + test8(param: string): string; + test8(param: string): string { + } +} + +export class Test2 { + public method1() { + } + protected method2() { + } + private tsPrivateMethod() { + } + #privateMethod() { + } +} + +class PublicOther { +} +class PublicOther2 { +} + +class PrivateOther {} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 833, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export class Test { + test1(): void {} + test2(): number { + return {} as any; + } + test3(param: number): number { + return {} as any; + } + test4(param?: number): number { + return {} as any; + } + test5(param?: number): T { + return {} as any; + } + test6(param?: number): T { + return {} as any; + } + test7(param?: number): number; + test7(param?: number, param2?: PublicOther2): number; + test7(param0?: any, param1?: any): any { + return {} as any; + } + test8(param: number): number; + test8(param: string): string; + test8(param0?: any): any { + return {} as any; + } + } + export class Test2 { + #private!: unknown; + public method1(): void {} + protected method2(): void {} + private tsPrivateMethod!: unknown; + } + class PublicOther { + } + class PublicOther2 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_MissingReturnType.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_MissingReturnType.txt new file mode 100644 index 000000000..b323748a8 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_MissingReturnType.txt @@ -0,0 +1,62 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export function missingReturnType() { + return Math.random(); +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 64, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Missing explicit return type in the public API. + at https://jsr.io/@scope/a/1.0.0/mod.ts:1:17 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Properties.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Properties.txt new file mode 100644 index 000000000..b5111f9b5 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Properties.txt @@ -0,0 +1,87 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class Test { + prop1: PublicOther; + protected prop2?: PublicOther2; + private prop3: PrivateOther; + prop4 = 5; + #prop5; + #prop6: PrivateOther2; + static myMember: string; +} + +class PublicOther { +} +class PublicOther2 { +} + +class PrivateOther {} +class PrivateOther2 {} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 276, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export class Test { + #private!: unknown; + prop1!: PublicOther; + protected prop2?: PublicOther2; + private prop3!: unknown; + prop4!: number; + static myMember: string; + } + class PublicOther { + } + class PublicOther2 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_RefObjType.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_RefObjType.txt new file mode 100644 index 000000000..d636905cc --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_RefObjType.txt @@ -0,0 +1,77 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export const STATUS_CODE = { + /** RFC 7231, 6.2.1 */ + Continue: 100, + /** RFC 7231, 6.2.2 */ + SwitchingProtocols: 101, + /** RFC 2518, 10.1 */ + Processing: 102, + /** RFC 8297 **/ + EarlyHints: 103, +} + +export type Status = typeof STATUS_CODE.Continue; + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 257, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export const STATUS_CODE = { + /** RFC 7231, 6.2.1 */ Continue: 100, + /** RFC 7231, 6.2.2 */ SwitchingProtocols: 101, + /** RFC 2518, 10.1 */ Processing: 102, + /** RFC 8297 **/ EarlyHints: 103 + }; + export type Status = typeof STATUS_CODE.Continue; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Require.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Require.txt new file mode 100644 index 000000000..8dc1e7f61 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Require.txt @@ -0,0 +1,84 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +import test = require("./other.ts"); + +console.log(test); + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "dependencies": [ + { + "specifier": "./other.ts", + "code": { + "specifier": "https://jsr.io/@scope/a/1.0.0/other.ts", + "span": { + "start": { + "line": 0, + "character": 22 + }, + "end": { + "line": 0, + "character": 34 + } + } + } + } + ], + "size": 57, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + { + "specifier": "https://jsr.io/@scope/a/1.0.0/other.ts", + "error": "Module not found \"https://jsr.io/@scope/a/1.0.0/other.ts\"." + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Require is not supported in ES modules. + at https://jsr.io/@scope/a/1.0.0/mod.ts:1:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_TsModule.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_TsModule.txt new file mode 100644 index 000000000..3a50fa7a3 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_TsModule.txt @@ -0,0 +1,73 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export namespace Test.Test { + export class MyClass extends Public1 { + prop: string; + } +} + +class Public1 { +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 113, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export module Test.Test { + export class MyClass extends Public1 { + prop!: string; + } + } + class Public1 { + } diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_TsNamespaceExport.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_TsNamespaceExport.txt new file mode 100644 index 000000000..306b493db --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_TsNamespaceExport.txt @@ -0,0 +1,62 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +const test = 5; + +export as namespace test; + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 43, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Global augmentations such as namespace exports are not supported. + at https://jsr.io/@scope/a/1.0.0/mod.ts:3:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Types.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Types.txt new file mode 100644 index 000000000..175bd90f2 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Types.txt @@ -0,0 +1,70 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export type NewLocation = [lat: number, long: number] +export type Value = [string, number]; +export type Value2 = { + prop: string; + prop2?: number; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 151, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export type NewLocation = [lat: number, long: number]; + export type Value = [string, number]; + export type Value2 = { + prop: string; + prop2?: number; + }; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedAmbientDecl.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedAmbientDecl.txt new file mode 100644 index 000000000..c44e33a23 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedAmbientDecl.txt @@ -0,0 +1,62 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +declare module "test" { + interface Test {} +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 46, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Global augmentations such as ambient modules are not supported. + at https://jsr.io/@scope/a/1.0.0/mod.ts:1:16 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedDefaultExportExpr.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedDefaultExportExpr.txt new file mode 100644 index 000000000..01854ef40 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedDefaultExportExpr.txt @@ -0,0 +1,64 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +class Class {} + +export default { + test: new Class(), +}; + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 57, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Default export expression was too complex. Extract it out to a variable and add an explicit type. + at https://jsr.io/@scope/a/1.0.0/mod.ts:3:1 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedPrivateMemberRef.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedPrivateMemberRef.txt new file mode 100644 index 000000000..c196f1655 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_UnsupportedPrivateMemberRef.txt @@ -0,0 +1,63 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +export class MyClass { + prop!: typeof MyClass.prototype.myPrivateMember; + private myPrivateMember!: string; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 112, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + Public API members (MyClass.prototype.prop) referencing or transitively referencing a class private member (MyClass.prototype.myPrivateMember) are not supported. Extract out the shared type to a type alias. + at https://jsr.io/@scope/a/1.0.0/mod.ts:3:3 diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_Vars.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_Vars.txt new file mode 100644 index 000000000..6f188b431 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_Vars.txt @@ -0,0 +1,151 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +const public1: Public1 = new Public1(), private1 = new Private(); + +export { public1 } + +class Public1 { +} + +class Private {} + +// can be left as-is because this is low cost inference +const ERROR_STATUS_MAP = { + "BadRequest": 400, + "Unauthorized": 401, + "PaymentRequired": 402, + "Forbidden": 403, + // etc... +} as const; +export type ErrorStatusKeys = keyof typeof ERROR_STATUS_MAP; +export const asIs1 = [1, 2, 3]; +export const asIs2 = { + a: [1, 2, 3], + b: true, + c: 10, + d: 10n, + e: 1 * 2, + [1]: 4, + f: 1 ? true : false, + g: 1++, + h: [...[1, 2], ...["test"]], +}; + +// inferring simple types +export const inferred1 = 1 as 1; +export const inferred2 = 1 as string; +export const inferred3 = 1 as true; +export const inferred4 = /test/; +export const inferred5 = 1 as boolean; +export const inferred6 = 2; +export const inferred7 = 1 as 1 | 2 | 3 & 4; +export const inferred8 = 1 as ["test", 1]; +export const inferred9 = 1 as [...string]; +export const inferred10 = 1 as (1 | 2n)[]; +export const inferred10 = 1 as (keyof string)[]; + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 1038, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + const public1: Public1 = {} as any; + export { public1 }; + class Public1 { + } + const ERROR_STATUS_MAP = { + "BadRequest": 400, + "Unauthorized": 401, + "PaymentRequired": 402, + "Forbidden": 403 + } as const; + export type ErrorStatusKeys = keyof typeof ERROR_STATUS_MAP; + export const asIs1 = [ + 1, + 2, + 3 + ]; + export const asIs2 = { + a: [ + 1, + 2, + 3 + ], + b: true, + c: 10, + d: 10n, + e: 1 * 2, + [1]: 4, + f: 1 ? true : false, + g: 1++, + h: [ + ...[ + 1, + 2 + ], + ...[ + "test" + ] + ] + }; + export const inferred1: 1 = {} as any; + export const inferred2: string = {} as any; + export const inferred3: true = {} as any; + export const inferred4: RegExp = {} as any; + export const inferred5: boolean = {} as any; + export const inferred6: boolean = {} as any; + export const inferred7: 1 | 2 | 3 & 4 = {} as any; + export const inferred8: ["test", 1] = {} as any; + export const inferred9: [...string] = {} as any; + export const inferred10: (1 | 2n)[] = {} as any; + export const inferred10: (keyof string)[] = {} as any; diff --git a/tests/specs/graph/JsrSpecifiers_FastCheck_WorkspaceFastCheck.txt b/tests/specs/graph/JsrSpecifiers_FastCheck_WorkspaceFastCheck.txt new file mode 100644 index 000000000..a1d16d888 --- /dev/null +++ b/tests/specs/graph/JsrSpecifiers_FastCheck_WorkspaceFastCheck.txt @@ -0,0 +1,137 @@ +~~ {"workspaceFastCheck":true} ~~ +# workspace_members +[ + { + "base": "file:///", + "nv": "@scope/b@1.0.1", + "exports": { + ".": "./mod.ts" + } + } +] + +# https://jsr.io/@scope/b/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/b/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/b/1.0.0/mod.ts +export class B {} + +# https://jsr.io/@scope/c/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/c/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/c/1.0.0/mod.ts +export class C {} + +# mod.ts +import { B } from 'jsr:@scope/b' +import { C } from "jsr:@scope/c"; + +export class MyClass { + prop: Public1; + c: C; +} + +class Public1 {} + +class Private { + b: B; +} + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/b", + "code": { + "specifier": "jsr:@scope/b", + "span": { + "start": { + "line": 0, + "character": 18 + }, + "end": { + "line": 0, + "character": 32 + } + } + } + }, + { + "specifier": "jsr:@scope/c", + "code": { + "specifier": "jsr:@scope/c", + "span": { + "start": { + "line": 1, + "character": 18 + }, + "end": { + "line": 1, + "character": 32 + } + } + } + } + ], + "size": 163, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 18, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/c/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/b": "file:///mod.ts", + "jsr:@scope/c": "https://jsr.io/@scope/c/1.0.0/mod.ts" + }, + "packages": { + "@scope/c": "@scope/c@1.0.0" + } +} + +Fast check file:///mod.ts: + { + "jsr:@scope/c": { + "code": { + "specifier": "jsr:@scope/c", + "span": { + "start": { + "line": 1, + "character": 18 + }, + "end": { + "line": 1, + "character": 32 + } + } + } + } + } + import { C } from "jsr:@scope/c"; + export class MyClass { + prop!: Public1; + c!: C; + } + class Public1 { + } + +Fast check https://jsr.io/@scope/c/1.0.0/mod.ts: + {} + export class C { + } diff --git a/tests/specs/graph/JsrSpecifiers_ModuleGraphInfo.txt b/tests/specs/graph/JsrSpecifiers_ModuleGraphInfo.txt index f38691e44..8e7b7f1da 100644 --- a/tests/specs/graph/JsrSpecifiers_ModuleGraphInfo.txt +++ b/tests/specs/graph/JsrSpecifiers_ModuleGraphInfo.txt @@ -248,3 +248,7 @@ import './c.ts'; "@scope/b": "@scope/b@9.0.0" } } + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + diff --git a/tests/specs/graph/JsrSpecifiers_ModuleGraphInfo_ModifiedCachedFiles.txt b/tests/specs/graph/JsrSpecifiers_ModuleGraphInfo_ModifiedCachedFiles.txt index 9cdd7a9ff..64a07d1ce 100644 --- a/tests/specs/graph/JsrSpecifiers_ModuleGraphInfo_ModifiedCachedFiles.txt +++ b/tests/specs/graph/JsrSpecifiers_ModuleGraphInfo_ModifiedCachedFiles.txt @@ -233,3 +233,7 @@ import 'jsr:@scope/b'; "@scope/b": "@scope/b@9.0.0" } } + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + diff --git a/tests/specs/graph/JsrSpecifiers_NoModuleGraphInfo.txt b/tests/specs/graph/JsrSpecifiers_NoModuleGraphInfo.txt index f2385d395..482cebb79 100644 --- a/tests/specs/graph/JsrSpecifiers_NoModuleGraphInfo.txt +++ b/tests/specs/graph/JsrSpecifiers_NoModuleGraphInfo.txt @@ -177,3 +177,10 @@ console.log(1); "@scope/c@3": "@scope/c@3.0.0" } } + +Fast check https://jsr.io/@scope/a/1.0.1/mod.ts: + {} + +Fast check https://jsr.io/@scope/c/3.0.0/mod.ts: + {} + diff --git a/tests/specs/graph/JsrSpecifiers_SamePackageMultipleTimes.txt b/tests/specs/graph/JsrSpecifiers_SamePackageMultipleTimes.txt index 20880e43f..068b1ae56 100644 --- a/tests/specs/graph/JsrSpecifiers_SamePackageMultipleTimes.txt +++ b/tests/specs/graph/JsrSpecifiers_SamePackageMultipleTimes.txt @@ -46,6 +46,12 @@ import "jsr:@scope/b@1"; # https://jsr.io/@scope/b/1.1.0/mod.ts console.log(1); +export class Test { + method(value: string) { + console.log(1); + } +} + # mod.ts import "jsr:@scope/a@^1.0"; import "jsr:@scope/b@1.1"; @@ -122,7 +128,7 @@ import "jsr:@scope/b@1.1"; }, { "kind": "esm", - "size": 16, + "size": 89, "mediaType": "TypeScript", "specifier": "https://jsr.io/@scope/b/1.1.0/mod.ts" } @@ -138,3 +144,12 @@ import "jsr:@scope/b@1.1"; "@scope/b@1": "@scope/b@1.1.0" } } + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + +Fast check https://jsr.io/@scope/b/1.1.0/mod.ts: + {} + export class Test { + method(value: string): void {} + } diff --git a/tests/specs/symbols/Basic.txt b/tests/specs/symbols/Basic.txt index e5b055ac1..696cf3891 100644 --- a/tests/specs/symbols/Basic.txt +++ b/tests/specs/symbols/Basic.txt @@ -378,7 +378,11 @@ file:///a.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +6:102..115 [Id(("AInner", #2))] +9:173..198 [Id(("C", #2))] + +== symbol deps (types only) == 6:102..115 [Id(("AInner", #2))] 9:173..198 [Id(("C", #2))] @@ -622,10 +626,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 87, + 96, ), end: SourcePos( - 100, + 97, ), }, flags: 0, @@ -637,7 +641,11 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +4:48..69 [Id(("A", #2))] +5:78..85 [Id(("B", #2))] + +== symbol deps (types only) == 4:48..69 [Id(("A", #2))] 5:78..85 [Id(("B", #2))] diff --git a/tests/specs/symbols/ClassPrivateCtorParamProps.txt b/tests/specs/symbols/ClassPrivateCtorParamProps.txt new file mode 100644 index 000000000..018f51abf --- /dev/null +++ b/tests/specs/symbols/ClassPrivateCtorParamProps.txt @@ -0,0 +1,275 @@ +# mod.ts +export class Class { + private constructor( + public param: PublicClass, + private privateParam: PrivateClass, + regularParam: PrivateClass, + ) { + } +} + +class PublicClass {} +class PrivateClass {} + +# output +file:///mod.ts: EsmModuleInfo { + module_id: ModuleId( + 0, + ), + specifier: "file:///mod.ts", + re_exports: [], + swc_id_to_symbol_id: { + ( + "Class", + #2, + ): 1, + ( + "PublicClass", + #2, + ): 5, + ( + "PrivateClass", + #2, + ): 6, + }, + symbols: { + 0: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 0, + parent_id: None, + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "export class Class {\n private constructor(\n public param: PublicClass,\n private privateParam: PrivateClass,\n regularParam: PrivateClass,\n ) {\n }\n}\n\nclass PublicClass {}\nclass PrivateClass {}", + ), + ), + range: SourceRange { + start: SourcePos( + 0, + ), + end: SourcePos( + 202, + ), + }, + flags: 0, + }, + ], + child_ids: { + 1, + 5, + 6, + }, + exports: { + "Class": 1, + }, + members: {}, + }, + 1: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 1, + parent_id: Some( + 0, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "export class Class {\n private constructor(\n public param: PublicClass,\n private privateParam: PrivateClass,\n regularParam: PrivateClass,\n ) {\n }\n}", + ), + ), + range: SourceRange { + start: SourcePos( + 0, + ), + end: SourcePos( + 158, + ), + }, + flags: 0, + }, + ], + child_ids: { + 2, + }, + exports: { + "%%dg_ctor%%": 2, + }, + members: { + 3, + 4, + }, + }, + 2: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 2, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "private constructor(\n public param: PublicClass,\n private privateParam: PrivateClass,\n regularParam: PrivateClass,\n ) {\n }", + ), + ), + range: SourceRange { + start: SourcePos( + 23, + ), + end: SourcePos( + 156, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 3: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 3, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "public param: PublicClass", + ), + ), + range: SourceRange { + start: SourcePos( + 48, + ), + end: SourcePos( + 73, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 4: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 4, + parent_id: Some( + 1, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "private privateParam: PrivateClass", + ), + ), + range: SourceRange { + start: SourcePos( + 79, + ), + end: SourcePos( + 113, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 5: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 5, + parent_id: Some( + 0, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "class PublicClass {}", + ), + ), + range: SourceRange { + start: SourcePos( + 160, + ), + end: SourcePos( + 180, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + 6: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 6, + parent_id: Some( + 0, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "class PrivateClass {}", + ), + ), + range: SourceRange { + start: SourcePos( + 181, + ), + end: SourcePos( + 202, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, + }, +} +== symbol deps (types and exprs) == +2:23..156 [Id(("PublicClass", #2)), Id(("PrivateClass", #2)), Id(("PrivateClass", #2))] +3:48..73 [Id(("PublicClass", #2))] +4:79..113 [Id(("PrivateClass", #2))] + +== symbol deps (types only) == +2:23..156 [Id(("PublicClass", #2)), Id(("PrivateClass", #2)), Id(("PrivateClass", #2))] +3:48..73 [Id(("PublicClass", #2))] +4:79..113 [Id(("PrivateClass", #2))] + +== export definitions == +[Class]: file:///mod.ts:0..158 + export class Class { + private constructor( + ... + } + } diff --git a/tests/specs/symbols/Classes01.txt b/tests/specs/symbols/Classes01.txt index 8eba319b9..736eeb22e 100644 --- a/tests/specs/symbols/Classes01.txt +++ b/tests/specs/symbols/Classes01.txt @@ -1136,7 +1136,20 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +2:19..24 [Id(("B", #2))] +3:27..50 [Id(("C", #2))] +7:109..599 [Id(("BBase", #2)), Id(("IBase", #2))] +8:152..190 [Id(("CtorProp", #2))] +9:193..209 [Id(("PropValue", #2))] +9:340..366 [Id(("PrivateProp", #2))] +10:212..239 [Id(("ReturnValue", #2))] +11:243..276 [Id(("Param", #2))] +12:280..336 [Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] +13:370..423 [Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] +13:426..510 [Id(("PrivateImplementationParam", #2)), Id(("PrivateImplementationReturn", #2))] + +== symbol deps (types only) == 2:19..24 [Id(("B", #2))] 3:27..50 [Id(("C", #2))] 7:109..599 [Id(("BBase", #2)), Id(("IBase", #2))] @@ -1148,9 +1161,6 @@ file:///mod.ts: EsmModuleInfo { 12:280..336 [Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] 13:370..423 [Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] 13:426..510 [Id(("PrivateImplementationParam", #2)), Id(("PrivateImplementationReturn", #2))] -28:946..977 [Id(("value", #1))] -29:980..1004 [Id(("value", #1))] -29:1007..1031 [Id(("value", #1))] == export definitions == [A]: file:///mod.ts:0..52 diff --git a/tests/specs/symbols/DeclarationMerging01.txt b/tests/specs/symbols/DeclarationMerging01.txt index 47c0713be..767dcdfbb 100644 --- a/tests/specs/symbols/DeclarationMerging01.txt +++ b/tests/specs/symbols/DeclarationMerging01.txt @@ -352,10 +352,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 259, + 268, ), end: SourcePos( - 289, + 273, ), }, flags: 0, @@ -383,10 +383,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 259, + 275, ), end: SourcePos( - 289, + 280, ), }, flags: 0, @@ -414,10 +414,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 259, + 282, ), end: SourcePos( - 289, + 286, ), }, flags: 0, diff --git a/tests/specs/symbols/ExportDefault01.txt b/tests/specs/symbols/ExportDefault01.txt index bf4a768ad..59d4e2c55 100644 --- a/tests/specs/symbols/ExportDefault01.txt +++ b/tests/specs/symbols/ExportDefault01.txt @@ -452,7 +452,11 @@ file:///a.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +5:172..180 [Id(("A", #2))] +8:213..218 [Id(("B", #2))] + +== symbol deps (types only) == 5:172..180 [Id(("A", #2))] 8:213..218 [Id(("B", #2))] @@ -926,10 +930,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 101, + 110, ), end: SourcePos( - 131, + 112, ), }, flags: 0, @@ -957,10 +961,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 101, + 114, ), end: SourcePos( - 131, + 116, ), }, flags: 0, @@ -988,10 +992,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 101, + 118, ), end: SourcePos( - 131, + 128, ), }, flags: 0, diff --git a/tests/specs/symbols/ExportDefault03.txt b/tests/specs/symbols/ExportDefault03.txt index 6832faeae..4c230e7d3 100644 --- a/tests/specs/symbols/ExportDefault03.txt +++ b/tests/specs/symbols/ExportDefault03.txt @@ -107,10 +107,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 37, + 46, ), end: SourcePos( - 64, + 61, ), }, flags: 0, diff --git a/tests/specs/symbols/ExportDefault04.txt b/tests/specs/symbols/ExportDefault04.txt index f6f7b7d34..839e8513a 100644 --- a/tests/specs/symbols/ExportDefault04.txt +++ b/tests/specs/symbols/ExportDefault04.txt @@ -34,21 +34,63 @@ file:///mod.ts: EsmModuleInfo { flags: 0, }, ], + child_ids: { + 1, + }, + exports: { + "default": 1, + }, + members: {}, + }, + 1: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 1, + parent_id: Some( + 0, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "export default 1 + 1;", + ), + ), + range: SourceRange { + start: SourcePos( + 0, + ), + end: SourcePos( + 21, + ), + }, + flags: 0, + }, + ], child_ids: {}, exports: {}, members: {}, }, }, } +== export definitions == +[default]: file:///mod.ts:0..21 + export default 1 + 1; # diagnostics [ { "kind": "UnsupportedDefaultExpr", - "specifier": "file:///mod.ts", - "line_and_column": { - "line_number": 1, - "column_number": 1 + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 21 + } } } ] diff --git a/tests/specs/symbols/ExportDestructured.txt b/tests/specs/symbols/ExportDestructured.txt index 07b6f7ff8..deb578357 100644 --- a/tests/specs/symbols/ExportDestructured.txt +++ b/tests/specs/symbols/ExportDestructured.txt @@ -198,11 +198,6 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == -2:34..81 [Id(("f", #0)), Id(("g", #0))] -3:97..232 [Id(("a", #0)), Id(("b", #0))] -4:97..232 [Id(("a", #0)), Id(("b", #0))] - == export definitions == [a]: file:///mod.ts:97..232 { diff --git a/tests/specs/symbols/ExportStar01.txt b/tests/specs/symbols/ExportStar01.txt index 66379f857..4ae4af501 100644 --- a/tests/specs/symbols/ExportStar01.txt +++ b/tests/specs/symbols/ExportStar01.txt @@ -447,7 +447,11 @@ file:///a.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +5:172..180 [Id(("A", #2))] +8:213..218 [Id(("B", #2))] + +== symbol deps (types only) == 5:172..180 [Id(("A", #2))] 8:213..218 [Id(("B", #2))] diff --git a/tests/specs/symbols/ExportedDeclare01.txt b/tests/specs/symbols/ExportedDeclare01.txt index 711c96a95..5db2734f6 100644 --- a/tests/specs/symbols/ExportedDeclare01.txt +++ b/tests/specs/symbols/ExportedDeclare01.txt @@ -195,10 +195,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 82, + 91, ), end: SourcePos( - 98, + 95, ), }, flags: 0, @@ -383,10 +383,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 201, + 210, ), end: SourcePos( - 246, + 220, ), }, flags: 0, @@ -414,10 +414,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 201, + 222, ), end: SourcePos( - 246, + 234, ), }, flags: 0, @@ -445,10 +445,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 201, + 236, ), end: SourcePos( - 246, + 243, ), }, flags: 0, @@ -460,7 +460,10 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +8:160..172 [Id(("Other", #2))] + +== symbol deps (types only) == 8:160..172 [Id(("Other", #2))] == export definitions == diff --git a/tests/specs/symbols/FunctionOverloads01.txt b/tests/specs/symbols/FunctionOverloads01.txt index 84f3665dc..4a12f94b5 100644 --- a/tests/specs/symbols/FunctionOverloads01.txt +++ b/tests/specs/symbols/FunctionOverloads01.txt @@ -284,10 +284,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 312, + 321, ), end: SourcePos( - 334, + 331, ), }, flags: 0, diff --git a/tests/specs/symbols/Functions01.txt b/tests/specs/symbols/Functions01.txt index 9d6be3909..cd6737f00 100644 --- a/tests/specs/symbols/Functions01.txt +++ b/tests/specs/symbols/Functions01.txt @@ -517,7 +517,12 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +1:0..77 [Id(("TypeParam", #2)), Id(("Default", #2)), Id(("Param", #2)), Id(("Return", #2))] +6:201..295 [Id(("OverloadTypeParam", #2)), Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] +6:334..428 [Id(("PrivateTypeParam", #2)), Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] + +== symbol deps (types only) == 1:0..77 [Id(("TypeParam", #2)), Id(("Default", #2)), Id(("Param", #2)), Id(("Return", #2))] 6:201..295 [Id(("OverloadTypeParam", #2)), Id(("OverloadParam", #2)), Id(("OverloadReturn", #2))] 6:334..428 [Id(("PrivateTypeParam", #2)), Id(("PrivateParam", #2)), Id(("PrivateReturn", #2))] diff --git a/tests/specs/symbols/ImportEquals02.txt b/tests/specs/symbols/ImportEquals02.txt index 63569dbaf..4daeacf72 100644 --- a/tests/specs/symbols/ImportEquals02.txt +++ b/tests/specs/symbols/ImportEquals02.txt @@ -327,10 +327,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 182, + 191, ), end: SourcePos( - 195, + 192, ), }, flags: 0, @@ -551,10 +551,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 119, + 128, ), end: SourcePos( - 150, + 147, ), }, flags: 0, diff --git a/tests/specs/symbols/ImportType01.txt b/tests/specs/symbols/ImportType01.txt index 68a501eb0..996429605 100644 --- a/tests/specs/symbols/ImportType01.txt +++ b/tests/specs/symbols/ImportType01.txt @@ -267,7 +267,10 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +2:26..53 [ImportType("./a.ts", ["A", "B"])] + +== symbol deps (types only) == 2:26..53 [ImportType("./a.ts", ["A", "B"])] == export definitions == diff --git a/tests/specs/symbols/ImportType02.txt b/tests/specs/symbols/ImportType02.txt index 36dc9beea..3d0604c8c 100644 --- a/tests/specs/symbols/ImportType02.txt +++ b/tests/specs/symbols/ImportType02.txt @@ -233,7 +233,10 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +1:0..43 [ImportType("./a.ts", [])] + +== symbol deps (types only) == 1:0..43 [ImportType("./a.ts", [])] == export definitions == diff --git a/tests/specs/symbols/Interfaces01.txt b/tests/specs/symbols/Interfaces01.txt index dbcc82572..d74029846 100644 --- a/tests/specs/symbols/Interfaces01.txt +++ b/tests/specs/symbols/Interfaces01.txt @@ -468,11 +468,18 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == 2:17..142 [Id(("Extends", #2))] 3:59..70 [Id(("Prop", #2))] -4:73..103 [Id(("method", #1)), Id(("Param", #2)), Id(("Return", #2))] -5:106..140 [Id(("key", #1)), Id(("SignatureValueType", #2))] +4:73..103 [Id(("Param", #2)), Id(("Return", #2))] +5:106..140 [Id(("SignatureValueType", #2))] +11:255..264 [Id(("B", #2))] + +== symbol deps (types only) == +2:17..142 [Id(("Extends", #2))] +3:59..70 [Id(("Prop", #2))] +4:73..103 [Id(("Param", #2)), Id(("Return", #2))] +5:106..140 [Id(("SignatureValueType", #2))] 11:255..264 [Id(("B", #2))] == export definitions == diff --git a/tests/specs/symbols/Interfaces02.txt b/tests/specs/symbols/Interfaces02.txt index 4f7c12a1e..62e5c1e2a 100644 --- a/tests/specs/symbols/Interfaces02.txt +++ b/tests/specs/symbols/Interfaces02.txt @@ -234,10 +234,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 88, + 97, ), end: SourcePos( - 102, + 99, ), }, flags: 0, @@ -249,7 +249,10 @@ file:///a.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +3:51..64 [Id(("Prop2", #2))] + +== symbol deps (types only) == 3:51..64 [Id(("Prop2", #2))] file:///mod.ts: EsmModuleInfo { @@ -496,10 +499,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 133, + 142, ), end: SourcePos( - 149, + 146, ), }, flags: 0, @@ -511,7 +514,11 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +2:42..55 [Id(("Prop1", #2))] +3:77..90 [Id(("Prop2", #2))] + +== symbol deps (types only) == 2:42..55 [Id(("Prop1", #2))] 3:77..90 [Id(("Prop2", #2))] diff --git a/tests/specs/symbols/MemberExpr.txt b/tests/specs/symbols/MemberExpr.txt index c098e318c..d9869ed3a 100644 --- a/tests/specs/symbols/MemberExpr.txt +++ b/tests/specs/symbols/MemberExpr.txt @@ -233,7 +233,11 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +1:0..51 [QualifiedId(("MyNamespace", #2), ["MyClass"])] +2:52..112 [QualifiedId(("MyNamespace", #2), ["MyClass"]), Id(("Test", #2))] + +== symbol deps (types only) == 1:0..51 [QualifiedId(("MyNamespace", #2), ["MyClass"])] 2:52..112 [QualifiedId(("MyNamespace", #2), ["MyClass"]), Id(("Test", #2))] diff --git a/tests/specs/symbols/ModuleExportNameStr01.txt b/tests/specs/symbols/ModuleExportNameStr01.txt index 09f5df379..d86415307 100644 --- a/tests/specs/symbols/ModuleExportNameStr01.txt +++ b/tests/specs/symbols/ModuleExportNameStr01.txt @@ -107,10 +107,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 51, + 60, ), end: SourcePos( - 85, + 82, ), }, flags: 0, diff --git a/tests/specs/symbols/Namespaces01.txt b/tests/specs/symbols/Namespaces01.txt index be0d56cfd..53c634754 100644 --- a/tests/specs/symbols/Namespaces01.txt +++ b/tests/specs/symbols/Namespaces01.txt @@ -314,10 +314,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 205, + 214, ), end: SourcePos( - 228, + 218, ), }, flags: 0, @@ -345,10 +345,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 205, + 220, ), end: SourcePos( - 228, + 225, ), }, flags: 0, diff --git a/tests/specs/symbols/NamespacesAmbient.txt b/tests/specs/symbols/NamespacesAmbient.txt index 28af3fd97..24775738d 100644 --- a/tests/specs/symbols/NamespacesAmbient.txt +++ b/tests/specs/symbols/NamespacesAmbient.txt @@ -1239,7 +1239,10 @@ file:///other.d.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +2:33..52 [QualifiedId(("Inner", #2), ["Other1"])] + +== symbol deps (types only) == 2:33..52 [QualifiedId(("Inner", #2), ["Other1"])] == export definitions == diff --git a/tests/specs/symbols/ReexportAll.txt b/tests/specs/symbols/ReexportAll.txt index 67f63f477..32f821368 100644 --- a/tests/specs/symbols/ReexportAll.txt +++ b/tests/specs/symbols/ReexportAll.txt @@ -255,10 +255,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 113, + 122, ), end: SourcePos( - 144, + 127, ), }, flags: 0, @@ -286,10 +286,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 113, + 129, ), end: SourcePos( - 144, + 134, ), }, flags: 0, @@ -317,10 +317,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 113, + 136, ), end: SourcePos( - 144, + 141, ), }, flags: 0, @@ -348,10 +348,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 145, + 154, ), end: SourcePos( - 169, + 159, ), }, flags: 0, @@ -379,10 +379,10 @@ file:///a.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 145, + 161, ), end: SourcePos( - 169, + 166, ), }, flags: 0, diff --git a/tests/specs/symbols/ReexportExport.txt b/tests/specs/symbols/ReexportExport.txt index 404379f85..9c8d1e6ce 100644 --- a/tests/specs/symbols/ReexportExport.txt +++ b/tests/specs/symbols/ReexportExport.txt @@ -97,10 +97,10 @@ file:///mod.ts: EsmModuleInfo { ), range: SourceRange { start: SourcePos( - 31, + 40, ), end: SourcePos( - 53, + 50, ), }, flags: 0, diff --git a/tests/specs/symbols/TsExternalModuleRef01.txt b/tests/specs/symbols/TsExternalModuleRef01.txt index 598410448..97709f362 100644 --- a/tests/specs/symbols/TsExternalModuleRef01.txt +++ b/tests/specs/symbols/TsExternalModuleRef01.txt @@ -271,7 +271,10 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +2:31..61 [QualifiedId(("A", #2), ["B"])] + +== symbol deps (types only) == 2:31..61 [QualifiedId(("A", #2), ["B"])] == export definitions == diff --git a/tests/specs/symbols/TsQualifiedName01.txt b/tests/specs/symbols/TsQualifiedName01.txt index 17a2d2f5b..d5b41714d 100644 --- a/tests/specs/symbols/TsQualifiedName01.txt +++ b/tests/specs/symbols/TsQualifiedName01.txt @@ -194,7 +194,10 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +1:0..37 [QualifiedId(("Other", #2), ["Test"])] + +== symbol deps (types only) == 1:0..37 [QualifiedId(("Other", #2), ["Test"])] == export definitions == diff --git a/tests/specs/symbols/TypeAlias01.txt b/tests/specs/symbols/TypeAlias01.txt index 8a48196bb..55c1034f4 100644 --- a/tests/specs/symbols/TypeAlias01.txt +++ b/tests/specs/symbols/TypeAlias01.txt @@ -4,6 +4,9 @@ export type Test = Other2; interface Other {} interface Other2 {} +// should not have lat and long in deps +export type Tuple = [lat: number, long: number]; + # output file:///mod.ts: EsmModuleInfo { module_id: ModuleId( @@ -24,6 +27,10 @@ file:///mod.ts: EsmModuleInfo { "Other2", #2, ): 3, + ( + "Tuple", + #2, + ): 4, }, symbols: { 0: Symbol { @@ -36,7 +43,7 @@ file:///mod.ts: EsmModuleInfo { SymbolDecl { kind: Definition( SymbolNode( - "export type Test = Other2;\n\ninterface Other {}\ninterface Other2 {}", + "export type Test = Other2;\n\ninterface Other {}\ninterface Other2 {}\n\n// should not have lat and long in deps\nexport type Tuple = [lat: number, long: number];", ), ), range: SourceRange { @@ -44,7 +51,7 @@ file:///mod.ts: EsmModuleInfo { 0, ), end: SourcePos( - 83, + 173, ), }, flags: 0, @@ -54,9 +61,11 @@ file:///mod.ts: EsmModuleInfo { 1, 2, 3, + 4, }, exports: { "Test": 1, + "Tuple": 4, }, members: {}, }, @@ -150,11 +159,46 @@ file:///mod.ts: EsmModuleInfo { exports: {}, members: {}, }, + 4: Symbol { + module_id: ModuleId( + 0, + ), + symbol_id: 4, + parent_id: Some( + 0, + ), + decls: [ + SymbolDecl { + kind: Definition( + SymbolNode( + "export type Tuple = [lat: number, long: number];", + ), + ), + range: SourceRange { + start: SourcePos( + 125, + ), + end: SourcePos( + 173, + ), + }, + flags: 0, + }, + ], + child_ids: {}, + exports: {}, + members: {}, + }, }, } -== symbol deps == +== symbol deps (types and exprs) == +1:0..43 [Id(("Other", #2)), Id(("Other2", #2))] + +== symbol deps (types only) == 1:0..43 [Id(("Other", #2)), Id(("Other2", #2))] == export definitions == [Test]: file:///mod.ts:0..43 export type Test = Other2; +[Tuple]: file:///mod.ts:125..173 + export type Tuple = [lat: number, long: number]; diff --git a/tests/specs/symbols/TypeScriptTypesHeader.txt b/tests/specs/symbols/TypeScriptTypesHeader.txt index 87681cddc..153ce138f 100644 --- a/tests/specs/symbols/TypeScriptTypesHeader.txt +++ b/tests/specs/symbols/TypeScriptTypesHeader.txt @@ -221,7 +221,10 @@ file:///mod.ts: EsmModuleInfo { }, }, } -== symbol deps == +== symbol deps (types and exprs) == +4:148..223 [Id(("MyInterface", #2)), Id(("MyInterface2", #2)), Id(("MyInterface3", #2))] + +== symbol deps (types only) == 4:148..223 [Id(("MyInterface", #2)), Id(("MyInterface2", #2)), Id(("MyInterface3", #2))] file:///other.d.ts: EsmModuleInfo {