diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index f2f87105..c10ea2aa 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -26,7 +26,7 @@ jobs: sponsors: ${{ steps.get-sponsors.outputs.sponsors }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-tags: true @@ -93,9 +93,9 @@ jobs: # Important that everything here works in all the above OSes! steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} @@ -122,7 +122,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/lines-of-code.yml b/.github/workflows/lines-of-code.yml index 1c21c3f3..52364079 100644 --- a/.github/workflows/lines-of-code.yml +++ b/.github/workflows/lines-of-code.yml @@ -8,7 +8,7 @@ jobs: update: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download scc run: | mkdir scc diff --git a/.github/workflows/performance-and-size.yml b/.github/workflows/performance-and-size.yml index b95ae6a4..94dd46f8 100644 --- a/.github/workflows/performance-and-size.yml +++ b/.github/workflows/performance-and-size.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | ~/.cargo/bin/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 746c3403..4bb3ef30 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,8 +34,8 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 308beb93..8d6155cd 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -30,10 +30,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} @@ -47,7 +47,7 @@ jobs: formating: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Check Rust formatting with rustfmt @@ -65,14 +65,14 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: changes with: filters: | @@ -82,16 +82,16 @@ jobs: - 'checker/**' - name: Run parser tests - if: steps.changes.outputs.parser == 'true' + if: steps.changes.outputs.parser == 'true' || github.ref_name == 'main' run: | cargo test curl https://esm.sh/v128/react-dom@18.2.0/es2022/react-dom.mjs > react.js - cargo run -p ezno-parser --example parse react.js --timings --render-timings + cargo run -p ezno-parser --example parse react.js working-directory: parser - name: Run checker specification - if: steps.changes.outputs.checker == 'true' && github.event_name != 'pull_request' + if: (steps.changes.outputs.checker == 'true' && github.event_name != 'pull_request') || github.ref_name == 'main' run: cargo test working-directory: checker/specification @@ -103,7 +103,7 @@ jobs: EZNO_DEBUG: 1 - name: Run checker tests - if: steps.changes.outputs.checker == 'true' + if: steps.changes.outputs.checker == 'true' || github.ref_name == 'main' run: | # Test checker with the parser features cargo test -F ezno-parser @@ -117,15 +117,15 @@ jobs: needs: validity timeout-minutes: 10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: changes with: filters: | @@ -137,20 +137,38 @@ jobs: - 'checker/**' - uses: brndnmtthws/rust-action-cargo-binstall@v1 - if: steps.changes.outputs.src == 'true' + if: steps.changes.outputs.src == 'true' || github.ref_name == 'main' with: packages: wasm-pack@0.12.1 - uses: denoland/setup-deno@v1 - if: steps.changes.outputs.src == 'true' + if: steps.changes.outputs.src == 'true' || github.ref_name == 'main' with: deno-version: v1.x - uses: actions/setup-node@v3 - if: steps.changes.outputs.src == 'true' + if: steps.changes.outputs.src == 'true' || github.ref_name == 'main' with: node-version: 18 + + - name: Check parser without extras + if: steps.changes.outputs.parser == 'true' + # TODO want `continue-on-error: true` but doesn't report error + run: + cargo check -p ezno-parser --no-default-features + + - name: Check parser generator + if: steps.changes.outputs.parser == 'true' + # TODO want `continue-on-error: true` but doesn't report error + run: + cargo test -p ezno-ast-generator + + - name: Check checker without default features + if: steps.changes.outputs.checker == 'true' + # TODO want `continue-on-error: true` but doesn't report error + run: + cargo check -p ezno-checker --no-default-features - name: Build and test WASM - if: steps.changes.outputs.src == 'true' + if: steps.changes.outputs.src == 'true' || github.ref_name == 'main' # TODO want `continue-on-error: true` but doesn't report error run: | # TODO `cargo check --target wasm32-unknown-unknown --lib` might be good enough @@ -164,9 +182,10 @@ jobs: deno run -A ./dist/cli.mjs info npx -p typescript tsc --strict --pretty ./build/ezno_lib.d.ts + echo "debug checked with TSC" + cargo run -p ezno-parser --example parse ./build/ezno_lib.d.ts --type-definition-module # TODO temp as the types generated can be a bit unpredicatible - echo "::debug::On branch '${{ github.ref_name }}'" if ${{ contains(fromJSON('["main", "ast-typegen-direct"]'), github.ref_name ) }}; then npm run build-release npx -p typescript tsc --strict --pretty ./build/ezno_lib.d.ts @@ -174,24 +193,6 @@ jobs: working-directory: src/js-cli-and-library shell: bash - - name: Check parser without extras - if: steps.changes.outputs.parser == 'true' - # TODO want `continue-on-error: true` but doesn't report error - run: - cargo check -p ezno-parser --no-default-features - - - name: Check parser generator - if: steps.changes.outputs.parser == 'true' - # TODO want `continue-on-error: true` but doesn't report error - run: - cargo test -p ezno-ast-generator - - - name: Check checker without default features - if: steps.changes.outputs.checker == 'true' - # TODO want `continue-on-error: true` but doesn't report error - run: - cargo check -p ezno-checker --no-default-features - fuzzing: needs: validity runs-on: ubuntu-latest @@ -202,14 +203,14 @@ jobs: fuzz-target: [module_roundtrip_naive, module_roundtrip_structured] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: changes with: filters: | @@ -228,9 +229,19 @@ jobs: packages: cargo-fuzz - name: Run fuzzing + env: + SHORT_CIRCUIT: true if: steps.changes.outputs.parser == 'true' run: | - cargo fuzz run -s none ${{ matrix.fuzz-target }} -- -timeout=10 -max_total_time=120 -use_value_profile=1 + if ${{ env.SHORT_CIRCUIT }}; then + cargo fuzz run -s none ${{ matrix.fuzz-target }} -- -timeout=10 -use_value_profile=1 -max_total_time=120 + else + cargo fuzz run -s none ${{ matrix.fuzz-target }} -- -timeout=10 -use_value_profile=1 -max_total_time=300 -fork=1 -ignore_crashes=1 + + if test -d fuzz/artifacts; then + find fuzz/artifacts -type f -print -exec xxd {} \; -exec cargo fuzz fmt -s none module_roundtrip_structured {} \;; false; + fi + fi working-directory: parser/fuzz clippy: @@ -238,9 +249,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} @@ -253,10 +264,10 @@ jobs: # `publish --dry-run` reports too many false positives and doesn't catch actual errors. So disabling for now if: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: changes with: filters: | diff --git a/Cargo.lock b/Cargo.lock index 0626c4b2..081038e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,7 +21,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.52", ] [[package]] @@ -41,9 +41,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bimap" @@ -66,21 +66,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" [[package]] name = "cfg-if" @@ -100,15 +100,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -123,71 +123,66 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.10" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "derive-debug-extras" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e7a68c77a933db6b8907154fbd45f15f6ccc11f796b00b04354b8899d9d131" +checksum = "b06a04c5959ac637aa6063625751357337a9067038e9cb194fd197861b01aa0d" dependencies = [ "syn-helpers", ] [[package]] name = "derive-enum-from-into" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc2a1b7c0031fb651e9bc1fa4255da82747c187b9ac1dc36b3783d71fadd9d5" +checksum = "42e5ddace13a8459cb452b19e01f59f16d3e2049c8b808f338a13eeadc326e33" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] name = "derive-finite-automaton" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18c3334a02026fc281427161fea9dacbf650d8b7b01e35a7cf445403c318221" +checksum = "9c7422b4dd6fa30fcdc1a90811502d5212697fae80568dca8d5a4e4965f4cc3d" dependencies = [ "derive-finite-automaton-derive", ] [[package]] name = "derive-finite-automaton-derive" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4bb462e792fda597bc05207ebf41948ff8e7c5a6ab737bd5c75286193fc3613" +checksum = "c26ee8656bfb9b2763f2522162e43752d983c0c43cf1c5364028fd5e219df693" dependencies = [ "either_n", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] name = "derive-partial-eq-extras" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98494f2b89f1fa56b5cff4c1a4beb84441d51b5042fb74a403a7d01ab5fd4a19" +checksum = "8de8afba3dd2062a2b599b198b30118f902c29bea9c8f82ea071023122079bdf" dependencies = [ - "syn 1.0.109", "syn-helpers", ] @@ -199,9 +194,9 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "either_n" @@ -217,24 +212,24 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "enum-variants-strings" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "208ec1cfed58007d641f74552a523a405cd374417ec65ba01fb89ab2796054a1" +checksum = "bf78b873650dcfe36b4f4b39cbad0615bfdda80b0dbd302bcc43f729e26d9cfc" dependencies = [ "enum-variants-strings-derive", ] [[package]] name = "enum-variants-strings-derive" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acea45431925e008a911e3fded23d302c9eb81493e7b9cae0c5aa29a9342a5a" +checksum = "5c219bbaeb0dfcf75ef2a911d080245affeb04ad8da4b53986a0e4bbe6b966a2" dependencies = [ "either_n", "proc-macro2", "quote", "string-cases", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -366,18 +361,18 @@ dependencies = [ [[package]] name = "get-field-by-type" -version = "0.0.3" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f4ba71362701e2913eabce7b108cf6f2db1a413f661138862f2632b117ab98" +checksum = "510f901658aa70b274793aeb82d6ca00f423a7421c82024f64f3bb79f39bb0fc" dependencies = [ "get-field-by-type-derive", ] [[package]] name = "get-field-by-type-derive" -version = "0.0.3" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1777fe05ec14229f7eede7ecb245051776a405a90501139aa6967a9eba7feb" +checksum = "3931371c1191271ed6608fb4ac821fbab479f0defa5b1d85cdc756736209225c" dependencies = [ "syn-helpers", ] @@ -403,9 +398,9 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown", @@ -486,15 +481,15 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "macro_rules_attribute" @@ -559,7 +554,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "crossbeam-channel", "filetime", "fsevent-sys", @@ -574,9 +569,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -632,9 +627,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.74" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -668,9 +663,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -683,9 +678,9 @@ dependencies = [ [[package]] name = "self-rust-tokenize" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235c30ffb76b003dc69cc556d0d5ab8bb561a40572b1710fd641c69a3d6c4078" +checksum = "45c3191133be4b3e5c19f3a9f23efb784e982060373c0f3c9f3362fcfcb5d1e6" dependencies = [ "proc-macro2", "quote", @@ -694,28 +689,27 @@ dependencies = [ [[package]] name = "self-rust-tokenize-derive" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e075556adeec065f185a7811a9808f8523c1299aa7e318b3dd7d1eb72417f6b" +checksum = "0f0d9ce060439c2bc0821a7e8b3123ee1743bebd88101a9357c3405010ed7826" dependencies = [ - "syn 1.0.109", "syn-helpers", ] [[package]] name = "serde" -version = "1.0.194" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde-wasm-bindgen" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b713f70513ae1f8d92665bbbbda5c295c2cf1da5542881ae5eefe20c9af132" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" dependencies = [ "js-sys", "serde", @@ -724,13 +718,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.194" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.52", ] [[package]] @@ -741,14 +735,14 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.52", ] [[package]] name = "serde_json" -version = "1.0.110" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -787,9 +781,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.46" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -798,14 +792,14 @@ dependencies = [ [[package]] name = "syn-helpers" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2919350d44e7bdba75bf541da7a15c415a84fea1c823f37f0c48a6b0133639" +checksum = "59c391dcee7e7a1efd7dc921922b97b4be54129a195b6baec95bb2a223203d9d" dependencies = [ "either_n", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -816,9 +810,9 @@ checksum = "7e9c16d33fb2759286102fa235fd7029a8de2c2961165b2942c9cac81607a044" [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -854,7 +848,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.46", + "syn 2.0.52", ] [[package]] @@ -906,7 +900,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.52", "wasm-bindgen-shared", ] @@ -950,7 +944,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1013,15 +1007,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -1037,22 +1022,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -1072,25 +1042,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -1099,15 +1063,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -1117,15 +1075,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -1135,15 +1087,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -1153,15 +1099,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -1171,15 +1111,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -1189,15 +1123,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -1207,9 +1135,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "yansi" diff --git a/Cargo.toml b/Cargo.toml index fc0b1187..45ad2cc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,22 +10,6 @@ members = [ ] -[workspace.lints.clippy] -all = "deny" -pedantic = "deny" -cast_precision_loss = "warn" -cast_possible_truncation = "warn" -cast_sign_loss = "warn" -default_trait_access = "allow" -missing_errors_doc = "allow" -missing_panics_doc = "allow" -implicit_hasher = "allow" -module_name_repetitions = "allow" -too_many_lines = "allow" -new_without_default = "allow" -result_unit_err = "allow" - - [package] name = "ezno" description = "A JavaScript type checker and compiler. For use as a library or through the CLI" @@ -54,13 +38,13 @@ path = "src/main.rs" [dependencies] # ezno-web-framework = { path = "./plugins/web" } -console = "0.15.7" -codespan-reporting = "0.11.1" -argh = "0.1.6" -base64 = "0.21.5" -enum-variants-strings = "0.2.2" +console = "0.15" +codespan-reporting = "0.11" +argh = "0.1" +base64 = "0.21" +enum-variants-strings = "0.3" serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.107" +serde_json = "1.0" [dependencies.checker] path = "./checker" @@ -87,3 +71,18 @@ tsify = "0.4.5" multiline-term-input = "0.1.0" notify = "6.1.0" + +[workspace.lints.clippy] +all = "deny" +pedantic = "deny" +cast_precision_loss = "warn" +cast_possible_truncation = "warn" +cast_sign_loss = "warn" +default_trait_access = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" +implicit_hasher = "allow" +module_name_repetitions = "allow" +too_many_lines = "allow" +new_without_default = "allow" +result_unit_err = "allow" diff --git a/checker/Cargo.toml b/checker/Cargo.toml index fff8f97d..54782566 100644 --- a/checker/Cargo.toml +++ b/checker/Cargo.toml @@ -29,11 +29,11 @@ source-map = { version = "0.14.10", features = [ binary-serialize-derive = { path = "./binary-serialize-derive", version = "0.0.1" } temporary-annex = "0.1.0" -derive-enum-from-into = "0.1.0" -derive-debug-extras = { version = "0.2.0", features = [ +derive-enum-from-into = "0.2" +derive-debug-extras = { version = "0.3", features = [ "auto-debug-single-tuple-inline", ] } -enum-variants-strings = "0.2" +enum-variants-strings = "0.3" iterator-endiate = "0.2" bimap = "0.6.2" diff --git a/checker/binary-serialize-derive/Cargo.toml b/checker/binary-serialize-derive/Cargo.toml index 51ae31af..e87f0484 100644 --- a/checker/binary-serialize-derive/Cargo.toml +++ b/checker/binary-serialize-derive/Cargo.toml @@ -11,7 +11,7 @@ authors = ["Ben "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -syn-helpers = "0.4.2" +syn-helpers = "0.5" [lib] path = "macro.rs" diff --git a/checker/binary-serialize-derive/macro.rs b/checker/binary-serialize-derive/macro.rs index 8d6d8cd5..d124e0d4 100644 --- a/checker/binary-serialize-derive/macro.rs +++ b/checker/binary-serialize-derive/macro.rs @@ -78,7 +78,7 @@ pub fn derive_binary_serializable(input: TokenStream) -> TokenStream { } Structure::Struct(r#struct) => r#struct .build_constructor(|_| Ok(deserialize_call.clone())) - .map(|expr| vec![Stmt::Expr(expr)]), + .map(|expr| vec![Stmt::Expr(expr, None)]), } }, ), diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 7de3bf25..839bd507 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -1942,10 +1942,10 @@ import { a } from "./export"; console.log(a.prop); // in export.ts -export default const x = 2; +export default const; ``` -- Expected SemiColon found Identifier(\"x\") +- Found reserved identifier #### Only synthesis module once diff --git a/checker/src/diagnostics.rs b/checker/src/diagnostics.rs index 2a841f58..0cf2ef6f 100644 --- a/checker/src/diagnostics.rs +++ b/checker/src/diagnostics.rs @@ -758,13 +758,14 @@ mod defined_errors_and_warnings { MergingInterfaceInSameContext { position: SpanWithSource, }, + InvalidOrUnimplementedDefinitionFileItem(SpanWithSource), } impl From for Diagnostic { - fn from(val: TypeCheckWarning) -> Self { + fn from(warning: TypeCheckWarning) -> Self { let kind = super::DiagnosticKind::Warning; - match val { + match warning { TypeCheckWarning::AwaitUsedOnNonPromise(position) => Diagnostic::Position { reason: "Unnecessary await expression / type is not promise".to_owned(), position, @@ -799,6 +800,14 @@ mod defined_errors_and_warnings { kind, } } + TypeCheckWarning::InvalidOrUnimplementedDefinitionFileItem(position) => { + Diagnostic::Position { + reason: "Invalid (or unimplemented) item in definition file skipped" + .to_owned(), + position, + kind, + } + } } } } diff --git a/checker/src/synthesis/assignments.rs b/checker/src/synthesis/assignments.rs index bd048110..577c1284 100644 --- a/checker/src/synthesis/assignments.rs +++ b/checker/src/synthesis/assignments.rs @@ -86,7 +86,8 @@ pub(super) fn synthesise_lhs_of_assignment_to_reference( parser::VariableField::Array(_, _) => todo!(), parser::VariableField::Object(_, _) => todo!(), }, - parser::ArrayDestructuringField::None => None, + parser::ArrayDestructuringField::Comment { .. } + | parser::ArrayDestructuringField::None => None, }) .collect(), ), diff --git a/checker/src/synthesis/block.rs b/checker/src/synthesis/block.rs index ebad5d85..3ac2b098 100644 --- a/checker/src/synthesis/block.rs +++ b/checker/src/synthesis/block.rs @@ -95,6 +95,7 @@ pub(crate) fn synthesise_declaration( | Declaration::Enum(_) | Declaration::Interface(_) | Declaration::TypeAlias(_) + | Declaration::Namespace(_) | Declaration::Import(_) => {} Declaration::Export(exported) => match &exported.on { parser::declarations::ExportDeclaration::Variable { exported, position: _ } => { @@ -159,6 +160,9 @@ pub(crate) fn synthesise_declaration( ); } } + parser::declarations::ExportDeclaration::DefaultFunction { .. } => { + todo!() + } }, } } diff --git a/checker/src/synthesis/definitions.rs b/checker/src/synthesis/definitions.rs index fc574a6c..1b4c477e 100644 --- a/checker/src/synthesis/definitions.rs +++ b/checker/src/synthesis/definitions.rs @@ -1,7 +1,8 @@ -use parser::ASTNode; +use parser::{ASTNode, Declaration, Statement, StatementOrDeclaration}; use crate::{ context::{Names, RootContext, VariableRegisterArguments}, + diagnostics::TypeCheckWarning, synthesis::{ functions::synthesise_function_annotation, type_annotations::synthesise_type_annotation, }, @@ -11,7 +12,7 @@ use crate::{ /// Interprets a definition module (.d.ts) and produces a [Environment]. Consumes the [`TypeDefinitionModule`] /// TODO remove unwraps here and add to the existing error handler pub(super) fn type_definition_file( - definition: parser::TypeDefinitionModule, + definition: parser::Module, checking_data: &mut crate::CheckingData, root: &RootContext, ) -> (Names, Facts) { @@ -19,20 +20,20 @@ pub(super) fn type_definition_file( use parser::{ declarations::{DeclareVariableDeclaration, TypeAlias}, - TypeDeclaration, TypeDefinitionModuleDeclaration, + TypeDeclaration, }; let mut idx_to_types = HashMap::new(); // TODO NULL let source = source_map::Nullable::NULL; - let mut env = root.new_lexical_environment(crate::Scope::DefinitionModule { source }); + let mut environment = root.new_lexical_environment(crate::Scope::DefinitionModule { source }); // Hoisting names of interfaces, namespaces and types - // At some point with binaries could remove this pass - for statement in &definition.declarations { + for statement in &definition.items { + // TODO classes and exports match statement { - TypeDefinitionModuleDeclaration::Interface(interface) => { - let ty = env.new_interface( + StatementOrDeclaration::Declaration(Declaration::Interface(interface)) => { + let ty = environment.new_interface( &interface.on.name, interface.on.is_nominal, interface.on.type_parameters.as_deref(), @@ -42,16 +43,8 @@ pub(super) fn type_definition_file( ); idx_to_types.insert(interface.on.position.start, ty); } - TypeDefinitionModuleDeclaration::Class(_class) => { - todo!(); - // ( - // class.type_id, - // // env.register_type(&class.name, class.type_parameters.is_some(), None), - // env.register_type(Type::NamedRooted { name class.name.clone())), - // ), - } - TypeDefinitionModuleDeclaration::TypeAlias(alias) => { - env.new_alias( + StatementOrDeclaration::Declaration(Declaration::TypeAlias(alias)) => { + environment.new_alias( &alias.type_name.name, alias.type_name.type_parameters.as_deref(), &alias.type_expression, @@ -63,16 +56,17 @@ pub(super) fn type_definition_file( } } - for declaration in definition.declarations { + for declaration in definition.items { + // TODO more match declaration { - TypeDefinitionModuleDeclaration::Function(func) => { + StatementOrDeclaration::Declaration(Declaration::DeclareFunction(func)) => { // TODO abstract let declared_at = func.get_position().with_source(source); let base = synthesise_function_annotation( &func.type_parameters, &func.parameters, func.return_type.as_ref(), - &mut env, + &mut environment, checking_data, func.performs.as_ref().into(), &declared_at, @@ -92,7 +86,7 @@ pub(super) fn type_definition_file( let _context = decorators_to_context(&func.decorators); - env.register_variable_handle_error( + environment.register_variable_handle_error( func.name.as_str(), VariableRegisterArguments { constant: true, @@ -103,18 +97,15 @@ pub(super) fn type_definition_file( &mut checking_data.diagnostics_container, ); } - TypeDefinitionModuleDeclaration::Variable(DeclareVariableDeclaration { - keyword: _, - declarations, - position: _, - decorators: _, - }) => { + StatementOrDeclaration::Declaration(Declaration::DeclareVariable( + DeclareVariableDeclaration { keyword: _, declarations, position: _, decorators: _ }, + )) => { for declaration in &declarations { // TODO is it ever `None`...? let constraint = declaration.type_annotation.as_ref().map_or( TypeId::ANY_TYPE, |annotation| { - synthesise_type_annotation(annotation, &mut env, checking_data) + synthesise_type_annotation(annotation, &mut environment, checking_data) }, ); @@ -123,33 +114,27 @@ pub(super) fn type_definition_file( )); crate::synthesis::variables::register_variable( declaration.name.get_ast_ref(), - &mut env, + &mut environment, checking_data, VariableRegisterArguments { constant: true, space: None, initial_value }, ); } } - TypeDefinitionModuleDeclaration::Interface(interface) => { + StatementOrDeclaration::Declaration(Declaration::Interface(interface)) => { let ty = idx_to_types.remove(&interface.on.position.start).unwrap(); super::interfaces::synthesise_signatures( interface.on.type_parameters.as_deref(), &interface.on.members, super::interfaces::OnToType(ty), - &mut env, + &mut environment, checking_data, ); } - // TODO handle locals differently, (maybe squash ast as well) - TypeDefinitionModuleDeclaration::LocalTypeAlias(TypeAlias { - type_name, - type_expression: _, - .. - }) - | TypeDefinitionModuleDeclaration::TypeAlias(TypeAlias { + StatementOrDeclaration::Declaration(Declaration::TypeAlias(TypeAlias { type_name, type_expression: _, .. - }) => { + })) => { let TypeDeclaration { type_parameters, .. } = &type_name; // To remove when implementing @@ -179,41 +164,16 @@ pub(super) fn type_definition_file( // env.register_type(ty); } } - TypeDefinitionModuleDeclaration::Namespace(_) => unimplemented!(), - TypeDefinitionModuleDeclaration::LocalVariableDeclaration(_) => { - unimplemented!() - } - TypeDefinitionModuleDeclaration::Comment(_comment) => {} - TypeDefinitionModuleDeclaration::Class(_class) => { - todo!(); - // let existing_type = - // checking_data.type_mappings.get_type_declaration(&class.type_id).unwrap(); - // if let Some(extends_type) = &class.extends { - // let extending_type = root - // .get_type( - // extends_type, - // checking_data, - // &crate::root::GetTypeFromReferenceOptions::Default, - // ) - // .expect("Class should have been initialised"); - // todo!(); - // // match existing_type { - // // TypeDeclaration::NonGenericType(ngt) => { - // // // *ngt.extends.borrow_mut() = ExtendsType::Single(extending_type); - // // } - // // TypeDeclaration::GenericType(gt) => { - // // // *gt.extends.borrow_mut() = ExtendsType::Single(extending_type); - // // } - // // TypeDeclaration::PrimitiveType(_) => unreachable!(), - // // } - // } - // TODO parse members - // for member in class. - } + StatementOrDeclaration::Statement(Statement::Comment(..) | Statement::Empty(..)) => {} + item => checking_data.diagnostics_container.add_warning( + TypeCheckWarning::InvalidOrUnimplementedDefinitionFileItem( + item.get_position().with_source(environment.get_source()), + ), + ), } } - let Environment { named_types, facts, variable_names, variables, .. } = env; + let Environment { named_types, facts, variable_names, variables, .. } = environment; (Names { variables, named_types, variable_names }, facts) } diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index ce4c8e36..c613ec78 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -3,11 +3,12 @@ use std::{borrow::Cow, convert::TryInto}; use parser::{ expressions::{ object_literal::{ObjectLiteral, ObjectLiteralMember}, - ArrayElement, MultipleExpression, SpecialOperators, SpreadExpression, SuperReference, + operators::IncrementOrDecrement, + operators::{BinaryOperator, UnaryOperator, UnaryPrefixAssignmentOperator}, + ArrayElement, FunctionArgument, MultipleExpression, SpecialOperators, SuperReference, TemplateLiteral, }, functions::MethodHeader, - operators::{BinaryOperator, UnaryOperator, UnaryPrefixAssignmentOperator}, ASTNode, Expression, }; @@ -100,7 +101,7 @@ pub(super) fn synthesise_expression( checking_data: &mut CheckingData, ) -> Option<(PropertyKey<'static>, TypeId)> { element.0.as_ref().map(|element| match element { - SpreadExpression::NonSpread(element) => { + FunctionArgument::Standard(element) => { // TODO based off above let expecting = TypeId::ANY_TYPE; let expression_type = @@ -113,7 +114,7 @@ pub(super) fn synthesise_expression( expression_type, ) } - SpreadExpression::Spread(_expr, position) => { + FunctionArgument::Spread(_expr, position) => { { checking_data.raise_unimplemented_error( "Spread elements", @@ -129,6 +130,7 @@ pub(super) fn synthesise_expression( TypeId::ERROR_TYPE, ) } + FunctionArgument::Comment { .. } => todo!(), }) } @@ -444,10 +446,10 @@ pub(super) fn synthesise_expression( lhs, crate::features::assignments::AssignmentKind::IncrementOrDecrement( match direction { - parser::operators::IncrementOrDecrement::Increment => { + IncrementOrDecrement::Increment => { crate::features::assignments::IncrementOrDecrement::Increment } - parser::operators::IncrementOrDecrement::Decrement => { + IncrementOrDecrement::Decrement => { crate::features::assignments::IncrementOrDecrement::Decrement } }, @@ -467,12 +469,12 @@ pub(super) fn synthesise_expression( checking_data, )); match operator { - parser::operators::UnaryPostfixAssignmentOperator(direction) => { + parser::expressions::operators::UnaryPostfixAssignmentOperator(direction) => { let direction = match direction { - parser::operators::IncrementOrDecrement::Increment => { + IncrementOrDecrement::Increment => { crate::features::assignments::IncrementOrDecrement::Increment } - parser::operators::IncrementOrDecrement::Decrement => { + IncrementOrDecrement::Decrement => { crate::features::assignments::IncrementOrDecrement::Decrement } }; @@ -692,10 +694,9 @@ pub(super) fn synthesise_expression( Expression::JSXRoot(jsx_root) => { Instance::RValue(synthesise_jsx_root(jsx_root, environment, checking_data)) } - Expression::Comment { on: Some(on), .. } => { + Expression::Comment { on, .. } => { return synthesise_expression(on, environment, checking_data, expecting); } - Expression::Comment { on: None, .. } => return TypeId::ERROR_TYPE, Expression::ParenthesizedExpression(inner_expression, _) => Instance::RValue( synthesise_multiple_expression(inner_expression, environment, checking_data, expecting), ), @@ -707,7 +708,7 @@ pub(super) fn synthesise_expression( return TypeId::ERROR_TYPE; } Expression::SpecialOperators(operator, position) => match operator { - SpecialOperators::AsExpression { value, .. } => { + SpecialOperators::AsCast { value, .. } => { checking_data.diagnostics_container.add_warning( TypeCheckWarning::IgnoringAsExpression( position.with_source(environment.get_source()), @@ -716,10 +717,10 @@ pub(super) fn synthesise_expression( return synthesise_expression(value, environment, checking_data, expecting); } - SpecialOperators::IsExpression { value: _, type_annotation: _ } => { + SpecialOperators::Is { value: _, type_annotation: _ } => { todo!() } - SpecialOperators::SatisfiesExpression { value, type_annotation, .. } => { + SpecialOperators::Satisfies { value, type_annotation, .. } => { let value = synthesise_expression(value, environment, checking_data, expecting); let satisfying = synthesise_type_annotation(type_annotation, environment, checking_data); @@ -733,7 +734,7 @@ pub(super) fn synthesise_expression( return value; } - SpecialOperators::InExpression { lhs, rhs } => { + SpecialOperators::In { lhs, rhs } => { let lhs = match lhs { parser::expressions::InExpressionLHS::PrivateProperty(_) => { checking_data.raise_unimplemented_error( @@ -752,13 +753,14 @@ pub(super) fn synthesise_expression( Instance::RValue(if result { TypeId::TRUE } else { TypeId::FALSE }) } - SpecialOperators::InstanceOfExpression { .. } => { + SpecialOperators::InstanceOf { .. } => { checking_data.raise_unimplemented_error( "instanceof expression", position.with_source(environment.get_source()), ); return TypeId::ERROR_TYPE; } + SpecialOperators::NonNullAssertion(_) => todo!(), }, Expression::DynamicImport { position, .. } => { checking_data.raise_unimplemented_error( @@ -782,10 +784,10 @@ pub(super) fn synthesise_expression( } fn operator_to_assignment_kind( - operator: parser::operators::BinaryAssignmentOperator, + operator: parser::expressions::operators::BinaryAssignmentOperator, ) -> crate::features::assignments::AssignmentKind { use crate::features::assignments::AssignmentKind; - use parser::operators::BinaryAssignmentOperator; + use parser::expressions::operators::BinaryAssignmentOperator; match operator { BinaryAssignmentOperator::LogicalAndAssign => { @@ -841,7 +843,7 @@ fn call_function( function_type_id: TypeId, called_with_new: CalledWithNew, type_arguments: &Option>, - arguments: Option<&Vec>, + arguments: Option<&Vec>, environment: &mut Environment, checking_data: &mut CheckingData, call_site: parser::Span, @@ -863,12 +865,13 @@ fn call_function( arguments .iter() .map(|a| match a { - SpreadExpression::Spread(e, _) => { + FunctionArgument::Spread(e, _) => { UnsynthesisedArgument { spread: true, expression: e } } - SpreadExpression::NonSpread(e) => { + FunctionArgument::Standard(e) => { UnsynthesisedArgument { spread: false, expression: e } } + FunctionArgument::Comment { .. } => todo!(), }) .collect() }) diff --git a/checker/src/synthesis/extensions/jsx.rs b/checker/src/synthesis/extensions/jsx.rs index d69706e9..0a679055 100644 --- a/checker/src/synthesis/extensions/jsx.rs +++ b/checker/src/synthesis/extensions/jsx.rs @@ -134,8 +134,10 @@ pub(crate) fn synthesise_jsx_element( &mut environment.facts, ); - let children_iterator = - children.iter().filter(|p| !matches!(p, JSXNode::LineBreak)).enumerate(); + let children_iterator = children + .iter() + .filter(|p| !matches!(p, JSXNode::LineBreak | JSXNode::Comment(..))) + .enumerate(); for (idx, child) in children_iterator { // TODO idx bad! and should override item @@ -362,6 +364,7 @@ pub(crate) fn synthesise_jsx_element( // } } +/// TODO function argument fn synthesise_jsx_child( child: &JSXNode, environment: &mut Environment, @@ -370,14 +373,16 @@ fn synthesise_jsx_child( match child { JSXNode::Element(element) => synthesise_jsx_element(element, environment, checking_data), JSXNode::InterpolatedExpression(expression, _expression_position) => { - if matches!(&**expression, Expression::Comment { .. }) { - return TypeId::UNDEFINED_TYPE; + match &**expression { + parser::ast::FunctionArgument::Spread(_, _) => todo!(), + parser::ast::FunctionArgument::Standard(expression) => { + crate::utils::notify!("Cast JSX interpolated value?"); + synthesise_expression(expression, environment, checking_data, TypeId::ANY_TYPE) + } + parser::ast::FunctionArgument::Comment { .. } => { + todo!() + } } - - crate::utils::notify!("Cast to node!"); - // TODO expecting - synthesise_expression(expression, environment, checking_data, TypeId::ANY_TYPE) - // function intoNode(data) { // if typeof data === "string" || typeof data === "number" { // new Text(data) @@ -413,7 +418,7 @@ fn synthesise_jsx_child( JSXNode::TextNode(text, _) => { checking_data.types.new_constant_type(Constant::String(text.clone())) } - JSXNode::LineBreak => { + JSXNode::LineBreak | JSXNode::Comment(..) => { unreachable!("Should have been skipped higher up"); } } diff --git a/checker/src/synthesis/functions.rs b/checker/src/synthesis/functions.rs index 60f50125..281562c5 100644 --- a/checker/src/synthesis/functions.rs +++ b/checker/src/synthesis/functions.rs @@ -2,8 +2,10 @@ use iterator_endiate::EndiateIteratorExt; use parser::{ - expressions::ExpressionOrBlock, parameters::ParameterData, ASTNode, Block, FunctionBased, - GenericTypeConstraint, TypeAnnotation, VariableField, VariableIdentifier, WithComment, + expressions::ExpressionOrBlock, + functions::{LeadingParameter, ParameterData}, + ASTNode, Block, FunctionBased, TypeAnnotation, TypeParameter, VariableField, + VariableIdentifier, WithComment, }; use source_map::{SourceId, SpanWithSource}; @@ -62,9 +64,11 @@ where environment: &mut Environment, checking_data: &mut CheckingData, ) -> Option { - if let Some((ref annotation, _)) = self.parameters.this_type { + if let Some(parser::functions::ThisParameter { constraint, .. }) = + self.parameters.leading.get_this_parameter() + { crate::utils::notify!("Synthesising this restriction"); - Some(synthesise_type_annotation(annotation, environment, checking_data)) + Some(synthesise_type_annotation(constraint, environment, checking_data)) } else { None } @@ -75,8 +79,10 @@ where environment: &mut Environment, checking_data: &mut CheckingData, ) -> Option { - if let Some((ref annotation, _)) = self.parameters.super_type { - Some(synthesise_type_annotation(annotation, environment, checking_data)) + if let Some(parser::functions::SuperParameter { constraint, .. }) = + self.parameters.leading.get_super_parameter() + { + Some(synthesise_type_annotation(constraint, environment, checking_data)) } else { None } @@ -138,6 +144,19 @@ impl SynthesisableFunctionBody for Block { } } +impl SynthesisableFunctionBody for parser::functions::FunctionBody { + fn synthesise_function_body( + &self, + environment: &mut Environment, + checking_data: &mut CheckingData, + ) { + self.0 + .as_ref() + .expect("TODO overloading") + .synthesise_function_body(environment, checking_data); + } +} + impl SynthesisableFunctionBody for ExpressionOrBlock { fn synthesise_function_body( &self, @@ -160,35 +179,29 @@ impl SynthesisableFunctionBody for ExpressionOrBlock { } pub(crate) fn synthesise_type_parameters( - type_parameters: &[GenericTypeConstraint], + type_parameters: &[TypeParameter], environment: &mut crate::Environment, checking_data: &mut crate::CheckingData, ) -> GenericTypeParameters { type_parameters .iter() - .map(|constraint| match constraint { - GenericTypeConstraint::Parameter { name, default } => { - let default_type = default - .as_ref() - .map(|ta| synthesise_type_annotation(ta, environment, checking_data)); - environment.new_explicit_type_parameter( - name.as_str(), - None, - default_type, - &mut checking_data.types, - ) - } - GenericTypeConstraint::Extends(name, extends) => { - let extends = synthesise_type_annotation(extends, environment, checking_data); - environment.new_explicit_type_parameter( - name.as_str(), - Some(extends), - None, - &mut checking_data.types, - ) - } - GenericTypeConstraint::ExtendsKeyOf(_, _) => todo!(), - GenericTypeConstraint::Spread { name: _, default: _ } => todo!(), + .map(|constraint| { + let extends = constraint + .extends + .as_ref() + .map(|extends| synthesise_type_annotation(extends, environment, checking_data)); + + let default_type = constraint + .default + .as_ref() + .map(|ta| synthesise_type_annotation(ta, environment, checking_data)); + + environment.new_explicit_type_parameter( + &constraint.name, + extends, + default_type, + &mut checking_data.types, + ) }) .collect() } @@ -303,8 +316,12 @@ pub(super) fn synthesise_type_annotation_function_parameters( - ast_parameters: &parser::FunctionParameters, +fn synthesise_function_parameters< + T: crate::ReadFromFS, + L: parser::functions::LeadingParameter, + V: parser::functions::ParameterVisibility, +>( + ast_parameters: &parser::functions::FunctionParameters, expected_parameters: Option<&SynthesisedParameters>, environment: &mut Environment, checking_data: &mut CheckingData, @@ -424,33 +441,28 @@ fn synthesise_function_parameters( TypeId::ERROR_TYPE }; - let ty = checking_data.types.new_function_parameter(parameter_constraint); + let variable_ty = checking_data.types.new_function_parameter(parameter_constraint); - environment.object_constraints.insert(ty, vec![parameter_constraint]); + environment.object_constraints.insert(variable_ty, vec![parameter_constraint]); - match rest_parameter.name { - VariableIdentifier::Standard(ref name, pos) => environment - .register_variable_handle_error( - name, - VariableRegisterArguments { - // TODO constant parameter option - constant: false, - space: Some(parameter_constraint), - initial_value: Some(ty), - }, - pos.with_source(environment.get_source()), - &mut checking_data.diagnostics_container, - ), - VariableIdentifier::Marker(_, _) => todo!(), - }; + register_variable( + &rest_parameter.name, + environment, + checking_data, + VariableRegisterArguments { + // TODO constant parameter option + constant: false, + space: Some(parameter_constraint), + initial_value: Some(variable_ty), + }, + ); + + let name = param_name_to_string(&rest_parameter.name); SynthesisedRestParameter { item_type, - ty, - name: match rest_parameter.name { - VariableIdentifier::Standard(ref name, _) => name.to_owned(), - VariableIdentifier::Marker(_, _) => String::new(), - }, + ty: variable_ty, + name, position: rest_parameter.position.with_source(environment.get_source()), } }); @@ -458,7 +470,7 @@ fn synthesise_function_parameters( SynthesisedParameters { parameters, rest_parameter } } -fn param_name_to_string(param: &VariableField) -> String { +fn param_name_to_string(param: &VariableField) -> String { match param { VariableField::Name(name) => { if let VariableIdentifier::Standard(name, ..) = name { @@ -480,7 +492,8 @@ fn param_name_to_string(param: &VariableField parser::ArrayDestructuringField::Name(name, _) => { buf.push_str(¶m_name_to_string(name)); } - parser::ArrayDestructuringField::None => {} + parser::ArrayDestructuringField::Comment { .. } + | parser::ArrayDestructuringField::None => {} } if not_at_end { buf.push_str(", "); @@ -530,9 +543,7 @@ fn param_name_to_string(param: &VariableField } // TODO don't print values -fn get_parameter_name( - parameter: &parser::VariableField, -) -> String { +fn get_parameter_name(parameter: &parser::VariableField) -> String { match parameter { VariableField::Name(name) => match name { VariableIdentifier::Standard(ref name, _) => name.to_owned(), @@ -550,7 +561,7 @@ fn get_parameter_name( /// TODO abstract #[allow(clippy::too_many_arguments)] pub(super) fn synthesise_function_annotation( - type_parameters: &Option>, + type_parameters: &Option>, parameters: &parser::type_annotations::TypeAnnotationFunctionParameters, // This Option rather than Option because function type references are always some return_type: Option<&TypeAnnotation>, diff --git a/checker/src/synthesis/hoisting.rs b/checker/src/synthesis/hoisting.rs index 12a53021..9dbf0914 100644 --- a/checker/src/synthesis/hoisting.rs +++ b/checker/src/synthesis/hoisting.rs @@ -43,6 +43,10 @@ pub(crate) fn hoist_statements( "enum", r#enum.on.position.with_source(environment.get_source()), ), + parser::Declaration::Namespace(ns) => checking_data.raise_unimplemented_error( + "namespace", + ns.position.with_source(environment.get_source()), + ), parser::Declaration::DeclareInterface(interface) => { // TODO any difference bc declare? let ty = environment.new_interface( @@ -190,6 +194,10 @@ pub(crate) fn hoist_statements( } } StatementOrDeclaration::Declaration(dec) => match dec { + parser::Declaration::Namespace(ns) => checking_data.raise_unimplemented_error( + "namespace", + ns.position.with_source(environment.get_source()), + ), parser::Declaration::Variable(declaration) => { hoist_variable_declaration(declaration, environment, checking_data); } @@ -346,6 +354,9 @@ pub(crate) fn hoist_statements( } } parser::declarations::ExportDeclaration::Default { .. } => {} + parser::declarations::ExportDeclaration::DefaultFunction { .. } => { + todo!() + } }, parser::Declaration::Class(_) | parser::Declaration::TypeAlias(_) diff --git a/checker/src/synthesis/interfaces.rs b/checker/src/synthesis/interfaces.rs index e3909f14..ff578fab 100644 --- a/checker/src/synthesis/interfaces.rs +++ b/checker/src/synthesis/interfaces.rs @@ -114,7 +114,7 @@ impl SynthesiseInterfaceBehavior for OnToType { } pub(super) fn synthesise_signatures( - type_parameters: Option<&[parser::GenericTypeConstraint]>, + type_parameters: Option<&[parser::TypeParameter]>, signatures: &[WithComment>], mut behavior: B, environment: &mut Environment, @@ -261,7 +261,7 @@ pub(super) fn synthesise_signatures = parser::Module; + type Module<'_a> = parser::Module; type OwnedModule = parser::Module; + type DefinitionFile<'_a> = parser::Module; - type TypeAnnotation<'a> = parser::TypeAnnotation; - type TypeParameter<'a> = parser::GenericTypeConstraint; - type Expression<'a> = parser::Expression; - type MultipleExpression<'a> = parser::expressions::MultipleExpression; - type ClassMethod<'a> = parser::FunctionBase; + type TypeAnnotation<'_a> = parser::TypeAnnotation; + type TypeParameter<'_a> = parser::TypeParameter; + type Expression<'_a> = parser::Expression; + type MultipleExpression<'_a> = parser::expressions::MultipleExpression; + type ClassMethod<'_a> = parser::FunctionBase; - type VariableField<'a> = parser::VariableField; + type VariableField<'_a> = parser::VariableField; - type ForStatementInitiliser<'a> = parser::statements::ForLoopStatementInitializer; + type ForStatementInitiliser<'_a> = parser::statements::ForLoopStatementInitializer; fn module_from_string( // TODO remove @@ -72,12 +71,14 @@ impl crate::ASTImplementation for EznoParser { string: String, _parser_requirements: &mut Self::ParserRequirements, ) -> Result, Self::ParseError> { - let options = Default::default(); - parser::TypeDefinitionModule::from_string(&string, options).map_err(|err| (err, source_id)) + let options = ParseOptions { type_definition_module: true, ..Default::default() }; + + ::from_string(string, options) + .map_err(|err| (err, source_id)) } - fn synthesise_module<'a, T: crate::ReadFromFS>( - module: &Self::Module<'a>, + fn synthesise_module( + module: &Self::Module<'_>, _source_id: SourceId, module_environment: &mut Environment, checking_data: &mut crate::CheckingData, @@ -85,8 +86,8 @@ impl crate::ASTImplementation for EznoParser { synthesise_block(&module.items, module_environment, checking_data); } - fn synthesise_expression<'a, U: crate::ReadFromFS>( - expression: &Self::Expression<'a>, + fn synthesise_expression( + expression: &Self::Expression<'_>, expecting: TypeId, environment: &mut Environment, checking_data: &mut CheckingData, @@ -94,26 +95,24 @@ impl crate::ASTImplementation for EznoParser { synthesise_expression(expression, environment, checking_data, expecting) } - fn expression_position<'a>(expression: &'a Self::Expression<'a>) -> source_map::Span { + fn expression_position<'_a>(expression: &'_a Self::Expression<'_a>) -> source_map::Span { *ASTNode::get_position(expression) } - fn type_parameter_name<'a>(parameter: &'a Self::TypeParameter<'a>) -> &'a str { - parameter.name() + fn type_parameter_name<'_a>(parameter: &'_a Self::TypeParameter<'_a>) -> &'_a str { + ¶meter.name } - fn synthesise_type_annotation<'a, T: crate::ReadFromFS>( - annotation: &Self::TypeAnnotation<'a>, + fn synthesise_type_annotation( + annotation: &Self::TypeAnnotation<'_>, environment: &mut Environment, checking_data: &mut crate::CheckingData, ) -> TypeId { synthesise_type_annotation(annotation, environment, checking_data) } - type DefinitionFile<'a> = parser::TypeDefinitionModule; - - fn synthesise_definition_file<'a, T: crate::ReadFromFS>( - file: Self::DefinitionFile<'a>, + fn synthesise_definition_file( + file: Self::DefinitionFile<'_>, root: &RootContext, checking_data: &mut CheckingData, ) -> (Names, Facts) { @@ -137,8 +136,8 @@ impl crate::ASTImplementation for EznoParser { m } - fn synthesise_multiple_expression<'a, T: crate::ReadFromFS>( - expression: &'a Self::MultipleExpression<'a>, + fn synthesise_multiple_expression<'_a, T: crate::ReadFromFS>( + expression: &'_a Self::MultipleExpression<'_a>, expected_type: TypeId, environment: &mut Environment, checking_data: &mut crate::CheckingData, @@ -146,8 +145,8 @@ impl crate::ASTImplementation for EznoParser { synthesise_multiple_expression(expression, environment, checking_data, expected_type) } - fn synthesise_for_loop_initialiser<'a, T: crate::ReadFromFS>( - for_loop_initialiser: &'a Self::ForStatementInitiliser<'a>, + fn synthesise_for_loop_initialiser<'_a, T: crate::ReadFromFS>( + for_loop_initialiser: &'_a Self::ForStatementInitiliser<'_a>, environment: &mut Environment, checking_data: &mut crate::CheckingData, ) { @@ -216,9 +215,9 @@ pub enum Performs<'a> { None, } -impl crate::GenericTypeParameter for parser::GenericTypeConstraint { +impl crate::GenericTypeParameter for parser::TypeParameter { fn get_name(&self) -> &str { - self.name() + &self.name } } diff --git a/checker/src/synthesis/statements.rs b/checker/src/synthesis/statements.rs index f398e2f7..b374b3a5 100644 --- a/checker/src/synthesis/statements.rs +++ b/checker/src/synthesis/statements.rs @@ -129,6 +129,7 @@ pub(super) fn synthesise_statement( ), Statement::ForLoop(stmt) => match &stmt.condition { parser::statements::ForLoopCondition::ForOf { + is_await: _, keyword: _, variable, of, diff --git a/checker/src/synthesis/type_annotations.rs b/checker/src/synthesis/type_annotations.rs index e13b5043..31189c53 100644 --- a/checker/src/synthesis/type_annotations.rs +++ b/checker/src/synthesis/type_annotations.rs @@ -22,7 +22,7 @@ use std::{convert::TryInto, iter::FromIterator}; use parser::{ type_annotations::{ - AnnotationWithBinder, CommonTypes, SpreadKind, TypeCondition, TypeConditionResult, + AnnotationWithBinder, CommonTypes, TupleElementKind, TypeCondition, TypeConditionResult, }, ASTNode, TypeAnnotation, }; @@ -332,7 +332,7 @@ pub(super) fn synthesise_type_annotation( for (idx, (spread, member)) in members.iter().enumerate() { // TODO binder name under data...? match spread { - SpreadKind::NonSpread => { + TupleElementKind::Standard => { let type_annotation = match member { AnnotationWithBinder::Annotated { ty, .. } | AnnotationWithBinder::NoAnnotation(ty) => ty, @@ -352,7 +352,10 @@ pub(super) fn synthesise_type_annotation( Some(ty_position), ); } - SpreadKind::Spread => { + TupleElementKind::Optional => { + todo!() + } + TupleElementKind::Spread => { todo!(); } } @@ -423,6 +426,7 @@ pub(super) fn synthesise_type_annotation( synthesise_type_annotation(inner, environment, checking_data) } TypeAnnotation::TemplateLiteral(_, _) => todo!(), + TypeAnnotation::Symbol { .. } => todo!(), }; checking_data diff --git a/checker/src/synthesis/variables.rs b/checker/src/synthesis/variables.rs index 938c5c59..92b2a878 100644 --- a/checker/src/synthesis/variables.rs +++ b/checker/src/synthesis/variables.rs @@ -16,8 +16,8 @@ use crate::{ }; /// For eagerly registering variables, before the statement and its RHS is actually evaluate -pub(crate) fn register_variable( - name: &parser::VariableField, +pub(crate) fn register_variable( + name: &parser::VariableField, environment: &mut Environment, checking_data: &mut CheckingData, argument: VariableRegisterArguments, @@ -82,7 +82,7 @@ pub(crate) fn register_variable {} + ArrayDestructuringField::Comment { .. } | ArrayDestructuringField::None => {} } } } @@ -200,7 +200,7 @@ pub(super) fn synthesise_variable_declaration_item< } fn assign_to_fields( - item: &VariableField, + item: &VariableField, environment: &mut Environment, checking_data: &mut CheckingData, value: TypeId, @@ -257,7 +257,7 @@ fn assign_to_fields( // TODO } - ArrayDestructuringField::None => {} + ArrayDestructuringField::Comment { .. } | ArrayDestructuringField::None => {} } } } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 530052af..b7de23c8 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -22,35 +22,38 @@ default = [ "extras", "codespan-reporting", "serde-serialize", + "full-typescript", ] self-rust-tokenize = ["dep:self-rust-tokenize", "source-map/self-rust-tokenize"] codespan-reporting = ["source-map/codespan-reporting"] serde-serialize = ["dep:serde"] extras = [] +# Some additional syntax (that I don't think should exist 😂) +full-typescript = [] [dependencies] visitable-derive = { path = "./visitable-derive", version = "0.0.5", package = "ezno-parser-visitable-derive" } -derive-finite-automaton = "0.1.3" -derive-debug-extras = { version = "0.2.2", features = [ +derive-finite-automaton = "0.2" +derive-debug-extras = { version = "0.3", features = [ "auto-debug-single-tuple-inline", ] } -derive-partial-eq-extras = "0.1.2" -derive-enum-from-into = "0.1.1" +derive-partial-eq-extras = "0.2" +derive-enum-from-into = "0.2" temporary-annex = "0.1.0" -iterator-endiate = "0.2.1" +iterator-endiate = "0.2" -enum-variants-strings = "0.2" +enum-variants-strings = "0.3" -get-field-by-type = "0.0.3" +get-field-by-type = "0" macro_rules_attribute = { version = "0.2.0" } serde = { version = "1.0", features = ["derive"], optional = true } -self-rust-tokenize = { version = "0.3.3", optional = true } +self-rust-tokenize = { version = "0.3", optional = true } -source-map = { version = "0.14.10", features = [ +source-map = { version = "0.14", features = [ "serde-serialize", "self-rust-tokenize", ] } diff --git a/parser/examples/lex.rs b/parser/examples/lex.rs index 650c1802..229de93d 100644 --- a/parser/examples/lex.rs +++ b/parser/examples/lex.rs @@ -4,35 +4,42 @@ use ezno_parser::lex_script; use tokenizer_lib::{sized_tokens::SizedToken, ParallelTokenQueue, Token, TokenReader}; fn main() -> Result<(), Box> { - let path = std::env::args().nth(1).ok_or("expected argument")?; - let content = std::fs::read_to_string(path)?; - lex_and_print_tokens(content); + let mut args: std::collections::VecDeque<_> = std::env::args().skip(1).collect(); + let path = args.pop_front().ok_or("expected argument")?; + let print_tokens = args.iter().any(|item| item == "--print-tokens"); + + let script = std::fs::read_to_string(path)?; + lex_and_print_tokens(script, print_tokens); Ok(()) } -fn lex_and_print_tokens(script: String) { +fn lex_and_print_tokens(script: String, print_tokens: bool) { let (mut sender, mut receiver) = ParallelTokenQueue::new(); let lexer_options: ezno_parser::LexerOptions = Default::default(); let other = script.clone(); let thread = spawn(move || lex_script(&script, &mut sender, &lexer_options, None)); + // let mut count = 0; - println!("token | start (1 based) | length"); - while let Some(Token(token, start)) = receiver.next() { - // count += 1; - let length = token.length(); - println!( - "{:?} {} {} {:?}", - token, - start.0 + 1u32, - length, - other.get((start.0 as usize)..(start.0 as usize + length as usize)) - ); + if print_tokens { + println!("token | start (1 based) | length"); + while let Some(Token(token, start)) = receiver.next() { + // count += 1; + let length = token.length(); + let represents = + other.get((start.0 as usize)..(start.0 as usize + length as usize)).unwrap(); + println!("{:?} {} {} {:?}", token, start.0 + 1u32, length, represents); + } + } else { + // Drain anyway + while let Some(_) = receiver.next() {} } // println!("{count} tokens"); match thread.join().unwrap() { - Ok(()) => {} + Ok(()) => { + eprintln!("lexed source 🎉"); + } Err((lexer_err, _)) => { eprintln!("lexer error: {lexer_err:?}"); } diff --git a/parser/examples/parse.rs b/parser/examples/parse.rs index 9757b054..d0e29c72 100644 --- a/parser/examples/parse.rs +++ b/parser/examples/parse.rs @@ -1,11 +1,11 @@ -use std::{fs::read_to_string, time::Instant}; +use std::{collections::VecDeque, time::Instant}; use ezno_parser::{ASTNode, Comments, Module, ParseOptions, ToStringOptions}; use source_map::FileSystem; fn main() -> Result<(), Box> { - let mut args: Vec<_> = std::env::args().skip(1).collect(); - let path = args.drain(0..1).next().ok_or("expected argument")?; + let mut args: VecDeque<_> = std::env::args().skip(1).collect(); + let path = args.pop_front().ok_or("expected argument")?; let comments = if args.iter().any(|item| item == "--no-comments") { Comments::None @@ -13,33 +13,46 @@ fn main() -> Result<(), Box> { Comments::All }; - // TODO temp - const STACK_SIZE_MB: usize = 32; - let display_keywords = args.iter().any(|item| item == "--keywords"); let partial_syntax = args.iter().any(|item| item == "--partial"); let source_maps = args.iter().any(|item| item == "--source-map"); let timings = args.iter().any(|item| item == "--timings"); let render_timings = args.iter().any(|item| item == "--render-timings"); - let no_type_annotations = args.iter().any(|item| item == "--no-type-annotations"); + let type_definition_module = args.iter().any(|item| item == "--type-definition-module"); + let type_annotations = !args.iter().any(|item| item == "--no-type-annotations"); let now = Instant::now(); - let options = ParseOptions { + // TODO temp + const STACK_SIZE_MB: usize = 32; + let parse_options = ParseOptions { stack_size: Some(STACK_SIZE_MB * 1024 * 1024), comments, record_keyword_positions: display_keywords, partial_syntax, - type_annotations: !no_type_annotations, + type_annotations, + type_definition_module, ..ParseOptions::all_features() }; + // let parse_options = ParseOptions { + // stack_size: Some(STACK_SIZE_MB * 1024 * 1024), + // jsx: false, + // type_annotations: false, + // ..Default::default() + // }; + let mut fs = source_map::MapFileStore::::default(); - let source = read_to_string(path.clone())?; + + let source = std::fs::read_to_string(path.clone())?; + + // let source = String::from_utf8([0x2f, 0x8, 0x2f, 0xa].to_vec()).unwrap(); + // let source = "const [,,/* hi */] = []".to_string(); + let source_id = fs.new_source_id(path.into(), source.clone()); eprintln!("parsing {:?} bytes", source.len()); - let result = Module::from_string_with_options(source, options, None); + let result = Module::from_string_with_options(source, parse_options, None); match result { Ok((module, state)) => { @@ -61,9 +74,8 @@ fn main() -> Result<(), Box> { let now = Instant::now(); let to_string_options = ToStringOptions { - trailing_semicolon: true, expect_markers: true, - include_types: true, + include_type_annotations: type_annotations, pretty, comments: if pretty { Comments::All } else { Comments::None }, // 60 is temp @@ -71,6 +83,8 @@ fn main() -> Result<(), Box> { ..Default::default() }; + // let to_string_options = ToStringOptions::default(); + let (output, source_map) = module.to_string_with_source_map(&to_string_options, source_id, &fs); @@ -84,32 +98,33 @@ fn main() -> Result<(), Box> { if render_output { println!("{output}"); } + if double { - let result2 = Module::from_string_with_options(output.clone(), options, None); + let result2 = + Module::from_string_with_options(output.clone(), parse_options, None); return match result2 { Ok((module2, _state)) => { let output2 = module2 .to_string_with_source_map(&to_string_options, source_id, &fs) .0; - eprintln!("{output:?}\n{output2:?}"); - if output != output2 { - eprintln!("initial was {:?}", module); - eprintln!("re-parsed was {:?}", module2); - return Err(Box::::from("not equal")); - } else { + if output == output2 { + eprintln!("{output:?} == {output2:?}"); eprintln!("re-parse was equal ✅"); Ok(()) + } else { + eprintln!("{output:?} != {output2:?}"); + eprintln!("initial {:?}", module); + eprintln!("re-parsed {:?}", module2); + Err(Box::::from("not equal")) } } Err(parse_err) => { eprintln!("error parsing output: {output:?} from {module:?}"); - return Err(Box::::from(parse_err)); + Err(Box::::from(parse_err)) } }; } - } else if !timings { - eprintln!("parsed in: {:?}", now.elapsed()); } if display_keywords { diff --git a/parser/fuzz/Cargo.lock b/parser/fuzz/Cargo.lock index 6ba7b66c..2472ab22 100644 --- a/parser/fuzz/Cargo.lock +++ b/parser/fuzz/Cargo.lock @@ -19,13 +19,15 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -138,7 +140,7 @@ source = "git+https://github.com/boa-dev/boa.git#dda713d42e698f4095b8bd17453e327 dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", "synstructure", ] @@ -229,19 +231,21 @@ dependencies = [ [[package]] name = "derive-finite-automaton" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "547520acf7db9e4384e3734644acdee8edce4ff8da544afd2ef679e788b1cd9b" +checksum = "a18c3334a02026fc281427161fea9dacbf650d8b7b01e35a7cf445403c318221" dependencies = [ "derive-finite-automaton-derive", ] [[package]] name = "derive-finite-automaton-derive" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f1f1ef49cdc13ee8c642c6afa3e56fb3a082a48c89e3f75006cc00da6cce51" +checksum = "d4bb462e792fda597bc05207ebf41948ff8e7c5a6ab737bd5c75286193fc3613" dependencies = [ + "either_n", + "proc-macro2", "quote", "syn 1.0.109", ] @@ -264,7 +268,7 @@ checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -310,18 +314,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "enum_variant_type" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b4d0447be46f06039b1e13c5c77df8d9f9f37fa5fab578a1827b8f136aab64" -dependencies = [ - "proc-macro2", - "proc_macro_roids", - "quote", - "syn 1.0.109", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -351,17 +343,18 @@ dependencies = [ [[package]] name = "ezno-parser" -version = "0.0.3" +version = "0.1.3" dependencies = [ "derive-debug-extras", "derive-enum-from-into", "derive-finite-automaton", "derive-partial-eq-extras", "enum-variants-strings", - "enum_variant_type", "ezno-parser-visitable-derive", + "get-field-by-type", "iterator-endiate", "self-rust-tokenize", + "serde", "source-map", "temporary-annex", "tokenizer-lib", @@ -371,6 +364,7 @@ dependencies = [ name = "ezno-parser-fuzz" version = "0.0.0" dependencies = [ + "ahash", "arbitrary", "boa_ast", "boa_interner", @@ -383,7 +377,7 @@ dependencies = [ [[package]] name = "ezno-parser-visitable-derive" -version = "0.0.1" +version = "0.0.5" dependencies = [ "string-cases", "syn-helpers", @@ -473,12 +467,54 @@ dependencies = [ "slab", ] +[[package]] +name = "get-field-by-type" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f4ba71362701e2913eabce7b108cf6f2db1a413f661138862f2632b117ab98" +dependencies = [ + "get-field-by-type-derive", +] + +[[package]] +name = "get-field-by-type-derive" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c1777fe05ec14229f7eede7ecb245051776a405a90501139aa6967a9eba7feb" +dependencies = [ + "syn-helpers", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "h2" version = "0.3.21" @@ -629,9 +665,9 @@ checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "iterator-endiate" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cfeef3929ed54a0312797e6e75a9a962df67a30beda87029387e810e61a44cc" +checksum = "cab947031a0a0cb37f982ef2a0ab3bacfd3de5ed97dd5c7e98bcc92bba357112" [[package]] name = "itoa" @@ -665,9 +701,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libfuzzer-sys" @@ -825,7 +861,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -882,7 +918,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -931,17 +967,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc_macro_roids" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06675fa2c577f52bcf77fbb511123927547d154faa08097cc012c66ec3c9611a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "quote" version = "1.0.33" @@ -1113,7 +1138,18 @@ checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", +] + +[[package]] +name = "serde_derive_internals" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", ] [[package]] @@ -1176,13 +1212,15 @@ dependencies = [ [[package]] name = "source-map" -version = "0.13.0" +version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebacbfb6ea7b5257c72f8d2f7790cf4fc3e72f399d76b56d48f6290c8ed7789" +checksum = "562cbf03968c48809f6bd48115b894b6b3bb5af0437f6077c19cb1bd0fb51080" dependencies = [ "codespan-reporting", "self-rust-tokenize", "serde", + "tsify", + "wasm-bindgen", ] [[package]] @@ -1210,9 +1248,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -1221,9 +1259,9 @@ dependencies = [ [[package]] name = "syn-helpers" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872f211f0299a39fff932355b2e304ef0e95a486a8147244fa8d477c1b0acfce" +checksum = "2a2919350d44e7bdba75bf541da7a15c415a84fea1c823f37f0c48a6b0133639" dependencies = [ "either_n", "proc-macro2", @@ -1239,7 +1277,7 @@ checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", "unicode-xid", ] @@ -1288,9 +1326,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokenizer-lib" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921ae88b4cdbf7aa5bc708c9d02d37c194c3fc4189c9698320ef19968354d36a" +checksum = "aedcdc3b4760676e1ed01c749e219c98f8da59ef08c62547c19a74bea228c5d8" +dependencies = [ + "source-map", +] [[package]] name = "tokio" @@ -1364,6 +1405,31 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tsify" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" +dependencies = [ + "gloo-utils", + "serde", + "serde_json", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-macros" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a94b0f0954b3e59bfc2c246b4c8574390d94a4ad4ad246aaf2fb07d7dfd3b47" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.32", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -1437,9 +1503,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1447,16 +1513,16 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", "wasm-bindgen-shared", ] @@ -1474,9 +1540,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1484,22 +1550,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" @@ -1623,3 +1689,23 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] diff --git a/parser/fuzz/Cargo.toml b/parser/fuzz/Cargo.toml index 2abb8b98..46464a48 100644 --- a/parser/fuzz/Cargo.toml +++ b/parser/fuzz/Cargo.toml @@ -11,6 +11,7 @@ cargo-fuzz = true reqwest = { version = "0.11.14", features = ["blocking"] } [dependencies] +ahash = "0.8.8" arbitrary = "1" boa_ast = { git = "https://github.com/boa-dev/boa.git", features = [ "arbitrary", diff --git a/parser/fuzz/fuzz_targets/module_roundtrip_naive.rs b/parser/fuzz/fuzz_targets/module_roundtrip_naive.rs index d26466ae..edb513da 100644 --- a/parser/fuzz/fuzz_targets/module_roundtrip_naive.rs +++ b/parser/fuzz/fuzz_targets/module_roundtrip_naive.rs @@ -1,6 +1,6 @@ #![no_main] -use ezno_parser::{ASTNode, Module, ParseOptions, SourceId, ToStringOptions}; +use ezno_parser::{ASTNode, Module, ParseOptions, ToStringOptions}; use libfuzzer_sys::{fuzz_target, Corpus}; use pretty_assertions::assert_eq; use std::str; @@ -11,12 +11,18 @@ use std::str; fn do_fuzz(data: &str) -> Corpus { let input = data.trim_start(); - let parse_options = ParseOptions { jsx: false, type_annotations: false, ..Default::default() }; + const STACK_SIZE_MB: usize = 32; + let parse_options = ParseOptions { + stack_size: Some(STACK_SIZE_MB * 1024 * 1024), + jsx: false, + type_annotations: false, + ..Default::default() + }; let Ok(module) = Module::from_string(input.to_owned(), parse_options) else { return Corpus::Reject; }; - let to_string_options = ToStringOptions { trailing_semicolon: true, ..Default::default() }; + let to_string_options = ToStringOptions::default(); let output1 = module.to_string(&to_string_options); diff --git a/parser/fuzz/fuzz_targets/module_roundtrip_structured.rs b/parser/fuzz/fuzz_targets/module_roundtrip_structured.rs index 35dd01fb..3022b912 100644 --- a/parser/fuzz/fuzz_targets/module_roundtrip_structured.rs +++ b/parser/fuzz/fuzz_targets/module_roundtrip_structured.rs @@ -4,7 +4,7 @@ mod common { include!(concat!(env!("OUT_DIR"), "/common.rs")); // from build.rs } -use ezno_parser::{ASTNode, Module, ParseOptions, SourceId, ToStringOptions}; +use ezno_parser::{ASTNode, Module, ParseOptions, ToStringOptions}; use libfuzzer_sys::{fuzz_target, Corpus}; use pretty_assertions::assert_eq; @@ -13,12 +13,18 @@ use pretty_assertions::assert_eq; fn do_fuzz(data: common::FuzzSource) -> Corpus { let input = data.source; - let parse_options = ParseOptions { jsx: false, type_annotations: false, ..Default::default() }; + const STACK_SIZE_MB: usize = 32; + let parse_options = ParseOptions { + stack_size: Some(STACK_SIZE_MB * 1024 * 1024), + jsx: false, + type_annotations: false, + ..Default::default() + }; let Ok(module) = Module::from_string(input.to_owned(), parse_options) else { return Corpus::Reject; }; - let to_string_options = ToStringOptions { trailing_semicolon: true, ..Default::default() }; + let to_string_options = ToStringOptions::default(); let output1 = module.to_string(&to_string_options); diff --git a/parser/src/block.rs b/parser/src/block.rs index c93796c1..a7b7b50f 100644 --- a/parser/src/block.rs +++ b/parser/src/block.rs @@ -15,7 +15,7 @@ use crate::{ #[apply(derive_ASTNode)] #[derive(Debug, Clone, PartialEq, Visitable, get_field_by_type::GetFieldByType, EnumFrom)] #[get_field_by_type_target(Span)] -#[visit_self(under statement)] +#[visit_self(under = statement)] pub enum StatementOrDeclaration { Statement(Statement), Declaration(Declaration), @@ -26,6 +26,7 @@ pub enum StatementOrDeclaration { impl StatementOrDeclaration { pub(crate) fn requires_semi_colon(&self) -> bool { + // TODO maybe more? match self { StatementOrDeclaration::Statement(stmt) => stmt.requires_semi_colon(), StatementOrDeclaration::Declaration(dec) => matches!( @@ -40,6 +41,7 @@ impl StatementOrDeclaration { }, .. }) | Declaration::Import(..) + | Declaration::TypeAlias(..) ), Self::Marker(..) => false, } @@ -160,18 +162,22 @@ impl ASTNode for Block { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - buf.push('{'); - if local.depth > 0 && options.pretty { - buf.push_new_line(); - } - statements_and_declarations_to_string(&self.0, buf, options, local); - if options.pretty && !self.0.is_empty() { - buf.push_new_line(); - } - if local.depth > 1 { - options.add_indent(local.depth - 1, buf); + if options.pretty && self.0.is_empty() { + buf.push_str("{}"); + } else { + buf.push('{'); + if local.depth > 0 && options.pretty { + buf.push_new_line(); + } + statements_and_declarations_to_string(&self.0, buf, options, local); + if options.pretty && !self.0.is_empty() { + buf.push_new_line(); + } + if local.depth > 1 { + options.add_indent(local.depth - 1, buf); + } + buf.push('}'); } - buf.push('}'); } fn get_position(&self) -> &Span { @@ -366,10 +372,8 @@ pub(crate) fn parse_statements_and_declarations( let value = StatementOrDeclaration::from_reader(reader, state, options)?; if value.requires_semi_colon() { expect_semi_colon(reader, &state.line_starts, value.get_position().end)?; - } else { - // Skip over semi colons regardless - // reader.conditional_next(|t| matches!(t, TSXToken::SemiColon)); } + // Could skip over semi colons regardless. But they are technically empty statements 🤷‍♂️ items.push(value); } Ok(items) diff --git a/parser/src/comments.rs b/parser/src/comments.rs index e65e21bd..31ecb678 100644 --- a/parser/src/comments.rs +++ b/parser/src/comments.rs @@ -1,7 +1,7 @@ //! Contains wrappers for AST with comments -use super::{ASTNode, ParseError, Span, TSXToken, TokenReader}; -use crate::ParseOptions; +use super::{ASTNode, Span, TSXToken, TokenReader}; +use crate::{ParseOptions, ParseResult}; use tokenizer_lib::Token; use visitable_derive::Visitable; @@ -95,7 +95,7 @@ impl ASTNode for WithComment { reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, - ) -> Result, ParseError> { + ) -> ParseResult { if let Some(token) = reader.conditional_next(|t| matches!(t, TSXToken::MultiLineComment(..))) { diff --git a/parser/src/declarations/classes/class_member.rs b/parser/src/declarations/classes/class_member.rs index 8b53adb0..1b4df345 100644 --- a/parser/src/declarations/classes/class_member.rs +++ b/parser/src/declarations/classes/class_member.rs @@ -1,23 +1,26 @@ use std::fmt::Debug; use crate::{ - derive_ASTNode, errors::parse_lexing_error, functions::HeadingAndPosition, - property_key::PublicOrPrivate, visiting::Visitable, + derive_ASTNode, + errors::parse_lexing_error, + functions::{ + FunctionBased, FunctionBody, HeadingAndPosition, MethodHeader, SuperParameter, + ThisParameter, + }, + property_key::PublicOrPrivate, + visiting::Visitable, + ASTNode, Block, Expression, FunctionBase, ParseOptions, ParseResult, PropertyKey, TSXKeyword, + TSXToken, TypeAnnotation, WithComment, }; use source_map::Span; use tokenizer_lib::{sized_tokens::TokenStart, Token, TokenReader}; use visitable_derive::Visitable; -use crate::{ - functions::{FunctionBased, MethodHeader}, - ASTNode, Block, Expression, FunctionBase, ParseOptions, ParseResult, PropertyKey, TSXKeyword, - TSXToken, TypeAnnotation, WithComment, -}; #[cfg_attr(target_family = "wasm", tsify::declare)] pub type IsStatic = bool; -#[derive(Debug, Clone, PartialEq, Eq, Visitable)] #[apply(derive_ASTNode)] +#[derive(Debug, Clone, PartialEq, Eq, Visitable)] pub enum ClassMember { Constructor(ClassConstructor), Method(IsStatic, ClassFunction), @@ -29,23 +32,10 @@ pub enum ClassMember { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ClassConstructorBase; pub type ClassConstructor = FunctionBase; -#[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] -const CLASS_CONSTRUCTOR_TYPES: &str = r" - export interface ClassConstructor extends Omit { - body: Block - } -"; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ClassFunctionBase; pub type ClassFunction = FunctionBase; -#[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] -const CLASS_FUNCTION_TYPES: &str = r" - export interface ClassFunction extends FunctionBase { - header: MethodHeader, - body: Block, - name: WithComment> - } -"; #[derive(Debug, Clone, PartialEq, Eq, Visitable)] #[apply(derive_ASTNode)] @@ -163,7 +153,9 @@ impl ASTNode for ClassMember { buf.push_str("readonly "); } key.to_string_from_buffer(buf, options, local); - if let (true, Some(type_annotation)) = (options.include_types, type_annotation) { + if let (true, Some(type_annotation)) = + (options.include_type_annotations, type_annotation) + { buf.push_str(": "); type_annotation.to_string_from_buffer(buf, options, local); } @@ -221,9 +213,16 @@ impl ClassFunction { } impl FunctionBased for ClassFunctionBase { - type Body = Block; type Header = MethodHeader; type Name = WithComment>; + type LeadingParameter = (Option, Option); + type ParameterVisibility = (); + type Body = FunctionBody; + + #[cfg(feature = "full-typescript")] + fn has_body(body: &Self::Body) -> bool { + body.0.is_some() + } #[allow(clippy::similar_names)] fn header_and_name_from_reader( @@ -281,12 +280,19 @@ impl FunctionBased for ClassFunctionBase { impl FunctionBased for ClassConstructorBase { type Header = (); type Name = (); - type Body = Block; + type Body = FunctionBody; + type LeadingParameter = (Option, Option); + type ParameterVisibility = Option; // fn get_chain_variable(this: &FunctionBase) -> ChainVariable { // ChainVariable::UnderClassConstructor(this.body.1) // } + #[cfg(feature = "full-typescript")] + fn has_body(body: &Self::Body) -> bool { + body.0.is_some() + } + fn header_and_name_from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, @@ -328,3 +334,19 @@ impl FunctionBased for ClassConstructorBase { None } } + +#[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] +#[allow(dead_code)] +const CLASS_CONSTRUCTOR_AND_FUNCTION_TYPES: &str = r" + export interface ClassConstructor extends FunctionBase { + body: FunctionBody, + parameters: FunctionParameters<[ThisParameter | null, SuperParameter | null], Visibility>, + } + + export interface ClassFunction extends FunctionBase { + header: MethodHeader, + name: WithComment> + parameters: FunctionParameters, + body: FunctionBody, + } +"; diff --git a/parser/src/declarations/classes/mod.rs b/parser/src/declarations/classes/mod.rs index 18a9529d..8ef6757e 100644 --- a/parser/src/declarations/classes/mod.rs +++ b/parser/src/declarations/classes/mod.rs @@ -8,7 +8,7 @@ use iterator_endiate::EndiateIteratorExt; use crate::{ extensions::decorators::Decorated, visiting::Visitable, ASTNode, ExpressionOrStatementPosition, - GenericTypeConstraint, ParseOptions, ParseResult, Span, TSXKeyword, TSXToken, TypeAnnotation, + ParseOptions, ParseResult, Span, TSXKeyword, TSXToken, TypeAnnotation, TypeParameter, VisitOptions, }; use tokenizer_lib::{ @@ -21,7 +21,7 @@ use tokenizer_lib::{ #[get_field_by_type_target(Span)] pub struct ClassDeclaration { pub name: T, - pub type_parameters: Option>, + pub type_parameters: Option>, pub extends: Option>, pub implements: Option>, pub members: Vec>, @@ -71,34 +71,43 @@ impl ClassDeclaration { }) .transpose()?; - let extends = match reader.peek() { - Some(Token(TSXToken::Keyword(TSXKeyword::Extends), _)) => { - reader.next(); - Some(Expression::from_reader(reader, state, options)?.into()) - } - _ => None, + let extends = if reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Extends))) + .is_some() + { + Some(Expression::from_reader(reader, state, options)?.into()) + } else { + None }; - let implements = match reader.peek() { - Some(Token(TSXToken::Keyword(TSXKeyword::Implements), _)) => { - reader.next(); - let mut implements = Vec::new(); + + let implements = if reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Implements))) + .is_some() + { + let type_annotation = TypeAnnotation::from_reader(reader, state, options)?; + let mut implements = vec![type_annotation]; + if reader.conditional_next(|t| matches!(t, TSXToken::Comma)).is_some() { loop { implements.push(TypeAnnotation::from_reader(reader, state, options)?); - match reader.next().ok_or_else(crate::errors::parse_lexing_error)? { - Token(TSXToken::Comma, _) => {} - Token(TSXToken::OpenBrace, _pos) => break, - token => { + match reader.peek() { + Some(Token(TSXToken::Comma, _)) => { + reader.next(); + } + Some(Token(TSXToken::OpenBrace, _)) | None => break, + _ => { return throw_unexpected_token_with_token( - token, - &[TSXToken::OpenBrace, TSXToken::Comma], - ); + reader.next().unwrap(), + &[TSXToken::Comma, TSXToken::OpenBrace], + ) } } } - Some(implements) } - _ => None, + Some(implements) + } else { + None }; + reader.expect_next(TSXToken::OpenBrace)?; let mut members: Vec> = Vec::new(); loop { diff --git a/parser/src/declarations/export.rs b/parser/src/declarations/export.rs index 5e3a7b17..0efb81ad 100644 --- a/parser/src/declarations/export.rs +++ b/parser/src/declarations/export.rs @@ -1,7 +1,8 @@ use crate::{ - derive_ASTNode, errors::parse_lexing_error, throw_unexpected_token, ASTNode, Expression, - ListItem, ParseError, ParseOptions, ParseResult, Span, StatementPosition, TSXKeyword, TSXToken, - Token, VariableIdentifier, + derive_ASTNode, errors::parse_lexing_error, throw_unexpected_token, + type_annotations::TypeAnnotationFunctionParameters, ASTNode, Expression, ListItem, ParseError, + ParseOptions, ParseResult, Span, StatementPosition, TSXKeyword, TSXToken, Token, + TypeAnnotation, VariableIdentifier, }; use super::{ @@ -14,23 +15,35 @@ use iterator_endiate::EndiateIteratorExt; use tokenizer_lib::TokenReader; use visitable_derive::Visitable; -/// TODO tidy up into struct -/// /// [See](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) #[apply(derive_ASTNode)] #[derive(Debug, PartialEq, Eq, Clone, Visitable, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] pub enum ExportDeclaration { - // TODO listed object thing - // TODO export * - Variable { exported: Exportable, position: Span }, + Variable { + exported: Exportable, + position: Span, + }, // `export default ...` - Default { expression: Box, position: Span }, + Default { + expression: Box, + position: Span, + }, + + DefaultFunction { + /// Technically not allowed in TypeScript + is_async: bool, + identifier: Option, + #[visit_skip_field] + parameters: TypeAnnotationFunctionParameters, + return_type: Option, + position: Span, + }, } -#[derive(Debug, PartialEq, Eq, Clone, Visitable)] #[apply(derive_ASTNode)] +#[derive(Debug, PartialEq, Eq, Clone, Visitable)] pub enum Exportable { Class(ClassDeclaration), Function(StatementFunction), @@ -44,10 +57,7 @@ pub enum Exportable { impl ASTNode for ExportDeclaration { fn get_position(&self) -> &Span { - match self { - ExportDeclaration::Variable { position, .. } - | ExportDeclaration::Default { position, .. } => position, - } + self.get() } fn from_reader( @@ -60,9 +70,54 @@ impl ASTNode for ExportDeclaration { match reader.peek().ok_or_else(parse_lexing_error)? { Token(TSXToken::Keyword(TSXKeyword::Default), _) => { reader.next(); - let expression = Expression::from_reader(reader, state, options)?; - let position = start.union(expression.get_position()); - Ok(ExportDeclaration::Default { expression: Box::new(expression), position }) + if options.type_definition_module + && reader.peek().map_or( + false, + |t| matches!(t.0, TSXToken::Keyword(kw) if kw.is_in_function_header()), + ) { + let is_async = reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Async))) + .is_some(); + + #[allow(unused)] + let token = reader.next(); + debug_assert!(matches!( + token.unwrap().0, + TSXToken::Keyword(TSXKeyword::Function) + )); + + let identifier = + if let Some(Token(TSXToken::OpenParentheses, _)) = reader.peek() { + None + } else { + Some(VariableIdentifier::from_reader(reader, state, options)?) + }; + + let parameters = + TypeAnnotationFunctionParameters::from_reader(reader, state, options)?; + + let return_type = reader + .conditional_next(|tok| matches!(tok, TSXToken::Colon)) + .is_some() + .then(|| TypeAnnotation::from_reader(reader, state, options)) + .transpose()?; + + let position = start.union( + return_type.as_ref().map_or(¶meters.position, ASTNode::get_position), + ); + + Ok(ExportDeclaration::DefaultFunction { + position, + is_async, + identifier, + parameters, + return_type, + }) + } else { + let expression = Expression::from_reader(reader, state, options)?; + let position = start.union(expression.get_position()); + Ok(ExportDeclaration::Default { expression: Box::new(expression), position }) + } } Token(TSXToken::Multiply, _) => { reader.next(); @@ -297,6 +352,30 @@ impl ASTNode for ExportDeclaration { buf.push_str("export default "); expression.to_string_from_buffer(buf, options, local); } + ExportDeclaration::DefaultFunction { + is_async, + identifier, + parameters, + return_type, + position: _, + } => { + if options.include_type_annotations { + buf.push_str("export default "); + if *is_async { + buf.push_str("async "); + } + buf.push_str("function "); + if let Some(ref identifier) = identifier { + identifier.to_string_from_buffer(buf, options, local); + buf.push(' '); + } + parameters.to_string_from_buffer(buf, options, local); + if let Some(ref return_type) = return_type { + buf.push_str(": "); + return_type.to_string_from_buffer(buf, options, local); + } + } + } } } } diff --git a/parser/src/declarations/import.rs b/parser/src/declarations/import.rs index 2c88deb5..66d113cb 100644 --- a/parser/src/declarations/import.rs +++ b/parser/src/declarations/import.rs @@ -4,9 +4,10 @@ use source_map::Span; use tokenizer_lib::{sized_tokens::TokenStart, Token, TokenReader}; use crate::{ - derive_ASTNode, errors::parse_lexing_error, parse_bracketed, throw_unexpected_token, - tokens::token_as_identifier, ASTNode, ListItem, Marker, ParseOptions, ParseResult, - ParsingState, Quoted, TSXKeyword, TSXToken, VariableIdentifier, + ast::object_literal::ObjectLiteral, derive_ASTNode, errors::parse_lexing_error, + parse_bracketed, throw_unexpected_token, tokens::token_as_identifier, ASTNode, ListItem, + Marker, ParseOptions, ParseResult, ParsingState, Quoted, TSXKeyword, TSXToken, + VariableIdentifier, }; use visitable_derive::Visitable; @@ -20,28 +21,30 @@ pub enum ImportedItems { All { under: VariableIdentifier }, } -/// TODO a few more thing needed here #[apply(derive_ASTNode)] #[derive(Debug, Clone, PartialEq, Eq, Visitable, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] pub struct ImportDeclaration { #[cfg(feature = "extras")] pub is_deferred: bool, + #[cfg(feature = "full-typescript")] pub is_type_annotation_import_only: bool, pub default: Option, pub items: ImportedItems, pub from: ImportLocation, + pub with: Option, pub position: Span, #[cfg(feature = "extras")] pub reversed: bool, } -/// TODO default +/// TODO `default` should have its own variant? #[derive(Debug, Clone, PartialEq, Eq)] #[apply(derive_ASTNode)] pub enum ImportExportName { Reference(String), Quoted(String, Quoted), + /// For typing here #[cfg_attr(feature = "self-rust-tokenize", self_tokenize_field(0))] Marker( #[cfg_attr(target_family = "wasm", tsify(type = "Marker"))] Marker, @@ -93,13 +96,21 @@ impl ASTNode for ImportDeclaration { let (from, end) = ImportLocation::from_reader(reader, state, options, Some(start))?; + let with = reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::With))) + .is_some() + .then(|| ObjectLiteral::from_reader(reader, state, options)) + .transpose()?; + Ok(ImportDeclaration { default: out.default, items: out.items, + #[cfg(feature = "full-typescript")] is_type_annotation_import_only: out.is_type_annotation_import_only, #[cfg(feature = "extras")] is_deferred: out.is_deferred, from, + with, position: out.start.union(end), #[cfg(feature = "extras")] reversed: false, @@ -113,7 +124,9 @@ impl ASTNode for ImportDeclaration { local: crate::LocalToStringInformation, ) { buf.push_str("import"); - if self.is_type_annotation_import_only && options.include_types { + + #[cfg(feature = "full-typescript")] + if self.is_type_annotation_import_only && options.include_type_annotations { buf.push_str(" type"); } @@ -183,10 +196,17 @@ impl ImportDeclaration { let out = parse_import_specifier_and_parts(reader, state, options)?; + let with = reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Assert))) + .is_some() + .then(|| ObjectLiteral::from_reader(reader, state, options)) + .transpose()?; + Ok(ImportDeclaration { default: out.default, items: out.items, is_type_annotation_import_only: out.is_type_annotation_import_only, + with, #[cfg(feature = "extras")] is_deferred: out.is_deferred, from, @@ -249,7 +269,7 @@ pub(crate) fn parse_import_specifier_and_parts( let peek = reader.peek(); let (items, end) = if let Some(Token(TSXToken::Multiply, _)) = peek { reader.next(); - let _as = reader.expect_next(TSXToken::Keyword(TSXKeyword::As))?; + state.expect_keyword(reader, TSXKeyword::As)?; let under = VariableIdentifier::from_reader(reader, state, options)?; let end = under.get_position().get_end(); (ImportedItems::All { under }, end) @@ -334,6 +354,7 @@ impl ASTNode for ImportPart { let (ident, pos) = token_as_identifier(token, "import alias")?; (ImportExportName::Reference(ident), pos) }; + let mut value = match alias { ImportExportName::Quoted(..) => { let _ = state.expect_keyword(reader, TSXKeyword::As)?; @@ -345,8 +366,7 @@ impl ASTNode for ImportPart { Self::NameWithAlias { name, alias, position } } ImportExportName::Reference(reference) => { - if let Some(Token(TSXToken::Keyword(TSXKeyword::As), _)) = reader.peek() { - reader.next(); + if state.optionally_expect_keyword(reader, TSXKeyword::As).is_some() { let (name, pos) = token_as_identifier( reader.next().ok_or_else(parse_lexing_error)?, "import name", @@ -361,10 +381,10 @@ impl ASTNode for ImportPart { Self::Name(VariableIdentifier::Standard(reference, alias_pos)) } } - ImportExportName::Marker(_id) => { - todo!("marker id change") - // Self::Name(VariableIdentifier::Marker(id, pos)) - } + ImportExportName::Marker(id) => Self::Name(VariableIdentifier::Marker( + Marker(id.0, Default::default()), + alias_pos, + )), }; while let Some(Token(TSXToken::MultiLineComment(_), _)) = reader.peek() { let Some(Token(TSXToken::MultiLineComment(c), start)) = reader.next() else { diff --git a/parser/src/declarations/mod.rs b/parser/src/declarations/mod.rs index 98a50a87..ca5c19a6 100644 --- a/parser/src/declarations/mod.rs +++ b/parser/src/declarations/mod.rs @@ -7,7 +7,7 @@ use visitable_derive::Visitable; use crate::{ derive_ASTNode, errors::parse_lexing_error, extensions::decorators, throw_unexpected_token_with_token, Decorated, Marker, ParseError, ParseErrors, ParseOptions, - Quoted, StatementPosition, TSXKeyword, TSXToken, TypeDefinitionModuleDeclaration, + Quoted, StatementPosition, TSXKeyword, TSXToken, }; pub use self::{ @@ -19,13 +19,16 @@ pub type StatementFunctionBase = crate::functions::GeneralFunctionBase; #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] +#[allow(dead_code)] const TYPES_STATEMENT_FUNCTION: &str = r" export interface StatementFunction extends FunctionBase { header: FunctionHeader, + parameters: FunctionParameters, body: Block, name: StatementPosition } "; + pub mod classes; pub mod export; pub mod import; @@ -56,6 +59,8 @@ pub enum Declaration { // Special TS only DeclareVariable(DeclareVariableDeclaration), DeclareFunction(DeclareFunctionDeclaration), + #[cfg(feature = "full-typescript")] + Namespace(crate::types::namespace::Namespace), #[from_ignore] DeclareInterface(InterfaceDeclaration), // Top level only @@ -84,6 +89,7 @@ impl Declaration { #[cfg(feature = "extras")] return result || matches!(token, TSXToken::Keyword(kw) if options.custom_function_headers && kw.is_special_function_header()) + || (matches!(token, TSXToken::Keyword(TSXKeyword::Namespace) if cfg!(feature = "full-typescript"))) || { let TSXToken::Keyword(token) = *token else { return false }; let Some(Token(after, _)) = reader.peek_n(1) else { return false }; @@ -234,6 +240,11 @@ impl crate::ASTNode for Declaration { TSXToken::Keyword(TSXKeyword::From) => { ImportDeclaration::reversed_from_reader(reader, state, options).map(Into::into) } + #[cfg(feature = "full-typescript")] + TSXToken::Keyword(TSXKeyword::Namespace) => { + crate::types::namespace::Namespace::from_reader(reader, state, options) + .map(Into::into) + } TSXToken::Keyword(TSXKeyword::Interface) if options.type_annotations => { InterfaceDeclaration::from_reader(reader, state, options) .map(|on| Declaration::Interface(Decorated::new(decorators, on))) @@ -243,34 +254,33 @@ impl crate::ASTNode for Declaration { } TSXToken::Keyword(TSXKeyword::Declare) if options.type_annotations => { let Token(_, start) = reader.next().unwrap(); - crate::modules::parse_declare_item(reader, state, options, decorators, start) - .and_then(|ty_def_mod_stmt| match ty_def_mod_stmt { - TypeDefinitionModuleDeclaration::Variable(declare_var) => { - Ok(Declaration::DeclareVariable(declare_var)) - } - TypeDefinitionModuleDeclaration::Function(declare_func) => { - Ok(Declaration::DeclareFunction(declare_func)) - } - TypeDefinitionModuleDeclaration::Class(item) => Err(ParseError::new( - ParseErrors::InvalidDeclareItem("class"), - *item.get_position(), - )), - TypeDefinitionModuleDeclaration::Interface(item) => Err(ParseError::new( - ParseErrors::InvalidDeclareItem("interface"), - *item.get_position(), - )), - TypeDefinitionModuleDeclaration::TypeAlias(item) => Err(ParseError::new( - ParseErrors::InvalidDeclareItem("type alias"), - *item.get_position(), - )), - TypeDefinitionModuleDeclaration::Namespace(item) => Err(ParseError::new( - ParseErrors::InvalidDeclareItem("namespace"), - *item.get_position(), - )), - TypeDefinitionModuleDeclaration::LocalTypeAlias(_) - | TypeDefinitionModuleDeclaration::LocalVariableDeclaration(_) - | TypeDefinitionModuleDeclaration::Comment(_) => unreachable!(), - }) + match reader.peek().ok_or_else(parse_lexing_error)?.0 { + TSXToken::Keyword(TSXKeyword::Let | TSXKeyword::Const | TSXKeyword::Var) => { + DeclareVariableDeclaration::from_reader_sub_declare( + reader, + state, + options, + Some(start), + decorators, + ) + .map(Into::into) + } + TSXToken::Keyword(t) if t.is_in_function_header() => { + DeclareFunctionDeclaration::from_reader_sub_declare_with_decorators( + reader, state, options, decorators, + ) + .map(Into::into) + } + _ => throw_unexpected_token_with_token( + reader.next().ok_or_else(parse_lexing_error)?, + &[ + TSXToken::Keyword(TSXKeyword::Let), + TSXToken::Keyword(TSXKeyword::Const), + TSXToken::Keyword(TSXKeyword::Var), + TSXToken::Keyword(TSXKeyword::Function), + ], + ), + } } _ => throw_unexpected_token_with_token( reader.next().ok_or_else(parse_lexing_error)?, @@ -299,20 +309,24 @@ impl crate::ASTNode for Declaration { local: crate::LocalToStringInformation, ) { match self { - Declaration::Function(f) => f.to_string_from_buffer(buf, options, local), Declaration::Variable(var) => var.to_string_from_buffer(buf, options, local), Declaration::Class(cls) => cls.to_string_from_buffer(buf, options, local), Declaration::Import(is) => is.to_string_from_buffer(buf, options, local), Declaration::Export(es) => es.to_string_from_buffer(buf, options, local), + Declaration::Function(f) => f.to_string_from_buffer(buf, options, local), + // TODO should skip these under no types Declaration::Interface(id) => id.to_string_from_buffer(buf, options, local), Declaration::TypeAlias(ta) => ta.to_string_from_buffer(buf, options, local), Declaration::Enum(r#enum) => r#enum.to_string_from_buffer(buf, options, local), - // TODO should skip these under no types Declaration::DeclareFunction(dfd) => dfd.to_string_from_buffer(buf, options, local), Declaration::DeclareVariable(dvd) => dvd.to_string_from_buffer(buf, options, local), + #[cfg(feature = "full-typescript")] + Declaration::Namespace(ns) => ns.to_string_from_buffer(buf, options, local), Declaration::DeclareInterface(did) => { - buf.push_str("declare "); - did.to_string_from_buffer(buf, options, local); + if options.include_type_annotations { + buf.push_str("declare "); + did.to_string_from_buffer(buf, options, local); + } } } } diff --git a/parser/src/declarations/variable.rs b/parser/src/declarations/variable.rs index 1cde5854..bfed7993 100644 --- a/parser/src/declarations/variable.rs +++ b/parser/src/declarations/variable.rs @@ -3,10 +3,9 @@ use get_field_by_type::GetFieldByType; use iterator_endiate::EndiateIteratorExt; use crate::{ - derive_ASTNode, errors::parse_lexing_error, operators::COMMA_PRECEDENCE, + derive_ASTNode, errors::parse_lexing_error, expressions::operators::COMMA_PRECEDENCE, throw_unexpected_token_with_token, ASTNode, Expression, ParseOptions, ParseResult, Span, - TSXKeyword, TSXToken, Token, TokenReader, TypeAnnotation, VariableField, - VariableFieldInSourceCode, WithComment, + TSXKeyword, TSXToken, Token, TokenReader, TypeAnnotation, VariableField, WithComment, }; use visitable_derive::Visitable; @@ -125,7 +124,7 @@ impl DeclarationExpression for crate::Expression { #[get_field_by_type_target(Span)] #[partial_eq_ignore_types(Span)] pub struct VariableDeclarationItem { - pub name: WithComment>, + pub name: WithComment, pub type_annotation: Option, pub expression: TExpr, pub position: Span, @@ -137,9 +136,7 @@ impl ASTNode for VariableDeclarationItem state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult { - let name = WithComment::>::from_reader( - reader, state, options, - )?; + let name = WithComment::::from_reader(reader, state, options)?; let type_annotation = if reader .conditional_next(|tok| options.type_annotations && matches!(tok, TSXToken::Colon)) .is_some() @@ -167,7 +164,9 @@ impl ASTNode for VariableDeclarationItem local: crate::LocalToStringInformation, ) { self.name.to_string_from_buffer(buf, options, local); - if let (true, Some(type_annotation)) = (options.include_types, &self.type_annotation) { + if let (true, Some(type_annotation)) = + (options.include_type_annotations, &self.type_annotation) + { buf.push_str(": "); type_annotation.to_string_from_buffer(buf, options, local); } diff --git a/parser/src/errors.rs b/parser/src/errors.rs index 77b9f2e5..6d43e96f 100644 --- a/parser/src/errors.rs +++ b/parser/src/errors.rs @@ -23,6 +23,11 @@ pub enum ParseErrors<'a> { DestructuringRequiresValue, CannotAccessObjectLiteralDirectly, TrailingCommaNotAllowedHere, + InvalidNumberLiteral, + ReservedIdentifier, + AwaitRequiresForOf, + CannotUseLeadingParameterHere, + ExpectedIdentifier, } #[allow(missing_docs)] @@ -49,6 +54,7 @@ pub enum LexingErrors { InvalidExponentUsage, InvalidUnderscore, CannotLoadLargeFile(usize), + ExpectedDashInComment, } impl Display for LexingErrors { @@ -95,6 +101,9 @@ impl Display for LexingErrors { LexingErrors::CannotLoadLargeFile(size) => { write!(f, "Cannot parse {size:?} byte file (4GB maximum)") } + LexingErrors::ExpectedDashInComment => { + f.write_str("JSX comments must have two dashes after ` Display for ParseErrors<'a> { ParseErrors::TrailingCommaNotAllowedHere => { write!(f, "Trailing comma not allowed here") } + ParseErrors::InvalidNumberLiteral => { + write!(f, "Invalid number literal") + } + ParseErrors::ReservedIdentifier => { + write!(f, "Found reserved identifier") + } + ParseErrors::AwaitRequiresForOf => { + write!(f, "Can only use await on for (.. of ..)") + } + ParseErrors::CannotUseLeadingParameterHere => { + write!(f, "Cannot write this constraint in this kind of function") + } + ParseErrors::ExpectedIdentifier => { + write!(f, "Expected variable identifier") + } } } } diff --git a/parser/src/expressions/arrow_function.rs b/parser/src/expressions/arrow_function.rs index e5b4082d..1fabcf36 100644 --- a/parser/src/expressions/arrow_function.rs +++ b/parser/src/expressions/arrow_function.rs @@ -3,9 +3,11 @@ use tokenizer_lib::sized_tokens::TokenStart; use visitable_derive::Visitable; use crate::{ - errors::parse_lexing_error, functions::FunctionBased, parameters::FunctionParameters, - tokens::token_as_identifier, ASTNode, Block, Expression, FunctionBase, Parameter, ParseOptions, - ParseResult, Span, TSXToken, Token, TokenReader, TypeAnnotation, VariableField, WithComment, + errors::parse_lexing_error, + functions::{FunctionBased, FunctionParameters, Parameter}, + tokens::token_as_identifier, + ASTNode, Block, Expression, FunctionBase, ParseOptions, ParseResult, Span, TSXToken, Token, + TokenReader, TypeAnnotation, VariableField, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -16,8 +18,9 @@ pub type ArrowFunction = FunctionBase; pub type IsAsync = bool; #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] +#[allow(dead_code)] const TYPES: &str = r" - export interface ArrowFunction extends Omit { + export interface ArrowFunction extends FunctionBase { header: IsAsync, body: ExpressionOrBlock } @@ -27,6 +30,8 @@ impl FunctionBased for ArrowFunctionBase { type Name = (); type Header = IsAsync; type Body = ExpressionOrBlock; + type LeadingParameter = (); + type ParameterVisibility = (); // fn get_chain_variable(this: &FunctionBase) -> ChainVariable { // ChainVariable::UnderArrowFunction(this.body.get_block_id()) @@ -57,7 +62,7 @@ impl FunctionBased for ArrowFunctionBase { reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, - ) -> ParseResult { + ) -> ParseResult> { match reader.next().ok_or_else(parse_lexing_error)? { Token(TSXToken::OpenParentheses, open_paren) => { FunctionParameters::from_reader_sub_open_parenthesis( @@ -68,25 +73,20 @@ impl FunctionBased for ArrowFunctionBase { token => { let (name, position) = token_as_identifier(token, "arrow function parameter")?; let parameters = vec![Parameter { - name: WithComment::None(VariableIdentifier::Standard(name, position).into()), + visibility: (), + name: VariableField::Name(VariableIdentifier::Standard(name, position)).into(), type_annotation: None, additionally: None, position, }]; - Ok(FunctionParameters { - parameters, - rest_parameter: None, - position, - this_type: None, - super_type: None, - }) + Ok(FunctionParameters { leading: (), parameters, rest_parameter: None, position }) } } } fn parameters_to_string_from_buffer( buf: &mut T, - parameters: &FunctionParameters, + parameters: &FunctionParameters<(), ()>, options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { @@ -142,10 +142,14 @@ impl ArrowFunction { first_parameter: (String, Span), is_async: IsAsync, ) -> ParseResult { - let parameters = vec![crate::Parameter { - name: WithComment::None( - VariableIdentifier::Standard(first_parameter.0, first_parameter.1).into(), - ), + let (first_parameter_name, first_parameter_position) = first_parameter; + let name = VariableField::Name(VariableIdentifier::Standard( + first_parameter_name, + first_parameter_position, + )); + let parameters = vec![Parameter { + visibility: (), + name: name.into(), type_annotation: None, additionally: None, position: first_parameter.1, @@ -156,12 +160,11 @@ impl ArrowFunction { header: is_async, position: first_parameter.1.union(body.get_position()), name: (), - parameters: crate::FunctionParameters { + parameters: FunctionParameters { parameters, rest_parameter: None, position: first_parameter.1, - this_type: None, - super_type: None, + leading: (), }, return_type: None, type_parameters: None, diff --git a/parser/src/expressions/assignments.rs b/parser/src/expressions/assignments.rs index e8dc750d..950fc692 100644 --- a/parser/src/expressions/assignments.rs +++ b/parser/src/expressions/assignments.rs @@ -8,12 +8,11 @@ use visitable_derive::Visitable; use crate::{ ASTNode, ArrayDestructuringField, Expression, ObjectDestructuringField, ParseError, - ParseResult, VariableFieldInSourceCode, WithComment, + ParseResult, WithComment, }; use super::MultipleExpression; -/// TODO marker #[apply(derive_ASTNode)] #[derive(Debug, Clone, PartialEqExtras, Eq, Visitable, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] @@ -57,7 +56,16 @@ impl ASTNode for VariableOrPropertyAccess { buf.push_str(name); } VariableOrPropertyAccess::PropertyAccess { parent, property, .. } => { - parent.to_string_from_buffer(buf, options, local); + if let Expression::NumberLiteral(..) + | Expression::ObjectLiteral(..) + | Expression::ArrowFunction(..) = parent.get_non_parenthesized() + { + buf.push('('); + parent.to_string_from_buffer(buf, options, local); + buf.push(')'); + } else { + parent.to_string_from_buffer(buf, options, local); + } buf.push('.'); if let PropertyReference::Standard { property, is_private } = property { if *is_private { @@ -161,21 +169,14 @@ impl VariableOrPropertyAccess { } } -/// TODO should be different from `VariableFieldInSourceCode` here /// TODO visitable is current skipped... #[apply(derive_ASTNode)] #[derive(PartialEqExtras, Debug, Clone, Visitable, derive_enum_from_into::EnumFrom)] #[partial_eq_ignore_types(Span)] pub enum LHSOfAssignment { - ObjectDestructuring( - #[visit_skip_field] Vec>>, - Span, - ), - ArrayDestructuring( - #[visit_skip_field] Vec>>, - Span, - ), VariableOrPropertyAccess(VariableOrPropertyAccess), + ArrayDestructuring(#[visit_skip_field] Vec>, Span), + ObjectDestructuring(#[visit_skip_field] Vec>, Span), } impl LHSOfAssignment { @@ -214,8 +215,11 @@ impl LHSOfAssignment { buf.push('['); for (at_end, member) in members.iter().endiate() { member.to_string_from_buffer(buf, options, local); - if !at_end || matches!(member.get_ast_ref(), ArrayDestructuringField::None) { + if !at_end { buf.push(','); + if !matches!(member.get_ast_ref(), ArrayDestructuringField::None) { + options.push_gap_optionally(buf); + } } } buf.push(']'); diff --git a/parser/src/expressions/mod.rs b/parser/src/expressions/mod.rs index 4936a535..eea98da2 100644 --- a/parser/src/expressions/mod.rs +++ b/parser/src/expressions/mod.rs @@ -1,28 +1,25 @@ use crate::{ - declarations::ClassDeclaration, - derive_ASTNode, - errors::parse_lexing_error, - functions, - operators::{ - AssociativityDirection, BinaryAssignmentOperator, UnaryPostfixAssignmentOperator, - UnaryPrefixAssignmentOperator, ASSIGNMENT_PRECEDENCE, FUNCTION_CALL_PRECEDENCE, - RELATION_PRECEDENCE, - }, + declarations::ClassDeclaration, derive_ASTNode, errors::parse_lexing_error, functions, parse_bracketed, throw_unexpected_token_with_token, to_string_bracketed, - type_annotations::generic_arguments_from_reader_sub_open_angle, - ExpressionPosition, FunctionHeader, ListItem, Marker, NumberRepresentation, ParseErrors, - ParseResult, Quoted, TSXKeyword, + type_annotations::generic_arguments_from_reader_sub_open_angle, ExpressionPosition, + FunctionHeader, ListItem, Marker, NumberRepresentation, ParseErrors, ParseResult, Quoted, + TSXKeyword, }; use self::{ assignments::{LHSOfAssignment, VariableOrPropertyAccess}, object_literal::ObjectLiteral, + operators::{ + IncrementOrDecrement, Operator, ARROW_FUNCTION_PRECEDENCE, COMMA_PRECEDENCE, + CONDITIONAL_TERNARY_PRECEDENCE, CONSTRUCTOR_PRECEDENCE, + CONSTRUCTOR_WITHOUT_PARENTHESIS_PRECEDENCE, INDEX_PRECEDENCE, MEMBER_ACCESS_PRECEDENCE, + PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE, + }, }; -#[allow(clippy::wildcard_imports)] use super::{ - operators::*, tokens::token_as_identifier, ASTNode, Block, FunctionBase, JSXRoot, ParseError, - ParseOptions, Span, TSXToken, Token, TokenReader, TypeAnnotation, + tokens::token_as_identifier, ASTNode, Block, FunctionBase, JSXRoot, ParseError, ParseOptions, + Span, TSXToken, Token, TokenReader, TypeAnnotation, }; #[cfg(feature = "extras")] @@ -37,11 +34,18 @@ use visitable_derive::Visitable; pub mod arrow_function; pub mod assignments; pub mod object_literal; +pub mod operators; pub mod template_literal; pub use arrow_function::{ArrowFunction, ExpressionOrBlock}; pub use template_literal::{TemplateLiteral, TemplateLiteralPart}; +use operators::{ + AssociativityDirection, BinaryAssignmentOperator, BinaryOperator, UnaryOperator, + UnaryPostfixAssignmentOperator, UnaryPrefixAssignmentOperator, ASSIGNMENT_PRECEDENCE, + FUNCTION_CALL_PRECEDENCE, RELATION_PRECEDENCE, +}; + pub type ExpressionFunctionBase = functions::GeneralFunctionBase; pub type ExpressionFunction = FunctionBase; @@ -133,14 +137,14 @@ pub enum Expression { FunctionCall { function: Box, type_arguments: Option>, - arguments: Vec, + arguments: Vec, is_optional: bool, position: Span, }, ConstructorCall { constructor: Box, type_arguments: Option>, - arguments: Option>, + arguments: Option>, position: Span, }, /// e.g `... ? ... ? ...` @@ -158,13 +162,11 @@ pub enum Expression { Null(Span), Comment { content: String, - /// Allowed to be `None` in trailing functions arguments and JSX for some reason - on: Option>, + on: Box, position: Span, is_multiline: bool, prefix: bool, }, - /// TODO under cfg /// A start of a JSXNode JSXRoot(JSXRoot), /// Not to be confused with binary operator `is` @@ -206,7 +208,12 @@ impl ASTNode for Expression { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - self.to_string_using_precedence(buf, options, local, u8::MAX); + self.to_string_using_precedence( + buf, + options, + local, + ExpressionToStringArgument { on_left: false, parent_precedence: u8::MAX }, + ); } fn get_position(&self) -> &Span { @@ -252,7 +259,10 @@ impl Expression { let res = value.parse::(); match res { Ok(number) => Expression::NumberLiteral(number, position), - Err(_) => unreachable!("Could not parse {value}"), + Err(_) => { + // TODO this should never happen + return Err(ParseError::new(ParseErrors::InvalidNumberLiteral, position)); + } } } Token(TSXToken::RegexLiteral(pattern), start) => { @@ -396,8 +406,13 @@ impl Expression { rhs: Box::new(rhs), } } else { - let (items, end) = - parse_bracketed(reader, state, options, None, TSXToken::CloseBracket)?; + let (items, end) = parse_bracketed::( + reader, + state, + options, + None, + TSXToken::CloseBracket, + )?; Expression::ArrayLiteral(items, start.union(end)) } @@ -543,17 +558,6 @@ impl Expression { // TODO discern between multi-line here let (content, is_multiline, position) = TSXToken::try_into_comment(t).unwrap(); - if let Some(Token(TSXToken::CloseParentheses | TSXToken::JSXExpressionEnd, _)) = - reader.peek() - { - return Ok(Expression::Comment { - is_multiline, - content, - position, - on: None, - prefix: false, - }); - } let expression = Self::from_reader_with_precedence( reader, state, @@ -566,7 +570,7 @@ impl Expression { is_multiline, content, position, - on: Some(Box::new(expression)), + on: Box::new(expression), prefix: true, } } @@ -582,7 +586,7 @@ impl Expression { Expression::TemplateLiteral(template_literal) } Token(TSXToken::Keyword(kw), start) if function_header_ish(kw, reader) => { - // TODO fix + // TODO not great to recreate token, but that is how Rust works :) let token = Token(TSXToken::Keyword(kw), start); let (is_async, start, token) = if let Token(TSXToken::Keyword(TSXKeyword::Async), start) = token { @@ -609,81 +613,126 @@ impl Expression { (name, position), is_async, )?; - return Ok(Expression::ArrowFunction(function)); - } + Expression::ArrowFunction(function) + } else { + #[cfg(feature = "extras")] + { + use crate::functions::FunctionLocationModifier; + let (generator_keyword, token) = + if let Token(TSXToken::Keyword(TSXKeyword::Generator), _) = token { + (Some(token.get_span()), reader.next().unwrap()) + } else { + (None, token) + }; - #[cfg(feature = "extras")] - { - use crate::functions::FunctionLocationModifier; - let (generator_keyword, token) = - if let Token(TSXToken::Keyword(TSXKeyword::Generator), _) = token { - (Some(token.get_span()), reader.next().unwrap()) - } else { - (None, token) + let (location, token) = match token { + Token(TSXToken::Keyword(TSXKeyword::Server), _) => { + (Some(FunctionLocationModifier::Server), reader.next().unwrap()) + } + Token(TSXToken::Keyword(TSXKeyword::Worker), _) => { + (Some(FunctionLocationModifier::Worker), reader.next().unwrap()) + } + token => (None, token), }; - let (location, token) = match token { - Token(TSXToken::Keyword(TSXKeyword::Server), _) => { - (Some(FunctionLocationModifier::Server), reader.next().unwrap()) - } - Token(TSXToken::Keyword(TSXKeyword::Worker), _) => { - (Some(FunctionLocationModifier::Worker), reader.next().unwrap()) - } - token => (None, token), - }; + // Here because `token` (can't use `.expect_next()`) + let Token(TSXToken::Keyword(TSXKeyword::Function), function_start) = token + else { + return throw_unexpected_token_with_token( + token, + &[TSXToken::Keyword(TSXKeyword::Function)], + ); + }; - // Here because `token` (can't use `.expect_next()`) - let Token(TSXToken::Keyword(TSXKeyword::Function), function_start) = token - else { - return throw_unexpected_token_with_token( - token, - &[TSXToken::Keyword(TSXKeyword::Function)], - ); - }; + let function_end = + function_start.get_end_after(TSXKeyword::Function.length() as usize); - let function_end = - function_start.get_end_after(TSXKeyword::Function.length() as usize); + if generator_keyword.is_some() { + let position = start.union(function_end); - if generator_keyword.is_some() { - let position = start.union(function_end); + let header = FunctionHeader::ChadFunctionHeader { + is_async, + is_generator: true, + location, + position, + }; - let header = FunctionHeader::ChadFunctionHeader { - is_async, - is_generator: true, - location, - position, - }; + let name = if let Some(Token(TSXToken::OpenParentheses, _)) = + reader.peek() + { + None + } else { + let (token, span) = + token_as_identifier(reader.next().unwrap(), "function name")?; + Some(crate::VariableIdentifier::Standard(token, span)) + }; - let name = if let Some(Token(TSXToken::OpenParentheses, _)) = reader.peek() - { - None + FunctionBase::from_reader_with_header_and_name( + reader, + state, + options, + (Some(header.get_position().get_start()), header), + ExpressionPosition(name), + ) + .map(Expression::ExpressionFunction)? } else { - let (token, span) = - token_as_identifier(reader.next().unwrap(), "function name")?; - Some(crate::VariableIdentifier::Standard(token, span)) + let generator_star_token_position = reader + .conditional_next(|tok| matches!(tok, TSXToken::Multiply)) + .map(|token| token.get_span()); + + let end = generator_star_token_position + .as_ref() + .map_or(function_end, Span::get_end); + + let header = FunctionHeader::VirginFunctionHeader { + position: start.union(end), + is_async, + location, + generator_star_token_position, + }; + + let name = if let Some(Token(TSXToken::OpenParentheses, _)) = + reader.peek() + { + None + } else { + let (token, span) = + token_as_identifier(reader.next().unwrap(), "function name")?; + Some(crate::VariableIdentifier::Standard(token, span)) + }; + + FunctionBase::from_reader_with_header_and_name( + reader, + state, + options, + (Some(header.get_position().get_start()), header), + ExpressionPosition(name), + ) + .map(Expression::ExpressionFunction)? + } + } + + #[cfg(not(feature = "extras"))] + { + let Token(TSXToken::Keyword(TSXKeyword::Function), _) = token else { + return throw_unexpected_token_with_token( + token, + &[TSXToken::Keyword(TSXKeyword::Function)], + ); }; - FunctionBase::from_reader_with_header_and_name( - reader, - state, - options, - (Some(header.get_position().get_start()), header), - ExpressionPosition(name), - ) - .map(Expression::ExpressionFunction)? - } else { let generator_star_token_position = reader .conditional_next(|tok| matches!(tok, TSXToken::Multiply)) .map(|token| token.get_span()); - let end = generator_star_token_position - .as_ref() - .map_or(function_end, Span::get_end); + let position = + start.union(generator_star_token_position.as_ref().unwrap_or( + &start.with_length(TSXKeyword::Function.length() as usize), + )); let header = FunctionHeader::VirginFunctionHeader { - position: start.union(end), + position, is_async, - location, generator_star_token_position, }; @@ -693,62 +742,19 @@ impl Expression { } else { let (token, span) = token_as_identifier(reader.next().unwrap(), "function name")?; + Some(crate::VariableIdentifier::Standard(token, span)) }; - FunctionBase::from_reader_with_header_and_name( reader, state, options, - (Some(header.get_position().get_start()), header), + (Some(start), header), ExpressionPosition(name), ) .map(Expression::ExpressionFunction)? } } - - #[cfg(not(feature = "extras"))] - { - let Token(TSXToken::Keyword(TSXKeyword::Function), _) = token else { - return throw_unexpected_token_with_token( - token, - &[TSXToken::Keyword(TSXKeyword::Function)], - ); - }; - - let generator_star_token_position = reader - .conditional_next(|tok| matches!(tok, TSXToken::Multiply)) - .map(|token| token.get_span()); - - let position = start.union( - generator_star_token_position - .as_ref() - .unwrap_or(&start.with_length(TSXKeyword::Function.length() as usize)), - ); - - let header = FunctionHeader::VirginFunctionHeader { - position, - is_async, - generator_star_token_position, - }; - - let name = if let Some(Token(TSXToken::OpenParentheses, _)) = reader.peek() { - None - } else { - let (token, span) = - token_as_identifier(reader.next().unwrap(), "function name")?; - - Some(crate::VariableIdentifier::Standard(token, span)) - }; - FunctionBase::from_reader_with_header_and_name( - reader, - state, - options, - (Some(start), header), - ExpressionPosition(name), - ) - .map(Expression::ExpressionFunction)? - } } #[cfg(feature = "extras")] t @ Token(TSXToken::Keyword(TSXKeyword::Is), start) if options.is_expressions => { @@ -796,7 +802,7 @@ impl Expression { )?; let position = start.union(rhs.get_position()); Self::SpecialOperators( - SpecialOperators::InExpression { + SpecialOperators::In { lhs: InExpressionLHS::PrivateProperty(property_name), rhs: Box::new(rhs), }, @@ -837,8 +843,17 @@ impl Expression { position, } } else { - // else if options.partial_syntax + if let TSXToken::Keyword(ref keyword) = token.0 { + if keyword.is_invalid_identifier() { + return Err(ParseError::new( + ParseErrors::ReservedIdentifier, + token.get_span(), + )); + } + } + let (name, position) = token_as_identifier(token, "variable reference")?; + if options.interpolation_points && name == crate::marker::MARKER { let marker_id = state.new_partial_point_marker(position.get_start()); Expression::Marker { marker_id, position } @@ -850,7 +865,7 @@ impl Expression { (name, position), false, )?; - return Ok(Expression::ArrowFunction(function)); + Expression::ArrowFunction(function) } else { Expression::VariableReference(name, position) } @@ -858,6 +873,11 @@ impl Expression { } }; + // Operator precedence == 2, nothing can beat so + if let Expression::ArrowFunction(..) = first_expression { + return Ok(first_expression); + } + Self::from_reader_sub_first_expression( reader, state, @@ -875,7 +895,7 @@ impl Expression { first_expression: Expression, ) -> ParseResult { let mut top = first_expression; - loop { + while top.get_precedence() != 2 { let Token(peeked_token, _peeked_pos) = &reader.peek().unwrap(); match peeked_token { @@ -984,7 +1004,6 @@ impl Expression { let (property, position) = if options.partial_syntax && is_next_not_identifier { let marker = state.new_partial_point_marker(accessor_position); - // TODO maybe just "" (PropertyReference::Marker(marker), accessor_position.with_length(1)) } else { let is_private = @@ -1028,7 +1047,7 @@ impl Expression { TSXToken::try_into_comment(reader.next().unwrap()).unwrap(); top = Expression::Comment { content, - on: Some(Box::new(top)), + on: Box::new(top), position, is_multiline, prefix: false, @@ -1043,8 +1062,10 @@ impl Expression { return Ok(top); } - if cfg!(not(feature = "extras")) - && matches!(peeked_token, TSXToken::Keyword(TSXKeyword::Is)) + if (cfg!(not(feature = "extras")) + && matches!(peeked_token, TSXToken::Keyword(TSXKeyword::Is))) + || (cfg!(not(feature = "full-typescript")) + && matches!(peeked_token, TSXToken::Keyword(TSXKeyword::As))) { return Ok(top); } @@ -1054,18 +1075,23 @@ impl Expression { let position = top.get_position().union(reference.get_position()); let special_operators = match token.0 { - TSXToken::Keyword(TSXKeyword::As) => SpecialOperators::AsExpression { + #[cfg(feature = "full-typescript")] + TSXToken::Keyword(TSXKeyword::As) => SpecialOperators::AsCast { + value: top.into(), + rhs: match reference { + // TODO temp :0 + TypeAnnotation::Name(name, span) if name == "const" => { + TypeOrConst::Const(span) + } + reference => TypeOrConst::Type(Box::new(reference)), + }, + }, + TSXToken::Keyword(TSXKeyword::Satisfies) => SpecialOperators::Satisfies { value: top.into(), type_annotation: Box::new(reference), }, - TSXToken::Keyword(TSXKeyword::Satisfies) => { - SpecialOperators::SatisfiesExpression { - value: top.into(), - type_annotation: Box::new(reference), - } - } #[cfg(feature = "extras")] - TSXToken::Keyword(TSXKeyword::Is) => SpecialOperators::IsExpression { + TSXToken::Keyword(TSXKeyword::Is) => SpecialOperators::Is { value: top.into(), type_annotation: Box::new(reference), }, @@ -1073,6 +1099,15 @@ impl Expression { }; top = Self::SpecialOperators(special_operators, position); } + #[cfg(feature = "full-typescript")] + TSXToken::LogicalNot if options.type_annotations => { + let Token(_token, not_pos) = reader.next().unwrap(); + let position = top.get_position().union(not_pos.get_end_after(1)); + top = Self::SpecialOperators( + SpecialOperators::NonNullAssertion(Box::new(top)), + position, + ); + } TSXToken::Keyword(TSXKeyword::In) => { if AssociativityDirection::LeftToRight .should_return(parent_precedence, RELATION_PRECEDENCE) @@ -1090,7 +1125,7 @@ impl Expression { )?; let position = top.get_position().union(rhs.get_position()); top = Self::SpecialOperators( - SpecialOperators::InExpression { + SpecialOperators::In { lhs: InExpressionLHS::Expression(Box::new(top)), rhs: Box::new(rhs), }, @@ -1114,19 +1149,18 @@ impl Expression { )?; let position = top.get_position().union(rhs.get_position()); top = Self::SpecialOperators( - SpecialOperators::InstanceOfExpression { - lhs: Box::new(top), - rhs: Box::new(rhs), - }, + SpecialOperators::InstanceOf { lhs: Box::new(top), rhs: Box::new(rhs) }, position, ); } token => { - let token = if *token == TSXToken::OpenChevron { + // Splitting here side-steps some complaints the borrow checker has with passing + // a mutable reader here + let token = if let TSXToken::OpenChevron = token { if is_generic_arguments(reader) { let _ = reader.next(); let (type_arguments, _) = generic_arguments_from_reader_sub_open_angle( - reader, state, options, false, + reader, state, options, None, )?; let (arguments, end) = parse_bracketed( reader, @@ -1144,8 +1178,7 @@ impl Expression { }; continue; } - // TODO - &reader.peek().unwrap().0 + &TSXToken::OpenChevron } else { token }; @@ -1216,55 +1249,58 @@ impl Expression { } } } + + Ok(top) } #[must_use] pub fn get_precedence(&self) -> u8 { + // TODO unsure about some of these match self { - Self::NumberLiteral(..) - | Self::BooleanLiteral(..) - | Self::StringLiteral(..) - | Self::RegexLiteral { .. } - | Self::ArrayLiteral(..) - | Self::TemplateLiteral(..) - | Self::ParenthesizedExpression(..) - | Self::JSXRoot(..) - | Self::ArrowFunction(..) - | Self::ExpressionFunction(..) - | Self::Null(..) - | Self::ObjectLiteral(..) - | Self::VariableReference(..) - | Self::ThisReference(..) - | Self::SuperExpression(..) - | Self::NewTarget(..) - | Self::ClassExpression(..) - // TODO unsure about this one...? - | Self::DynamicImport { .. } - | Self::Marker { .. } => PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE, // TODO think this is true <- - Self::BinaryOperation { operator, .. } => operator.precedence(), - Self::UnaryOperation{ operator, .. } => operator.precedence(), - Self::Assignment { .. } => ASSIGNMENT_PRECEDENCE, - Self::BinaryAssignmentOperation { operator, .. } => operator.precedence(), - Self::UnaryPrefixAssignmentOperation{ operator, .. } => operator.precedence(), - Self::UnaryPostfixAssignmentOperation{ operator, .. } => operator.precedence(), - Self::PropertyAccess { .. } => MEMBER_ACCESS_PRECEDENCE, - Self::FunctionCall { .. } => FUNCTION_CALL_PRECEDENCE, - Self::ConstructorCall { arguments: Some(_), .. } => CONSTRUCTOR_PRECEDENCE, - Self::ConstructorCall { arguments: None, .. } => { - CONSTRUCTOR_WITHOUT_PARENTHESIS_PRECEDENCE - } - Self::Index { .. } => INDEX_PRECEDENCE, - Self::ConditionalTernary { .. } => CONDITIONAL_TERNARY_PRECEDENCE, - Self::Comment { on: Some(ref on), .. } => { - on.get_precedence() - } - Self::Comment { .. } => PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE, // TODO unsure about this + Self::NumberLiteral(..) + | Self::BooleanLiteral(..) + | Self::StringLiteral(..) + | Self::RegexLiteral { .. } + | Self::ArrayLiteral(..) + | Self::TemplateLiteral(..) + | Self::ParenthesizedExpression(..) + | Self::JSXRoot(..) + | Self::ExpressionFunction(..) + | Self::Null(..) + | Self::ObjectLiteral(..) + | Self::VariableReference(..) + | Self::ThisReference(..) + | Self::SuperExpression(..) + | Self::NewTarget(..) + | Self::ClassExpression(..) + | Self::DynamicImport { .. } + | Self::Marker { .. } => PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE, + Self::BinaryOperation { operator, .. } => operator.precedence(), + Self::UnaryOperation { operator, .. } => operator.precedence(), + Self::Assignment { .. } => ASSIGNMENT_PRECEDENCE, + Self::BinaryAssignmentOperation { operator, .. } => operator.precedence(), + Self::UnaryPrefixAssignmentOperation { operator, .. } => operator.precedence(), + Self::UnaryPostfixAssignmentOperation { operator, .. } => operator.precedence(), + Self::PropertyAccess { .. } => MEMBER_ACCESS_PRECEDENCE, + Self::FunctionCall { .. } => FUNCTION_CALL_PRECEDENCE, + Self::ConstructorCall { arguments: Some(_), .. } => CONSTRUCTOR_PRECEDENCE, + Self::ConstructorCall { arguments: None, .. } => { + CONSTRUCTOR_WITHOUT_PARENTHESIS_PRECEDENCE + } + Self::ArrowFunction(..) => ARROW_FUNCTION_PRECEDENCE, + Self::Index { .. } => INDEX_PRECEDENCE, + Self::ConditionalTernary { .. } => CONDITIONAL_TERNARY_PRECEDENCE, + Self::Comment { ref on, .. } => on.get_precedence(), + #[cfg(feature = "full-typescript")] + Self::SpecialOperators(SpecialOperators::NonNullAssertion(..), _) => { + PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE + } // All these are relational and have the same precedence - Self::SpecialOperators(..) => RELATION_PRECEDENCE, + Self::SpecialOperators(..) => RELATION_PRECEDENCE, // TODO unsure about this one...? #[cfg(feature = "extras")] - Self::IsExpression(..) => PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE, - } + Self::IsExpression(..) => PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE, + } } pub(crate) fn to_string_using_precedence( @@ -1272,10 +1308,10 @@ impl Expression { buf: &mut T, options: &crate::ToStringOptions, local: crate::LocalToStringInformation, - _parent_precedence: u8, + local2: ExpressionToStringArgument, ) { let self_precedence = self.get_precedence(); - // let inverted = parent_precedence < self_precedence; + // let inverted = local2.parent_precedence < self_precedence; // if inverted { // buf.push('('); // } @@ -1301,7 +1337,13 @@ impl Expression { } } Self::BinaryOperation { lhs, operator, rhs, .. } => { - lhs.to_string_using_precedence(buf, options, local, self_precedence); + lhs.to_string_using_precedence( + buf, + options, + local, + local2.with_precedence(self_precedence), + ); + // TODO not great if options.pretty || matches!( (operator, &**lhs), @@ -1326,6 +1368,7 @@ impl Expression { buf.push(' '); } buf.push_str(operator.to_str()); + // TODO not great if options.pretty || matches!( (operator, &**rhs), @@ -1352,49 +1395,97 @@ impl Expression { ) { buf.push(' '); } - rhs.to_string_using_precedence(buf, options, local, self_precedence); + rhs.to_string_using_precedence( + buf, + options, + local, + local2.with_precedence(self_precedence), + ); } Self::SpecialOperators(special, _) => match special { - SpecialOperators::AsExpression { value, type_annotation, .. } - | SpecialOperators::SatisfiesExpression { value, type_annotation, .. } => { + #[cfg(feature = "full-typescript")] + SpecialOperators::AsCast { value, rhs, .. } => { value.to_string_from_buffer(buf, options, local); - if options.include_types { - buf.push_str(match special { - SpecialOperators::AsExpression { .. } => " as ", - SpecialOperators::SatisfiesExpression { .. } => " satisfies ", - _ => unreachable!(), - }); + if options.include_type_annotations { + buf.push_str(" as "); + match rhs { + TypeOrConst::Type(type_annotation) => { + type_annotation.to_string_from_buffer(buf, options, local); + } + TypeOrConst::Const(_) => { + buf.push_str("const"); + } + } + } + } + SpecialOperators::Satisfies { value, type_annotation, .. } => { + value.to_string_from_buffer(buf, options, local); + if options.include_type_annotations { + buf.push_str(" satisfies "); type_annotation.to_string_from_buffer(buf, options, local); } } - SpecialOperators::InExpression { lhs, rhs } => { + SpecialOperators::In { lhs, rhs } => { match lhs { InExpressionLHS::PrivateProperty(property) => { buf.push('#'); buf.push_str(property); } InExpressionLHS::Expression(lhs) => { - lhs.to_string_using_precedence(buf, options, local, self_precedence); + lhs.to_string_using_precedence( + buf, + options, + local, + local2.with_precedence(self_precedence), + ); } } // TODO whitespace can be dropped depending on LHS and RHS buf.push_str(" in "); - rhs.to_string_using_precedence(buf, options, local, self_precedence); + rhs.to_string_using_precedence( + buf, + options, + local, + local2.with_precedence(self_precedence), + ); } - SpecialOperators::InstanceOfExpression { lhs, rhs } => { - lhs.to_string_using_precedence(buf, options, local, self_precedence); + SpecialOperators::InstanceOf { lhs, rhs } => { + lhs.to_string_using_precedence( + buf, + options, + local, + local2.with_precedence(self_precedence), + ); // TODO whitespace can be dropped depending on LHS and RHS buf.push_str(" instanceof "); - rhs.to_string_using_precedence(buf, options, local, self_precedence); + rhs.to_string_using_precedence( + buf, + options, + local, + local2.with_precedence(self_precedence), + ); + } + #[cfg(feature = "full-typescript")] + SpecialOperators::NonNullAssertion(on) => { + on.to_string_using_precedence( + buf, + options, + local, + local2.with_precedence(self_precedence), + ); + if options.include_type_annotations { + buf.push('!'); + } } #[cfg(feature = "extras")] - SpecialOperators::IsExpression { value, type_annotation, .. } => { + SpecialOperators::Is { value, type_annotation, .. } => { value.to_string_from_buffer(buf, options, local); type_annotation.to_string_from_buffer(buf, options, local); } }, Self::UnaryOperation { operand, operator, .. } => { buf.push_str(operator.to_str()); + // TODO not great if let ( UnaryOperator::Negation, Expression::UnaryPrefixAssignmentOperation { @@ -1420,19 +1511,22 @@ impl Expression { { buf.push(' '); } - operand.to_string_using_precedence(buf, options, local, self_precedence); + let right_argument = local2.with_precedence(self_precedence).on_right(); + operand.to_string_using_precedence(buf, options, local, right_argument); } Self::Assignment { lhs, rhs, .. } => { lhs.to_string_from_buffer(buf, options, local); buf.push_str(if options.pretty { " = " } else { "=" }); - rhs.to_string_using_precedence(buf, options, local, self_precedence); + let right_argument = local2.with_precedence(self_precedence).on_right(); + rhs.to_string_using_precedence(buf, options, local, right_argument); } Self::BinaryAssignmentOperation { lhs, operator, rhs, .. } => { lhs.to_string_from_buffer(buf, options, local); options.push_gap_optionally(buf); buf.push_str(operator.to_str()); options.push_gap_optionally(buf); - rhs.to_string_using_precedence(buf, options, local, self_precedence); + let right_argument = local2.with_precedence(self_precedence).on_right(); + rhs.to_string_using_precedence(buf, options, local, right_argument); } Self::UnaryPrefixAssignmentOperation { operand, operator, .. } => { buf.push_str(operator.to_str()); @@ -1460,6 +1554,7 @@ impl Expression { Self::PropertyAccess { parent, property, is_optional, position, .. } => { buf.add_mapping(&position.with_source(local.under)); + // TODO number okay, others don't quite get? if let Self::NumberLiteral(..) | Self::ObjectLiteral(..) | Self::ArrowFunction(..) = parent.get_non_parenthesized() { @@ -1490,18 +1585,17 @@ impl Expression { } Self::ParenthesizedExpression(expr, _) => { // TODO more expressions could be considered for parenthesis elision - if let MultipleExpression::Single(inner) = &**expr { - if inner.get_precedence() == PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE { - inner.to_string_from_buffer(buf, options, local); - return; - } + if matches!(&**expr, MultipleExpression::Single(inner) if inner.get_precedence() == PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE) + { + expr.to_string_on_left(buf, options, local); + } else { + buf.push('('); + expr.to_string_from_buffer(buf, options, local); + buf.push(')'); } - buf.push('('); - expr.to_string_from_buffer(buf, options, local); - buf.push(')'); } Self::Index { indexee: expression, indexer, is_optional, .. } => { - expression.to_string_from_buffer(buf, options, local); + expression.to_string_using_precedence(buf, options, local, local2); if *is_optional { buf.push_str("?."); } @@ -1516,20 +1610,14 @@ impl Expression { return; } - if let Self::ArrowFunction(..) | Self::ObjectLiteral(..) = - function.get_non_parenthesized() - { - buf.push('('); - function.to_string_from_buffer(buf, options, local); - buf.push(')'); - } else { - function.to_string_from_buffer(buf, options, local); - } + function.to_string_using_precedence(buf, options, local, local2); if *is_optional { buf.push_str("?."); } - if let (true, Some(type_arguments)) = (options.include_types, type_arguments) { + if let (true, Some(type_arguments)) = + (options.include_type_annotations, type_arguments) + { to_string_bracketed(type_arguments, ('<', '>'), buf, options, local); } arguments_to_string(arguments, buf, options, local); @@ -1537,7 +1625,9 @@ impl Expression { Self::ConstructorCall { constructor, type_arguments, arguments, .. } => { buf.push_str("new "); constructor.to_string_from_buffer(buf, options, local); - if let (true, Some(type_arguments)) = (options.include_types, type_arguments) { + if let (true, Some(type_arguments)) = + (options.include_type_annotations, type_arguments) + { to_string_bracketed(type_arguments, ('<', '>'), buf, options, local); } if let Some(arguments) = arguments { @@ -1551,16 +1641,43 @@ impl Expression { to_string_bracketed(values, ('[', ']'), buf, options, local); } Self::JSXRoot(root) => root.to_string_from_buffer(buf, options, local), - Self::ObjectLiteral(object_literal) => { - object_literal.to_string_from_buffer(buf, options, local); - } Self::ArrowFunction(arrow_function) => { + // `async () => {}` looks like async statement declaration when in declaration + if local2.on_left && arrow_function.header { + buf.push('('); + } arrow_function.to_string_from_buffer(buf, options, local); + if local2.on_left && arrow_function.header { + buf.push(')'); + } } Self::ExpressionFunction(function) => { + if local2.on_left { + buf.push('('); + } function.to_string_from_buffer(buf, options, local); + if local2.on_left { + buf.push(')'); + } + } + Self::ObjectLiteral(object_literal) => { + if local2.on_left { + buf.push('('); + } + object_literal.to_string_from_buffer(buf, options, local); + if local2.on_left { + buf.push(')'); + } + } + Self::ClassExpression(class) => { + if local2.on_left { + buf.push('('); + } + class.to_string_from_buffer(buf, options, local); + if local2.on_left { + buf.push(')'); + } } - Self::ClassExpression(class) => class.to_string_from_buffer(buf, options, local), Self::Comment { content, on, is_multiline, prefix, position: _ } => { if *prefix && options.should_add_comment(content.starts_with('*')) { if *is_multiline { @@ -1573,9 +1690,7 @@ impl Expression { buf.push_new_line(); } } - if let Some(on) = on { - on.to_string_from_buffer(buf, options, local); - } + on.to_string_using_precedence(buf, options, local, local2); if !prefix && options.should_add_comment(content.starts_with('*')) { if *is_multiline { buf.push_str("/*"); @@ -1589,41 +1704,44 @@ impl Expression { } } Self::TemplateLiteral(template_literal) => { - template_literal.to_string_from_buffer(buf, options, local); + if let Some(tag) = &template_literal.tag { + tag.to_string_using_precedence(buf, options, local, local2); + } + buf.push('`'); + for part in &template_literal.parts { + match part { + TemplateLiteralPart::Static(content) => { + buf.push_str_contains_new_line(content.as_str()); + } + TemplateLiteralPart::Dynamic(expression) => { + buf.push_str("${"); + expression.to_string_from_buffer(buf, options, local); + buf.push('}'); + } + } + } + buf.push('`'); } Self::ConditionalTernary { condition, truthy_result, falsy_result, .. } => { - if let Self::ArrowFunction(..) | Self::ExpressionFunction(..) = - condition.get_non_parenthesized() - { - buf.push('('); - condition.to_string_using_precedence( - buf, - options, - local, - CONDITIONAL_TERNARY_PRECEDENCE, - ); - buf.push(')'); - } else { - condition.to_string_using_precedence( - buf, - options, - local, - CONDITIONAL_TERNARY_PRECEDENCE, - ); - } + condition.to_string_using_precedence( + buf, + options, + local, + local2.with_precedence(CONDITIONAL_TERNARY_PRECEDENCE), + ); buf.push_str(if options.pretty { " ? " } else { "?" }); truthy_result.to_string_using_precedence( buf, options, local, - CONDITIONAL_TERNARY_PRECEDENCE, + local2.with_precedence(CONDITIONAL_TERNARY_PRECEDENCE).on_right(), ); buf.push_str(if options.pretty { " : " } else { ":" }); falsy_result.to_string_using_precedence( buf, options, local, - CONDITIONAL_TERNARY_PRECEDENCE, + local2.with_precedence(CONDITIONAL_TERNARY_PRECEDENCE).on_right(), ); } Self::Null(..) => buf.push_str("null"), @@ -1665,7 +1783,23 @@ fn function_header_ish( )) } -/// Represents expressions that can be `,` +#[derive(Clone, Copy)] +pub(crate) struct ExpressionToStringArgument { + pub on_left: bool, + pub parent_precedence: u8, +} + +impl ExpressionToStringArgument { + pub fn on_right(self) -> Self { + Self { on_left: false, parent_precedence: self.parent_precedence } + } + + pub fn with_precedence(self, precedence: u8) -> Self { + Self { on_left: self.on_left, parent_precedence: precedence } + } +} + +/// Represents expressions under the comma operator #[apply(derive_ASTNode)] #[derive(Debug, Clone, PartialEq, Eq, Visitable, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] @@ -1684,16 +1818,23 @@ impl MultipleExpression { } } - /// These are valid in expression position but are parsed different in statement mode - pub(crate) fn left_is_statement_like(&self) -> bool { + pub(crate) fn to_string_on_left( + &self, + buf: &mut T, + options: &crate::ToStringOptions, + local: crate::LocalToStringInformation, + ) { match self { - MultipleExpression::Multiple { lhs, .. } => lhs.left_is_statement_like(), - MultipleExpression::Single(e) => matches!( - e.get_non_parenthesized(), - Expression::ObjectLiteral(_) - | Expression::ExpressionFunction(_) - | Expression::ClassExpression(_) - ), + MultipleExpression::Multiple { lhs, rhs, position: _ } => { + lhs.to_string_on_left(buf, options, local); + buf.push(','); + rhs.to_string_from_buffer(buf, options, local); + } + MultipleExpression::Single(single) => { + let local2 = + ExpressionToStringArgument { on_left: true, parent_precedence: u8::MAX }; + single.to_string_using_precedence(buf, options, local, local2); + } } } } @@ -1802,7 +1943,7 @@ fn is_generic_arguments(reader: &mut impl TokenReader( - nodes: &[SpreadExpression], + nodes: &[FunctionArgument], buf: &mut T, options: &crate::ToStringOptions, local: crate::LocalToStringInformation, @@ -1826,11 +1967,19 @@ pub(crate) fn arguments_to_string( options.add_indent(local.depth + 1, buf); } for (at_end, node) in iterator_endiate::EndiateIteratorExt::endiate(nodes.iter()) { - // Hack for arrays, this is just easier for generators - if let SpreadExpression::Spread(Expression::ArrayLiteral(items, _), _) = node { - for (at_end, item) in iterator_endiate::EndiateIteratorExt::endiate(items.iter()) { - item.to_string_from_buffer(buf, options, local); - if !at_end { + // Hack for arrays, this is just easier for generators and ends up in a smaller output + if let FunctionArgument::Spread(Expression::ArrayLiteral(items, _), _) = node { + if items.is_empty() { + continue; + } + for (inner_at_end, item) in iterator_endiate::EndiateIteratorExt::endiate(items.iter()) + { + if item.0.is_none() { + buf.push_str("undefined"); + } else { + item.to_string_from_buffer(buf, options, local); + } + if !inner_at_end { buf.push(','); options.push_gap_optionally(buf); } @@ -1855,54 +2004,65 @@ pub(crate) fn arguments_to_string( buf.push(')'); } -#[apply(derive_ASTNode)] -#[derive(PartialEqExtras, Debug, Clone, Visitable)] -#[partial_eq_ignore_types(Span)] -pub enum InExpressionLHS { - PrivateProperty(String), - Expression(Box), -} - /// Binary operations whose RHS are types rather than [Expression]s #[apply(derive_ASTNode)] #[derive(PartialEqExtras, Debug, Clone, Visitable)] #[partial_eq_ignore_types(Span)] pub enum SpecialOperators { /// TS Only - AsExpression { + Satisfies { value: Box, type_annotation: Box, }, - /// TS Only - SatisfiesExpression { - value: Box, - type_annotation: Box, - }, - InExpression { + In { lhs: InExpressionLHS, rhs: Box, }, - InstanceOfExpression { + InstanceOf { lhs: Box, rhs: Box, }, #[cfg(feature = "extras")] - IsExpression { + Is { value: Box, type_annotation: Box, }, + #[cfg(feature = "full-typescript")] + NonNullAssertion(Box), + #[cfg(feature = "full-typescript")] + AsCast { + value: Box, + rhs: TypeOrConst, + }, } +#[cfg(feature = "full-typescript")] +#[apply(derive_ASTNode)] #[derive(Debug, Clone, PartialEq, Eq, Visitable)] +pub enum TypeOrConst { + Type(Box), + Const(Span), +} + +#[apply(derive_ASTNode)] +#[derive(PartialEqExtras, Debug, Clone, Visitable)] +#[partial_eq_ignore_types(Span)] +pub enum InExpressionLHS { + PrivateProperty(String), + Expression(Box), +} + #[apply(derive_ASTNode)] -pub enum SpreadExpression { +#[derive(Debug, Clone, PartialEq, Eq, Visitable)] +pub enum FunctionArgument { Spread(Expression, Span), - NonSpread(Expression), + Standard(Expression), + Comment { content: String, is_multiline: bool, position: Span }, } -impl ListItem for SpreadExpression {} +impl ListItem for FunctionArgument {} -impl ASTNode for SpreadExpression { +impl ASTNode for FunctionArgument { fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, @@ -1919,31 +2079,47 @@ impl ASTNode for SpreadExpression { t if t.is_comment() => { let (content, is_multiline, position) = TSXToken::try_into_comment(reader.next().unwrap()).unwrap(); + + // Function arguments, JSX interpolated expressions and array elements don't have to have a value + if let Some(Token( + TSXToken::CloseParentheses + | TSXToken::JSXExpressionEnd + | TSXToken::CloseBracket + | TSXToken::Comma, + _, + )) = reader.peek() + { + return Ok(Self::Comment { content, is_multiline, position }); + } + let expr = Self::from_reader(reader, state, options)?; let position = position.union(expr.get_position()); + Ok(match expr { - SpreadExpression::Spread(expr, _end) => SpreadExpression::Spread( + FunctionArgument::Spread(expr, _end) => FunctionArgument::Spread( Expression::Comment { content, - on: Some(Box::new(expr)), + on: Box::new(expr), position, is_multiline, prefix: true, }, position, ), - SpreadExpression::NonSpread(expr) => { - SpreadExpression::NonSpread(Expression::Comment { + FunctionArgument::Standard(expr) => { + FunctionArgument::Standard(Expression::Comment { content, - on: Some(Box::new(expr)), + on: Box::new(expr), position, is_multiline, prefix: true, }) } + // TODO + c @ FunctionArgument::Comment { .. } => c, }) } - _ => Ok(Self::NonSpread(Expression::from_reader(reader, state, options)?)), + _ => Ok(Self::Standard(Expression::from_reader(reader, state, options)?)), } } @@ -1954,33 +2130,42 @@ impl ASTNode for SpreadExpression { local: crate::LocalToStringInformation, ) { match self { - SpreadExpression::Spread(expression, _) => { + FunctionArgument::Spread(expression, _) => { buf.push_str("..."); expression.to_string_from_buffer(buf, options, local); } - SpreadExpression::NonSpread(expression) => { + FunctionArgument::Standard(expression) => { expression.to_string_from_buffer(buf, options, local); } + FunctionArgument::Comment { content, is_multiline, position: _ } => { + if options.should_add_comment(*is_multiline && content.starts_with('*')) { + buf.push_str("/*"); + buf.push_str(content); + buf.push_str("*/"); + } + } } } fn get_position(&self) -> &Span { match self { - SpreadExpression::Spread(_, pos) => pos, - SpreadExpression::NonSpread(expr) => expr.get_position(), + FunctionArgument::Comment { position, .. } | FunctionArgument::Spread(_, position) => { + position + } + FunctionArgument::Standard(expr) => expr.get_position(), } } } -impl From for SpreadExpression { +impl From for FunctionArgument { fn from(value: Expression) -> Self { - SpreadExpression::NonSpread(value) + FunctionArgument::Standard(value) } } #[derive(Debug, Clone, PartialEq, Eq, Visitable)] #[apply(derive_ASTNode)] -pub struct ArrayElement(pub Option); +pub struct ArrayElement(pub Option); impl ASTNode for ArrayElement { fn get_position(&self) -> &Span { @@ -1992,7 +2177,7 @@ impl ASTNode for ArrayElement { state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult { - Ok(Self(Some(ASTNode::from_reader(reader, state, options)?))) + Ok(Self(Some(FunctionArgument::from_reader(reader, state, options)?))) } fn to_string_from_buffer( @@ -2024,12 +2209,11 @@ impl Expression { // TODO maybe async header: false, name: (), - parameters: crate::FunctionParameters { + parameters: crate::functions::FunctionParameters { parameters: Default::default(), rest_parameter: Default::default(), position, - this_type: None, - super_type: None, + leading: (), }, return_type: None, type_parameters: None, @@ -2074,26 +2258,12 @@ impl Expression { // TODO could return a variant here... self } - } else if let Expression::Comment { on: Some(on), .. } = self { + } else if let Expression::Comment { on, .. } = self { on.get_non_parenthesized() } else { self } } - - /// For prettier printing - /// - /// TODO temp - #[must_use] - pub fn is_small(&self) -> bool { - match self { - Self::NumberLiteral(..) | Self::BooleanLiteral(..) | Self::VariableReference(..) => { - true - } - Self::StringLiteral(value, ..) => value.len() < 8, - _ => false, - } - } } /// "super" cannot be used alone @@ -2101,18 +2271,15 @@ impl Expression { #[derive(PartialEqExtras, Debug, Clone, Visitable)] #[partial_eq_ignore_types(Span)] pub enum SuperReference { - Call { arguments: Vec }, + Call { arguments: Vec }, PropertyAccess { property: String }, Index { indexer: Box }, } #[cfg(test)] mod tests { - use super::{ASTNode, Expression, Expression::*, MultipleExpression}; - use crate::{ - assert_matches_ast, ast::SpreadExpression, operators::BinaryOperator, span, - NumberRepresentation, Quoted, - }; + use super::{ASTNode, BinaryOperator, Expression, Expression::*, MultipleExpression}; + use crate::{assert_matches_ast, ast::FunctionArgument, span, NumberRepresentation, Quoted}; #[test] fn literal() { @@ -2174,7 +2341,7 @@ mod tests { fn spread_function_argument() { assert_matches_ast!( "console.table(...a)", - FunctionCall { arguments: Deref @ [SpreadExpression::Spread(VariableReference(..), span!(14, 18))], .. } + FunctionCall { arguments: Deref @ [FunctionArgument::Spread(VariableReference(..), span!(14, 18))], .. } ); } diff --git a/parser/src/expressions/object_literal.rs b/parser/src/expressions/object_literal.rs index a722dc09..6b8a12dd 100644 --- a/parser/src/expressions/object_literal.rs +++ b/parser/src/expressions/object_literal.rs @@ -1,13 +1,7 @@ -use derive_partial_eq_extras::PartialEqExtras; -use iterator_endiate::EndiateIteratorExt; -use std::fmt::Debug; -use tokenizer_lib::sized_tokens::{TokenReaderWithTokenEnds, TokenStart}; -use visitable_derive::Visitable; - use crate::{ derive_ASTNode, errors::parse_lexing_error, - functions::{FunctionBased, HeadingAndPosition, MethodHeader}, + functions::{FunctionBased, HeadingAndPosition, MethodHeader, ThisParameter}, property_key::AlwaysPublic, throw_unexpected_token_with_token, visiting::Visitable, @@ -15,6 +9,12 @@ use crate::{ TSXToken, Token, TokenReader, WithComment, }; +use derive_partial_eq_extras::PartialEqExtras; +use iterator_endiate::EndiateIteratorExt; +use std::fmt::Debug; +use tokenizer_lib::sized_tokens::{TokenReaderWithTokenEnds, TokenStart}; +use visitable_derive::Visitable; + #[apply(derive_ASTNode)] #[derive(Debug, Clone, Eq, PartialEq, Visitable, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] @@ -70,11 +70,13 @@ pub struct ObjectLiteralMethodBase; pub type ObjectLiteralMethod = FunctionBase; #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] -const TYPES: &str = r" +#[allow(dead_code)] +const OBJECT_LITERAL_METHOD_TYPE: &str = r" export interface ObjectLiteralMethod extends FunctionBase { header: MethodHeader, body: Block, - name: WithComment> + name: WithComment>, + parameters: FunctionParameters } "; @@ -82,6 +84,8 @@ impl FunctionBased for ObjectLiteralMethodBase { type Name = WithComment>; type Header = MethodHeader; type Body = Block; + type LeadingParameter = Option; + type ParameterVisibility = (); fn header_and_name_from_reader( reader: &mut impl TokenReader, diff --git a/parser/src/operators.rs b/parser/src/expressions/operators.rs similarity index 98% rename from parser/src/operators.rs rename to parser/src/expressions/operators.rs index f9c1c8fc..ea0f4f1d 100644 --- a/parser/src/operators.rs +++ b/parser/src/expressions/operators.rs @@ -454,10 +454,12 @@ impl TryFrom<&TSXToken> for UnaryPrefixAssignmentOperator { impl BinaryOperator { /// Operators which return true may or may not evaluate RHS based on their own value - /// TODO might be more #[must_use] pub fn is_rhs_conditional_evaluation(&self) -> bool { - matches!(self, BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr) + matches!( + self, + BinaryOperator::LogicalAnd | BinaryOperator::LogicalOr | BinaryOperator::NullCoalescing + ) } } @@ -471,4 +473,5 @@ pub(crate) const CONSTRUCTOR_PRECEDENCE: u8 = 18; pub(crate) const CONSTRUCTOR_WITHOUT_PARENTHESIS_PRECEDENCE: u8 = 17; pub(crate) const RELATION_PRECEDENCE: u8 = 10; pub(crate) const PARENTHESIZED_EXPRESSION_AND_LITERAL_PRECEDENCE: u8 = 19; +pub(crate) const ARROW_FUNCTION_PRECEDENCE: u8 = 2; pub(crate) const ASSIGNMENT_PRECEDENCE: u8 = 2; diff --git a/parser/src/expressions/template_literal.rs b/parser/src/expressions/template_literal.rs index 8ba967e3..8f347093 100644 --- a/parser/src/expressions/template_literal.rs +++ b/parser/src/expressions/template_literal.rs @@ -14,8 +14,8 @@ pub struct TemplateLiteral { pub position: Span, } -#[derive(Debug, Clone, PartialEq, Eq)] #[apply(derive_ASTNode)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum TemplateLiteralPart { Static(String), Dynamic(Box), diff --git a/parser/src/extensions/is_expression.rs b/parser/src/extensions/is_expression.rs index eda7f3ad..b4098c1a 100644 --- a/parser/src/extensions/is_expression.rs +++ b/parser/src/extensions/is_expression.rs @@ -65,8 +65,14 @@ pub(crate) fn is_expression_from_reader_sub_is_keyword( reader.expect_next(TSXToken::OpenBrace)?; let mut branches = Vec::new(); loop { - let type_annotation = - TypeAnnotation::from_reader_with_config(reader, state, options, false, true, None)?; + // Function important here for + let type_annotation = TypeAnnotation::from_reader_with_config( + reader, + state, + options, + Some(crate::type_annotations::TypeOperatorKind::Function), + None, + )?; reader.expect_next(TSXToken::Arrow)?; let body = ExpressionOrBlock::from_reader(reader, state, options)?; if let Some(token) = reader.conditional_next(|t| matches!(t, TSXToken::CloseBrace)) { diff --git a/parser/src/extensions/jsx.rs b/parser/src/extensions/jsx.rs index 404931f8..2e197b60 100644 --- a/parser/src/extensions/jsx.rs +++ b/parser/src/extensions/jsx.rs @@ -1,6 +1,6 @@ use crate::{ - derive_ASTNode, errors::parse_lexing_error, ASTNode, Expression, ParseError, ParseOptions, - ParseResult, Span, TSXToken, Token, TokenReader, + ast::FunctionArgument, derive_ASTNode, errors::parse_lexing_error, ASTNode, Expression, + ParseError, ParseOptions, ParseResult, Span, TSXToken, Token, TokenReader, }; use tokenizer_lib::sized_tokens::{TokenEnd, TokenReaderWithTokenEnds, TokenStart}; use visitable_derive::Visitable; @@ -70,7 +70,7 @@ impl ASTNode for JSXElement { buf.push('>'); } JSXElementChildren::SelfClosing => { - buf.push_str("/>"); + buf.push_str(">"); } } } @@ -165,7 +165,7 @@ fn parse_jsx_children( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, -) -> Result, ParseError> { +) -> ParseResult> { let mut children = Vec::new(); loop { if matches!( @@ -184,16 +184,23 @@ fn jsx_children_to_string( options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - let indent = + let element_of_line_break_in_children = children.iter().any(|node| matches!(node, JSXNode::Element(..) | JSXNode::LineBreak)); + + let mut previous_was_break = true; + for node in children { - if indent { + if element_of_line_break_in_children + && !matches!(node, JSXNode::LineBreak) + && previous_was_break + { options.add_indent(local.depth + 1, buf); } node.to_string_from_buffer(buf, options, local); + previous_was_break = matches!(node, JSXNode::Element(..) | JSXNode::LineBreak); } - if options.pretty && local.depth > 0 && matches!(children.last(), Some(JSXNode::LineBreak)) { + if options.pretty && local.depth > 0 && previous_was_break { options.add_indent(local.depth, buf); } } @@ -214,20 +221,23 @@ impl JSXRoot { } } -// TODO Fragment +// TODO can `JSXFragment` appear here? #[derive(Debug, Clone, PartialEq, Eq, Visitable)] #[apply(derive_ASTNode)] pub enum JSXNode { - TextNode(String, Span), - InterpolatedExpression(Box, Span), Element(JSXElement), + TextNode(String, Span), + InterpolatedExpression(Box, Span), + Comment(String, Span), LineBreak, } impl ASTNode for JSXNode { fn get_position(&self) -> &Span { match self { - JSXNode::TextNode(_, pos) | JSXNode::InterpolatedExpression(_, pos) => pos, + JSXNode::TextNode(_, pos) + | JSXNode::InterpolatedExpression(_, pos) + | JSXNode::Comment(_, pos) => pos, JSXNode::Element(element) => element.get_position(), JSXNode::LineBreak => &source_map::Nullable::NULL, } @@ -242,10 +252,11 @@ impl ASTNode for JSXNode { match token { Token(TSXToken::JSXContent(content), start) => { let position = start.with_length(content.len()); - Ok(JSXNode::TextNode(content, position)) + // TODO `trim` debatable + Ok(JSXNode::TextNode(content.trim_start().into(), position)) } Token(TSXToken::JSXExpressionStart, pos) => { - let expression = Expression::from_reader(reader, state, options)?; + let expression = FunctionArgument::from_reader(reader, state, options)?; let end_pos = reader.expect_next_get_end(TSXToken::JSXExpressionEnd)?; Ok(JSXNode::InterpolatedExpression(Box::new(expression), pos.union(end_pos))) } @@ -253,6 +264,10 @@ impl ASTNode for JSXNode { JSXElement::from_reader_sub_start(reader, state, options, pos).map(JSXNode::Element) } Token(TSXToken::JSXContentLineBreak, _) => Ok(JSXNode::LineBreak), + Token(TSXToken::JSXComment(comment), start) => { + let pos = start.with_length(comment.len() + 7); + Ok(JSXNode::Comment(comment, pos)) + } _token => Err(parse_lexing_error()), } } @@ -269,11 +284,6 @@ impl ASTNode for JSXNode { } JSXNode::TextNode(text, _) => buf.push_str(text), JSXNode::InterpolatedExpression(expression, _) => { - if !options.should_add_comment(false) - && matches!(&**expression, Expression::Comment { .. }) - { - return; - } buf.push('{'); expression.to_string_from_buffer(buf, options, local.next_level()); buf.push('}'); @@ -283,6 +293,13 @@ impl ASTNode for JSXNode { buf.push_new_line(); } } + JSXNode::Comment(comment, _) => { + if options.pretty { + buf.push_str(""); + } + } } } } @@ -294,7 +311,6 @@ pub enum JSXAttribute { Static(String, String, Span), Dynamic(String, Box, Span), BooleanAttribute(String, Span), - // TODO could combine these two Spread(Expression, Span), /// Preferably want a identifier here not an expr Shorthand(Expression), @@ -316,7 +332,7 @@ impl ASTNode for JSXAttribute { _state: &mut crate::ParsingState, _options: &ParseOptions, ) -> ParseResult { - todo!("this is currently done in JSXElement::from_reader") + todo!("this is currently done in `JSXElement::from_reader`") } fn to_string_from_buffer( diff --git a/parser/src/functions.rs b/parser/src/functions/mod.rs similarity index 85% rename from parser/src/functions.rs rename to parser/src/functions/mod.rs index 2a526524..ba3fdf24 100644 --- a/parser/src/functions.rs +++ b/parser/src/functions/mod.rs @@ -4,8 +4,8 @@ use crate::property_key::PropertyKeyKind; use crate::visiting::{ImmutableVariableOrProperty, MutableVariableOrProperty}; use crate::{ derive_ASTNode, parse_bracketed, to_string_bracketed, ASTNode, Block, - ExpressionOrStatementPosition, ExpressionPosition, GenericTypeConstraint, ParseOptions, - ParseResult, TSXToken, TypeAnnotation, VisitOptions, Visitable, WithComment, + ExpressionOrStatementPosition, ExpressionPosition, ParseOptions, ParseResult, TSXToken, + TypeAnnotation, TypeParameter, VisitOptions, Visitable, WithComment, }; use crate::{PropertyKey, TSXKeyword}; use derive_partial_eq_extras::PartialEqExtras; @@ -13,7 +13,8 @@ use source_map::{Span, ToString}; use tokenizer_lib::sized_tokens::TokenStart; use tokenizer_lib::{Token, TokenReader}; -pub use crate::parameters::*; +mod parameters; +pub use parameters::*; pub mod bases { pub use crate::{ @@ -34,8 +35,26 @@ pub type HeadingAndPosition = (Option, ::Head pub trait FunctionBased: Debug + Clone + PartialEq + Eq + Send + Sync { /// Includes a keyword and/or modifiers type Header: Debug + Clone + PartialEq + Eq + Send + Sync; + /// A name of the function type Name: Debug + Clone + PartialEq + Eq + Send + Sync; + + /// For `this` constraint + #[cfg(not(feature = "serde-serialize"))] + type LeadingParameter: LeadingParameter; + + /// Cfg to make up for the fact `serde_derive` does not use `syn_helpers` + #[cfg(feature = "serde-serialize")] + type LeadingParameter: LeadingParameter + serde::Serialize; + + /// For constructors + #[cfg(not(feature = "serde-serialize"))] + type ParameterVisibility: ParameterVisibility; + + /// Cfg to make up for the fact `serde_derive` does not use `syn_helpers` + #[cfg(feature = "serde-serialize")] + type ParameterVisibility: ParameterVisibility + serde::Serialize; + /// The body of the function type Body: ASTNode; @@ -62,19 +81,25 @@ pub trait FunctionBased: Debug + Clone + PartialEq + Eq + Send + Sync { None } + /// For overloading + #[must_use] + fn has_body(_: &Self::Body) -> bool { + true + } + /// For [`crate::ArrowFunction`] fn parameters_from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, - ) -> ParseResult { + ) -> ParseResult> { FunctionParameters::from_reader(reader, state, options) } /// For [`crate::ArrowFunction`] fn parameters_to_string_from_buffer( buf: &mut T, - parameters: &FunctionParameters, + parameters: &FunctionParameters, options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { @@ -116,18 +141,18 @@ pub trait FunctionBased: Debug + Clone + PartialEq + Eq + Send + Sync { pub struct FunctionBase { pub header: T::Header, pub name: T::Name, - pub type_parameters: Option>, - pub parameters: FunctionParameters, + pub type_parameters: Option>, + pub parameters: FunctionParameters, pub return_type: Option, pub body: T::Body, pub position: Span, } #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] +#[allow(dead_code)] const TYPES: &str = r" export interface FunctionBase { - type_parameters?: GenericTypeConstraint[], - parameters: FunctionParameters, + type_parameters?: TypeParameter[], return_type?: TypeAnnotation, position: Span } @@ -152,16 +177,26 @@ impl ASTNode for FunctionBase { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { + // Don't print overloads + #[cfg(feature = "full-typescript")] + if !options.include_type_annotations && !T::has_body(&self.body) { + return; + } + T::header_and_name_to_string_from_buffer(buf, &self.header, &self.name, options, local); - if let (true, Some(type_parameters)) = (options.include_types, &self.type_parameters) { + if let (true, Some(type_parameters)) = + (options.include_type_annotations, &self.type_parameters) + { to_string_bracketed(type_parameters, ('<', '>'), buf, options, local); } T::parameters_to_string_from_buffer(buf, &self.parameters, options, local); - if let (true, Some(return_type)) = (options.include_types, &self.return_type) { + if let (true, Some(return_type)) = (options.include_type_annotations, &self.return_type) { buf.push_str(": "); return_type.to_string_from_buffer(buf, options, local); } - T::parameter_body_boundary_token_to_string_from_buffer(buf, options); + if T::has_body(&self.body) { + T::parameter_body_boundary_token_to_string_from_buffer(buf, options); + } self.body.to_string_from_buffer(buf, options, local.next_level()); } @@ -209,7 +244,7 @@ impl FunctionBase { impl Visitable for FunctionBase where T::Body: Visitable, - // T::Header: Visitable, + // T::Parameters: Visitable, { fn visit( &self, @@ -250,19 +285,28 @@ pub struct GeneralFunctionBase(PhantomData) pub type ExpressionFunction = FunctionBase>; #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] +#[allow(dead_code)] const TYPES_EXPRESSION_FUNCTION: &str = r" export interface ExpressionFunction extends FunctionBase { header: FunctionHeader, body: Block, - name: ExpressionPosition + name: ExpressionPosition, + parameters: FunctionParameters } "; #[allow(clippy::similar_names)] impl FunctionBased for GeneralFunctionBase { - type Body = Block; - type Header = FunctionHeader; type Name = T; + type Header = FunctionHeader; + type LeadingParameter = Option; + type ParameterVisibility = (); + type Body = T::FunctionBody; + + #[cfg(feature = "full-typescript")] + fn has_body(body: &Self::Body) -> bool { + T::has_function_body(body) + } fn header_and_name_from_reader( reader: &mut impl TokenReader, @@ -629,3 +673,46 @@ pub(crate) fn get_method_name( }; Ok((function_header, key)) } + +/// None if overloaded (declaration only) +#[cfg(feature = "full-typescript")] +#[apply(derive_ASTNode)] +#[derive(Debug, Clone, PartialEq, Eq, visitable_derive::Visitable)] +pub struct FunctionBody(pub Option); + +#[cfg(feature = "full-typescript")] +impl ASTNode for FunctionBody { + fn get_position(&self) -> &Span { + self.0.as_ref().map_or(&source_map::Nullable::NULL, |Block(_, pos)| pos) + } + + fn from_reader( + reader: &mut impl TokenReader, + state: &mut crate::ParsingState, + options: &ParseOptions, + ) -> ParseResult { + let inner = if !options.type_annotations + || matches!(reader.peek(), Some(Token(TSXToken::OpenBrace, _))) + { + Some(Block::from_reader(reader, state, options)?) + } else { + None + }; + + Ok(Self(inner)) + } + + fn to_string_from_buffer( + &self, + buf: &mut T, + options: &crate::ToStringOptions, + local: crate::LocalToStringInformation, + ) { + if let Some(ref b) = self.0 { + b.to_string_from_buffer(buf, options, local); + } + } +} + +#[cfg(not(feature = "full-typescript"))] +pub type FunctionBody = Block; diff --git a/parser/src/parameters.rs b/parser/src/functions/parameters.rs similarity index 51% rename from parser/src/parameters.rs rename to parser/src/functions/parameters.rs index bffb615f..006c777f 100644 --- a/parser/src/parameters.rs +++ b/parser/src/functions/parameters.rs @@ -1,4 +1,10 @@ -use crate::{derive_ASTNode, TSXKeyword, TSXToken}; +use std::fmt::Debug; + +use crate::{ + derive_ASTNode, ASTNode, Expression, ParseError, ParseErrors, ParseResult, TSXKeyword, + TSXToken, TypeAnnotation, VariableField, WithComment, +}; + use derive_partial_eq_extras::PartialEqExtras; use iterator_endiate::EndiateIteratorExt; use source_map::Span; @@ -8,22 +14,58 @@ use tokenizer_lib::{ }; use visitable_derive::Visitable; -use crate::{ - errors::parse_lexing_error, tokens::token_as_identifier, ASTNode, Expression, ParseError, - ParseResult, TypeAnnotation, VariableField, VariableFieldInSourceCode, VariableIdentifier, - WithComment, -}; - #[apply(derive_ASTNode)] #[derive(Debug, Clone, Eq, PartialEq, Visitable, get_field_by_type::GetFieldByType)] #[get_field_by_type_target(Span)] -pub struct Parameter { - pub name: WithComment>, +pub struct Parameter { + #[visit_skip_field] + pub visibility: V, + pub name: WithComment, pub type_annotation: Option, pub additionally: Option, pub position: Span, } +pub trait ParameterVisibility: Send + Sync + Sized + Debug + PartialEq + Clone + 'static { + fn from_reader( + reader: &mut impl TokenReader, + state: &mut crate::ParsingState, + options: &crate::ParseOptions, + ) -> Self; +} + +impl ParameterVisibility for () { + fn from_reader( + _: &mut impl TokenReader, + _: &mut crate::ParsingState, + _: &crate::ParseOptions, + ) -> Self { + } +} + +impl ParameterVisibility for Option { + fn from_reader( + reader: &mut impl TokenReader, + _: &mut crate::ParsingState, + options: &crate::ParseOptions, + ) -> Option { + if !options.type_annotations { + None + } else if let Some(Token(TSXToken::Keyword(t), _)) = + reader.conditional_next(crate::types::Visibility::token_is_visibility_specifier) + { + Some(match t { + TSXKeyword::Private => crate::types::Visibility::Private, + TSXKeyword::Public => crate::types::Visibility::Public, + TSXKeyword::Protected => crate::types::Visibility::Protected, + _ => unreachable!(), + }) + } else { + None + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, Visitable)] #[apply(derive_ASTNode)] pub enum ParameterData { @@ -31,30 +73,124 @@ pub enum ParameterData { WithDefaultValue(Box), } -#[derive(Debug, Clone, Eq, PartialEq, Visitable)] +#[cfg(feature = "extras")] +#[cfg_attr(target_family = "wasm", tsify::declare)] +pub type SpreadParameterName = VariableField; + +#[cfg(not(feature = "extras"))] +#[cfg_attr(target_family = "wasm", tsify::declare)] +pub type SpreadParameterName = crate::VariableIdentifier; + #[apply(derive_ASTNode)] +#[derive(Debug, Clone, Eq, PartialEq, Visitable)] pub struct SpreadParameter { - pub name: VariableIdentifier, + pub name: SpreadParameterName, pub type_annotation: Option, pub position: Span, } -/// TODO need to something special to not enable `OptionalFunctionParameter::WithValue` in interfaces and other -/// type structure #[apply(derive_ASTNode)] -#[derive(Debug, Clone, PartialEqExtras, Visitable)] -pub struct FunctionParameters { - pub this_type: Option<(TypeAnnotation, Span)>, - pub super_type: Option<(TypeAnnotation, Span)>, - pub parameters: Vec, +#[derive(Debug, Clone, PartialEqExtras, Eq, Visitable)] +#[partial_eq_ignore_types(Span)] +pub struct FunctionParameters { + #[visit_skip_field] + pub leading: L, + pub parameters: Vec>, pub rest_parameter: Option>, - #[partial_eq_ignore] pub position: Span, } -impl Eq for FunctionParameters {} +pub trait LeadingParameter: Send + Sync + Sized + Debug + PartialEq + Clone + 'static { + fn try_make( + this_annotation: Option, + super_annotation: Option, + ) -> ParseResult; + + fn get_this_parameter(&self) -> Option<&ThisParameter>; + fn get_super_parameter(&self) -> Option<&SuperParameter>; +} + +#[apply(derive_ASTNode)] +#[derive(Debug, Clone, PartialEqExtras, Eq, Visitable)] +#[partial_eq_ignore_types(Span)] +pub struct ThisParameter { + pub constraint: TypeAnnotation, + pub position: Span, +} + +/// TODO WIP! +#[apply(derive_ASTNode)] +#[derive(Debug, Clone, PartialEqExtras, Eq, Visitable)] +#[partial_eq_ignore_types(Span)] +pub struct SuperParameter { + pub constraint: TypeAnnotation, + pub position: Span, +} + +impl LeadingParameter for () { + fn try_make( + this_annotation: Option, + super_annotation: Option, + ) -> ParseResult { + if this_annotation.is_some() || super_annotation.is_some() { + let position = + this_annotation.map_or(super_annotation.unwrap().position, |a| a.position); + + Err(ParseError::new(ParseErrors::CannotUseLeadingParameterHere, position)) + } else { + Ok(()) + } + } + + fn get_this_parameter(&self) -> Option<&ThisParameter> { + None + } + fn get_super_parameter(&self) -> Option<&SuperParameter> { + None + } +} + +impl LeadingParameter for Option { + fn try_make( + this_annotation: Option, + super_annotation: Option, + ) -> ParseResult { + if let Some(s) = super_annotation { + Err(ParseError::new(ParseErrors::CannotUseLeadingParameterHere, s.position)) + } else { + Ok(this_annotation) + } + } + + fn get_this_parameter(&self) -> Option<&ThisParameter> { + self.as_ref() + } + fn get_super_parameter(&self) -> Option<&SuperParameter> { + None + } +} + +impl LeadingParameter for (Option, Option) { + fn try_make( + this_annotation: Option, + super_annotation: Option, + ) -> ParseResult { + Ok((this_annotation, super_annotation)) + } + + fn get_this_parameter(&self) -> Option<&ThisParameter> { + self.0.as_ref() + } + fn get_super_parameter(&self) -> Option<&SuperParameter> { + self.1.as_ref() + } +} -impl ASTNode for FunctionParameters { +impl ASTNode for FunctionParameters +where + L: LeadingParameter, + V: ParameterVisibility, +{ fn get_position(&self) -> &Span { &self.position } @@ -81,7 +217,9 @@ impl ASTNode for FunctionParameters { { // decorators_to_string_from_buffer(decorators, buf, options, local); name.to_string_from_buffer(buf, options, local); - if let (true, Some(ref type_annotation)) = (options.include_types, type_annotation) { + if let (true, Some(ref type_annotation)) = + (options.include_type_annotations, type_annotation) + { if let Some(ParameterData::Optional) = additionally { buf.push('?'); } @@ -109,16 +247,21 @@ impl ASTNode for FunctionParameters { } } -impl FunctionParameters { +impl FunctionParameters +where + L: LeadingParameter, + V: ParameterVisibility, +{ pub(crate) fn from_reader_sub_open_parenthesis( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &crate::ParseOptions, start: TokenStart, - ) -> Result { - let mut this_type = None; - let mut super_type = None; + ) -> ParseResult { let mut parameters = Vec::new(); + + let mut this_type = None::; + let mut super_type = None::; let mut rest_parameter = None; loop { @@ -131,10 +274,9 @@ impl FunctionParameters { if let Some(Token(_, spread_pos)) = reader.conditional_next(|tok| matches!(tok, TSXToken::Spread)) { - let (name, name_pos) = token_as_identifier( - reader.next().ok_or_else(parse_lexing_error)?, - "spread function parameter", - )?; + let name = SpreadParameterName::from_reader(reader, state, options)?; + let name_position = *name.get_position(); + let type_annotation = if options.type_annotations && reader.conditional_next(|tok| matches!(tok, TSXToken::Colon)).is_some() { @@ -144,13 +286,10 @@ impl FunctionParameters { }; let position = spread_pos - .union(type_annotation.as_ref().map_or(&name_pos, ASTNode::get_position)); + .union(type_annotation.as_ref().map_or(&name_position, ASTNode::get_position)); - rest_parameter = Some(Box::new(SpreadParameter { - name: VariableIdentifier::Standard(name, name_pos), - type_annotation, - position, - })); + rest_parameter = + Some(Box::new(SpreadParameter { name, type_annotation, position })); break; } else if let Some(Token(_, start)) = reader.conditional_next(|tok| { options.type_annotations @@ -158,22 +297,22 @@ impl FunctionParameters { && matches!(tok, TSXToken::Keyword(TSXKeyword::This)) }) { reader.expect_next(TSXToken::Colon)?; - let type_annotation = TypeAnnotation::from_reader(reader, state, options)?; - let position = start.union(type_annotation.get_position()); - this_type = Some((type_annotation, position)); + let constraint = TypeAnnotation::from_reader(reader, state, options)?; + let position = start.union(constraint.get_position()); + this_type = Some(ThisParameter { constraint, position }); } else if let Some(Token(_, start)) = reader.conditional_next(|tok| { options.type_annotations && parameters.is_empty() && matches!(tok, TSXToken::Keyword(TSXKeyword::Super)) }) { reader.expect_next(TSXToken::Colon)?; - let type_annotation = TypeAnnotation::from_reader(reader, state, options)?; - let position = start.union(type_annotation.get_position()); - super_type = Some((type_annotation, position)); + let constraint = TypeAnnotation::from_reader(reader, state, options)?; + let position = start.union(constraint.get_position()); + super_type = Some(SuperParameter { constraint, position }); } else { - let name = WithComment::>::from_reader( - reader, state, options, - )?; + let visibility = V::from_reader(reader, state, options); + + let name = WithComment::::from_reader(reader, state, options)?; let (is_optional, type_annotation) = match reader.peek() { Some(Token(TSXToken::Colon, _)) if options.type_annotations => { @@ -225,6 +364,7 @@ impl FunctionParameters { }; parameters.push(Parameter { + visibility, position: name.get_position().union(end_position), name, type_annotation, @@ -237,13 +377,11 @@ impl FunctionParameters { break; } } + let close = reader.expect_next_get_end(TSXToken::CloseParentheses)?; - Ok(FunctionParameters { - position: start.union(close), - parameters, - rest_parameter, - this_type, - super_type, - }) + + let leading = L::try_make(this_type, super_type)?; + + Ok(FunctionParameters { position: start.union(close), parameters, rest_parameter, leading }) } } diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index f1ed30dd..b5a1ada0 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -48,6 +48,7 @@ fn is_number_delimiter(chr: char) -> bool { | '%' | '=' | ':' | '<' | '>' | '?' | '"' | '\'' | '`' + | '#' ) } @@ -88,7 +89,7 @@ pub fn lex_script( AttributeKey, AttributeEqual, AttributeValue(JSXAttributeValueDelimiter), - Comment(JSXCommentState), + Comment, Content, /// For script and style tags LiteralContent { @@ -96,13 +97,6 @@ pub fn lex_script( }, } - #[derive(PartialEq, Debug)] - enum JSXCommentState { - None, - FirstDash, - SecondDash, - } - #[derive(PartialEq, Debug)] enum NumberLiteralType { BinaryLiteral, @@ -206,6 +200,8 @@ pub fn lex_script( } for (idx, chr) in script.char_indices() { + // dbg!(chr, &state); + // Sets current parser state and updates start track macro_rules! set_state { ($s:expr) => {{ @@ -534,6 +530,7 @@ pub fn lex_script( start = idx + 1; state = LexingState::None; + expect_expression = true; continue; } '`' if !*escaped => { @@ -544,6 +541,7 @@ pub fn lex_script( push_token!(TSXToken::TemplateLiteralEnd); start = idx + 1; state = LexingState::None; + expect_expression = false; continue; } '\\' => { @@ -624,11 +622,11 @@ pub fn lex_script( '/' if start + 1 == idx => { *direction = JSXTagNameDirection::Closing; } - // Comment + // HTML comments!!! '!' if start + 1 == idx => { - *jsx_state = JSXLexingState::Comment(JSXCommentState::None); + *jsx_state = JSXLexingState::Comment; } - // Non tag name character + // Non-tag name character chr => { if *direction == JSXTagNameDirection::Closing { return_err!(LexingErrors::ExpectedJSXEndTag); @@ -636,6 +634,7 @@ pub fn lex_script( let tag_name = script[start..idx].trim(); *is_self_closing_tag = html_tag_is_self_closing(tag_name); push_token!(TSXToken::JSXTagName(tag_name.to_owned())); + start = idx; *tag_depth += 1; match chr { '/' if *is_self_closing_tag => { @@ -880,6 +879,7 @@ pub fn lex_script( if !source.is_empty() { push_token!(TSXToken::JSXContent(source.to_owned())); } + dbg!("here"); start = end; push_token!(TSXToken::JSXClosingTagStart); start = idx + '/'.len_utf8(); @@ -895,26 +895,24 @@ pub fn lex_script( } } // TODO this will allow for as a valid comment - JSXLexingState::Comment(ref mut comment_state) => match (&comment_state, chr) { - (JSXCommentState::None, '-') => { - *comment_state = JSXCommentState::FirstDash; - } - (JSXCommentState::FirstDash, '-') => { - *comment_state = JSXCommentState::SecondDash; - } - (JSXCommentState::SecondDash, '>') => { + JSXLexingState::Comment => { + if idx - start < 4 { + if chr != '-' { + return_err!(LexingErrors::ExpectedDashInComment); + } + } else if chr == '>' && script[..idx].ends_with("--") { push_token!(TSXToken::JSXComment( script[(start + 4)..(idx - 2)].to_owned() )); + start = idx + 1; if *tag_depth == 0 { set_state!(LexingState::None); - continue; + } else { + *jsx_state = JSXLexingState::Content; } + continue; } - _ => { - *comment_state = JSXCommentState::None; - } - }, + } } } LexingState::None => {} @@ -1096,9 +1094,18 @@ pub fn lex_script( sender.push(Token(TSXToken::EOS, current_position!())); return_err!(LexingErrors::ExpectedEndToMultilineComment); } - LexingState::RegexLiteral { .. } => { - sender.push(Token(TSXToken::EOS, current_position!())); - return_err!(LexingErrors::ExpectedEndToRegexLiteral); + // This is okay as the state is not cleared until it finds flags. + LexingState::RegexLiteral { after_last_slash, .. } => { + if after_last_slash { + sender.push(Token( + TSXToken::RegexFlagLiteral(script[start..].to_owned()), + TokenStart::new(start as u32 + offset), + )); + sender.push(Token(TSXToken::EOS, current_position!())); + } else { + sender.push(Token(TSXToken::EOS, current_position!())); + return_err!(LexingErrors::ExpectedEndToRegexLiteral); + } } LexingState::JSXLiteral { .. } => { sender.push(Token(TSXToken::EOS, current_position!())); diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 86b386f4..dda585c0 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -12,8 +12,6 @@ pub mod generator_helpers; mod lexer; pub mod marker; mod modules; -pub mod operators; -pub mod parameters; pub mod property_key; pub mod statements; mod tokens; @@ -24,6 +22,7 @@ pub mod visiting; pub use block::{Block, BlockLike, BlockLikeMut, BlockOrSingleStatement, StatementOrDeclaration}; pub use comments::WithComment; pub use declarations::Declaration; +use functions::FunctionBody; pub use marker::Marker; pub use errors::{ParseError, ParseErrors, ParseResult}; @@ -37,15 +36,14 @@ pub use functions::{FunctionBase, FunctionBased, FunctionHeader}; pub use generator_helpers::IntoAST; use iterator_endiate::EndiateIteratorExt; pub use lexer::{lex_script, LexerOptions}; -pub use modules::{Module, TypeDefinitionModule, TypeDefinitionModuleDeclaration}; -pub use parameters::{FunctionParameters, Parameter, SpreadParameter}; +pub use modules::Module; pub use property_key::PropertyKey; pub use source_map::{self, SourceId, Span}; pub use statements::Statement; pub use tokens::{TSXKeyword, TSXToken}; pub use types::{ type_annotations::{self, TypeAnnotation}, - type_declarations::{self, GenericTypeConstraint, TypeDeclaration}, + type_declarations::{self, TypeDeclaration, TypeParameter}, }; pub use variable_fields::*; pub(crate) use visiting::{ @@ -54,23 +52,21 @@ pub(crate) use visiting::{ use tokenizer_lib::{ sized_tokens::{SizedToken, TokenEnd}, - Token, TokenReader, TokenTrait, + Token, TokenReader, }; pub(crate) use tokenizer_lib::sized_tokens::TokenStart; use std::{borrow::Cow, str::FromStr}; +use crate::errors::parse_lexing_error; + #[macro_use] extern crate macro_rules_attribute; attribute_alias! { - // Warning: known to break under the following circumstances: - // 1. in combination with partial_eq_ignore_types (from the PartialEqExtras derive macro) - // 2. in combination with get_field_by_type_target (from the crate of the same name) - // any variation (even just a single, straightforward, cfg_attr) will break the other macros. - // If adding a seemingly innocuous macro triggers a bunch of 'cannot find attribute within this scope' errors, - // keep this macro in mind. + // Warning: can produce errors when used with other macro attributes. Always put this attribute first + // TODO #[derive(Debug, Clone)] and maybe some others #[apply(derive_ASTNode!)] = #[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))] #[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))] @@ -104,8 +100,10 @@ impl Quoted { pub struct ParseOptions { /// Parsing of [JSX](https://facebook.github.io/jsx/) (includes some additions) pub jsx: bool, - /// only type annotations + /// allow type annotations pub type_annotations: bool, + /// just definition file + pub type_definition_module: bool, /// Allow custom characters in JSX attributes pub special_jsx_attributes: bool, /// Parses decorators on items @@ -118,9 +116,7 @@ pub struct ParseOptions { pub custom_function_headers: bool, /// TODO temp for seeing how channel performs pub buffer_size: usize, - /// TODO temp for seeing how channel performs - /// - /// Has no effect on WASM + /// Has no effect on WASM. Increase for deeply nested AST structures pub stack_size: Option, /// Useful for LSP information pub record_keyword_positions: bool, @@ -144,6 +140,7 @@ impl ParseOptions { Self { jsx: true, type_annotations: true, + type_definition_module: false, special_jsx_attributes: true, comments: Comments::All, decorators: true, @@ -165,6 +162,7 @@ impl Default for ParseOptions { Self { jsx: true, type_annotations: true, + type_definition_module: false, special_jsx_attributes: false, comments: Comments::All, decorators: true, @@ -191,8 +189,8 @@ pub struct ToStringOptions { pub trailing_semicolon: bool, /// Single statements get put on the same line as their parent statement pub single_statement_on_new_line: bool, - /// Include type annotation syntax - pub include_types: bool, + /// Include type annotations (and additional TypeScript) syntax + pub include_type_annotations: bool, /// TODO unsure about this pub include_decorators: bool, pub comments: Comments, @@ -212,7 +210,7 @@ impl Default for ToStringOptions { fn default() -> Self { ToStringOptions { pretty: true, - include_types: false, + include_type_annotations: false, single_statement_on_new_line: true, include_decorators: false, comments: Comments::All, @@ -236,10 +234,10 @@ impl ToStringOptions { } } - /// With typescript type syntax + /// With TypeScript type syntax #[must_use] pub fn typescript() -> Self { - ToStringOptions { include_types: true, ..Default::default() } + ToStringOptions { include_type_annotations: true, ..Default::default() } } /// Whether to include comment in source @@ -348,7 +346,7 @@ pub fn lex_and_parse_script( options: ParseOptions, script: &str, offset: Option, -) -> Result<(T, ParsingState), ParseError> { +) -> ParseResult<(T, ParsingState)> { let (mut sender, mut reader) = tokenizer_lib::ParallelTokenQueue::new_with_buffer_size(options.buffer_size); let lex_options = options.get_lex_options(); @@ -393,7 +391,7 @@ pub fn lex_and_parse_script( options: ParseOptions, script: &str, offset: Option, -) -> Result<(T, ParsingState), ParseError> { +) -> ParseResult<(T, ParsingState)> { let mut queue = tokenizer_lib::BufferedTokenQueue::new(); let lex_result = lexer::lex_script(script, &mut queue, &options.get_lex_options(), offset); @@ -418,14 +416,14 @@ pub fn lex_and_parse_script( pub(crate) fn throw_unexpected_token( reader: &mut impl TokenReader, expected: &[TSXToken], -) -> Result { +) -> ParseResult { throw_unexpected_token_with_token(reader.next().unwrap(), expected) } pub(crate) fn throw_unexpected_token_with_token( token: Token, expected: &[TSXToken], -) -> Result { +) -> ParseResult { let position = token.get_span(); Err(ParseError::new(ParseErrors::UnexpectedToken { expected, found: token.0 }, position)) } @@ -733,6 +731,7 @@ impl std::fmt::Display for NumberRepresentation { } } +// TODO not great impl PartialEq for NumberRepresentation { fn eq(&self, other: &Self) -> bool { if let (Ok(a), Ok(b)) = (f64::try_from(self.clone()), f64::try_from(other.clone())) { @@ -781,7 +780,7 @@ impl NumberRepresentation { format!("{sign}0x{value:x}") } NumberRepresentation::Bin { sign, value, .. } => { - format!("{sign}0b{value}") + format!("{sign}0b{value:b}") } NumberRepresentation::Octal { sign, value } => { format!("{sign}0o{value:o}") @@ -800,6 +799,8 @@ impl NumberRepresentation { pub trait ExpressionOrStatementPosition: Clone + std::fmt::Debug + Sync + Send + PartialEq + Eq + 'static { + type FunctionBody: ASTNode; + fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, @@ -817,6 +818,9 @@ pub trait ExpressionOrStatementPosition: None } } + + #[cfg(feature = "full-typescript")] + fn has_function_body(body: &Self::FunctionBody) -> bool; } #[derive(Debug, PartialEq, Eq, Clone)] @@ -824,6 +828,8 @@ pub trait ExpressionOrStatementPosition: pub struct StatementPosition(pub VariableIdentifier); impl ExpressionOrStatementPosition for StatementPosition { + type FunctionBody = FunctionBody; + fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, @@ -839,6 +845,11 @@ impl ExpressionOrStatementPosition for StatementPosition { fn as_option_variable_identifier_mut(&mut self) -> Option<&mut VariableIdentifier> { Some(&mut self.0) } + + #[cfg(feature = "full-typescript")] + fn has_function_body(body: &Self::FunctionBody) -> bool { + body.0.is_some() + } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -846,6 +857,8 @@ impl ExpressionOrStatementPosition for StatementPosition { pub struct ExpressionPosition(pub Option); impl ExpressionOrStatementPosition for ExpressionPosition { + type FunctionBody = Block; + fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, @@ -872,6 +885,11 @@ impl ExpressionOrStatementPosition for ExpressionPosition { fn as_option_variable_identifier_mut(&mut self) -> Option<&mut VariableIdentifier> { self.0.as_mut() } + + #[cfg(feature = "full-typescript")] + fn has_function_body(_: &Self::FunctionBody) -> bool { + true + } } pub trait ListItem: Sized { @@ -897,32 +915,20 @@ pub(crate) fn parse_bracketed( } let mut nodes: Vec = Vec::new(); loop { - if let Some(token) = reader.conditional_next(|token| *token == end) { - return Ok((nodes, token.get_end())); - } - if let Some(empty) = T::EMPTY { - // TODO bad. Because [/* hi */, x] is valid. Need to adjust the scan tokenizer reader API - let mut is_comma = false; - let _ = reader.scan(|t, _| { - if t.is_skippable() { - false - } else if let TSXToken::Comma = t { - is_comma = true; - true - } else { - true + let Token(next, _) = reader.peek().ok_or_else(parse_lexing_error)?; + if matches!(next, TSXToken::Comma) || *next == end { + if matches!(next, TSXToken::Comma) || (*next == end && !nodes.is_empty()) { + nodes.push(empty); } - }); - if is_comma { - while let Some(token) = reader.next() { - if let TSXToken::Comma = token.0 { - break; - } + let Token(token, s) = reader.next().unwrap(); + if token == end { + return Ok((nodes, s.get_end_after(token.length() as usize))); } - nodes.push(empty); continue; } + } else if let Some(token) = reader.conditional_next(|token| *token == end) { + return Ok((nodes, token.get_end())); } let node = T::from_reader(reader, state, options)?; @@ -1011,9 +1017,7 @@ fn receiver_to_tokens( }) } -/// *`to_strings`* items surrounded in `{`, `[`, `(`, etc -/// -/// TODO delimiter +/// *`to_strings`* items surrounded in `{`, `[`, `(`, etc. Defaults to `,` as delimiter pub(crate) fn to_string_bracketed( nodes: &[U], brackets: (char, char), @@ -1152,8 +1156,8 @@ pub mod ast { expressions::*, extensions::jsx::*, functions::{ - FunctionBase, FunctionHeader, FunctionParameters, MethodHeader, Parameter, - ParameterData, SpreadParameter, + FunctionBase, FunctionBody, FunctionHeader, FunctionParameters, MethodHeader, + Parameter, ParameterData, SpreadParameter, }, statements::*, Block, Decorated, ExpressionPosition, NumberRepresentation, PropertyKey, diff --git a/parser/src/marker.rs b/parser/src/marker.rs index 40509d70..465da41e 100644 --- a/parser/src/marker.rs +++ b/parser/src/marker.rs @@ -5,6 +5,7 @@ use std::marker::PhantomData; pub struct Marker(pub u8, pub PhantomData); #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] +#[allow(dead_code)] const TYPES: &str = r" type Marker = number; "; diff --git a/parser/src/modules.rs b/parser/src/modules.rs index f7dec79c..f97bad9d 100644 --- a/parser/src/modules.rs +++ b/parser/src/modules.rs @@ -1,24 +1,10 @@ -use tokenizer_lib::sized_tokens::TokenStart; - use crate::{ block::{parse_statements_and_declarations, statements_and_declarations_to_string}, - derive_ASTNode, - errors::parse_lexing_error, - extensions::decorators::decorators_from_reader, - throw_unexpected_token_with_token, - types::{ - declares::{ - DeclareClassDeclaration, DeclareFunctionDeclaration, DeclareVariableDeclaration, - }, - namespace::Namespace, - type_alias::TypeAlias, - InterfaceDeclaration, - }, - BlockLike, BlockLikeMut, Decorated, Decorator, LocalToStringInformation, ParseOptions, - ParseResult, StatementOrDeclaration, TSXKeyword, VisitOptions, + derive_ASTNode, BlockLike, BlockLikeMut, LocalToStringInformation, ParseOptions, ParseResult, + StatementOrDeclaration, VisitOptions, }; -use super::{ASTNode, ParseError, Span, TSXToken, Token, TokenReader}; +use super::{ASTNode, Span, TSXToken, TokenReader}; #[derive(Debug, Clone)] #[apply(derive_ASTNode)] @@ -151,197 +137,3 @@ impl<'a> From<&'a mut Module> for BlockLikeMut<'a> { BlockLikeMut { items: &mut module.items } } } - -/// Statements for '.d.ts' files -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TypeDefinitionModuleDeclaration { - Variable(DeclareVariableDeclaration), - Function(DeclareFunctionDeclaration), - Class(DeclareClassDeclaration), - Interface(Decorated), - TypeAlias(TypeAlias), - Namespace(Namespace), - /// Information for upcoming declaration - Comment(String), - /// Local alias, not exported from module. Does not start with declare - LocalTypeAlias(TypeAlias), - // Variable without declare in front. Found in namespaces - LocalVariableDeclaration(DeclareVariableDeclaration), -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct TypeDefinitionModule { - pub declarations: Vec, - pub position: Span, -} - -impl TypeDefinitionModule { - pub fn from_string(script: &str, mut options: ParseOptions) -> ParseResult { - // Important not to parse JSX as <> is used for casting - options.jsx = false; - - let line_starts = source_map::LineStarts::new(script); - super::lex_and_parse_script(line_starts, options, script, None).map(|(ast, _)| ast) - } -} - -impl ASTNode for TypeDefinitionModule { - fn get_position(&self) -> &Span { - &self.position - } - - fn from_reader( - reader: &mut impl TokenReader, - state: &mut crate::ParsingState, - options: &ParseOptions, - ) -> ParseResult { - let mut declarations = Vec::new(); - loop { - declarations - .push(TypeDefinitionModuleDeclaration::from_reader(reader, state, options)?); - match reader.peek() { - Some(Token(TSXToken::SemiColon, _)) => { - reader.next(); - if matches!(reader.peek(), Some(Token(TSXToken::EOS, _))) { - break; - } - } - Some(Token(TSXToken::EOS, _)) => { - break; - } - _ => { - // TODO - } - } - } - let end = state.length_of_source; - Ok(Self { declarations, position: Span { start: 0, end, source: () } }) - } - - fn to_string_from_buffer( - &self, - _buf: &mut T, - _options: &crate::ToStringOptions, - _local: crate::LocalToStringInformation, - ) { - todo!("tdm to buffer") - } -} - -impl ASTNode for TypeDefinitionModuleDeclaration { - fn from_reader( - reader: &mut impl TokenReader, - state: &mut crate::ParsingState, - options: &ParseOptions, - ) -> ParseResult { - let decorators = decorators_from_reader(reader, state, options)?; - match reader.peek().ok_or_else(parse_lexing_error)? { - Token(TSXToken::Keyword(TSXKeyword::Declare), _) => { - let declare_span = reader.next().unwrap().1; - parse_declare_item(reader, state, options, decorators, declare_span) - } - Token(TSXToken::Keyword(TSXKeyword::Interface), _) => { - let on = InterfaceDeclaration::from_reader(reader, state, options)?; - Ok(TypeDefinitionModuleDeclaration::Interface(Decorated::new(decorators, on))) - } - Token(TSXToken::Keyword(TSXKeyword::Type), _) => { - Ok(TypeDefinitionModuleDeclaration::LocalTypeAlias(TypeAlias::from_reader( - reader, state, options, - )?)) - } - Token(TSXToken::Keyword(TSXKeyword::Var), _) => { - Ok(TypeDefinitionModuleDeclaration::LocalVariableDeclaration( - DeclareVariableDeclaration::from_reader_sub_declare( - reader, state, options, None, decorators, - )?, - )) - } - Token(TSXToken::Comment(_) | TSXToken::MultiLineComment(_), _) => { - let (TSXToken::MultiLineComment(comment) | TSXToken::Comment(comment)) = - reader.next().unwrap().0 - else { - unreachable!() - }; - Ok(TypeDefinitionModuleDeclaration::Comment(comment)) - } - _ => throw_unexpected_token_with_token( - reader.next().unwrap(), - &[ - TSXToken::Keyword(TSXKeyword::Declare), - TSXToken::Keyword(TSXKeyword::Interface), - TSXToken::Keyword(TSXKeyword::Type), - TSXToken::Keyword(TSXKeyword::Var), - TSXToken::At, - ], - ), - } - } - - fn to_string_from_buffer( - &self, - _buf: &mut T, - _options: &crate::ToStringOptions, - _local: crate::LocalToStringInformation, - ) { - todo!("tdms to_string_from_buffer"); - } - - fn get_position(&self) -> &Span { - todo!("tdms get_position"); - } -} - -pub(crate) fn parse_declare_item( - reader: &mut impl TokenReader, - state: &mut crate::ParsingState, - options: &ParseOptions, - decorators: Vec, - start: TokenStart, -) -> Result { - match reader.peek() { - Some(Token( - TSXToken::Keyword(TSXKeyword::Var | TSXKeyword::Const | TSXKeyword::Let), - _, - )) => Ok(TypeDefinitionModuleDeclaration::Variable( - DeclareVariableDeclaration::from_reader_sub_declare( - reader, - state, - options, - Some(start), - decorators, - )?, - )), - Some(Token(TSXToken::Keyword(TSXKeyword::Class), _)) => { - Ok(TypeDefinitionModuleDeclaration::Class( - DeclareClassDeclaration::from_reader_sub_declare(reader, state, options)?, - )) - } - Some(Token(TSXToken::Keyword(TSXKeyword::Function), _)) => { - Ok(TypeDefinitionModuleDeclaration::Function( - DeclareFunctionDeclaration::from_reader_sub_declare_with_decorators( - reader, state, options, decorators, - )?, - )) - } - Some(Token(TSXToken::Keyword(TSXKeyword::Type), _)) => { - Ok(TypeDefinitionModuleDeclaration::TypeAlias(TypeAlias::from_reader( - reader, state, options, - )?)) - } - Some(Token(TSXToken::Keyword(TSXKeyword::Namespace), _)) => { - Ok(TypeDefinitionModuleDeclaration::Namespace(Namespace::from_reader( - reader, state, options, - )?)) - } - _ => throw_unexpected_token_with_token( - reader.next().ok_or_else(parse_lexing_error)?, - &[ - TSXToken::Keyword(TSXKeyword::Var), - TSXToken::Keyword(TSXKeyword::Class), - TSXToken::Keyword(TSXKeyword::Type), - TSXToken::Keyword(TSXKeyword::Namespace), - TSXToken::Keyword(TSXKeyword::Function), - ], - ), - } -} diff --git a/parser/src/property_key.rs b/parser/src/property_key.rs index cac94f2b..b9d84fc0 100644 --- a/parser/src/property_key.rs +++ b/parser/src/property_key.rs @@ -30,6 +30,7 @@ pub trait PropertyKeyKind: Debug + PartialEq + Eq + Clone + Sized + Send + Sync pub struct AlwaysPublic; // #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] +#[allow(dead_code)] // const TYPES_ALWAYS_PUBLIC: &str = r" // export type AlwaysPublic = false; // "; @@ -60,6 +61,7 @@ pub enum PublicOrPrivate { } // #[cfg_attr(target_family = "wasm", wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))] +#[allow(dead_code)] // const TYPES_PUBLIC_OR_PRIVATE: &str = r" // export type PublicOrPrivate = boolean; // "; diff --git a/parser/src/statements/for_statement.rs b/parser/src/statements/for_statement.rs index 989a5e9d..b6aa22c7 100644 --- a/parser/src/statements/for_statement.rs +++ b/parser/src/statements/for_statement.rs @@ -1,7 +1,7 @@ use crate::{ ast::MultipleExpression, block::BlockOrSingleStatement, - declarations::variable::VariableDeclaration, derive_ASTNode, ParseOptions, TSXKeyword, - VariableField, VariableFieldInSourceCode, VariableKeyword, WithComment, + declarations::variable::VariableDeclaration, derive_ASTNode, ParseError, ParseErrors, + ParseOptions, TSXKeyword, VariableField, VariableKeyword, WithComment, }; use tokenizer_lib::sized_tokens::TokenReaderWithTokenEnds; use visitable_derive::Visitable; @@ -30,7 +30,20 @@ impl ASTNode for ForLoopStatement { options: &ParseOptions, ) -> ParseResult { let start = state.expect_keyword(reader, TSXKeyword::For)?; - let condition = ForLoopCondition::from_reader(reader, state, options)?; + let is_await = reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Await))) + .is_some(); + let mut condition = ForLoopCondition::from_reader(reader, state, options)?; + if is_await { + if let ForLoopCondition::ForOf { is_await: ref mut a, .. } = condition { + *a = is_await; + } else { + return Err(ParseError::new( + ParseErrors::AwaitRequiresForOf, + *condition.get_position(), + )); + } + } let inner = BlockOrSingleStatement::from_reader(reader, state, options)?; let position = start.union(inner.get_position()); Ok(ForLoopStatement { condition, inner, position }) @@ -43,6 +56,9 @@ impl ASTNode for ForLoopStatement { local: crate::LocalToStringInformation, ) { buf.push_str("for"); + if let ForLoopCondition::ForOf { is_await: true, .. } = self.condition { + buf.push_str(" await"); + } options.push_gap_optionally(buf); self.condition.to_string_from_buffer(buf, options, local); options.push_gap_optionally(buf); @@ -63,13 +79,14 @@ pub enum ForLoopStatementInitializer { pub enum ForLoopCondition { ForOf { keyword: Option, - variable: WithComment>, + variable: WithComment, of: Expression, + is_await: bool, position: Span, }, ForIn { keyword: Option, - variable: WithComment>, + variable: WithComment, /// Yes `of` is single expression, `in` is multiple r#in: MultipleExpression, position: Span, @@ -83,32 +100,6 @@ pub enum ForLoopCondition { } impl ASTNode for ForLoopCondition { - // fn get_position(&self) -> &Span { - // match self { - // ForLoopCondition::ForOf { keyword, variable, of: rhs } - // | ForLoopCondition::ForIn { keyword, variable, r#in: rhs } => Cow::Owned( - // keyword - // .as_ref() - // .map(ForLoopVariableKeyword::get_position) - // .unwrap_or_else(|| variable.get_position()) - // .union(&rhs.get_position()), - // ), - // ForLoopCondition::Statements { initializer, condition: _, afterthought } => { - // let initializer_position = match initializer.as_ref().expect("TODO what about None") - // { - // ForLoopStatementInitializer::VariableDeclaration(stmt) => stmt.get_position(), - // ForLoopStatementInitializer::VarStatement(stmt) => stmt.get_position(), - // ForLoopStatementInitializer::Expression(expr) => expr.get_position(), - // }; - // Cow::Owned( - // initializer_position.union( - // &afterthought.as_ref().expect("TODO what about None").get_position(), - // ), - // ) - // } - // } - // } - fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, @@ -139,8 +130,7 @@ impl ASTNode for ForLoopCondition { .map(|token| (token.1, VariableKeyword::from_reader(token).unwrap())) .unzip(); - let variable = - WithComment::>::from_reader(reader, state, options)?; + let variable = WithComment::::from_reader(reader, state, options)?; let _ = state.expect_keyword(reader, TSXKeyword::Of)?; @@ -148,7 +138,9 @@ impl ASTNode for ForLoopCondition { let position = start .unwrap_or_else(|| variable.get_position().get_start()) .union(of.get_position()); - Self::ForOf { variable, keyword, of, position } + + // Not great, set from above + Self::ForOf { variable, keyword, of, position, is_await: false } } Some(Token(TSXToken::Keyword(TSXKeyword::In), _)) => { let (start, keyword) = reader @@ -156,8 +148,7 @@ impl ASTNode for ForLoopCondition { .map(|token| (token.1, VariableKeyword::from_reader(token).unwrap())) .unzip(); - let variable = - WithComment::>::from_reader(reader, state, options)?; + let variable = WithComment::::from_reader(reader, state, options)?; let _ = state.expect_keyword(reader, TSXKeyword::In)?; @@ -229,7 +220,7 @@ impl ASTNode for ForLoopCondition { ) { buf.push('('); match self { - Self::ForOf { keyword, variable, of, position: _ } => { + Self::ForOf { keyword, variable, of, position: _, is_await: _ } => { if let Some(keyword) = keyword { buf.push_str(keyword.as_str()); } @@ -288,10 +279,28 @@ impl ASTNode for ForLoopCondition { #[cfg(test)] mod tests { use super::ForLoopCondition; - use crate::assert_matches_ast; + use crate::{assert_matches_ast, statements::ForLoopStatement, ASTNode}; #[test] fn condition_without_variable_keyword() { assert_matches_ast!("(k in x)", ForLoopCondition::ForIn { .. }); } + + #[test] + fn for_await() { + assert_matches_ast!( + "for await (let k of x) {}", + ForLoopStatement { condition: ForLoopCondition::ForOf { is_await: true, .. }, .. } + ); + assert_matches_ast!( + "for (let k of x) {}", + ForLoopStatement { condition: ForLoopCondition::ForOf { is_await: false, .. }, .. } + ); + + assert!(ForLoopStatement::from_string( + "for await (let x = 0; x < 5; x++) {}".into(), + Default::default() + ) + .is_err()); + } } diff --git a/parser/src/statements/mod.rs b/parser/src/statements/mod.rs index 18996d9e..3f95849c 100644 --- a/parser/src/statements/mod.rs +++ b/parser/src/statements/mod.rs @@ -26,7 +26,7 @@ pub use try_catch_statement::TryCatchStatement; use visitable_derive::Visitable; pub use while_statement::{DoWhileStatement, WhileStatement}; -/// A statement +/// A statement. See [Declaration]s and [StatementAndDeclaration] for more #[apply(derive_ASTNode)] #[derive(Debug, Clone, Visitable, EnumFrom, EnumTryInto, PartialEqExtras, GetFieldByType)] #[get_field_by_type_target(Span)] @@ -36,7 +36,6 @@ pub enum Statement { Expression(MultipleExpression), /// { ... } statement Block(Block), - // TODO as keyword Debugger(Span), // Loops and "condition-aries" If(IfStatement), @@ -47,8 +46,9 @@ pub enum Statement { TryCatch(TryCatchStatement), // Control flow Return(ReturnStatement), - // TODO maybe an actual label struct: + // TODO maybe an actual label struct instead of `Option` Continue(Option, Span), + // TODO maybe an actual label struct instead of `Option` Break(Option, Span), /// e.g `throw ...` Throw(ThrowStatement), @@ -262,13 +262,7 @@ impl ASTNode for Statement { } } Statement::Expression(val) => { - if val.left_is_statement_like() { - buf.push('('); - val.to_string_from_buffer(buf, options, local); - buf.push(')'); - } else { - val.to_string_from_buffer(buf, options, local); - } + val.to_string_on_left(buf, options, local); } Statement::Labelled { name, statement, .. } => { buf.push_str(name); diff --git a/parser/src/statements/switch_statement.rs b/parser/src/statements/switch_statement.rs index f892be1c..242a1b9b 100644 --- a/parser/src/statements/switch_statement.rs +++ b/parser/src/statements/switch_statement.rs @@ -36,12 +36,17 @@ impl ASTNode for SwitchStatement { options: &ParseOptions, ) -> Result { let start = state.expect_keyword(reader, TSXKeyword::Switch)?; + reader.expect_next(crate::TSXToken::OpenParentheses)?; let case = MultipleExpression::from_reader(reader, state, options)?; reader.expect_next(crate::TSXToken::CloseParentheses)?; reader.expect_next(crate::TSXToken::OpenBrace)?; + let mut branches = Vec::new(); + + // TODO not great has this works let close_brace_pos: TokenEnd; + loop { let case: Option = match reader.next().ok_or_else(parse_lexing_error)? { Token(TSXToken::Keyword(TSXKeyword::Default), _) => { @@ -54,7 +59,6 @@ impl ASTNode for SwitchStatement { Some(case) } Token(TSXToken::CloseBrace, pos) => { - // TODO bad close_brace_pos = TokenEnd::new(pos.0 + 1); break; } @@ -69,7 +73,10 @@ impl ASTNode for SwitchStatement { ); } }; - let mut statements = Vec::new(); + + // This is a modified form of Block::from_reader where `TSXKeyword::Case` and + // `TSXKeyword::Default` are delimiters + let mut items = Vec::new(); loop { if let Some(Token( TSXToken::Keyword(TSXKeyword::Case | TSXKeyword::Default) @@ -79,15 +86,17 @@ impl ASTNode for SwitchStatement { { break; } - statements.push(StatementOrDeclaration::from_reader(reader, state, options)?); - if let Some(Token(TSXToken::SemiColon, _)) = reader.peek() { - reader.next(); + let value = StatementOrDeclaration::from_reader(reader, state, options)?; + if value.requires_semi_colon() { + crate::expect_semi_colon(reader, &state.line_starts, value.get_position().end)?; } + // Could skip over semi colons regardless. But they are technically empty statements 🤷‍♂️ + items.push(value); } if let Some(case) = case { - branches.push(SwitchBranch::Case(case, statements)); + branches.push(SwitchBranch::Case(case, items)); } else { - branches.push(SwitchBranch::Default(statements)); + branches.push(SwitchBranch::Default(items)); } } Ok(Self { case, branches, position: start.union(close_brace_pos) }) diff --git a/parser/src/statements/try_catch_statement.rs b/parser/src/statements/try_catch_statement.rs index 9981e8e8..faab6dd1 100644 --- a/parser/src/statements/try_catch_statement.rs +++ b/parser/src/statements/try_catch_statement.rs @@ -1,13 +1,13 @@ use crate::{ derive_ASTNode, ASTNode, Block, ParseError, ParseErrors, TSXKeyword, TSXToken, TypeAnnotation, - VariableField, VariableFieldInSourceCode, WithComment, + VariableField, WithComment, }; use source_map::Span; use tokenizer_lib::Token; use visitable_derive::Visitable; #[cfg_attr(target_family = "wasm", tsify::declare)] -pub type ExceptionVarField = WithComment>; +pub type ExceptionVarField = WithComment; #[apply(derive_ASTNode)] #[derive(Debug, PartialEq, Eq, Clone, Visitable, get_field_by_type::GetFieldByType)] @@ -44,9 +44,7 @@ impl ASTNode for TryCatchStatement { if let Some(Token(TSXToken::OpenParentheses, _)) = reader.peek() { reader.expect_next(TSXToken::OpenParentheses)?; let variable_field = - WithComment::>::from_reader( - reader, state, options, - )?; + WithComment::::from_reader(reader, state, options)?; // Optional type reference `catch (e: type)` let mut exception_var_type: Option = None; diff --git a/parser/src/statements/while_statement.rs b/parser/src/statements/while_statement.rs index 184740e5..649871c9 100644 --- a/parser/src/statements/while_statement.rs +++ b/parser/src/statements/while_statement.rs @@ -71,7 +71,7 @@ impl ASTNode for DoWhileStatement { ) -> Result { let start = state.expect_keyword(reader, TSXKeyword::Do)?; let inner = BlockOrSingleStatement::from_reader(reader, state, options)?; - let _ = reader.expect_next(TSXToken::Keyword(TSXKeyword::While))?; + let _ = state.expect_keyword(reader, TSXKeyword::While)?; reader.expect_next(TSXToken::OpenParentheses)?; let condition = MultipleExpression::from_reader(reader, state, options)?; reader.expect_next(TSXToken::CloseParentheses)?; diff --git a/parser/src/tokens.rs b/parser/src/tokens.rs index 649e4708..340bda62 100644 --- a/parser/src/tokens.rs +++ b/parser/src/tokens.rs @@ -141,7 +141,7 @@ pub enum TSXToken { // JSX Tokens. Some like JSXComment are non standard JSXOpeningTagStart, JSXTagName(String), JSXOpeningTagEnd, JSXClosingTagStart, - /// This also covers the end of a token, thus no TSXToken::JSXClosingTagEnd + /// This also covers the end of a token, thus no 'TSXToken::JSXClosingTagEnd' JSXClosingTagName(String), /// /> JSXSelfClosingTag, @@ -184,12 +184,12 @@ impl tokenizer_lib::sized_tokens::SizedToken for TSXToken { | TSXToken::JSXAttributeKey(lit) | TSXToken::JSXAttributeValue(lit) | TSXToken::JSXContent(lit) - | TSXToken::JSXComment(lit) | TSXToken::JSXTagName(lit) | TSXToken::Identifier(lit) | TSXToken::NumberLiteral(lit) | TSXToken::RegexFlagLiteral(lit) => lit.len() as u32, + TSXToken::JSXComment(comment) => comment.len() as u32 + 7, TSXToken::MultiLineComment(comment) => comment.len() as u32 + 4, TSXToken::StringLiteral(comment, _) | TSXToken::Comment(comment) => { comment.len() as u32 + 2 @@ -228,7 +228,9 @@ impl tokenizer_lib::sized_tokens::SizedToken for TSXToken { | TSXToken::JSXOpeningTagEnd | TSXToken::JSXExpressionStart | TSXToken::JSXExpressionEnd - | TSXToken::JSXAttributeAssign => 1, + | TSXToken::JSXAttributeAssign + | TSXToken::JSXClosingTagStart + | TSXToken::JSXContentLineBreak => 1, TSXToken::AddAssign | TSXToken::SubtractAssign @@ -256,7 +258,8 @@ impl tokenizer_lib::sized_tokens::SizedToken for TSXToken { | TSXToken::BitwiseShiftLeft | TSXToken::BitwiseShiftRight | TSXToken::TemplateLiteralExpressionStart - | TSXToken::JSXFragmentStart => 2, + | TSXToken::JSXFragmentStart + | TSXToken::JSXSelfClosingTag => 2, TSXToken::Spread | TSXToken::StrictEqual @@ -275,10 +278,7 @@ impl tokenizer_lib::sized_tokens::SizedToken for TSXToken { TSXToken::BitwiseShiftRightUnsignedAssign => 4, // Marker nodes with no length - TSXToken::JSXClosingTagStart - | TSXToken::JSXSelfClosingTag - | TSXToken::JSXContentLineBreak - | TSXToken::EOS => 0, + TSXToken::EOS => 0, #[cfg(feature = "extras")] TSXToken::InvertAssign | TSXToken::DividesOperator | TSXToken::PipeOperator => 2, @@ -299,9 +299,9 @@ pub enum TSXKeyword { Class, Function, Constructor, New, This, Super, Case, Yield, Return, Continue, Break, - Import, Export, Default, From, + Import, Export, Default, From, With, In, Of, - TypeOf, InstanceOf, Void, Delete, + TypeOf, InstanceOf, Void, Delete, Assert, Debugger, Try, Catch, Finally, Throw, Async, Await, @@ -317,7 +317,10 @@ pub enum TSXKeyword { // TS publicity attributes Private, Public, Protected, // TS Keywords - As, Declare, Readonly, Infer, Is, Satisfies, Namespace, KeyOf, + As, Readonly, Satisfies, Declare, Namespace, + // TS & Ezno + Is, + Infer, KeyOf, Unique, Symbol, // TODO unsure #[cfg(feature = "extras")] Module, // Extra function modifiers @@ -351,6 +354,20 @@ impl TSXKeyword { pub(crate) fn length(self) -> u32 { self.to_str().len() as u32 } + + #[rustfmt::skip] + pub(crate) fn is_invalid_identifier(self) -> bool { + matches!( + self, + TSXKeyword::Const | TSXKeyword::Var | TSXKeyword::If | TSXKeyword::Else | TSXKeyword::For + | TSXKeyword::While | TSXKeyword::Do | TSXKeyword::Switch | TSXKeyword::Class | TSXKeyword::Function + | TSXKeyword::New | TSXKeyword::Super | TSXKeyword::Case | TSXKeyword::Return | TSXKeyword::Continue + | TSXKeyword::Break | TSXKeyword::Import | TSXKeyword::Export | TSXKeyword::Default | TSXKeyword::In + | TSXKeyword::TypeOf | TSXKeyword::InstanceOf | TSXKeyword::Void | TSXKeyword::Delete + | TSXKeyword::Debugger | TSXKeyword::Try | TSXKeyword::Catch | TSXKeyword::Finally | TSXKeyword::Throw + | TSXKeyword::Extends | TSXKeyword::Enum + ) + } } impl TSXToken { @@ -461,7 +478,7 @@ impl TSXToken { pub(crate) fn token_as_identifier( token: Token, at_location: &str, -) -> Result<(String, Span), ParseError> { +) -> crate::ParseResult<(String, Span)> { let position = token.get_span(); let name = match token.0 { TSXToken::Identifier(value) => value, diff --git a/parser/src/types/declares.rs b/parser/src/types/declares.rs index 29128ee4..a6b52351 100644 --- a/parser/src/types/declares.rs +++ b/parser/src/types/declares.rs @@ -3,9 +3,9 @@ use tokenizer_lib::{sized_tokens::TokenStart, Token}; use crate::{ declarations::VariableDeclarationItem, derive_ASTNode, errors::parse_lexing_error, parse_bracketed, to_string_bracketed, tokens::token_as_identifier, - types::type_annotations::TypeAnnotationFunctionParameters, ASTNode, Decorator, - GenericTypeConstraint, ParseOptions, ParseResult, Span, TSXKeyword, TSXToken, TokenReader, - TypeAnnotation, VariableKeyword, + types::type_annotations::TypeAnnotationFunctionParameters, ASTNode, Decorator, ParseOptions, + ParseResult, Span, TSXKeyword, TSXToken, TokenReader, TypeAnnotation, TypeParameter, + VariableKeyword, }; /// A `declare var/let/const` thingy. @@ -40,7 +40,7 @@ impl ASTNode for DeclareVariableDeclaration { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - if options.include_types { + if options.include_type_annotations { buf.push_str("declare "); buf.push_str(self.keyword.as_str()); crate::declarations::variable::declarations_to_string( @@ -86,7 +86,7 @@ impl DeclareVariableDeclaration { #[get_field_by_type_target(Span)] pub struct DeclareFunctionDeclaration { pub name: String, - pub type_parameters: Option>, + pub type_parameters: Option>, pub parameters: TypeAnnotationFunctionParameters, pub return_type: Option, #[cfg(feature = "extras")] @@ -115,7 +115,7 @@ impl ASTNode for DeclareFunctionDeclaration { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - if options.include_types { + if options.include_type_annotations { buf.push_str("declare function "); buf.push_str(self.name.as_str()); if let Some(type_parameters) = &self.type_parameters { @@ -181,7 +181,7 @@ impl DeclareFunctionDeclaration { #[cfg_attr(target_family = "wasm", derive(tsify::Tsify))] pub struct DeclareClassDeclaration { pub name: String, - pub type_parameters: Option>, + pub type_parameters: Option>, pub extends: Option, // members: Vec } diff --git a/parser/src/types/enum_declaration.rs b/parser/src/types/enum_declaration.rs index 8a904e8b..89ca5fd7 100644 --- a/parser/src/types/enum_declaration.rs +++ b/parser/src/types/enum_declaration.rs @@ -60,27 +60,29 @@ impl ASTNode for EnumDeclaration { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - if self.is_constant { - buf.push_str("const "); - } - buf.push_str("enum "); - buf.push_str(&self.name); - options.push_gap_optionally(buf); - buf.push_str("{"); - for (at_end, member) in self.members.iter().endiate() { - if options.pretty { - buf.push_new_line(); - options.add_indent(local.depth + 1, buf); + if options.include_type_annotations { + if self.is_constant { + buf.push_str("const "); } - member.to_string_from_buffer(buf, options, local); - if !options.pretty && !at_end { - buf.push(','); + buf.push_str("enum "); + buf.push_str(&self.name); + options.push_gap_optionally(buf); + buf.push_str("{"); + for (at_end, member) in self.members.iter().endiate() { + if options.pretty { + buf.push_new_line(); + options.add_indent(local.depth + 1, buf); + } + member.to_string_from_buffer(buf, options, local); + if !options.pretty && !at_end { + buf.push(','); + } } + if options.pretty && !self.members.is_empty() { + buf.push_new_line(); + } + buf.push('}'); } - if options.pretty && !self.members.is_empty() { - buf.push_new_line(); - } - buf.push('}'); } } diff --git a/parser/src/types/interface.rs b/parser/src/types/interface.rs index d0f0346a..2b27ce97 100644 --- a/parser/src/types/interface.rs +++ b/parser/src/types/interface.rs @@ -3,8 +3,8 @@ use crate::{ functions::MethodHeader, parse_bracketed, property_key::PublicOrPrivate, throw_unexpected_token_with_token, to_string_bracketed, tokens::token_as_identifier, types::type_annotations::TypeAnnotationFunctionParameters, ASTNode, Expression, - GenericTypeConstraint, NumberRepresentation, ParseOptions, ParseResult, PropertyKey, Span, - TSXKeyword, TSXToken, TypeAnnotation, TypeDeclaration, WithComment, + NumberRepresentation, ParseOptions, ParseResult, PropertyKey, Span, TSXKeyword, TSXToken, + TypeAnnotation, TypeDeclaration, TypeParameter, WithComment, }; use get_field_by_type::GetFieldByType; @@ -18,7 +18,7 @@ pub struct InterfaceDeclaration { pub name: String, #[cfg(feature = "extras")] pub is_nominal: bool, - pub type_parameters: Option>, + pub type_parameters: Option>, /// The document interface extends a multiple of other interfaces pub extends: Option>, pub members: Vec>>, @@ -64,23 +64,23 @@ impl ASTNode for InterfaceDeclaration { let TypeDeclaration { name, type_parameters, .. } = TypeDeclaration::from_reader(reader, state, options)?; - let extends = if let Some(Token(TSXToken::Keyword(TSXKeyword::Extends), _)) = reader.peek() + let extends = if reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Extends))) + .is_some() { - reader.next(); let type_annotation = TypeAnnotation::from_reader(reader, state, options)?; let mut extends = vec![type_annotation]; - if matches!(reader.peek(), Some(Token(TSXToken::Comma, _))) { - reader.next(); + if reader.conditional_next(|t| matches!(t, TSXToken::Comma)).is_some() { loop { extends.push(TypeAnnotation::from_reader(reader, state, options)?); - match reader.next().ok_or_else(parse_lexing_error)? { - Token(TSXToken::Comma, _) => { + match reader.peek() { + Some(Token(TSXToken::Comma, _)) => { reader.next(); } - Token(TSXToken::OpenBrace, _) => break, - token => { + Some(Token(TSXToken::OpenBrace, _)) | None => break, + _ => { return throw_unexpected_token_with_token( - token, + reader.next().unwrap(), &[TSXToken::Comma, TSXToken::OpenBrace], ) } @@ -112,22 +112,24 @@ impl ASTNode for InterfaceDeclaration { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - if options.include_types { + if options.include_type_annotations { buf.push_str("interface "); buf.push_str(&self.name); if let Some(type_parameters) = &self.type_parameters { to_string_bracketed(type_parameters, ('<', '>'), buf, options, local); + options.push_gap_optionally(buf); } - options.push_gap_optionally(buf); if let Some(extends) = &self.extends { buf.push_str(" extends "); for (at_end, extends) in extends.iter().endiate() { extends.to_string_from_buffer(buf, options, local); if !at_end { buf.push(','); + options.push_gap_optionally(buf); } } } + options.push_gap_optionally(buf); buf.push('{'); if options.pretty && !self.members.is_empty() { buf.push_new_line(); @@ -156,7 +158,7 @@ pub enum InterfaceMember { Method { header: MethodHeader, name: PropertyKey, - type_parameters: Option>, + type_parameters: Option>, parameters: TypeAnnotationFunctionParameters, return_type: Option, is_optional: bool, @@ -185,7 +187,7 @@ pub enum InterfaceMember { /// ``` Constructor { parameters: TypeAnnotationFunctionParameters, - type_parameters: Option>, + type_parameters: Option>, return_type: Option, is_readonly: bool, #[cfg(feature = "extras")] @@ -194,7 +196,7 @@ pub enum InterfaceMember { }, Caller { parameters: TypeAnnotationFunctionParameters, - type_parameters: Option>, + type_parameters: Option>, return_type: Option, is_readonly: bool, position: Span, diff --git a/parser/src/types/mod.rs b/parser/src/types/mod.rs index 89c46190..e048d315 100644 --- a/parser/src/types/mod.rs +++ b/parser/src/types/mod.rs @@ -10,25 +10,35 @@ pub mod type_declarations; pub use interface::InterfaceDeclaration; -use crate::derive_ASTNode; +use crate::{derive_ASTNode, TSXKeyword, TSXToken}; -// [See](https://www.typescriptlang.org/docs/handbook/2/classes.html#member-visibility) -// #[derive(Debug, Clone, PartialEq, Eq)] -// pub enum Visibility { -// Private, -// Public, -// Protected, -// } +/// [See](https://www.typescriptlang.org/docs/handbook/2/classes.html#member-visibility) +#[apply(derive_ASTNode)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Visibility { + Private, + Public, + Protected, +} + +impl Visibility { + #[must_use] + pub fn as_str(&self) -> &'static str { + match self { + Visibility::Private => "private ", + Visibility::Public => "public ", + Visibility::Protected => "protected ", + } + } -// impl Visibility { -// pub fn as_str(&self) -> &'static str { -// match self { -// Visibility::Private => "private ", -// Visibility::Public => "public ", -// Visibility::Protected => "protected ", -// } -// } -// } + #[must_use] + pub fn token_is_visibility_specifier(t: &TSXToken) -> bool { + matches!( + t, + TSXToken::Keyword(TSXKeyword::Private | TSXKeyword::Public | TSXKeyword::Protected) + ) + } +} #[cfg(feature = "extras")] #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/parser/src/types/namespace.rs b/parser/src/types/namespace.rs index 668b8542..e4c07e69 100644 --- a/parser/src/types/namespace.rs +++ b/parser/src/types/namespace.rs @@ -1,5 +1,17 @@ -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Namespace(String, Vec); +use get_field_by_type::GetFieldByType; +use source_map::Span; + +use crate::{derive_ASTNode, Block}; + +/// While `Block`, only some items are allowed +#[apply(derive_ASTNode)] +#[derive(Debug, Clone, PartialEq, get_field_by_type::GetFieldByType)] +#[get_field_by_type_target(Span)] +pub struct Namespace { + pub name: String, + pub inner: Block, + pub position: Span, +} impl crate::ASTNode for Namespace { fn from_reader( @@ -7,37 +19,31 @@ impl crate::ASTNode for Namespace { state: &mut crate::ParsingState, options: &crate::ParseOptions, ) -> crate::ParseResult { - reader.expect_next(crate::TSXToken::Keyword(crate::TSXKeyword::Namespace))?; - let (namespace_name, _) = crate::tokens::token_as_identifier( + let start = reader.expect_next(crate::TSXToken::Keyword(crate::TSXKeyword::Namespace))?; + let (name, _) = crate::tokens::token_as_identifier( reader.next().ok_or_else(crate::errors::parse_lexing_error)?, "namespace name", )?; - reader.expect_next(crate::TSXToken::OpenBrace)?; - let mut declarations = Vec::new(); - while let Some(token) = reader.peek() { - if let tokenizer_lib::Token(crate::TSXToken::CloseBrace, _) = token { - break; - } - declarations - .push(crate::TypeDefinitionModuleDeclaration::from_reader(reader, state, options)?); - if let Some(tokenizer_lib::Token(crate::TSXToken::SemiColon, _)) = reader.peek() { - reader.next(); - } - } - reader.next(); - Ok(Self(namespace_name, declarations)) + let inner = Block::from_reader(reader, state, options)?; + let position = start.union(inner.get_position()); + Ok(Self { name, inner, position }) } fn to_string_from_buffer( &self, - _buf: &mut T, - _options: &crate::ToStringOptions, - _local: crate::LocalToStringInformation, + buf: &mut T, + options: &crate::ToStringOptions, + local: crate::LocalToStringInformation, ) { - todo!() + if options.include_type_annotations { + buf.push_str("namespace "); + buf.push_str(&self.name); + buf.push(' '); + self.inner.to_string_from_buffer(buf, options, local); + } } fn get_position(&self) -> &source_map::Span { - todo!() + self.get() } } diff --git a/parser/src/types/type_alias.rs b/parser/src/types/type_alias.rs index 6aea260d..57d40c20 100644 --- a/parser/src/types/type_alias.rs +++ b/parser/src/types/type_alias.rs @@ -32,7 +32,7 @@ impl ASTNode for TypeAlias { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - if options.include_types { + if options.include_type_annotations { buf.push_str("type "); self.type_name.to_string_from_buffer(buf, options, local); buf.push_str(" = "); diff --git a/parser/src/types/type_annotations.rs b/parser/src/types/type_annotations.rs index 9ef87f18..5fdaf54a 100644 --- a/parser/src/types/type_annotations.rs +++ b/parser/src/types/type_annotations.rs @@ -4,8 +4,7 @@ use crate::{ }; use crate::{ errors::parse_lexing_error, expressions::TemplateLiteralPart, - extensions::decorators::Decorated, Decorator, Marker, ParseResult, VariableField, - VariableFieldInTypeAnnotation, WithComment, + extensions::decorators::Decorated, Decorator, Marker, ParseResult, VariableField, WithComment, }; use derive_partial_eq_extras::PartialEqExtras; use iterator_endiate::EndiateIteratorExt; @@ -13,7 +12,7 @@ use tokenizer_lib::sized_tokens::{TokenEnd, TokenReaderWithTokenEnds, TokenStart use super::{ interface::{parse_interface_members, InterfaceMember}, - type_declarations::GenericTypeConstraint, + type_declarations::TypeParameter, }; use crate::{ @@ -49,14 +48,14 @@ pub enum TypeAnnotation { ArrayLiteral(Box, Span), /// Function literal e.g. `(x: string) => string` FunctionLiteral { - type_parameters: Option>, + type_parameters: Option>, parameters: TypeAnnotationFunctionParameters, return_type: Box, position: Span, }, /// Construction literal e.g. `new (x: string) => string` ConstructorLiteral { - type_parameters: Option>, + type_parameters: Option>, parameters: TypeAnnotationFunctionParameters, return_type: Box, position: Span, @@ -64,7 +63,7 @@ pub enum TypeAnnotation { /// Object literal e.g. `{ y: string }` ObjectLiteral(Vec>>, Span), /// Tuple literal e.g. `[number, x: string]` - TupleLiteral(Vec<(SpreadKind, AnnotationWithBinder)>, Span), + TupleLiteral(Vec<(TupleElementKind, AnnotationWithBinder)>, Span), /// ? TemplateLiteral(Vec>, Span), /// Declares type as not assignable (still has interior mutability) e.g. `readonly number` @@ -81,6 +80,13 @@ pub enum TypeAnnotation { resolve_false: TypeConditionResult, position: Span, }, + Symbol { + /// TODO unsure + unique: bool, + #[cfg(feature = "extras")] + name: Option, + position: Span, + }, Decorated( Decorator, #[cfg_attr(target_family = "wasm", tsify(type = "TypeAnnotation"))] Box, @@ -142,9 +148,10 @@ impl ASTNode for AnnotationWithBinder { #[derive(Debug, Clone, PartialEq, Eq)] #[apply(derive_ASTNode)] -pub enum SpreadKind { - NonSpread, +pub enum TupleElementKind { + Standard, Spread, + Optional, } /// Condition in a [`TypeAnnotation::Conditional`] @@ -252,7 +259,7 @@ impl ASTNode for TypeAnnotation { state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult { - Self::from_reader_with_config(reader, state, options, false, false, None) + Self::from_reader_with_config(reader, state, options, None, None) } fn to_string_from_buffer( @@ -329,7 +336,7 @@ impl ASTNode for TypeAnnotation { Self::TupleLiteral(members, _) => { buf.push('['); for (at_end, (spread, member)) in members.iter().endiate() { - if matches!(spread, SpreadKind::Spread) { + if matches!(spread, TupleElementKind::Spread) { buf.push_str("..."); } member.to_string_from_buffer(buf, options, local); @@ -392,6 +399,7 @@ impl ASTNode for TypeAnnotation { } buf.push('`'); } + Self::Symbol { .. } => buf.push_str("symbol"), } } @@ -400,6 +408,16 @@ impl ASTNode for TypeAnnotation { } } +/// For parsing +#[derive(Clone, Copy)] +pub(crate) enum TypeOperatorKind { + Union, + Intersection, + // not an implication, not an implication, not an implication + Function, + Query, +} + impl TypeAnnotation { /// Also returns the local the generic arguments ran over /// TODO refactor and tidy a lot of this, precedence rather than config @@ -407,8 +425,7 @@ impl TypeAnnotation { reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, - return_on_union_or_intersection: bool, - return_on_arrow: bool, + parent_kind: Option, start: Option, ) -> ParseResult { if let (true, Some(Token(peek, at))) = (options.partial_syntax, reader.peek()) { @@ -440,6 +457,14 @@ impl TypeAnnotation { { reader.next(); } + + if let (None, Some(Token(TSXToken::BitwiseOr, _))) = (parent_kind, reader.peek()) { + reader.next(); + } + if let (None, Some(Token(TSXToken::BitwiseAnd, _))) = (parent_kind, reader.peek()) { + reader.next(); + } + let mut reference = match reader.next().ok_or_else(parse_lexing_error)? { // Literals: t @ Token(TSXToken::Keyword(TSXKeyword::True), _) => { @@ -448,6 +473,47 @@ impl TypeAnnotation { t @ Token(TSXToken::Keyword(TSXKeyword::False), _) => { Self::BooleanLiteral(false, t.get_span()) } + t @ Token(TSXToken::Keyword(TSXKeyword::Symbol), _) => { + let position = t.get_span(); + #[cfg(feature = "extras")] + let name = + reader.conditional_next(|t| matches!(t, TSXToken::StringLiteral(..))).map( + |t| { + if let Token(TSXToken::StringLiteral(content, _), _) = t { + content + } else { + unreachable!() + } + }, + ); + Self::Symbol { + unique: false, + position, + #[cfg(feature = "extras")] + name, + } + } + t @ Token(TSXToken::Keyword(TSXKeyword::Unique), _) => { + let sym_pos = reader.expect_next(TSXToken::Keyword(TSXKeyword::Symbol))?; + let position = t.get_span().union(sym_pos.with_length("symbol".len())); + #[cfg(feature = "extras")] + let name = + reader.conditional_next(|t| matches!(t, TSXToken::StringLiteral(..))).map( + |t| { + if let Token(TSXToken::StringLiteral(content, _), _) = t { + content + } else { + unreachable!() + } + }, + ); + Self::Symbol { + unique: false, + position, + #[cfg(feature = "extras")] + name, + } + } Token(TSXToken::NumberLiteral(num), start) => { let pos = start.with_length(num.len()); Self::NumberLiteral(num.parse::().unwrap(), pos) @@ -458,8 +524,14 @@ impl TypeAnnotation { } Token(TSXToken::At, pos) => { let decorator = Decorator::from_reader_sub_at_symbol(reader, state, options, pos)?; - let this_declaration = - Self::from_reader_with_config(reader, state, options, true, false, start)?; + // TODO ... + let this_declaration = Self::from_reader_with_config( + reader, + state, + options, + Some(TypeOperatorKind::Query), + start, + )?; let position = pos.union(this_declaration.get_position()); Self::Decorated(decorator, Box::new(this_declaration), position) } @@ -527,16 +599,26 @@ impl TypeAnnotation { if let Some(Token(TSXToken::CloseBrace, _)) = reader.peek() { break; } - let spread = if reader + let is_spread = reader .conditional_next(|token| matches!(token, TSXToken::Spread)) + .is_some(); + + let annotation_with_binder = + AnnotationWithBinder::from_reader(reader, state, options)?; + + let kind = if is_spread { + TupleElementKind::Spread + } else if reader + .conditional_next(|token| matches!(token, TSXToken::QuestionMark)) .is_some() { - SpreadKind::Spread + TupleElementKind::Optional } else { - SpreadKind::NonSpread + TupleElementKind::Standard }; - members - .push((spread, AnnotationWithBinder::from_reader(reader, state, options)?)); + + members.push((kind, annotation_with_binder)); + if let Some(Token(TSXToken::Comma, _)) = reader.peek() { reader.next(); } else { @@ -632,16 +714,12 @@ impl TypeAnnotation { )); }; reader.next(); - let (generic_arguments, end) = generic_arguments_from_reader_sub_open_angle( - reader, - state, - options, - return_on_union_or_intersection, - )?; + let (generic_arguments, end) = + generic_arguments_from_reader_sub_open_angle(reader, state, options, parent_kind)?; reference = Self::NameWithGenericArguments(name, generic_arguments, start_span.union(end)); - return Ok(reference); }; + // Array shorthand & indexing type references. Loops as number[][] // unsure if index type can be looped while reader.conditional_next(|tok| *tok == TSXToken::OpenBracket).is_some() { @@ -664,7 +742,11 @@ impl TypeAnnotation { Some(Token(TSXToken::Keyword(TSXKeyword::Extends), _)) => { reader.next(); let extends_type = TypeAnnotation::from_reader_with_config( - reader, state, options, true, false, start, + reader, + state, + options, + Some(TypeOperatorKind::Query), + start, )?; // TODO local let position = reference.get_position().union(extends_type.get_position()); @@ -693,7 +775,11 @@ impl TypeAnnotation { Some(Token(TSXToken::Keyword(TSXKeyword::Is), _)) => { reader.next(); let is_type = TypeAnnotation::from_reader_with_config( - reader, state, options, true, false, start, + reader, + state, + options, + Some(TypeOperatorKind::Query), + start, )?; // TODO local let position = reference.get_position().union(is_type.get_position()); @@ -711,14 +797,19 @@ impl TypeAnnotation { Ok(TypeAnnotation::Conditional { condition, resolve_true, resolve_false, position }) } Some(Token(TSXToken::BitwiseOr, _)) => { - if return_on_union_or_intersection { + if matches!(parent_kind, Some(TypeOperatorKind::Query | TypeOperatorKind::Function)) + { return Ok(reference); } let mut union_members = vec![reference]; while let Some(Token(TSXToken::BitwiseOr, _)) = reader.peek() { reader.next(); union_members.push(Self::from_reader_with_config( - reader, state, options, true, false, start, + reader, + state, + options, + Some(TypeOperatorKind::Union), + start, )?); } let position = union_members @@ -729,14 +820,24 @@ impl TypeAnnotation { Ok(Self::Union(union_members, position)) } Some(Token(TSXToken::BitwiseAnd, _)) => { - if return_on_union_or_intersection { + if matches!( + parent_kind, + Some( + TypeOperatorKind::Union + | TypeOperatorKind::Query | TypeOperatorKind::Function + ) + ) { return Ok(reference); } let mut intersection_members = vec![reference]; while let Some(Token(TSXToken::BitwiseAnd, _)) = reader.peek() { reader.next(); intersection_members.push(Self::from_reader_with_config( - reader, state, options, true, false, start, + reader, + state, + options, + Some(TypeOperatorKind::Intersection), + start, )?); } let position = intersection_members @@ -744,15 +845,22 @@ impl TypeAnnotation { .unwrap() .get_position() .union(intersection_members.last().unwrap().get_position()); + Ok(Self::Intersection(intersection_members, position)) } Some(Token(TSXToken::Arrow, _)) => { - if return_on_arrow { + if matches!(parent_kind, Some(TypeOperatorKind::Query | TypeOperatorKind::Function)) + { return Ok(reference); } reader.next(); - let return_type = - Self::from_reader_with_config(reader, state, options, true, false, start)?; + let return_type = Self::from_reader_with_config( + reader, + state, + options, + Some(TypeOperatorKind::Function), + start, + )?; let parameters_position = *reference.get_position(); let position = parameters_position.union(return_type.get_position()); Ok(Self::FunctionLiteral { @@ -784,19 +892,13 @@ pub(crate) fn generic_arguments_from_reader_sub_open_angle( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, - return_on_union_or_intersection: bool, + kind: Option, ) -> ParseResult<(Vec, TokenEnd)> { let mut generic_arguments = Vec::new(); loop { - let argument = TypeAnnotation::from_reader_with_config( - reader, - state, - options, - return_on_union_or_intersection, - false, - None, - )?; + let argument = TypeAnnotation::from_reader_with_config(reader, state, options, kind, None)?; + generic_arguments.push(argument); // Handling for the fact that concessive chevrons are grouped into bitwise shifts @@ -861,6 +963,7 @@ impl ASTNode for TypeAnnotationFunctionParameters { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { + buf.push('('); for parameter in &self.parameters { if let Some(ref name) = parameter.name { name.to_string_from_buffer(buf, options, local); @@ -877,6 +980,7 @@ impl ASTNode for TypeAnnotationFunctionParameters { buf.push_str(&rest_parameter.name); rest_parameter.type_annotation.to_string_from_buffer(buf, options, local); } + buf.push(')'); } } @@ -927,7 +1031,7 @@ impl TypeAnnotationFunctionParameters { } _ => local == 0, }); - let name: Option>> = + let name: Option> = if let Some(Token(TSXToken::Colon | TSXToken::OptionalMember, _)) = after_variable_field { @@ -946,15 +1050,17 @@ impl TypeAnnotationFunctionParameters { } }; let type_annotation = TypeAnnotation::from_reader(reader, state, options)?; + let position = name + .as_ref() + .map_or(type_annotation.get_position(), |name| name.get_position()) + .union(type_annotation.get_position()); + parameters.push(TypeAnnotationFunctionParameter { - position: name - .as_ref() - .map_or(type_annotation.get_position(), |name| name.get_position()) - .union(type_annotation.get_position()), decorators, name, type_annotation, is_optional, + position, }); if reader.conditional_next(|tok| matches!(tok, TSXToken::Comma)).is_none() { @@ -975,7 +1081,7 @@ impl TypeAnnotationFunctionParameters { pub struct TypeAnnotationFunctionParameter { pub decorators: Vec, /// Ooh nice optional - pub name: Option>>, + pub name: Option>, pub type_annotation: TypeAnnotation, pub is_optional: bool, pub position: Span, @@ -1059,6 +1165,16 @@ mod tests { _, ) ); + + // Leading | is valid + assert_matches_ast!( + "| string | number", + TypeAnnotation::Union( + Deref @ + [TypeAnnotation::CommonName(CommonTypes::String, span!(2, 8)), TypeAnnotation::CommonName(CommonTypes::Number, span!(11, 17))], + _, + ) + ); } #[test] @@ -1079,13 +1195,13 @@ mod tests { "[number, x: string]", TypeAnnotation::TupleLiteral( Deref @ [( - SpreadKind::NonSpread, + TupleElementKind::Standard, AnnotationWithBinder::NoAnnotation(TypeAnnotation::CommonName( CommonTypes::Number, span!(1, 7), )), ), ( - SpreadKind::NonSpread, + TupleElementKind::Standard, AnnotationWithBinder::Annotated { name: Deref @ "x", ty: TypeAnnotation::CommonName(CommonTypes::String, span!(12, 18)), diff --git a/parser/src/types/type_declarations.rs b/parser/src/types/type_declarations.rs index 1d4f0fe0..1dae9669 100644 --- a/parser/src/types/type_declarations.rs +++ b/parser/src/types/type_declarations.rs @@ -3,7 +3,7 @@ use crate::{ tokens::token_as_identifier, ASTNode, ListItem, ParseOptions, ParseResult, Span, TSXKeyword, TSXToken, TypeAnnotation, }; -use tokenizer_lib::{Token, TokenReader}; +use tokenizer_lib::TokenReader; /// Similar to type reference but no unions or intersections AND includes generic constraints. /// Used for declaring classes, interfaces and functions @@ -11,7 +11,7 @@ use tokenizer_lib::{Token, TokenReader}; #[apply(derive_ASTNode)] pub struct TypeDeclaration { pub name: String, - pub type_parameters: Option>, + pub type_parameters: Option>, pub position: Span, } @@ -60,61 +60,58 @@ impl ASTNode for TypeDeclaration { /// TODO is default and extends mut ex #[derive(Debug, Clone, PartialEq, Eq)] #[apply(derive_ASTNode)] -pub enum GenericTypeConstraint { - Parameter { name: String, default: Option }, - Extends(String, TypeAnnotation), - ExtendsKeyOf(String, TypeAnnotation), - // TODO this should go - Spread { name: String, default: Option }, -} - -impl GenericTypeConstraint { - #[must_use] - pub fn name(&self) -> &str { - match self { - GenericTypeConstraint::Parameter { name, .. } - | GenericTypeConstraint::Extends(name, _) - | GenericTypeConstraint::ExtendsKeyOf(name, _) - | GenericTypeConstraint::Spread { name, .. } => name, - } - } +pub struct TypeParameter { + pub name: String, + pub default: Option, + pub extends: Option, + pub position: Span, + #[cfg(feature = "full-typescript")] + pub is_constant: bool, } -impl ListItem for GenericTypeConstraint {} +impl ListItem for TypeParameter {} -impl ASTNode for GenericTypeConstraint { +impl ASTNode for TypeParameter { fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult { - // Get name: + #[cfg(feature = "full-typescript")] + let is_constant = reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Const))) + .is_some(); + let token = reader.next().ok_or_else(parse_lexing_error)?; - let (name, _pos) = token_as_identifier(token, "generic constraint name")?; - match reader.peek() { - Some(Token(TSXToken::Keyword(TSXKeyword::Extends), _)) => { - reader.next(); - let key_of = reader - .conditional_next(|token| *token == TSXToken::Keyword(TSXKeyword::KeyOf)) - .is_some(); - let extends_type = TypeAnnotation::from_reader_with_config( - reader, state, options, false, false, None, - )?; - if key_of { - Ok(Self::ExtendsKeyOf(name, extends_type)) - } else { - Ok(Self::Extends(name, extends_type)) - } - } - Some(Token(TSXToken::Assign, _)) => { - reader.next(); - let default_type = TypeAnnotation::from_reader_with_config( - reader, state, options, false, false, None, - )?; - Ok(Self::Parameter { name, default: Some(default_type) }) - } - _ => Ok(Self::Parameter { name, default: None }), - } + let (name, pos) = token_as_identifier(token, "type parameter name")?; + + let extends = reader + .conditional_next(|t| matches!(t, TSXToken::Keyword(TSXKeyword::Extends))) + .is_some() + .then(|| TypeAnnotation::from_reader(reader, state, options)) + .transpose()?; + + let default = reader + .conditional_next(|t| matches!(t, TSXToken::Assign)) + .is_some() + .then(|| TypeAnnotation::from_reader(reader, state, options)) + .transpose()?; + + let position = pos.get_start().union( + default + .as_ref() + .or(extends.as_ref()) + .map_or(pos.get_end(), |ta| ta.get_position().get_end()), + ); + + Ok(Self { + name, + default, + extends, + position, + #[cfg(feature = "full-typescript")] + is_constant, + }) } fn to_string_from_buffer( @@ -123,36 +120,16 @@ impl ASTNode for GenericTypeConstraint { options: &crate::ToStringOptions, local: crate::LocalToStringInformation, ) { - match self { - GenericTypeConstraint::Parameter { name, default } => { - buf.push_str(name); - if let Some(default) = default { - buf.push('='); - default.to_string_from_buffer(buf, options, local); - } - } - GenericTypeConstraint::Extends(name, extends) => { - buf.push_str(name); - buf.push_str(" extends "); - extends.to_string_from_buffer(buf, options, local); - } - GenericTypeConstraint::ExtendsKeyOf(name, extends_key_of) => { - buf.push_str(name); - buf.push_str(" extends keyof "); - extends_key_of.to_string_from_buffer(buf, options, local); - } - GenericTypeConstraint::Spread { name, default } => { - buf.push_str("..."); - buf.push_str(name); - if let Some(default) = default { - buf.push('='); - default.to_string_from_buffer(buf, options, local); - } - } + buf.push_str(&self.name); + if let Some(ref extends) = self.extends { + extends.to_string_from_buffer(buf, options, local); + } + if let Some(ref default) = self.default { + default.to_string_from_buffer(buf, options, local); } } fn get_position(&self) -> &Span { - todo!() + &self.position } } diff --git a/parser/src/variable_fields.rs b/parser/src/variable_fields.rs index 98383fea..680e5d09 100644 --- a/parser/src/variable_fields.rs +++ b/parser/src/variable_fields.rs @@ -11,8 +11,8 @@ use crate::{ throw_unexpected_token_with_token, tokens::token_as_identifier, visiting::{ImmutableVariableOrProperty, MutableVariableOrProperty}, - ASTNode, Expression, ListItem, Marker, ParseError, ParseOptions, ParseResult, Span, TSXToken, - Token, VisitOptions, Visitable, WithComment, + ASTNode, Expression, ListItem, Marker, ParseError, ParseErrors, ParseOptions, ParseResult, + Span, TSXToken, Token, VisitOptions, Visitable, WithComment, }; use derive_partial_eq_extras::PartialEqExtras; @@ -41,6 +41,9 @@ impl ASTNode for VariableIdentifier { options: &ParseOptions, ) -> ParseResult { let (ident, span) = token_as_identifier(reader.next().unwrap(), "variable identifier")?; + if ident == "let" { + return Err(ParseError::new(ParseErrors::ReservedIdentifier, span)); + } Ok(if options.interpolation_points && ident == crate::marker::MARKER { Self::Marker(state.new_partial_point_marker(span.get_start()), span) } else { @@ -69,190 +72,22 @@ impl ASTNode for VariableIdentifier { /// A variable declaration name, used in variable declarations and function parameters. /// See [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) -#[derive(Debug, Clone)] -#[cfg_attr(target_family = "wasm", derive(tsify::Tsify))] -pub enum VariableField { +#[derive(Debug, Clone, PartialEq, Eq)] +#[apply(derive_ASTNode)] +pub enum VariableField { /// `x` Name(VariableIdentifier), /// `[x, y, z]` /// TODO spread last - Array(Vec>>, Span), + Array(Vec>, Span), /// `{ x, y: z }`. /// TODO spread last - Object(Vec>>, Span), -} - -#[cfg(feature = "self-rust-tokenize")] -impl self_rust_tokenize::SelfRustTokenize for VariableField -where - T::OptionalExpression: self_rust_tokenize::SelfRustTokenize, -{ - fn append_to_token_stream( - &self, - token_stream: &mut self_rust_tokenize::proc_macro2::TokenStream, - ) { - use self_rust_tokenize::{quote, SelfRustTokenize}; - let tokens = match self { - VariableField::Name(identifier) => { - let tokens = identifier.to_tokens(); - quote!(VariableField::Name(#tokens)) - } - VariableField::Array(value, span) => { - let (value, span) = - (SelfRustTokenize::to_tokens(value), SelfRustTokenize::to_tokens(span)); - quote!(VariableField::Array(#value, #span)) - } - VariableField::Object(value, span) => { - let (value, span) = - (SelfRustTokenize::to_tokens(value), SelfRustTokenize::to_tokens(span)); - quote!(VariableField::Object(#value, #span)) - } - }; - token_stream.extend(tokens); - } -} - -#[cfg(feature = "serde-serialize")] -impl serde::Serialize for VariableField -where - T: serde::Serialize, - T::OptionalExpression: serde::Serialize, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - VariableField::Name(name) => { - serializer.serialize_newtype_variant("VariableField", 0, "Name", name) - } - VariableField::Array(items, _) => { - serializer.serialize_newtype_variant("VariableField", 0, "Array", items) - } - VariableField::Object(items, _) => { - serializer.serialize_newtype_variant("VariableField", 0, "Object", items) - } - } - } -} - -impl From for VariableField { - fn from(value: VariableIdentifier) -> Self { - Self::Name(value) - } -} - -impl PartialEq for VariableField { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Name(l0), Self::Name(r0)) => l0 == r0, - (Self::Array(l0, _), Self::Array(r0, _)) => l0 == r0, - (Self::Object(l0, _), Self::Object(r0, _)) => l0 == r0, - _ => false, - } - } -} - -impl Eq for VariableField {} - -/// Variable field can be used in type annotations but cannot have a value -/// -/// TODO value assignment this is `VariableOrFieldAccess` thingy -/// -/// TODO could have `get_optional_expression_as_option(&Self::OptionalExpression`) -> Option -pub trait VariableFieldKind: PartialEq + Eq + Debug + Clone + 'static { - type OptionalExpression: PartialEq + Eq + Debug + Clone + Sync + Send; - - fn optional_expression_from_reader( - reader: &mut impl TokenReader, - state: &mut crate::ParsingState, - options: &ParseOptions, - ) -> Result; - - fn optional_expression_to_string_from_buffer( - optional_expression: &Self::OptionalExpression, - buf: &mut T, - options: &crate::ToStringOptions, - local: crate::LocalToStringInformation, - ); - - fn optional_expression_get_position( - optional_expression: &Self::OptionalExpression, - ) -> Option<&Span>; + Object(Vec>, Span), } -#[derive(Debug, Clone, PartialEq, Eq)] -#[apply(derive_ASTNode)] -pub struct VariableFieldInSourceCode; - -impl VariableFieldKind for VariableFieldInSourceCode { - type OptionalExpression = Option; +impl ListItem for WithComment {} - fn optional_expression_from_reader( - reader: &mut impl TokenReader, - state: &mut crate::ParsingState, - options: &ParseOptions, - ) -> Result { - Ok(if reader.conditional_next(|tok| matches!(tok, TSXToken::Assign)).is_some() { - Some(Expression::from_reader(reader, state, options)?) - } else { - None - }) - } - - fn optional_expression_to_string_from_buffer( - optional_expression: &Self::OptionalExpression, - buf: &mut T, - options: &crate::ToStringOptions, - local: crate::LocalToStringInformation, - ) { - if let Some(optional_expression) = optional_expression { - buf.push_str(if options.pretty { " = " } else { "=" }); - optional_expression.to_string_from_buffer(buf, options, local); - } - } - - fn optional_expression_get_position( - optional_expression: &Self::OptionalExpression, - ) -> Option<&Span> { - optional_expression.as_ref().map(ASTNode::get_position) - } -} - -/// For function type references -#[derive(Debug, Clone, PartialEq, Eq)] -#[apply(derive_ASTNode)] -pub struct VariableFieldInTypeAnnotation; - -impl VariableFieldKind for VariableFieldInTypeAnnotation { - type OptionalExpression = (); - - fn optional_expression_from_reader( - _reader: &mut impl TokenReader, - _state: &mut crate::ParsingState, - _options: &ParseOptions, - ) -> Result { - Ok(()) - } - - fn optional_expression_to_string_from_buffer( - _optional_expression: &Self::OptionalExpression, - _buf: &mut T, - _options: &crate::ToStringOptions, - _local: crate::LocalToStringInformation, - ) { - } - - fn optional_expression_get_position( - _optional_expression: &Self::OptionalExpression, - ) -> Option<&Span> { - None - } -} - -impl ListItem for WithComment> {} - -impl ASTNode for VariableField { +impl ASTNode for VariableField { fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, @@ -323,69 +158,53 @@ impl ASTNode for VariableField { } } +#[derive(Debug, Clone, PartialEq, Eq)] #[apply(derive_ASTNode)] -#[derive(Debug, Clone, PartialEqExtras, get_field_by_type::GetFieldByType)] -#[get_field_by_type_target(Span)] -#[partial_eq_ignore_types(Span)] -pub enum ObjectDestructuringField { - /// `{ x }` - Name( - VariableIdentifier, - #[cfg_attr(target_family = "wasm", tsify(type = "Expression | null"))] - T::OptionalExpression, - Span, - ), - /// `{ ...x }` +pub enum ArrayDestructuringField { Spread(VariableIdentifier, Span), - /// `{ x: y }` - Map { - from: PropertyKey, - name: WithComment>, - #[cfg_attr(target_family = "wasm", tsify(type = "Expression | null"))] - default_value: T::OptionalExpression, - position: Span, - }, + Name(VariableField, Option>), + Comment { content: String, is_multiline: bool, position: Span }, + None, +} + +impl ListItem for WithComment { + const EMPTY: Option = Some(WithComment::None(ArrayDestructuringField::None)); + + fn allow_comma_after(&self) -> bool { + !matches!(self.get_ast_ref(), ArrayDestructuringField::Spread(..)) + } } -impl ASTNode for ObjectDestructuringField { +impl ASTNode for ArrayDestructuringField { fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult { - if let Token(TSXToken::Spread, _) = reader.peek().ok_or_else(parse_lexing_error)? { + let Token(token, _start) = reader.peek().ok_or_else(parse_lexing_error)?; + if let TSXToken::Spread = token { let token = reader.next().unwrap(); - let identifier = VariableIdentifier::from_reader(reader, state, options)?; - let position = token.get_span().union(identifier.get_position()); - Ok(Self::Spread(identifier, position)) + Ok(Self::Spread( + VariableIdentifier::from_reader(reader, state, options)?, + token.get_span(), + )) + } else if matches!(token, TSXToken::Comma | TSXToken::CloseBracket) { + Ok(Self::None) } else { - let key = PropertyKey::from_reader(reader, state, options)?; - if let Some(Token(TSXToken::Colon, _)) = reader.peek() { - reader.next(); - let variable_name = - WithComment::>::from_reader(reader, state, options)?; - let default_value = U::optional_expression_from_reader(reader, state, options)?; - let position = - if let Some(pos) = U::optional_expression_get_position(&default_value) { - key.get_position().union(pos) - } else { - *key.get_position() - }; - Ok(Self::Map { from: key, name: variable_name, default_value, position }) - } else if let PropertyKey::Ident(name, key_pos, _) = key { - let default_value = U::optional_expression_from_reader(reader, state, options)?; - let standard = VariableIdentifier::Standard(name, key_pos); - let position = - if let Some(pos) = U::optional_expression_get_position(&default_value) { - standard.get_position().union(pos) - } else { - *standard.get_position() - }; - Ok(Self::Name(standard, default_value, position)) - } else { - let token = reader.next().ok_or_else(parse_lexing_error)?; - throw_unexpected_token_with_token(token, &[TSXToken::Colon]) - } + let name = VariableField::from_reader(reader, state, options)?; + let default_value = reader + .conditional_next(|t| matches!(t, TSXToken::Assign)) + .is_some() + .then(|| ASTNode::from_reader(reader, state, options).map(Box::new)) + .transpose()?; + + // let position = + // if let Some(ref pos) = default_value { + // key.get_position().union(pos) + // } else { + // *key.get_position() + // }; + Ok(Self::Name(name, default_value)) } } @@ -400,72 +219,102 @@ impl ASTNode for ObjectDestructuringField { buf.push_str("..."); name.to_string_from_buffer(buf, options, local); } - Self::Name(name, default_value, _) => { + Self::Name(name, default_value) => { name.to_string_from_buffer(buf, options, local); - U::optional_expression_to_string_from_buffer(default_value, buf, options, local); + if let Some(default_value) = default_value { + buf.push('='); + default_value.to_string_from_buffer(buf, options, local); + } } - Self::Map { from, name: variable_name, default_value, .. } => { - from.to_string_from_buffer(buf, options, local); - buf.push(':'); - variable_name.to_string_from_buffer(buf, options, local); - U::optional_expression_to_string_from_buffer(default_value, buf, options, local); + Self::Comment { content, is_multiline, position: _ } => { + if options.should_add_comment(*is_multiline && content.starts_with('*')) { + buf.push_str("/*"); + buf.push_str(content); + buf.push_str("*/"); + } } + Self::None => {} } } fn get_position(&self) -> &Span { - self.get() + match self { + ArrayDestructuringField::Comment { position, .. } + | ArrayDestructuringField::Spread(_, position) => position, + // TODO misses out optional expression + ArrayDestructuringField::Name(vf, _) => vf.get_position(), + ArrayDestructuringField::None => &source_map::Nullable::NULL, + } } } -/// TODO unsure about the positions here, is potential duplication if `T::OptionalExpression` is none -#[derive(Debug, Clone)] #[apply(derive_ASTNode)] -pub enum ArrayDestructuringField { +#[derive(Debug, Clone, PartialEqExtras, get_field_by_type::GetFieldByType, Eq)] +#[get_field_by_type_target(Span)] +#[partial_eq_ignore_types(Span)] +pub enum ObjectDestructuringField { + /// `{ x }` + Name(VariableIdentifier, Option>, Span), + /// `{ ...x }` Spread(VariableIdentifier, Span), - Name( - VariableField, - #[cfg_attr(target_family = "wasm", tsify(type = "Expression?"))] T::OptionalExpression, - ), - None, -} - -impl PartialEq for ArrayDestructuringField { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Spread(_, l0), Self::Spread(_, r0)) => l0 == r0, - (Self::Name(l0, l1), Self::Name(r0, r1)) => l0 == r0 && l1 == r1, - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } -} - -impl Eq for ArrayDestructuringField {} - -impl ListItem for WithComment> { - const EMPTY: Option = Some(WithComment::None(ArrayDestructuringField::None)); - - fn allow_comma_after(&self) -> bool { - !matches!(self.get_ast_ref(), ArrayDestructuringField::Spread(..)) - } + /// `{ x: y }` + Map { + from: PropertyKey, + name: WithComment, + default_value: Option>, + position: Span, + }, } -impl ASTNode for ArrayDestructuringField { +impl ASTNode for ObjectDestructuringField { fn from_reader( reader: &mut impl TokenReader, state: &mut crate::ParsingState, options: &ParseOptions, ) -> ParseResult { - if let TSXToken::Spread = reader.peek().ok_or_else(parse_lexing_error)?.0 { + if let Token(TSXToken::Spread, _) = reader.peek().ok_or_else(parse_lexing_error)? { let token = reader.next().unwrap(); - Ok(Self::Spread( - VariableIdentifier::from_reader(reader, state, options)?, - token.get_span(), - )) + let identifier = VariableIdentifier::from_reader(reader, state, options)?; + let position = token.get_span().union(identifier.get_position()); + Ok(Self::Spread(identifier, position)) } else { - let name = VariableField::::from_reader(reader, state, options)?; - let expression = U::optional_expression_from_reader(reader, state, options)?; - Ok(Self::Name(name, expression)) + let key = PropertyKey::from_reader(reader, state, options)?; + if let Some(Token(TSXToken::Colon, _)) = reader.peek() { + reader.next(); + let variable_name = + WithComment::::from_reader(reader, state, options)?; + + let default_value = reader + .conditional_next(|t| matches!(t, TSXToken::Assign)) + .is_some() + .then(|| Expression::from_reader(reader, state, options).map(Box::new)) + .transpose()?; + + let position = if let Some(ref dv) = default_value { + key.get_position().union(dv.get_position()) + } else { + *key.get_position() + }; + + Ok(Self::Map { from: key, name: variable_name, default_value, position }) + } else if let PropertyKey::Ident(name, key_pos, _) = key { + let default_value = reader + .conditional_next(|t| matches!(t, TSXToken::Assign)) + .is_some() + .then(|| Expression::from_reader(reader, state, options).map(Box::new)) + .transpose()?; + + let standard = VariableIdentifier::Standard(name, key_pos); + let position = if let Some(ref dv) = default_value { + key_pos.union(dv.get_position()) + } else { + key_pos + }; + Ok(Self::Name(standard, default_value, position)) + } else { + let token = reader.next().ok_or_else(parse_lexing_error)?; + throw_unexpected_token_with_token(token, &[TSXToken::Colon]) + } } } @@ -480,26 +329,32 @@ impl ASTNode for ArrayDestructuringField { buf.push_str("..."); name.to_string_from_buffer(buf, options, local); } - Self::Name(name, default_value) => { + Self::Name(name, default_value, _) => { name.to_string_from_buffer(buf, options, local); - U::optional_expression_to_string_from_buffer(default_value, buf, options, local); + if let Some(default_value) = default_value { + buf.push('='); + default_value.to_string_from_buffer(buf, options, local); + } + } + Self::Map { from, name: variable_name, default_value, .. } => { + from.to_string_from_buffer(buf, options, local); + buf.push(':'); + variable_name.to_string_from_buffer(buf, options, local); + if let Some(default_value) = default_value { + buf.push('='); + default_value.to_string_from_buffer(buf, options, local); + } } - Self::None => {} } } fn get_position(&self) -> &Span { - match self { - ArrayDestructuringField::Spread(_, pos) => pos, - // TODO misses out optional expression - ArrayDestructuringField::Name(vf, _) => vf.get_position(), - ArrayDestructuringField::None => &source_map::Nullable::NULL, - } + self.get() } } /// For object literals and things with computable or literal keys -impl Visitable for VariableField { +impl Visitable for VariableField { fn visit( &self, visitors: &mut (impl crate::VisitorReceiver + ?Sized), @@ -507,7 +362,6 @@ impl Visitable for VariableField { options: &VisitOptions, chain: &mut temporary_annex::Annex, ) { - // TODO map match self { VariableField::Name(id) => { if let VariableIdentifier::Standard(name, pos) = id { @@ -515,45 +369,12 @@ impl Visitable for VariableField { visitors.visit_variable(&item, data, chain); } } - VariableField::Array(array_destructuring_fields, _) => { - for field in array_destructuring_fields { - let field = field.get_ast_ref(); - let array_destructuring_member = - ImmutableVariableOrProperty::ArrayDestructuringMember(field); - visitors.visit_variable(&array_destructuring_member, data, chain); - match field { - // TODO should be okay, no nesting here - ArrayDestructuringField::Spread(..) | ArrayDestructuringField::None => {} - ArrayDestructuringField::Name(variable_field, expression) => { - variable_field.visit(visitors, data, options, chain); - expression.visit(visitors, data, options, chain); - } - } - } - } - VariableField::Object(object_destructuring_fields, _) => { - for field in object_destructuring_fields { - visitors.visit_variable( - &ImmutableVariableOrProperty::ObjectDestructuringMember(field), - data, - chain, - ); - match field.get_ast_ref() { - ObjectDestructuringField::Spread(_name, _) => {} - ObjectDestructuringField::Name(_name, default_value, _) => { - default_value.visit(visitors, data, options, chain); - } - ObjectDestructuringField::Map { - name: variable_name, - default_value, - .. - } => { - variable_name.visit(visitors, data, options, chain); - default_value.visit(visitors, data, options, chain); - } - } - } - } + VariableField::Array(array_destructuring_fields, _) => array_destructuring_fields + .iter() + .for_each(|f| f.visit(visitors, data, options, chain)), + VariableField::Object(object_destructuring_fields, _) => object_destructuring_fields + .iter() + .for_each(|f| f.visit(visitors, data, options, chain)), } } @@ -574,45 +395,108 @@ impl Visitable for VariableField { ); } } - VariableField::Array(array_destructuring_fields, _) => { - for field in array_destructuring_fields.iter_mut().map(WithComment::get_ast_mut) { - let mut array_destructuring_member = - MutableVariableOrProperty::ArrayDestructuringMember(field); - visitors.visit_variable_mut(&mut array_destructuring_member, data, chain); - match field { - ArrayDestructuringField::Spread(_, _id) => { - // TODO should be okay, no nesting here - } - ArrayDestructuringField::None => {} - ArrayDestructuringField::Name(variable_field, default_value) => { - variable_field.visit_mut(visitors, data, options, chain); - default_value.visit_mut(visitors, data, options, chain); - } - } - } + VariableField::Array(array_destructuring_fields, _) => array_destructuring_fields + .iter_mut() + .for_each(|f| f.visit_mut(visitors, data, options, chain)), + VariableField::Object(object_destructuring_fields, _) => object_destructuring_fields + .iter_mut() + .for_each(|f| f.visit_mut(visitors, data, options, chain)), + } + } +} + +impl Visitable for WithComment { + fn visit( + &self, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + let field = self.get_ast_ref(); + let array_destructuring_member = + ImmutableVariableOrProperty::ArrayDestructuringMember(field); + visitors.visit_variable(&array_destructuring_member, data, chain); + match field { + // TODO should be okay, no nesting here + ArrayDestructuringField::Comment { .. } + | ArrayDestructuringField::Spread(..) + | ArrayDestructuringField::None => {} + ArrayDestructuringField::Name(variable_field, expression) => { + variable_field.visit(visitors, data, options, chain); + expression.visit(visitors, data, options, chain); } - VariableField::Object(object_destructuring_fields, _) => { - for field in object_destructuring_fields.iter_mut() { - visitors.visit_variable_mut( - &mut MutableVariableOrProperty::ObjectDestructuringMember(field), - data, - chain, - ); - match field.get_ast_mut() { - ObjectDestructuringField::Spread(_id, _) => {} - ObjectDestructuringField::Name(_id, default_value, _) => { - default_value.visit_mut(visitors, data, options, chain); - } - ObjectDestructuringField::Map { - name: variable_name, - default_value, - .. - } => { - variable_name.visit_mut(visitors, data, options, chain); - default_value.visit_mut(visitors, data, options, chain); - } - } - } + } + } + + fn visit_mut( + &mut self, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + let mut array_destructuring_member = + MutableVariableOrProperty::ArrayDestructuringMember(self.get_ast_mut()); + visitors.visit_variable_mut(&mut array_destructuring_member, data, chain); + match self.get_ast_mut() { + ArrayDestructuringField::Spread(_, _id) => { + // TODO should be okay, no nesting here + } + ArrayDestructuringField::Comment { .. } | ArrayDestructuringField::None => {} + ArrayDestructuringField::Name(variable_field, default_value) => { + variable_field.visit_mut(visitors, data, options, chain); + default_value.visit_mut(visitors, data, options, chain); + } + } + } +} + +impl Visitable for WithComment { + fn visit( + &self, + visitors: &mut (impl crate::VisitorReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + visitors.visit_variable( + &ImmutableVariableOrProperty::ObjectDestructuringMember(self), + data, + chain, + ); + match self.get_ast_ref() { + ObjectDestructuringField::Spread(_name, _) => {} + ObjectDestructuringField::Name(_name, default_value, _) => { + default_value.visit(visitors, data, options, chain); + } + ObjectDestructuringField::Map { name: variable_name, default_value, .. } => { + variable_name.visit(visitors, data, options, chain); + default_value.visit(visitors, data, options, chain); + } + } + } + + fn visit_mut( + &mut self, + visitors: &mut (impl crate::VisitorMutReceiver + ?Sized), + data: &mut TData, + options: &VisitOptions, + chain: &mut temporary_annex::Annex, + ) { + visitors.visit_variable_mut( + &mut MutableVariableOrProperty::ObjectDestructuringMember(self), + data, + chain, + ); + match self.get_ast_mut() { + ObjectDestructuringField::Spread(_id, _) => {} + ObjectDestructuringField::Name(_id, default_value, _) => { + default_value.visit_mut(visitors, data, options, chain); + } + ObjectDestructuringField::Map { name: variable_name, default_value, .. } => { + variable_name.visit_mut(visitors, data, options, chain); + default_value.visit_mut(visitors, data, options, chain); } } } @@ -623,8 +507,6 @@ mod tests { use super::*; use crate::{assert_matches_ast, span}; - type VariableField = super::VariableField; - #[test] fn name() { assert_matches_ast!( @@ -700,10 +582,12 @@ mod tests { VariableField::Object( Deref @ [WithComment::None(ObjectDestructuringField::Name( VariableIdentifier::Standard(Deref @ "x", span!(2, 3)), - Some(Expression::NumberLiteral( - crate::NumberRepresentation::Number { .. }, - span!(6, 7), - )), + Some( + Deref @ Expression::NumberLiteral( + crate::NumberRepresentation::Number { .. }, + span!(6, 7), + ), + ), span!(2, 7), ))], span!(0, 9), diff --git a/parser/src/visiting.rs b/parser/src/visiting.rs index 3799d03f..98251e23 100644 --- a/parser/src/visiting.rs +++ b/parser/src/visiting.rs @@ -233,12 +233,13 @@ mod ast { std::path::PathBuf, source_map::Span, crate::TypeAnnotation, + crate::types::Visibility, crate::NumberRepresentation, - crate::operators::BinaryOperator, - crate::operators::BinaryAssignmentOperator, - crate::operators::UnaryOperator, - crate::operators::UnaryPrefixAssignmentOperator, - crate::operators::UnaryPostfixAssignmentOperator, + crate::expressions::operators::BinaryOperator, + crate::expressions::operators::BinaryAssignmentOperator, + crate::expressions::operators::UnaryOperator, + crate::expressions::operators::UnaryPrefixAssignmentOperator, + crate::expressions::operators::UnaryPostfixAssignmentOperator, crate::types::InterfaceDeclaration, crate::types::type_alias::TypeAlias, crate::types::declares::DeclareFunctionDeclaration, @@ -250,7 +251,8 @@ mod ast { crate::declarations::ImportLocation, crate::functions::FunctionHeader, crate::functions::MethodHeader, - crate::VariableKeyword + crate::VariableKeyword, + crate::types::namespace::Namespace ]; } @@ -258,7 +260,7 @@ mod ast { mod structures { use crate::{ property_key::{AlwaysPublic, PublicOrPrivate}, - Statement, VariableFieldInSourceCode, VariableIdentifier, + Statement, VariableIdentifier, }; use super::{ @@ -339,10 +341,8 @@ mod structures { // TODO maybe WithComment on some of these VariableFieldName(&'a str, &'a Span), // TODO these should maybe only be the spread variables - ArrayDestructuringMember(&'a ArrayDestructuringField), - ObjectDestructuringMember( - &'a WithComment>, - ), + ArrayDestructuringMember(&'a ArrayDestructuringField), + ObjectDestructuringMember(&'a WithComment), ClassName(Option<&'a VariableIdentifier>), FunctionName(Option<&'a VariableIdentifier>), ClassPropertyKey(&'a PropertyKey), @@ -353,10 +353,8 @@ mod structures { pub enum MutableVariableOrProperty<'a> { VariableFieldName(&'a mut String), // TODO these should maybe only be the spread variables - ArrayDestructuringMember(&'a mut ArrayDestructuringField), - ObjectDestructuringMember( - &'a mut WithComment>, - ), + ArrayDestructuringMember(&'a mut ArrayDestructuringField), + ObjectDestructuringMember(&'a mut WithComment), ClassName(Option<&'a mut VariableIdentifier>), FunctionName(Option<&'a mut VariableIdentifier>), ClassPropertyKey(&'a mut PropertyKey), diff --git a/parser/tests/comments.rs b/parser/tests/comments.rs new file mode 100644 index 00000000..e90400b6 --- /dev/null +++ b/parser/tests/comments.rs @@ -0,0 +1,14 @@ +use ezno_parser::{ASTNode, Module}; + +#[test] +fn random_comments() { + let input = r#" + const [,,/* hi */] = []; +"# + .trim_start() + .replace(" ", "\t"); + + let _module = Module::from_string(input.clone(), Default::default()).unwrap(); + // let output = module.to_string(&ToStringOptions::typescript()); + // pretty_assertions::assert_eq!(output, input); +} diff --git a/parser/tests/expressions.rs b/parser/tests/expressions.rs index cc8a40c5..4f303295 100644 --- a/parser/tests/expressions.rs +++ b/parser/tests/expressions.rs @@ -35,7 +35,8 @@ y.t<4, 2>(3); y.t<4, Array<5>>(3); a(y<2>(4)); a.a?.(y<2>(4)); -a.a(...expr, y) +a.a(...expr, y); +a.a(...expr, y, /* something */) " .trim(); @@ -65,3 +66,70 @@ const c = { async e() { let output = module.to_string(&ezno_parser::ToStringOptions::typescript()); assert_eq!(output, input); } + +#[test] +fn arrays() { + let input = r" +const a = [{ a: 5 }]; +const b = [...x, 7]; +const c = [/* hi */, 6] + " + .trim(); + + let module = Module::from_string(input.to_owned(), Default::default()).unwrap(); + + eprintln!("Module: {module:#?}"); + + let output = module.to_string(&ezno_parser::ToStringOptions::typescript()); + assert_eq!(output, input); +} + +#[test] +fn regular_expressions() { + let input = r" +const a = /something/; +const b = /with global flag/g; +const c = /escaped \//; +const d = /in a set[=/]/ + " + .trim(); + + let module = Module::from_string(input.to_owned(), Default::default()).unwrap(); + + eprintln!("Module: {module:#?}"); + + let output = module.to_string(&ezno_parser::ToStringOptions::typescript()); + assert_eq!(output, input); +} + +#[cfg(feature = "extras")] +#[test] +fn jsx() { + // note the parser supports self closing tags with `` and HTML comments + let input = r#" +function Component(item) { + return
+

{item.heading}

+ + +

+ Something {item.content} +

+ {/* hi */} + +
+} + "# + .trim(); + + let module = Module::from_string(input.to_owned(), Default::default()).unwrap(); + + eprintln!("Module: {module:#?}"); + + let output = module.to_string(&ezno_parser::ToStringOptions::typescript()); + + eprintln!("{input:?}"); + eprintln!("{output:?}"); + + assert_eq!(output, input); +} diff --git a/parser/tests/statements.rs b/parser/tests/statements_and_declarations.rs similarity index 95% rename from parser/tests/statements.rs rename to parser/tests/statements_and_declarations.rs index 5aa6fa2d..8398e99f 100644 --- a/parser/tests/statements.rs +++ b/parser/tests/statements_and_declarations.rs @@ -27,9 +27,6 @@ try { doThing() } catch (e) { console.error(e) -} -interface X { - a: string }"# .trim_start() .replace(" ", "\t"); @@ -193,11 +190,23 @@ export type { name1, /* …, */ nameN } from "module-name";"# let _module = Module::from_string(input.to_owned(), Default::default()).unwrap(); - // TODO + // TODO doesn't work because of comments // let output = module.to_string(&ToStringOptions::typescript()); // assert_eq!(output, input); } +#[test] +fn import_attributes() { + let input = r#" +import { export1 } from "module-name" with { something: x }; + "# + .trim(); + + let module = Module::from_string(input.to_owned(), Default::default()).unwrap(); + + eprintln!("Module: {module:#?}"); +} + #[cfg(feature = "extras")] #[test] fn reversed_imports() { diff --git a/parser/tests/type_annotations.rs b/parser/tests/type_annotations.rs index c2173748..741a3373 100644 --- a/parser/tests/type_annotations.rs +++ b/parser/tests/type_annotations.rs @@ -2,12 +2,15 @@ use ezno_parser::{ASTNode, Module, ParseOptions, ToStringOptions}; use pretty_assertions::assert_eq; #[test] -fn random_statements() { +fn statements() { let input = r#" interface X {} -type Y = 2"# - .trim_start() - .replace(" ", "\t"); +interface Y extends number, Z2 {} +type Z = 2; +type Z2 = T +"# + .trim() + .to_owned(); let module = Module::from_string(input.clone(), Default::default()).unwrap(); let output = module.to_string(&ToStringOptions::typescript()); @@ -43,14 +46,50 @@ function y(a: string): string { .is_err()); } +// `satisfies` is actually not under `feature="full-typescript"` #[test] +#[cfg(feature = "full-typescript")] fn expression_level_expressions() { // 👎👎👎 let input = r#" (a satisfies number); -(b as number)"# - .trim_start() - .replace(" ", "\t"); +(b as number); +({ 1: 2 } as const); +(a! + 2) +"# + .trim() + .replace(" ", "\t"); + + let module = Module::from_string(input.clone(), Default::default()).unwrap(); + let output = module.to_string(&ToStringOptions::typescript()); + + assert_eq!(output, input.clone()); + + assert!(Module::from_string( + input, + ParseOptions { type_annotations: false, ..Default::default() } + ) + .is_err()); +} + +#[test] +#[cfg(feature = "full-typescript")] +fn function_and_method_overloading() { + // use generics and redesign your bad APIs sheeple + let input = r#" +function makeDate(timestamp: number): Date +function makeDate(m: number, d: number, y: number): Date +function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {} +class X { + constructor(a: string) + constructor(a: number) {} + makeDate(timestamp: number): Date + makeDate(m: number, d: number, y: number): Date + makeDate(mOrTimestamp: number, d?: number, y?: number): Date {} +} +"# + .trim() + .replace(" ", "\t"); let module = Module::from_string(input.clone(), Default::default()).unwrap(); let output = module.to_string(&ToStringOptions::typescript()); @@ -63,3 +102,19 @@ fn expression_level_expressions() { ) .is_err()); } + +#[test] +fn type_definition_module() { + let input = r#" +export default function (a: string): string +"# + .trim() + .replace(" ", "\t"); + + let parse_options = ParseOptions { type_definition_module: true, ..Default::default() }; + + let module = Module::from_string(input.clone(), parse_options).unwrap(); + let output = module.to_string(&ToStringOptions::typescript()); + + assert_eq!(output, input.clone()); +} diff --git a/parser/visitable-derive/Cargo.toml b/parser/visitable-derive/Cargo.toml index 0293fc25..659f39c8 100644 --- a/parser/visitable-derive/Cargo.toml +++ b/parser/visitable-derive/Cargo.toml @@ -14,5 +14,5 @@ path = "macro.rs" proc-macro = true [dependencies] -syn-helpers = "0.4.2" +syn-helpers = "0.5" string-cases = "0.2" diff --git a/parser/visitable-derive/macro.rs b/parser/visitable-derive/macro.rs index 22d4503a..76abde24 100644 --- a/parser/visitable-derive/macro.rs +++ b/parser/visitable-derive/macro.rs @@ -3,9 +3,12 @@ use std::error::Error; use string_cases::StringCasesExt; use syn_helpers::{ derive_trait, - proc_macro2::{Ident, Span, TokenTree}, + proc_macro2::{Ident, Span}, quote, - syn::{parse_macro_input, parse_quote, DeriveInput, Stmt, __private::quote::format_ident}, + syn::{ + self, parse_macro_input, parse_quote, DeriveInput, Stmt, __private::quote::format_ident, + parse::Parse, + }, Constructable, FieldMut, HasAttributes, NamedOrUnnamedFieldMut, Trait, TraitItem, }; @@ -72,13 +75,33 @@ fn generated_visit_item( ) -> Result, Box> { let attributes = item.structure.get_attributes(); - let visit_self = attributes - .iter() - .find_map(|attr| attr.path.is_ident(VISIT_SELF_NAME).then_some(&attr.tokens)); + let visit_self = attributes.iter().find_map(|attr| { + attr.path().is_ident(VISIT_SELF_NAME).then_some({ + let mut ident = None::; + let res = attr.parse_nested_meta(|meta| { + if meta.path.is_ident("under") { + let value = meta.value()?; + ident = value.parse()?; + Ok(()) + } else { + Err(meta.error("expected `under=...`")) + } + }); + if res.is_ok() { + Some(ident.unwrap()) + } else { + // TODO + None + } + }) + }); - let visit_with_chain = attributes + let visit_with_chain: Option = attributes .iter() - .find_map(|attr| attr.path.is_ident(VISIT_WITH_CHAIN_NAME).then_some(&attr.tokens)); + .find_map(|attr| { + attr.path().is_ident(VISIT_WITH_CHAIN_NAME).then_some(attr.parse_args().ok()) + }) + .flatten(); let mut lines = Vec::new(); @@ -86,22 +109,10 @@ fn generated_visit_item( lines.push(parse_quote!( let mut chain = &mut chain.push_annex(#expr_tokens); )) } - if let Some(tokens) = visit_self.cloned() { - let mut under = None; - if let Some(TokenTree::Group(g)) = tokens.into_iter().next() { - let mut tokens = g.stream().into_iter(); - if let Some(TokenTree::Ident(ident)) = tokens.next() { - if ident == "under" { - if let Some(TokenTree::Ident(literal)) = tokens.next() { - under = Some(literal.to_string()); - } - } - } - // TODO error - } - + if let Some(under) = visit_self { let mut_postfix = matches!(visit_type, VisitType::Mutable).then_some("_mut").unwrap_or_default(); + if let Some(under) = under { let func_name = format_ident!("visit_{}{}", under, mut_postfix); lines.push(parse_quote!(visitors.#func_name(self.into(), data, chain); )) @@ -120,7 +131,7 @@ fn generated_visit_item( let attributes = field.get_attributes(); let skip_field_attr = - attributes.iter().find(|attr| attr.path.is_ident(VISIT_SKIP_NAME)); + attributes.iter().find(|attr| attr.path().is_ident(VISIT_SKIP_NAME)); // TODO maybe? // // None == unconditional @@ -128,7 +139,11 @@ fn generated_visit_item( // skip_field_attr.as_ref().map(|attr| attr.bracket_token); let visit_with_chain = attributes.iter().find_map(|attr| { - attr.path.is_ident(VISIT_WITH_CHAIN_NAME).then_some(&attr.tokens) + // TODO error rather than flatten + attr.path() + .is_ident(VISIT_WITH_CHAIN_NAME) + .then_some(attr.parse_args_with(syn::Expr::parse).ok()) + .flatten() }); let chain = if let Some(expr_tokens) = visit_with_chain { diff --git a/src/transformers/optimisations.rs b/src/transformers/optimisations.rs index a5207131..c96da703 100644 --- a/src/transformers/optimisations.rs +++ b/src/transformers/optimisations.rs @@ -112,6 +112,7 @@ impl VisitorMut, CheckingOutputWithoutDiagnostics> for Statemen | parser::Declaration::DeclareVariable(_) | parser::Declaration::DeclareFunction(_) | parser::Declaration::DeclareInterface(_) + | parser::Declaration::Namespace(_) | parser::Declaration::Export(_) => {} } }