diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 041b0e4e4..000000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -data/*.tar.gz filter=lfs diff=lfs merge=lfs -text -data/*.tgz filter=lfs diff=lfs merge=lfs -text -data/*.zip filter=lfs diff=lfs merge=lfs -text diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c010ba8c1..a4042b8bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @github/semantic-code +* @github/semantic-code @github/blackbird diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c561bd429..e8bb697e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,5 @@ name: Continuous integration + on: push: branches: [main] @@ -6,6 +7,9 @@ on: schedule: - cron: "0 0 1,15 * *" +permissions: + contents: read + # In the event that there is a new push to the ref, cancel any running jobs because there are now obsolete, and wasting resources. concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -17,18 +21,23 @@ jobs: strategy: matrix: rust: [stable] + env: + # cargo hack does not use the default-members in Cargo.toml, so we restrict to those explicitly + CARGO_HACK: cargo hack -p lsp-positions -p stack-graphs -p tree-sitter-stack-graphs --feature-powerset --exclude-features copious-debugging steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 with: rust-version: ${{ matrix.rust }} + - name: Install cargo-hack + run: cargo install cargo-hack - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check formatting run: cargo fmt --all -- --check - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo @@ -36,43 +45,56 @@ jobs: key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.OS }}-cargo- - - name: Install cargo-valgrind + - name: Build library (all feature combinations) + run: ${{ env.CARGO_HACK }} --no-dev-deps build + - name: Run test suite (all feature combinations) + run: ${{ env.CARGO_HACK }} test + - name: Run test suite with all optimizations (default features) + run: cargo test --release + - name: Install valgrind run: | sudo apt-get update sudo apt-get install -y valgrind - cargo install cargo-valgrind - - name: Build library - run: cargo build + - name: Run test suite under valgrind (default features) + id: valgrind + # We only need to use valgrind to test the crates that have C bindings. + run: script/ci-test-valgrind -p stack-graphs + - name: Upload valgrind log + if: ${{ failure() && steps.valgrind.outcome == 'failure' }} + uses: actions/upload-artifact@v4 + with: + name: valgrind logs + path: ${{ runner.temp }}/valgrind.log - name: Ensure C headers are up to date run: | script/cbindgen test -z "$(git status --porcelain)" - - name: Run test suite - run: cargo test - - name: Run test suite under valgrind - # We only need to use valgrind to test the crates that have C bindings. - run: cargo valgrind test -p stack-graphs - - name: Run lsp-positions tests without tree-sitter - run: cargo test -p lsp-positions --no-default-features - - name: Run test suite with all features enabled - run: cargo test --all-features - - name: Run test suite with all optimizations - run: cargo test --release - # Do the new project test last, because it adds the crate in the current source - # folder, and that shouldn't influence other tests. + + test-init: + needs: [test-rust] + runs-on: ubuntu-latest + strategy: + matrix: + rust: [stable] + + steps: + - name: Install Rust environment + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 + with: + rust-version: ${{ matrix.rust }} + - name: Checkout code + uses: actions/checkout@v4 + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo + target + key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.OS }}-cargo- - name: Generate, build, and run new language project - run: | - cargo run --bin tree-sitter-stack-graphs --features cli -- init \ - --language-name InitTest \ - --language-id init_test \ - --language-file-extension it \ - --grammar-crate-name tree-sitter-python \ - --grammar-crate-version 0.20.0 \ - --internal \ - --non-interactive - cargo check -p tree-sitter-stack-graphs-init_test --all-features - cargo test -p tree-sitter-stack-graphs-init_test - cargo run -p tree-sitter-stack-graphs-init_test --features cli -- help + run: script/ci-test-init list-languages: runs-on: ubuntu-latest @@ -81,7 +103,7 @@ jobs: working-directory: languages steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: List languages id: language-list run: echo "languages=$(find -mindepth 1 -maxdepth 1 -type d -printf '%P\n' | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT @@ -98,11 +120,13 @@ jobs: steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 with: rust-version: ${{ matrix.rust }} + - name: Install cargo-hack + run: cargo install cargo-hack - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo @@ -111,11 +135,11 @@ jobs: restore-keys: | ${{ runner.OS }}-cargo- - name: Checkout code - uses: actions/checkout@v3 - - name: Build - run: cargo build -p ${{ matrix.language }} - - name: Test - run: cargo test -p ${{ matrix.language }} + uses: actions/checkout@v4 + - name: Build (all feature combinations) + run: cargo hack -p ${{ matrix.language }} --feature-powerset build + - name: Test (all features) + run: cargo test -p ${{ matrix.language }} --all-features test-cli: runs-on: ubuntu-latest @@ -128,11 +152,11 @@ jobs: steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 with: rust-version: ${{ matrix.rust }} - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo @@ -141,7 +165,7 @@ jobs: restore-keys: | ${{ runner.OS }}-cargo- - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: lfs: true - name: Build diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index 5c4288c84..d68ffa75d 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -1,9 +1,14 @@ name: Performance testing + on: pull_request: paths: - 'stack-graphs/**' +permissions: + contents: read + pull-requests: write + # In the event that there is a new push to the ref, cancel any running jobs because there are now obsolete, and wasting resources. concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -37,7 +42,7 @@ jobs: done: ${{ steps.done.outputs.cache-hit }} steps: - name: "Checkout base code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ env.BASE_REPO }} ref: ${{ env.BASE_SHA }} @@ -49,7 +54,7 @@ jobs: printf 'BASE_SHA=%s\n' "$(git rev-list -1 ${{ env.BASE_SHA }} -- stack-graphs)" >> $GITHUB_ENV working-directory: ${{ env.BASE_DIR }} - name: "Checkout head code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ env.HEAD_REPO }} ref: ${{ env.HEAD_SHA }} @@ -62,7 +67,7 @@ jobs: working-directory: ${{ env.HEAD_DIR }} - name: "Check cached status" id: done - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: done key: ${{ runner.os }}-perf-tested-${{ env.BASE_REPO }}@${{ env.BASE_SHA }}-${{ env.HEAD_REPO }}@${{ env.HEAD_SHA }}-${{ env.TEST_NAME }} @@ -79,11 +84,11 @@ jobs: BASE_SHA: ${{ needs.changes.outputs.base-sha }} steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 with: rust-version: stable - name: Cache Rust dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo @@ -94,7 +99,7 @@ jobs: sudo apt-get install -y valgrind - name: "Cache base result" id: cache-base-result - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ${{ env.MASSIF_OUT }} @@ -102,7 +107,7 @@ jobs: key: ${{ runner.os }}-perf-result-${{ env.BASE_REPO }}@${{ env.BASE_SHA }}-${{ env.TEST_NAME }} - name: "Checkout base code" if: steps.cache-base-result.outputs.cache-hit != 'true' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ env.BASE_REPO }} ref: ${{ env.BASE_SHA }} @@ -130,7 +135,7 @@ jobs: ${{ env.BASE_DIR }}/data/${{ env.TEST_NAME }} ms_print ${{ env.MASSIF_OUT }} > ${{ env.MASSIF_REPORT }} - name: Upload results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.BASE_ARTIFACT }} path: | @@ -148,11 +153,11 @@ jobs: HEAD_SHA: ${{ needs.changes.outputs.head-sha }} steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 with: rust-version: stable - name: Cache Rust dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo @@ -163,7 +168,7 @@ jobs: sudo apt-get install -y valgrind - name: "Cache head result" id: cache-head-result - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ${{ env.MASSIF_OUT }} @@ -171,7 +176,7 @@ jobs: key: ${{ runner.os }}-perf-result-${{ env.HEAD_REPO }}@${{ env.HEAD_SHA }}-${{ env.TEST_NAME }} - name: "Checkout head code" if: steps.cache-head-result.outputs.cache-hit != 'true' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ env.HEAD_REPO }} ref: ${{ env.HEAD_SHA }} @@ -199,7 +204,7 @@ jobs: ${{ env.HEAD_DIR }}/data/${{ env.TEST_NAME }} ms_print ${{ env.MASSIF_OUT }} > ${{ env.MASSIF_REPORT }} - name: Upload results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.HEAD_ARTIFACT }} path: | @@ -233,12 +238,12 @@ jobs: # Download results # - name: Download base results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ env.BASE_ARTIFACT }} path: ${{ env.BASE_ARTIFACT }} - name: Download head results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ env.HEAD_ARTIFACT }} path: ${{ env.HEAD_ARTIFACT }} @@ -246,7 +251,7 @@ jobs: # Create report # - name: "Checkout code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: ${{ env.SRC_DIR }} - name: Generate summary @@ -268,7 +273,7 @@ jobs: - name: Create status marker run: touch done - name: "Cache status" - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: done key: ${{ runner.os }}-perf-tested-${{ env.BASE_REPO }}@${{ env.BASE_SHA }}-${{ env.HEAD_REPO }}@${{ env.HEAD_SHA }}-${{ env.TEST_NAME }} diff --git a/.github/workflows/publish-lsp-positions.yml b/.github/workflows/publish-lsp-positions.yml index 210662a00..2ca05a133 100644 --- a/.github/workflows/publish-lsp-positions.yml +++ b/.github/workflows/publish-lsp-positions.yml @@ -5,6 +5,9 @@ on: tags: - lsp-positions-v* +permissions: + contents: write + jobs: publish-crate: runs-on: ubuntu-latest @@ -14,9 +17,9 @@ jobs: CRATE_DIR: './lsp-positions' steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # TODO Verify the crate version matches the tag - name: Test crate run: cargo test --all-features @@ -36,9 +39,9 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create GitHub release - uses: ncipollo/release-action@v1 + uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1 with: body: | Find more info on all releases at https://crates.io/crates/lsp-positions. diff --git a/.github/workflows/publish-stack-graphs.yml b/.github/workflows/publish-stack-graphs.yml index 641a93488..29b21020b 100644 --- a/.github/workflows/publish-stack-graphs.yml +++ b/.github/workflows/publish-stack-graphs.yml @@ -5,6 +5,9 @@ on: tags: - stack-graphs-v* +permissions: + contents: write + jobs: publish-crate: runs-on: ubuntu-latest @@ -14,9 +17,9 @@ jobs: CRATE_DIR: './stack-graphs' steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # TODO Verify the crate version matches the tag - name: Test crate run: cargo test --all-features @@ -36,9 +39,9 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create GitHub release - uses: ncipollo/release-action@v1 + uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1 with: body: | Find more info on all releases at https://crates.io/crates/stack-graphs. diff --git a/.github/workflows/publish-tree-sitter-stack-graphs-java.yml b/.github/workflows/publish-tree-sitter-stack-graphs-java.yml index 6d7afb769..40878de95 100644 --- a/.github/workflows/publish-tree-sitter-stack-graphs-java.yml +++ b/.github/workflows/publish-tree-sitter-stack-graphs-java.yml @@ -5,6 +5,9 @@ on: tags: - tree-sitter-stack-graphs-java-v* +permissions: + contents: write + jobs: publish-crate: runs-on: ubuntu-latest @@ -14,9 +17,9 @@ jobs: CRATE_DIR: './languages/tree-sitter-stack-graphs-java' steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # TODO Verify the crate version matches the tag - name: Test crate run: cargo test --all-features @@ -36,9 +39,9 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create GitHub release - uses: ncipollo/release-action@v1 + uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1 with: body: | Find more info on all releases at https://crates.io/crates/tree-sitter-stack-graphs-java. diff --git a/.github/workflows/publish-tree-sitter-stack-graphs-javascript.yml b/.github/workflows/publish-tree-sitter-stack-graphs-javascript.yml new file mode 100644 index 000000000..b9fbceec3 --- /dev/null +++ b/.github/workflows/publish-tree-sitter-stack-graphs-javascript.yml @@ -0,0 +1,48 @@ +name: Publish tree-sitter-stack-graphs-javascript release + +on: + push: + tags: + - tree-sitter-stack-graphs-javascript-v* + +permissions: + contents: write + +jobs: + publish-crate: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + CRATE_DIR: './languages/tree-sitter-stack-graphs-javascript' + steps: + - name: Install Rust environment + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 + - name: Checkout repository + uses: actions/checkout@v4 + # TODO Verify the crate version matches the tag + - name: Test crate + run: cargo test --all-features + working-directory: ${{ env.CRATE_DIR }} + - name: Verify publish crate + run: cargo publish --dry-run + working-directory: ${{ env.CRATE_DIR }} + - name: Publish crate + run: cargo publish + working-directory: ${{ env.CRATE_DIR }} + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + create-release: + needs: publish-crate + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create GitHub release + uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1 + with: + body: | + Find more info on all releases at https://crates.io/crates/tree-sitter-stack-graphs-javascript. + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-tree-sitter-stack-graphs-python.yml b/.github/workflows/publish-tree-sitter-stack-graphs-python.yml new file mode 100644 index 000000000..dc90a6974 --- /dev/null +++ b/.github/workflows/publish-tree-sitter-stack-graphs-python.yml @@ -0,0 +1,48 @@ +name: Publish tree-sitter-stack-graphs-python release + +on: + push: + tags: + - tree-sitter-stack-graphs-python-v* + +permissions: + contents: write + +jobs: + publish-crate: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + CRATE_DIR: './languages/tree-sitter-stack-graphs-python' + steps: + - name: Install Rust environment + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 + - name: Checkout repository + uses: actions/checkout@v4 + # TODO Verify the crate version matches the tag + - name: Test crate + run: cargo test --all-features + working-directory: ${{ env.CRATE_DIR }} + - name: Verify publish crate + run: cargo publish --dry-run + working-directory: ${{ env.CRATE_DIR }} + - name: Publish crate + run: cargo publish + working-directory: ${{ env.CRATE_DIR }} + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + create-release: + needs: publish-crate + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Create GitHub release + uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1 + with: + body: | + Find more info on all releases at https://crates.io/crates/tree-sitter-stack-graphs-python. + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-tree-sitter-stack-graphs-typescript.yml b/.github/workflows/publish-tree-sitter-stack-graphs-typescript.yml index a3409f397..9eb696e2a 100644 --- a/.github/workflows/publish-tree-sitter-stack-graphs-typescript.yml +++ b/.github/workflows/publish-tree-sitter-stack-graphs-typescript.yml @@ -5,6 +5,9 @@ on: tags: - tree-sitter-stack-graphs-typescript-v* +permissions: + contents: write + jobs: publish-crate: runs-on: ubuntu-latest @@ -14,9 +17,9 @@ jobs: CRATE_DIR: './languages/tree-sitter-stack-graphs-typescript' steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # TODO Verify the crate version matches the tag - name: Test crate run: cargo test --all-features @@ -36,9 +39,9 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create GitHub release - uses: ncipollo/release-action@v1 + uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1 with: body: | Find more info on all releases at https://crates.io/crates/tree-sitter-stack-graphs-typescript. diff --git a/.github/workflows/publish-tree-sitter-stack-graphs.yml b/.github/workflows/publish-tree-sitter-stack-graphs.yml index e9395ab1f..0bf56c143 100644 --- a/.github/workflows/publish-tree-sitter-stack-graphs.yml +++ b/.github/workflows/publish-tree-sitter-stack-graphs.yml @@ -5,6 +5,9 @@ on: tags: - tree-sitter-stack-graphs-v* +permissions: + contents: write + jobs: publish-crate: runs-on: ubuntu-latest @@ -14,9 +17,9 @@ jobs: CRATE_DIR: './tree-sitter-stack-graphs' steps: - name: Install Rust environment - uses: hecrj/setup-rust-action@v1 + uses: hecrj/setup-rust-action@110f36749599534ca96628b82f52ae67e5d95a3c # v2 - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # TODO Verify the crate version matches the tag - name: Test crate run: cargo test --all-features @@ -29,31 +32,6 @@ jobs: working-directory: ${{ env.CRATE_DIR }} env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - publish-npm: - needs: publish-crate - runs-on: ubuntu-latest - env: - PACKAGE_DIR: './tree-sitter-stack-graphs/npm' - steps: - - name: Install Node environment - uses: actions/setup-node@v3 - with: - node-version: 16.x - registry-url: 'https://registry.npmjs.org' - - name: Checkout repository - uses: actions/checkout@v3 - # TODO Verify the package version matches the tag - - name: Install dependencies - run: npm install - working-directory: ${{ env.PACKAGE_DIR }} - - name: Verify package - run: npm publish --dry-run - working-directory: ${{ env.PACKAGE_DIR }} - - name: Publish package - run: npm publish - working-directory: ${{ env.PACKAGE_DIR }} - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} create-release: needs: publish-crate runs-on: ubuntu-latest @@ -61,9 +39,9 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create GitHub release - uses: ncipollo/release-action@v1 + uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1 with: body: | Find more info on all releases at https://crates.io/crates/tree-sitter-stack-graphs. diff --git a/.gitignore b/.gitignore index c76d54cb8..9b72fd034 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ Cargo.lock /.crates.toml /.crates2.json /bin/ + +# MacOS stuff +.DS_Store \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1992fceca..3827e3473 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,8 +32,8 @@ Here are a few things you can do that will increase the likelihood of your pull If you are one of the maintainers of this package, bump the version numbers in [`Cargo.toml`](Cargo.toml) and [`README.md`](README.md), then follow the typical instructions to publish a new version to [crates.io][]: ``` -$ cargo package -$ cargo publish +cargo package +cargo publish ``` [crates.io]: https://crates.io/stack-graphs/ @@ -43,4 +43,3 @@ $ cargo publish - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) - [GitHub Help](https://help.github.com) - diff --git a/Cargo.toml b/Cargo.toml index e4469123a..587e70d61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "1" members = [ # library projects "lsp-positions", diff --git a/README.md b/README.md index df51b81c6..6ae144656 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +**NOTE:** _This repository is no longer supported or updated by GitHub. If you wish to continue to develop this code yourself, we recommend you fork it._ + # Stack graphs The crates in this repository provide a Rust implementation of _stack graphs_, diff --git a/data/typescript_benchmark.zip b/data/typescript_benchmark.zip index 97f24b96d..9cf6105ad 100644 Binary files a/data/typescript_benchmark.zip and b/data/typescript_benchmark.zip differ diff --git a/data/typescript_minimal_project.zip b/data/typescript_minimal_project.zip index ada17bfe7..924d57bff 100644 Binary files a/data/typescript_minimal_project.zip and b/data/typescript_minimal_project.zip differ diff --git a/languages/tree-sitter-stack-graphs-java/CHANGELOG.md b/languages/tree-sitter-stack-graphs-java/CHANGELOG.md index f7483cabc..d295d8ce0 100644 --- a/languages/tree-sitter-stack-graphs-java/CHANGELOG.md +++ b/languages/tree-sitter-stack-graphs-java/CHANGELOG.md @@ -5,9 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## v0.5.0 -- 2024-12-12 -## [0.2.0] - 2023-03-21 +- The `tree-sitter-stack-graphs` dependency is updated to version 0.10. + +- The `tree-sitter-java` dependency is updated to version 0.23.4. + +## v0.4.0 -- 2024-07-09 + +### Added + +- Add rules for the `condition` node that was missing. + +### Removed + +- The `FILE_PATH_VAR` constant has been replaced in favor of `tree_sitter_stack_graphs::FILE_PATH_VAR`. + +## v0.3.0 -- 2024-03-06 + +The `tree-sitter-stack-graphs` is updated to `v0.8`. + +### Changed + +- The `cli` feature is now required to install the CLI. + +## v0.2.0 -- 2023-03-21 ### Added diff --git a/languages/tree-sitter-stack-graphs-java/Cargo.toml b/languages/tree-sitter-stack-graphs-java/Cargo.toml index 814447be8..0ad8cb687 100644 --- a/languages/tree-sitter-stack-graphs-java/Cargo.toml +++ b/languages/tree-sitter-stack-graphs-java/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tree-sitter-stack-graphs-java" -version = "0.2.0" +version = "0.5.0" description = "Stack graphs for the Java programming language" homepage = "https://github.com/github/stack-graphs/tree/main/languages/tree-sitter-stack-graphs-java" @@ -23,6 +23,7 @@ keywords = ["tree-sitter", "stack-graphs", "java"] [[bin]] name = "tree-sitter-stack-graphs-java" path = "rust/bin.rs" +required-features = ["cli"] [lib] path = "rust/lib.rs" @@ -33,8 +34,15 @@ name = "test" path = "rust/test.rs" harness = false # need to provide own main function to handle running tests +[features] +cli = ["anyhow", "clap", "tree-sitter-stack-graphs/cli"] + [dependencies] -anyhow = "1.0" -clap = { version = "4", features = ["derive"] } -tree-sitter-stack-graphs = { version = "0.7", path = "../../tree-sitter-stack-graphs", features=["cli"] } -tree-sitter-java = { version = "~0.20.0" } +anyhow = { version = "1.0", optional = true } +clap = { version = "4", features = ["derive"], optional = true } +tree-sitter-java = { version = "=0.23.4" } +tree-sitter-stack-graphs = { version = "0.10", path = "../../tree-sitter-stack-graphs" } # explicit version is required to be able to publish crate + +[dev-dependencies] +anyhow = { version = "1.0" } +tree-sitter-stack-graphs = { path = "../../tree-sitter-stack-graphs", features = ["cli"] } diff --git a/languages/tree-sitter-stack-graphs-java/README.md b/languages/tree-sitter-stack-graphs-java/README.md index 821796b67..97820ed3f 100644 --- a/languages/tree-sitter-stack-graphs-java/README.md +++ b/languages/tree-sitter-stack-graphs-java/README.md @@ -1,30 +1,138 @@ # tree-sitter-stack-graphs definition for Java -This project defines tree-sitter-stack-graphs rules for Java using the [tree-sitter-java](https://www.npmjs.com/package/tree-sitter-java) grammar. +This project defines tree-sitter-stack-graphs rules for Java using the [tree-sitter-java][] grammar. -## Local Development +[tree-sitter-java]: https://crates.io/crates/tree-sitter-java + +- [API documentation](https://docs.rs/tree-sitter-stack-graphs-java/) +- [Release notes](https://github.com/github/stack-graphs/blob/main/languages/tree-sitter-stack-graphs-java/CHANGELOG.md) + +## Using the API + +To use this library, add the following to your `Cargo.toml`: + +```toml +[dependencies] +tree-sitter-stack-graphs-java = "0.5" +``` + +Check out our [documentation](https://docs.rs/tree-sitter-stack-graphs-java/*/) for more details on how to use this library. + +## Using the Command-line Program + +The command-line program for `tree-sitter-stack-graphs-java` lets you do stack graph based analysis and lookup from the command line. + +The CLI can be run as follows: + +1. _(Installed)_ Install the CLI using Cargo as follows: + + ```sh + cargo install --features cli tree-sitter-stack-graphs-java + ``` + + After this, the CLI should be available as `tree-sitter-stack-graphs-java`. + +2. _(From source)_ Instead of installing the CLI, it can also be run directly from the crate directory, as a replacement for a `tree-sitter-stack-graphs-java` invocation, as follows: + + ```sh + cargo run --features cli -- + ``` + +The basic CLI workflow for the command-line program is to index source code and issue queries against the resulting database: + +1. Index a source folder as follows: + + ```sh + tree-sitter-stack-graphs-java index SOURCE_DIR + ``` + + _Indexing will skip any files that have already be indexed. To force a re-index, add the `-f` flag._ + + To check the status if a source folder, run: + + ```sh + tree-sitter-stack-graphs-java status SOURCE_DIR + ``` + + To clean the database and start with a clean slate, run: + + ```sh + tree-sitter-stack-graphs-java clean + ``` + + _Pass the `--delete` flag to not just empty the database, but also delete it. This is useful to resolve `unsupported database version` errors that may occur after a version update._ + +2. Run a query to find the definition(s) for a reference on a given line and column, run: + + ```sh + tree-sitter-stack-graphs-java query definition SOURCE_PATH:LINE:COLUMN + ``` + + Resulting definitions are printed, including a source line if the source file is available. + +Discover all available commands and flags by passing the `-h` flag to the CLI directly, or to any of the subcommands. + +## Development + +The project is written in Rust, and requires a recent version installed. Rust can be installed and updated using [rustup][]. + +[rustup]: https://rustup.rs/ The project is organized as follows: - The stack graph rules are defined in `src/stack-graphs.tsg`. +- Builtins sources and configuration are defined in `src/builtins.it` and `builtins.cfg` respectively. - Tests are put into the `test` directory. -The following commands are intended to be run from the repo root. +### Running Tests + +Run the tests as follows: + +```sh +cargo test +``` + +The project consists of a library and a CLI. By default, running `cargo` only applies to the library. To run `cargo` commands on the CLI as well, add `--features cli` or `--all-features`. + +Run the CLI from source as follows: + +```sh +cargo run --features cli -- ARGS +``` + +Sources are formatted using the standard Rust formatted, which is applied by running: + +```sh +cargo fmt +``` + +### Writing TSG + +The stack graph rules are written in [tree-sitter-graph][]. Checkout the [examples][], +which contain self-contained TSG rules for specific language features. A VSCode +[extension][] is available that provides syntax highlighting for TSG files. -Run all tests in the project by executing the following: +[tree-sitter-graph]: https://github.com/tree-sitter/tree-sitter-graph +[examples]: https://github.com/github/stack-graphs/blob/main/tree-sitter-stack-graphs/examples/ +[extension]: https://marketplace.visualstudio.com/items?itemName=tree-sitter.tree-sitter-graph - cargo test -p tree-sitter-stack-graphs-java +Parse and test a single file by executing the following commands: -Parse a single test file: - `cargo run -p tree-sitter-stack-graphs-java -- parse OPTIONS FILENAME` +```sh +cargo run --features cli -- parse FILES... +cargo run --features cli -- test TESTFILES... +``` -Test a single file: - `cargo run -p tree-sitter-stack-graphs-java -- test OPTIONS FILENAME` +Generate a visualization to debug failing tests by passing the `-V` flag: -For debugging purposes, it is useful to run the visualization tool to generate graphs for the tests being run. +```sh +cargo run --features cli -- test -V TESTFILES... +``` -To run a test and generate the visualization: +To generate the visualization regardless of test outcome, execute: -`cargo run -p tree-sitter-stack-graphs-java -- test --output-mode=always -V=%r/%d/%n.html FILENAME` +```sh +cargo run --features cli -- test -V --output-mode=always TESTFILES... +``` -Go to https://crates.io/crates/tree-sitter-stack-graphs for links to examples and documentation. +Go to for links to examples and documentation. diff --git a/languages/tree-sitter-stack-graphs-java/rust/lib.rs b/languages/tree-sitter-stack-graphs-java/rust/lib.rs index 09cb043a5..9b2cb39c8 100644 --- a/languages/tree-sitter-stack-graphs-java/rust/lib.rs +++ b/languages/tree-sitter-stack-graphs-java/rust/lib.rs @@ -1,4 +1,3 @@ -use tree_sitter_stack_graphs::loader::FileAnalyzers; use tree_sitter_stack_graphs::loader::LanguageConfiguration; use tree_sitter_stack_graphs::loader::LoadError; use tree_sitter_stack_graphs::CancellationFlag; @@ -15,8 +14,6 @@ pub const STACK_GRAPHS_BUILTINS_PATH: &str = "src/builtins.java"; /// The stack graphs builtins source for this language. pub const STACK_GRAPHS_BUILTINS_SOURCE: &str = include_str!("../src/builtins.java"); -/// The name of the file path global variable -pub const FILE_PATH_VAR: &str = "FILE_PATH"; /// The name of the project name global variable pub const PROJECT_NAME_VAR: &str = "PROJECT_NAME"; @@ -28,7 +25,7 @@ pub fn try_language_configuration( cancellation_flag: &dyn CancellationFlag, ) -> Result { LanguageConfiguration::from_sources( - tree_sitter_java::language(), + tree_sitter_java::LANGUAGE.into(), Some(String::from("source.java")), None, vec![String::from("java")], @@ -39,7 +36,6 @@ pub fn try_language_configuration( STACK_GRAPHS_BUILTINS_SOURCE, )), Some(STACK_GRAPHS_BUILTINS_CONFIG), - FileAnalyzers::new(), cancellation_flag, ) } diff --git a/languages/tree-sitter-stack-graphs-javascript/.gitignore b/languages/tree-sitter-stack-graphs-javascript/.gitignore new file mode 100644 index 000000000..7392bff36 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/.gitignore @@ -0,0 +1,5 @@ +*.html +/Cargo.lock +/node_modules +/package-lock.json +/target diff --git a/languages/tree-sitter-stack-graphs-javascript/CHANGELOG.md b/languages/tree-sitter-stack-graphs-javascript/CHANGELOG.md new file mode 100644 index 000000000..ae90a2f01 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog for tree-sitter-stack-graphs-javascript + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## v0.3.0 -- 2024-12-12 + +- The `tree-sitter-stack-graphs` dependency is updated to version 0.10. + +- The `tree-sitter-javascript` dependency is updated to version 0.23.1. + +## v0.2.0 -- 2024-07-09 + +### Removed + +- The `FILE_PATH_VAR` constant has been replaced in favor of `tree_sitter_stack_graphs::FILE_PATH_VAR`. + +## v0.1.0 -- 2024-03-06 + +Initial release. diff --git a/languages/tree-sitter-stack-graphs-javascript/Cargo.toml b/languages/tree-sitter-stack-graphs-javascript/Cargo.toml new file mode 100644 index 000000000..d1206c6e5 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "tree-sitter-stack-graphs-javascript" +version = "0.3.0" +description = "Stack graphs definition for JavaScript using tree-sitter-javascript" +readme = "README.md" +keywords = ["tree-sitter", "stack-graphs", "javascript"] +authors = ["Beka Valentine "] +license = "MIT OR Apache-2.0" +edition = "2018" + +[[bin]] +name = "tree-sitter-stack-graphs-javascript" +path = "rust/bin.rs" +required-features = ["cli"] + +[lib] +path = "rust/lib.rs" +test = false + +[[test]] +name = "test" +path = "rust/test.rs" +harness = false + +[features] +cli = ["anyhow", "clap", "tree-sitter-stack-graphs/cli"] + +[dependencies] +anyhow = { version = "1.0", optional = true } +clap = { version = "4", optional = true } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +stack-graphs = { version = "0.14", path = "../../stack-graphs" } # explicit version is required to be able to publish crate +tree-sitter-graph = "0.12" +tree-sitter-javascript = "=0.23.1" +tree-sitter-stack-graphs = { version = "0.10", path = "../../tree-sitter-stack-graphs" } # explicit version is required to be able to publish crate + +[dev-dependencies] +anyhow = "1.0" +tree-sitter-stack-graphs = { path = "../../tree-sitter-stack-graphs", features = ["cli"] } diff --git a/languages/tree-sitter-stack-graphs-javascript/README.md b/languages/tree-sitter-stack-graphs-javascript/README.md new file mode 100644 index 000000000..c7eefa955 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/README.md @@ -0,0 +1,138 @@ +# tree-sitter-stack-graphs definition for JavaScript + +This project defines tree-sitter-stack-graphs rules for JavaScript using the [tree-sitter-javascript][] grammar. + +[tree-sitter-javascript]: https://crates.io/crates/tree-sitter-javascript + +- [API documentation](https://docs.rs/tree-sitter-stack-graphs-javascript/) +- [Release notes](https://github.com/github/stack-graphs/blob/main/languages/tree-sitter-stack-graphs-javascript/CHANGELOG.md) + +## Using the API + +To use this library, add the following to your `Cargo.toml`: + +```toml +[dependencies] +tree-sitter-stack-graphs-javascript = "0.3" +``` + +Check out our [documentation](https://docs.rs/tree-sitter-stack-graphs-javascript/*/) for more details on how to use this library. + +## Using the Command-line Program + +The command-line program for `tree-sitter-stack-graphs-javascript` lets you do stack graph based analysis and lookup from the command line. + +The CLI can be run as follows: + +1. _(Installed)_ Install the CLI using Cargo as follows: + + ```sh + cargo install --features cli tree-sitter-stack-graphs-javascript + ``` + + After this, the CLI should be available as `tree-sitter-stack-graphs-javascript`. + +2. _(From source)_ Instead of installing the CLI, it can also be run directly from the crate directory, as a replacement for a `tree-sitter-stack-graphs-javascript` invocation, as follows: + + ```sh + cargo run --features cli -- + ``` + +The basic CLI workflow for the command-line program is to index source code and issue queries against the resulting database: + +1. Index a source folder as follows: + + ```sh + tree-sitter-stack-graphs-javascript index SOURCE_DIR + ``` + + _Indexing will skip any files that have already be indexed. To force a re-index, add the `-f` flag._ + + To check the status if a source folder, run: + + ```sh + tree-sitter-stack-graphs-javascript status SOURCE_DIR + ``` + + To clean the database and start with a clean slate, run: + + ```sh + tree-sitter-stack-graphs-javascript clean + ``` + + _Pass the `--delete` flag to not just empty the database, but also delete it. This is useful to resolve `unsupported database version` errors that may occur after a version update._ + +2. Run a query to find the definition(s) for a reference on a given line and column, run: + + ```sh + tree-sitter-stack-graphs-javascript query definition SOURCE_PATH:LINE:COLUMN + ``` + + Resulting definitions are printed, including a source line if the source file is available. + +Discover all available commands and flags by passing the `-h` flag to the CLI directly, or to any of the subcommands. + +## Development + +The project is written in Rust, and requires a recent version installed. Rust can be installed and updated using [rustup][]. + +[rustup]: https://rustup.rs/ + +The project is organized as follows: + +- The stack graph rules are defined in `src/stack-graphs.tsg`. +- Builtins sources and configuration are defined in `src/builtins.it` and `builtins.cfg` respectively. +- Tests are put into the `test` directory. + +### Running Tests + +Run the tests as follows: + +```sh +cargo test +``` + +The project consists of a library and a CLI. By default, running `cargo` only applies to the library. To run `cargo` commands on the CLI as well, add `--features cli` or `--all-features`. + +Run the CLI from source as follows: + +```sh +cargo run --features cli -- ARGS +``` + +Sources are formatted using the standard Rust formatted, which is applied by running: + +```sh +cargo fmt +``` + +### Writing TSG + +The stack graph rules are written in [tree-sitter-graph][]. Checkout the [examples][], +which contain self-contained TSG rules for specific language features. A VSCode +[extension][] is available that provides syntax highlighting for TSG files. + +[tree-sitter-graph]: https://github.com/tree-sitter/tree-sitter-graph +[examples]: https://github.com/github/stack-graphs/blob/main/tree-sitter-stack-graphs/examples/ +[extension]: https://marketplace.visualstudio.com/items?itemName=tree-sitter.tree-sitter-graph + +Parse and test a single file by executing the following commands: + +```sh +cargo run --features cli -- parse FILES... +cargo run --features cli -- test TESTFILES... +``` + +Generate a visualization to debug failing tests by passing the `-V` flag: + +```sh +cargo run --features cli -- test -V TESTFILES... +``` + +To generate the visualization regardless of test outcome, execute: + +```sh +cargo run --features cli -- test -V --output-mode=always TESTFILES... +``` + +Go to for links to examples and documentation. diff --git a/languages/tree-sitter-stack-graphs-javascript/docs/package-structure.md b/languages/tree-sitter-stack-graphs-javascript/docs/package-structure.md new file mode 100644 index 000000000..30b96945a --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/docs/package-structure.md @@ -0,0 +1,11 @@ +# How package structure is modeled + +_This is WIP and only contains links to the example, no explanation yet._ + +An [example](package-structure/example.js) test shows a basic setup with a package `foo` exporting a function `foo`, and a package `bar` depending on `foo` and importing the function. + +The stack graph for this example looks as follows: + +![stack graph for example](package-structure/example.png) + +_Generate this yourself by running `cargo run --features cli -- test --save-graph='%d/%n.html' --output-mode=always docs/package-structure/example.js`._ diff --git a/languages/tree-sitter-stack-graphs-javascript/docs/package-structure/example.js b/languages/tree-sitter-stack-graphs-javascript/docs/package-structure/example.js new file mode 100644 index 000000000..ba8165fa7 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/docs/package-structure/example.js @@ -0,0 +1,31 @@ +/* --- path: foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=54EA007B --- */ + +{ + "name": "foo" +} + +/* --- path: foo/index.js --- */ +/* --- global: FILE_PATH=index.js --- */ +/* --- global: PROJECT_NAME=54EA007B --- */ + +export function foo() {} + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=202D9AA4 --- */ + +{ + "name": "bar", + "dependencies": { + "foo": "" + } +} + +/* --- path: bar/index.js --- */ +/* --- global: FILE_PATH=index.js --- */ +/* --- global: PROJECT_NAME=202D9AA4 --- */ + +import { foo } from "foo" +// ^ defined: 13 diff --git a/languages/tree-sitter-stack-graphs-javascript/docs/package-structure/example.png b/languages/tree-sitter-stack-graphs-javascript/docs/package-structure/example.png new file mode 100644 index 000000000..83889ee42 Binary files /dev/null and b/languages/tree-sitter-stack-graphs-javascript/docs/package-structure/example.png differ diff --git a/languages/tree-sitter-stack-graphs-javascript/rust/bin.rs b/languages/tree-sitter-stack-graphs-javascript/rust/bin.rs new file mode 100644 index 000000000..5d5e998ad --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/rust/bin.rs @@ -0,0 +1,33 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use anyhow::anyhow; +use clap::Parser; +use tree_sitter_stack_graphs::cli::database::default_user_database_path_for_crate; +use tree_sitter_stack_graphs::cli::provided_languages::Subcommands; +use tree_sitter_stack_graphs::NoCancellation; + +fn main() -> anyhow::Result<()> { + let lc = match tree_sitter_stack_graphs_javascript::try_language_configuration(&NoCancellation) + { + Ok(lc) => lc, + Err(err) => { + eprintln!("{}", err.display_pretty()); + return Err(anyhow!("Language configuration error")); + } + }; + let cli = Cli::parse(); + let default_db_path = default_user_database_path_for_crate(env!("CARGO_PKG_NAME"))?; + cli.subcommand.run(default_db_path, vec![lc]) +} + +#[derive(Parser)] +#[clap(about, version)] +pub struct Cli { + #[clap(subcommand)] + subcommand: Subcommands, +} diff --git a/languages/tree-sitter-stack-graphs-javascript/rust/lib.rs b/languages/tree-sitter-stack-graphs-javascript/rust/lib.rs new file mode 100644 index 000000000..cbf80e5cd --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/rust/lib.rs @@ -0,0 +1,56 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use tree_sitter_stack_graphs::loader::LanguageConfiguration; +use tree_sitter_stack_graphs::loader::LoadError; +use tree_sitter_stack_graphs::CancellationFlag; + +use crate::npm_package::NpmPackageAnalyzer; + +mod npm_package; +mod util; + +/// The stack graphs tsg source for this language. +pub const STACK_GRAPHS_TSG_PATH: &str = "src/stack-graphs.tsg"; +/// The stack graphs tsg source for this language. +pub const STACK_GRAPHS_TSG_SOURCE: &str = include_str!("../src/stack-graphs.tsg"); + +/// The stack graphs builtins configuration for this language. +pub const STACK_GRAPHS_BUILTINS_CONFIG: &str = include_str!("../src/builtins.cfg"); +/// The stack graphs builtins path for this language +pub const STACK_GRAPHS_BUILTINS_PATH: &str = "src/builtins.js"; +/// The stack graphs builtins source for this language. +pub const STACK_GRAPHS_BUILTINS_SOURCE: &str = include_str!("../src/builtins.js"); + +/// The name of the project name global variable +pub const PROJECT_NAME_VAR: &str = "PROJECT_NAME"; + +pub fn language_configuration(cancellation_flag: &dyn CancellationFlag) -> LanguageConfiguration { + try_language_configuration(cancellation_flag).unwrap_or_else(|err| panic!("{}", err)) +} + +pub fn try_language_configuration( + cancellation_flag: &dyn CancellationFlag, +) -> Result { + let mut lc = LanguageConfiguration::from_sources( + tree_sitter_javascript::LANGUAGE.into(), + Some(String::from("source.js")), + None, + vec![String::from("js")], + STACK_GRAPHS_TSG_PATH.into(), + STACK_GRAPHS_TSG_SOURCE, + Some(( + STACK_GRAPHS_BUILTINS_PATH.into(), + STACK_GRAPHS_BUILTINS_SOURCE, + )), + Some(STACK_GRAPHS_BUILTINS_CONFIG), + cancellation_flag, + )?; + lc.special_files + .add("package.json".to_string(), NpmPackageAnalyzer {}); + Ok(lc) +} diff --git a/languages/tree-sitter-stack-graphs-javascript/rust/npm_package.rs b/languages/tree-sitter-stack-graphs-javascript/rust/npm_package.rs new file mode 100644 index 000000000..13c15c7ea --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/rust/npm_package.rs @@ -0,0 +1,177 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2022, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use serde::Deserialize; +use std::collections::HashMap; +use std::path::Path; +use std::path::PathBuf; + +use stack_graphs::arena::Handle; +use stack_graphs::graph::File; +use stack_graphs::graph::StackGraph; +use tree_sitter_stack_graphs::BuildError; +use tree_sitter_stack_graphs::FileAnalyzer; + +use crate::util::*; + +pub struct NpmPackageAnalyzer {} + +impl FileAnalyzer for NpmPackageAnalyzer { + fn build_stack_graph_into<'a>( + &self, + graph: &mut StackGraph, + file: Handle, + _path: &Path, + source: &str, + _all_paths: &mut dyn Iterator, + globals: &HashMap, + _cancellation_flag: &dyn tree_sitter_stack_graphs::CancellationFlag, + ) -> Result<(), tree_sitter_stack_graphs::BuildError> { + // read globals + let pkg_internal_name = globals + .get(crate::PROJECT_NAME_VAR) + .map(String::as_str) + .unwrap_or_default(); + + // parse source + let npm_pkg: NpmPackage = + serde_json::from_str(source).map_err(|_| BuildError::ParseError)?; + + let root = StackGraph::root_node(); + + // reach package internals from root + // + // [root] -> [pop "GUARD:PKG_INTERNAL"] -> [pop pkg_internal_name] + // + let pkg_internal_guard_pop = add_pop( + graph, + file, + root, + PKG_INTERNAL_GUARD, + "pkg_internal_guard_pop", + ); + let pkg_internal_name_pop = add_pop( + graph, + file, + pkg_internal_guard_pop, + pkg_internal_name, + "pkg_internal_name_pop", + ); + // reach package internals via root + // + // [push pkg_internal_name] -> [push "GUARD:PKG_INTERNAL"] -> [root] + // + let pkg_internal_guard_push = add_push( + graph, + file, + root, + PKG_INTERNAL_GUARD, + "pkg_internal_guard_push", + ); + let pkg_internal_name_push = add_push( + graph, + file, + pkg_internal_guard_push, + pkg_internal_name, + "pkg_internal_name_push", + ); + + // reach exports via package name + // + // [root] -> [pop "GUARD:PKG"] -> [pop PKG_NAME]* -> [push "GUARD:MODULE"] -> [push PKG_INTERNAL_NAME] -> [push "GUARD:PKG_INTERNAL"] -> [root] + // + if !npm_pkg.name.is_empty() { + // NOTE Because all modules expose their exports at the top-level, both paths created below are equivalent for + // exports of the main module. This means multiple equivalent paths to those exports, which is bad for + // performance. At the moment, we have no mechanism to prevent this from happening. + + let module_guard = add_push( + graph, + file, + pkg_internal_name_push, + MODULE_GUARD, + "main_guard", + ); + + // reach package internals via package name + // + // [root] -> [pop "GUARD:PKG"] -> [pop pkg_name]* -> [push pkg_internal_name] + // + let pkg_guard_pop = add_pop(graph, file, root, PKG_GUARD, "pkg_guard_pop"); + let pkg_name_pop = add_module_pops( + graph, + file, + Path::new(&npm_pkg.name), + pkg_guard_pop, + "pkg_name_pop", + ); + add_edge(graph, pkg_name_pop, module_guard, 0); + + // Common main + let main = Some(npm_pkg.main) + .filter(|main| !main.is_empty()) + .and_then(|main| NormalizedRelativePath::from_str(&main)) + .map(|p| p.into_path_buf()) + .unwrap_or(PathBuf::from("index")) + .with_extension(""); + + let main_push = add_module_pushes(graph, file, &main, module_guard, "main_push"); + + // reach main directly via package name (with precedence) + // + // [pop pkg_name] -1-> [push main]* -> [push pkg_internal_name] + // + add_edge(graph, pkg_name_pop, main_push, 0); + } + + // reach dependencies via package internal name + // + // [pop pkg_internal_name] -> [pop "GUARD:PKG"] -> [pop dep_name]* -> [push dep_name]* -> [push "GUARD:PKG"] -> [root] + // + let dep_guard_pop = add_pop( + graph, + file, + pkg_internal_name_pop, + PKG_GUARD, + "dep_guard_pop", + ); + let dep_guard_push = add_push(graph, file, root, PKG_GUARD, "dep_guard_push"); + for (i, (dep_name, _)) in npm_pkg.dependencies.iter().enumerate() { + if dep_name.is_empty() { + continue; + } + let dep_name_pop = add_module_pops( + graph, + file, + Path::new(dep_name), + dep_guard_pop, + &format!("dep_name_pop[{}]", i), + ); + let dep_name_push = add_module_pushes( + graph, + file, + Path::new(dep_name), + dep_guard_push, + &format!("dep_name_push[{}", i), + ); + add_edge(graph, dep_name_pop, dep_name_push, 0); + } + + Ok(()) + } +} + +#[derive(Default, Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NpmPackage { + #[serde(default)] + pub name: String, + #[serde(default)] + pub main: String, + #[serde(default)] + pub dependencies: HashMap, +} diff --git a/languages/tree-sitter-stack-graphs-javascript/rust/test.rs b/languages/tree-sitter-stack-graphs-javascript/rust/test.rs new file mode 100644 index 000000000..ea396ec07 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/rust/test.rs @@ -0,0 +1,24 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use anyhow::anyhow; +use std::path::PathBuf; +use tree_sitter_stack_graphs::ci::Tester; +use tree_sitter_stack_graphs::NoCancellation; + +fn main() -> anyhow::Result<()> { + let lc = match tree_sitter_stack_graphs_javascript::try_language_configuration(&NoCancellation) + { + Ok(lc) => lc, + Err(err) => { + eprintln!("{}", err.display_pretty()); + return Err(anyhow!("Language configuration error")); + } + }; + let test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test"); + Tester::new(vec![lc], vec![test_path]).run() +} diff --git a/languages/tree-sitter-stack-graphs-javascript/rust/util.rs b/languages/tree-sitter-stack-graphs-javascript/rust/util.rs new file mode 100644 index 000000000..f4942df3b --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/rust/util.rs @@ -0,0 +1,179 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2022, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use stack_graphs::graph::Node; +use std::path::Component; +use std::path::Path; +use std::path::PathBuf; + +use stack_graphs::arena::Handle; +use stack_graphs::graph::File; +use stack_graphs::graph::StackGraph; + +pub const MODULE_GUARD: &str = "GUARD:MODULE"; +pub const PKG_GUARD: &str = "GUARD:PKG"; +pub const PKG_INTERNAL_GUARD: &str = "GUARD:PKG_INTERNAL"; + +pub fn add_debug_name(graph: &mut StackGraph, node: Handle, name: &str) { + if name.is_empty() { + return; + } + let key = graph.add_string("name"); + let value = graph.add_string(name); + graph.node_debug_info_mut(node).add(key, value); +} + +pub fn add_pop( + graph: &mut StackGraph, + file: Handle, + from: Handle, + name: &str, + debug_name: &str, +) -> Handle { + let id = graph.new_node_id(file); + let sym = graph.add_symbol(name); + let node = graph.add_pop_symbol_node(id, sym, false).unwrap(); + graph.add_edge(from, node, 0); + add_debug_name(graph, node, debug_name); + node +} + +pub fn add_push( + graph: &mut StackGraph, + file: Handle, + to: Handle, + name: &str, + debug_name: &str, +) -> Handle { + let id = graph.new_node_id(file); + let sym = graph.add_symbol(name); + let node = graph.add_push_symbol_node(id, sym, false).unwrap(); + graph.add_edge(node, to, 0); + add_debug_name(graph, node, debug_name); + node +} + +pub fn add_edge(graph: &mut StackGraph, from: Handle, to: Handle, precedence: i32) { + if from == to { + return; + } + graph.add_edge(from, to, precedence); +} + +pub fn add_module_pops( + graph: &mut StackGraph, + file: Handle, + path: &Path, + mut from: Handle, + debug_prefix: &str, +) -> Handle { + for (i, c) in path.components().enumerate() { + match c { + Component::Normal(name) => { + from = add_pop( + graph, + file, + from, + &name.to_string_lossy(), + &format!("{}[{}]", debug_prefix, i), + ); + } + _ => { + eprintln!( + "add_module_pops: expecting normalized, non-escaping, relative paths, got {}", + path.display() + ) + } + } + } + from +} + +pub fn add_module_pushes( + graph: &mut StackGraph, + file: Handle, + path: &Path, + mut to: Handle, + debug_prefix: &str, +) -> Handle { + for (i, c) in path.components().enumerate() { + match c { + Component::Normal(name) => { + to = add_push( + graph, + file, + to, + &name.to_string_lossy(), + &format!("{}[{}]", debug_prefix, i), + ); + } + _ => { + eprintln!( + "add_module_pushes: expecting normalized, non-escaping, relative paths, got {}", + path.display() + ) + } + } + } + to +} + +pub struct NormalizedRelativePath(PathBuf); + +impl NormalizedRelativePath { + pub fn from_str(path: &str) -> Option { + Self::from_path(Path::new(path)) + } + + /// Creates a new normalized, relative path from a path. + pub fn from_path(path: &Path) -> Option { + let mut np = PathBuf::new(); + let mut normal_components = 0usize; + for c in path.components() { + match c { + Component::Prefix(_) => { + return None; + } + Component::RootDir => { + return None; + } + Component::CurDir => {} + Component::ParentDir => { + if normal_components > 0 { + // we can pop a normal component + normal_components -= 1; + np.pop(); + } else { + // add the `..` to the beginning of this relative path which has no normal components + np.push(c); + } + } + Component::Normal(_) => { + normal_components += 1; + np.push(c); + } + } + } + Some(Self(np)) + } + + pub fn into_path_buf(self) -> PathBuf { + self.0 + } +} + +impl AsRef for NormalizedRelativePath { + fn as_ref(&self) -> &Path { + &self.0 + } +} + +impl Into for NormalizedRelativePath { + fn into(self) -> PathBuf { + self.0 + } +} diff --git a/languages/tree-sitter-stack-graphs-javascript/src/builtins.cfg b/languages/tree-sitter-stack-graphs-javascript/src/builtins.cfg new file mode 100644 index 000000000..d685061be --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/src/builtins.cfg @@ -0,0 +1 @@ +[globals] diff --git a/languages/tree-sitter-stack-graphs-javascript/src/builtins.js b/languages/tree-sitter-stack-graphs-javascript/src/builtins.js new file mode 100644 index 000000000..e69de29bb diff --git a/languages/tree-sitter-stack-graphs-javascript/src/stack-graphs.tsg b/languages/tree-sitter-stack-graphs-javascript/src/stack-graphs.tsg new file mode 100644 index 000000000..7ca4eee88 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/src/stack-graphs.tsg @@ -0,0 +1,5058 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Stack graphs definition for JavaScript +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Global Variables +;; ^^^^^^^^^^^^^^^^ + +global ROOT_NODE +global JUMP_TO_SCOPE_NODE + +global FILE_PATH +global PROJECT_NAME = "" + + +;; Attribute Shorthands +;; ^^^^^^^^^^^^^^^^^^^^ + +attribute node_definition = node => type = "pop_symbol", node_symbol = node, is_definition +attribute node_reference = node => type = "push_symbol", node_symbol = node, is_reference +attribute pop_node = node => type = "pop_symbol", node_symbol = node +attribute pop_scoped_node = node => type = "pop_scoped_symbol", node_symbol = node +attribute pop_scoped_symbol = symbol => type = "pop_scoped_symbol", symbol = symbol +attribute pop_symbol = symbol => type = "pop_symbol", symbol = symbol +attribute push_node = node => type = "push_symbol", node_symbol = node +attribute push_scoped_node = node => type = "push_scoped_symbol", node_symbol = node +attribute push_scoped_symbol = symbol => type = "push_scoped_symbol", symbol = symbol +attribute push_symbol = symbol => type = "push_symbol", symbol = symbol +attribute scoped_node_definition = node => type = "pop_scoped_symbol", node_symbol = node, is_definition +attribute scoped_node_reference = node => type = "push_scoped_symbol", node_symbol = node, is_reference +attribute symbol_definition = symbol => type = "pop_symbol", symbol = symbol, is_definition +attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, is_reference + +attribute node_symbol = node => symbol = (source-text node), source_node = node + +;; Stack Graph Rules +;; ^^^^^^^^^^^^^^^^^ + +;; # JavaScript + +;; This file defines StackGraph queries for JavaScript. It is written as a +;; semi-literate file, which is to say, comments starting with `;;` can be +;; treated as Markdown. This file can therefore be converted into a +;; corresponding pure Markdown document by removing the StackGraph comments, and +;; wrapping the uncommented code in Markdown code blocks. + +;; This file has a number of sections that it's divided into, and it's useful to +;; provide an overview here. Aside from conventions, the queries are broken up +;; into groups by what kind of syntactic objects the group covers. There are +;; Programs, Statements, Expressions, Patterns, and Special Cases. + +;; Within each major section, we break things down by the particular syntactic +;; form under consideration. Some particular syntactic forms are not one of the +;; main forms listed, but are tightly associated with other forms -- for +;; example, the branches of an `if` statement are not statements nor +;; expressions, they're simply components of an `if` statement. In that +;; situation, we group them with the primary statement that they're associated +;; with, where possible, and nearby when not. + +;; Additionally, some syntactic constructs have arbitrary numbers of child +;; nodes, which requires us to explain how to relate them as sequences of +;; nodes. In such cases, we have associated queries with the node type in +;; question. + +;; ## Design Conventions + +;; The general convention for how JavaScript is translated into a Stack Graph is +;; to treat the graph as a reverse Control Flow Graph. There are some corner +;; cases for which that analogy doesn't quite work, but it's sufficient for +;; conveying the overall approach. Many things matter for name resolution in a +;; Control Flow Graph, but one of the only ones we can really encode into a +;; Stack Graph, and arguably the most important one, is the flow of the variable +;; environment, and so we create scope nodes for those. + +;; In particular, for each node, during an execution traversal, there is an +;; incoming variable environment that the node will be executed in, and an +;; outgoing variable environment that resulted from executing the node and all +;; the various variable assignments and updates inside it. + +;; An example helps, so let's consider a simplified case: `if` statement with a +;; mandatory `else` branch. If we were to implement an execution function for +;; this fictionalized language, there would be some environment that goes into +;; the execution at the whole `if-then-else` statement, an environment that goes +;; into the evaluation of the test, an environment that comes out of the test +;; (because the test could be something like `x++` which changes the +;; environment), an environment which goes into each branch (which happens to be +;; the same one that came out of the test), and an environment that comes out of +;; each of the branches. So each major AST node has a pair of environments +;; associated with it: the incoming one to execute it in, and the outcoming node +;; that resulted from executing. + +;; We therefore would implement `if-then-else` statements like so: + +;; ``````stgdoc +;; (if_then_else +;; (_)@test +;; (_)@consequent +;; (_)alternative)@if_stmt { +;; +;; edge @test.before_scope -> @if_stmt.before_scope +;; edge @consequent.before_scope -> @test.after_scope +;; edge @alternative.before_scope -> @test.after_scope +;; edge @if_stmt.after_scope -> @consequent.after_scope +;; edge @if_stmt.after_scope -> @alternative.after_scope +;; +;; } +;; `````` +;; +;; Another important way that things build scopes is through values. When an +;; expression is executed and returns a value, that value in many ways acts like +;; a sub-environment, at least in that the value has different parts that can be +;; accessed in various ways. For a simple value, as constructed by a number +;; literal expression, the number itself has no parts. So it has an associated +;; value scope node in the graph, but no edges coming off that: + +;; ``````stgdoc +;; (number)@num { +;; attr (@num.value) pop_symbol = "NUM_VAL" +;; } +;; `````` + +;; Why we use a `pop` attribute here isn't deeply important so we'll gloss over +;; it. + +;; All kinds of expressions have values, what precisely goes into them depends +;; on what the expression is. Objects, for instance, would have corresponding +;; values that point to the values of each of the keys in the object. Arrays +;; would have values that point to the values of each of the indexes. + +;; The primary purpose of values is to act as the targets of assignments, so +;; that names can resolve to something in code. Normally we care only about the +;; location of the assignment, of course, but we also need to be able to do +;; further lookup on the values. For instance, in an expression like `x.y.z`, +;; we need to look up `x` of course, but also then further look up `y` on that, +;; and then `z`. So whatever `x` is assigned to has to also be present in the +;; graph as something which can itself have stuff hanging off it that can be +;; used for lookup. + +;; If you were to run some JavaScript to test whether or not number literals +;; have parts (really, members/fields), you'd discover that actually they do, +;; but not because of which number they are. Rather, their parts are for builtin +;; functionality like `toString`. To handle this, we can point the value to some +;; shared global variable that represents the prototype for the number, like so: + +;; ``````stgdoc +;; (number)@num { +;; attr (@num.value) pop_symbol = "NUM_VAL" +;; edge @num.value -> @num.number_prototype +;; } +;; `````` + +;; In the preamble definition for the `program` node type, we could then also +;; want to define number prototype, something like this: + +;; ``````stgdoc +;; inherit .number_prototype +;; +;; (program)@prog { +;; +;; ... +;; +;; node @prog.number_prototype +;; edge @prog.number_prototype -> @prog.number_prototype_toString_method_value +;; edge @prog.number_prototype -> @prog.number_prototype_valueOf_method_value +;; ...etc +;; +;; ... +;; +;; } +;; `````` + +;; We would then also want to have a bunch of hand-encoded graphs for the values +;; of those fields. That would then allow the number values to point to their +;; prototypes where the fields could be found and used for further lookup on +;; methods. + +;; One caveat of this is that we don't want to get too deep into explaining how +;; some method might be implemented. Indeed, sometimes we *can't*, because it's +;; not implementable in JavaScript at all and is instead some primitive function +;; implemented in the host language. What we want to do, instead, is to provide +;; just enough implementation that other data can flow through as much as +;; possible, to prevent calls to primitive methods from blocking downstream name +;; resolution. For instance, it would be unfortunate if calling `toString` on a +;; number did not let you look up string fields on the resultant value of the +;; call, i.e. if `length` in `(5).toString().length` just simply could not be +;; resolved at all. In such cases, the ideal approach is to implement a bare +;; minimum of `toString` so that we can recover the fact that its returned +;; value is a string. E.g.: + +;; ``````stgdoc +;; (program)@prog { +;; +;; ... +;; +;; edge @prog.number_prototype -> @prog.number_prototype_toString_method_value +;; edge @prog.number_prototype_toString_method_value -> @prog.number_prototype_toString_method_value_return +;; attr (@prog.number_prototype_toString_method_value_return) pop_symbol = "GUARD:RETURN" +;; edge @prog.number_prototype_toString_method_value_return -> @prog.string_prototype +;; +;; ... +;; +;; } +;; `````` + +;; As currently implemented, this document does not contain any rules for +;; builtins because of the scope of that task, but they are currently in the +;; pipeline, using the above approach. + +;; Something you may notice in the above code snippet is this pop node labelled +;; `GUARD:RETURN`. This is another part of the design conventions for this +;; library. We often want to use nodes in the graph to constrain name lookup. +;; In this case, we want to be able to pinpoint some of the graph nodes +;; associated with the body of a function as being the values returned by the +;; function. We do this by creating a so-called guard node, which will only be +;; traversed if there is a corresponding push somewhere else. We would generate +;; such a push node precisely when we call a function. This lets us treat the +;; function *ASTs* as if they have parts that we can inspect *in the graph*. + +;; By convention, this library of queries prefixes all guard nodes with "GUARD:" +;; to distinguish them from nodes that more directly correspond to aspects of +;; execution such as member lookup (labelled with "GUARD:MEMBER") or variable lookup +;; (labelled with the variable name itself). The current names used for guard +;; nodes are + +;; - `GUARD:CONSTRUCTOR` - used for the constructor method inside a `Class` +;; - `GUARD:DEFAULT` - used for the default export value +;; - `GUARD:EXPORTS` - used for the names exported by the module +;; - `GUARD:GANDALF` - used for situations where parts of a graph must be accessible +;; in order to avoid pruning, but which never the less should not be actually reached +;; during any normal traversals. By only ever popping, and never pushing, we can get +;; a connected subgraph that is never the less never traversed. +;; - `GUARD:LABEL` - used for the names of labels +;; - `GUARD:MEMBER` - used for the members/fields/properties of objects +;; - `GUARD:MODULE` - used for module names +;; - `GUARD:RETURN` - used for the AST nodes for values returned by a function +;; in the function body +;; - `GUARD:THIS` - used for the implicit `this` argument of a function inside +;; - `GUARD:PKG_INTERNAL` - used for the package internal structure that should not +;; be accessible directly from the root + +; ## Inherited + +inherit .builtins_Regex_prototype +inherit .builtins_arguments_prototype +inherit .builtins_boolean +inherit .builtins_empty_object +inherit .builtins_null +inherit .builtins_number +inherit .builtins_string +inherit .builtins_undefined +inherit .class_value +inherit .constructor +inherit .export_statement +inherit .exports +inherit .hoist_point +inherit .closure_point +inherit .import_statement +inherit .pkg_pop +inherit .pkg_push +inherit .return_or_yield +inherit .containing_class_value + + + + + + + + + + +;; ██████ ██████ ██████ ██████ ██████ █████ ███ ███ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ +;; ██████ ██████ ██ ██ ██ ███ ██████ ███████ ██ ████ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ██ ██ ██ ██████ ██████ ██ ██ ██ ██ ██ ██ ███████ + +;; ## Programs + +;; ### Attributes Defined on Programs +;; TODO + +(program)@prog { + node @prog.after_scope + node @prog.before_scope + let @prog.closure_point = @prog.after_scope + + ; apparently it's perfectly cromulent to `return` from the top level scope (because control flow happens there too), so we make a top-level return node. + node @prog.return_or_yield +} + +;; ### Program Queries + +(program)@prog { + + node prog_module_pop + node prog_exports_pop + node prog_pkg_pop_guard + node prog_pkg_push_guard + node prog_legacy_qname_guard + node @prog.exports + node @prog.hoist_point + node @prog.pkg_pop + node @prog.pkg_push + + attr (prog_pkg_pop_guard) pop_symbol = "GUARD:PKG_INTERNAL" + attr (@prog.pkg_pop) pop_symbol = PROJECT_NAME + edge ROOT_NODE -> prog_pkg_pop_guard + edge prog_pkg_pop_guard -> @prog.pkg_pop + + attr (@prog.pkg_push) push_symbol = PROJECT_NAME + attr (prog_pkg_push_guard) push_symbol = "GUARD:PKG_INTERNAL" + edge @prog.pkg_push -> prog_pkg_push_guard + edge prog_pkg_push_guard -> ROOT_NODE + + node module_guard_pop + attr (module_guard_pop) pop_symbol = "GUARD:MODULE" + edge @prog.pkg_pop -> module_guard_pop + + node prog_module_pop_start + edge module_guard_pop -> prog_module_pop_start + var module_pop_end = prog_module_pop_start + let module_name = (replace FILE_PATH "\.js$" "") + scan module_name { + "([^/]+)/" { + attr (module_pop_end) pop_symbol = $1 + node module_pop_end_next + edge module_pop_end -> module_pop_end_next + set module_pop_end = module_pop_end_next + } + "([^/]+)$" { + attr (module_pop_end) symbol_definition = $1, source_node = @prog + attr (module_pop_end) empty_source_span + attr (module_pop_end) definiens_node = @prog + } + } + + edge @prog.before_scope -> @prog.pkg_push + edge @prog.before_scope -> @prog.hoist_point + + + attr (prog_legacy_qname_guard) push_symbol = "GUARD:LEGACY_QNAME" + edge @prog.before_scope -> prog_legacy_qname_guard + edge prog_legacy_qname_guard -> ROOT_NODE + + attr (prog_module_pop) empty_source_span + attr (@prog.exports) empty_source_span + attr (prog_exports_pop) empty_source_span + attr (@prog.before_scope) empty_source_span + attr (@prog.after_scope) empty_source_span + + attr (prog_exports_pop) pop_symbol = "GUARD:EXPORTS" + edge module_pop_end -> prog_exports_pop + edge prog_exports_pop -> @prog.exports + + ;; builtin types + node @prog.builtins_number + node @prog.builtins_string + node @prog.builtins_boolean + node @prog.builtins_null + node @prog.builtins_undefined + node @prog.builtins_Regex_prototype + node @prog.builtins_arguments_prototype + node @prog.builtins_empty_object + ; !!!! HACK + ; stack graphs currently make it impossible to test if an inherited variable + ; like this is defined or not + let @prog.containing_class_value = @prog.builtins_null + +} + +; programs, first statement +(program + . + (_)@first_stmt)@prog { + + ; scopes flow from the program into the first statement + edge @first_stmt.before_scope -> @prog.before_scope +} + +; program, between statements +(program + (_)@left_stmt + . + (_)@right_stmt) { + + ; scopes flow from the left statement to the right one + edge @right_stmt.before_scope -> @left_stmt.after_scope +} + +; program, last statement +(program + (_)@last_stmt + .)@prog { + + ; scopes flow from the last statement to the program + edge @prog.after_scope -> @last_stmt.after_scope +} + +(hash_bang_line)@hashbang { + node @hashbang.after_scope + node @hashbang.before_scope + edge @hashbang.after_scope -> @hashbang.before_scope +} + +(comment)@comment { + node @comment.after_scope + node @comment.before_scope + node @comment.value + node @comment.covalue + node @comment.new_bindings ; for object patterns + edge @comment.after_scope -> @comment.before_scope + node @comment.source ; for export clauses with multiple exports +} + +(identifier) @identifier { + node @identifier.before_scope + node @identifier.after_scope + node @identifier.value + node @identifier.covalue + node @identifier.new_bindings + edge @identifier.after_scope -> @identifier.before_scope +} + + + + + + + + + + +;; ███████ ████████ █████ ████████ ███████ ███ ███ ███████ ███ ██ ████████ ███████ +;; ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ +;; ███████ ██ ███████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ███████ ██ ██ ██ ██ ███████ ██ ██ ███████ ██ ████ ██ ███████ + +;; ## Statements + +;; ### Attributes Defined on Statements +;; TODO + +[ + (export_statement) + (import_statement) + (debugger_statement) + (expression_statement) + (function_declaration) + (generator_function_declaration) + (class_declaration) + (lexical_declaration) + (variable_declaration) + (statement_block) + (if_statement) + (switch_statement) + (for_statement) + (for_in_statement) + (while_statement) + (do_statement) + (try_statement) + (with_statement) + (break_statement) + (continue_statement) + (return_statement) + (throw_statement) + (empty_statement) + (labeled_statement) +]@stmt { + node @stmt.after_scope + node @stmt.before_scope +} + +;; ### Statement Queries + + + +;; ███████ ██ ██ ██████ ██████ ██████ ████████ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; █████ ███ ██████ ██ ██ ██████ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ███████ ██ ██ ██ ██████ ██ ██ ██ ███████ + +;; #### Export + +(export_statement)@export_stmt { + let @export_stmt.export_statement = @export_stmt +} + +; exports of just names +; eg +; export { foo, bar as baz }; +(export_statement + (export_clause)@export_clause + !source)@export_stmt { + + edge @export_clause.source -> @export_clause.before_scope + + ; scope flows through the export clause + edge @export_clause.before_scope -> @export_stmt.before_scope + edge @export_stmt.after_scope -> @export_clause.after_scope +} + +(export_statement + (export_clause)@export_clause + source:(_)@source)@export_stmt { + + edge @export_clause.source -> @source.exports + + edge @export_clause.before_scope -> @export_stmt.before_scope + edge @export_stmt.after_scope -> @export_clause.after_scope +} + +(export_statement + (declaration)@decl)@export_stmt { + + edge @decl.before_scope -> @export_stmt.before_scope + edge @export_stmt.after_scope -> @decl.after_scope + +} + +(export_statement "default" + (declaration)@decl)@export_stmt { + + node pop_guard_default + attr (pop_guard_default) symbol_definition = "GUARD:DEFAULT", source_node = @decl, definiens_node = @export_stmt + edge @export_stmt.exports -> pop_guard_default + ;; edge export_stmt_pop_guard_default -> @decl.value ;; FIXME declarations have no .value + + ; !!!! HACK These detour nodes are a massive hack to allow find all refs land on defs + ; for the default values of modules that have useful names like the module name or + ; package name + + node detour_push + node detour_pop + + scan FILE_PATH { + + "^(.+/)?([^/]+)/index\.js$" { + let module_name = $2 + attr (detour_push) push_symbol = module_name + attr (detour_pop) symbol_definition = module_name, source_node = @decl, definiens_node = @export_stmt + edge pop_guard_default -> detour_push + edge detour_push -> detour_pop + ; edge detour_pop -> @decl.value ;; FIXME declarations have no .value + } + + "^(.+/)?([^/]+)\.js$" { + let module_name = $2 + attr (detour_push) push_symbol = module_name + attr (detour_pop) symbol_definition = module_name, source_node = @decl, definiens_node = @export_stmt + edge pop_guard_default -> detour_push + edge detour_push -> detour_pop + ; edge detour_pop -> @decl.value ;; FIXME declarations have no .value + } + + } + + node default_detour_push + node default_detour_pop + + attr (default_detour_push) push_symbol = "default" + attr (default_detour_pop) symbol_definition = "default", source_node = @decl, definiens_node = @export_stmt + edge pop_guard_default -> default_detour_push + edge default_detour_push -> default_detour_pop + ; edge default_detour_pop -> @decl.value ;; FIXME declarations have no .value + +} + +(export_statement "default" + value:(_)@value)@export_stmt { + + node @export_stmt.pop_guard_default + attr (@export_stmt.pop_guard_default) symbol_definition = "GUARD:DEFAULT", source_node = @value, definiens_node = @export_stmt + edge @export_stmt.exports -> @export_stmt.pop_guard_default + edge @export_stmt.pop_guard_default -> @value.value + + ; !!!! HACK These detour nodes are a massive hack to allow find all refs land on defs + ; for the default values of modules that have useful names like the module name or + ; package name + + node detour_push + node @export_stmt.detour_pop + + scan FILE_PATH { + + "^(.+/)?([^/]+)/index\.js$" { + let module_name = $2 + attr (detour_push) push_symbol = module_name + attr (@export_stmt.detour_pop) symbol_definition = module_name, source_node = @value, definiens_node = @export_stmt + edge @export_stmt.pop_guard_default -> detour_push + edge detour_push -> @export_stmt.detour_pop + edge @export_stmt.detour_pop -> @value.value + } + + "^(.+/)?([^/]+)\.js$" { + let module_name = $2 + attr (detour_push) push_symbol = module_name + attr (@export_stmt.detour_pop) symbol_definition = module_name, source_node = @value, definiens_node = @export_stmt + edge @export_stmt.pop_guard_default -> detour_push + edge detour_push -> @export_stmt.detour_pop + edge @export_stmt.detour_pop -> @value.value + } + + } + + node default_detour_push + node @export_stmt.default_detour_pop + + attr (default_detour_push) push_symbol = "default" + attr (@export_stmt.default_detour_pop) symbol_definition = "default", source_node = @value, definiens_node = @export_stmt + edge @export_stmt.pop_guard_default -> default_detour_push + edge default_detour_push -> @export_stmt.default_detour_pop + edge @export_stmt.default_detour_pop -> @value.value + +} + +(export_statement + declaration: [ + (function_declaration name:(identifier)@name) + (generator_function_declaration name:(identifier)@name) + (class_declaration name:(identifier)@name) + (lexical_declaration (variable_declarator name:(identifier)@name)) + (variable_declaration (variable_declarator name:(identifier)@name)) + ]@_decl)@export_stmt { + + ; TODO this doesn't support destructuring exports + + edge @export_stmt.exports -> @name.pop + +} + +; TODO +; export let [x,y] = [1,2]; +(export_statement + declaration: [ + (lexical_declaration + (variable_declarator + name: [ + (object_pattern) + (array_pattern) + ]@pattern)) + (variable_declaration + (variable_declarator + name: [ + (object_pattern) + (array_pattern) + ]@pattern)) + ])@export_stmt { + + edge @export_stmt.exports -> @pattern.new_bindings + +} + +(export_clause)@export_clause { + node @export_clause.after_scope + node @export_clause.before_scope + node @export_clause.source +} + +(export_clause (_)* @clauses)@export_clause { + if (is-empty @clauses) { + edge @export_clause.after_scope -> @export_clause.before_scope + } +} + +(export_clause + (_)@export)@export_clause { + + edge @export.source -> @export_clause.source +} + +(export_clause + . + (_)@first_export)@export_clause { + + edge @first_export.before_scope -> @export_clause.before_scope +} + +(export_clause + (_)@left_export + . + (_)@right_export) { + + edge @right_export.before_scope -> @left_export.after_scope +} + +(export_clause + (_)@last_export + .)@export_clause { + + edge @export_clause.after_scope -> @last_export.after_scope +} + +;; Export specifiers have several cases: +;; - the reference into the source module can be default or named +;; - the definition from this module can be default or named. +;; +;; We model this using seperate sets of rules, two rules for the reference, +;; and two rules for the definition. The `.def_to_ref` node is used to connect +;; the two. + +(export_specifier)@export_specifier { + + node @export_specifier.after_scope + node @export_specifier.before_scope + node @export_specifier.def_to_ref + node @export_specifier.source + + edge @export_specifier.after_scope -> @export_specifier.before_scope + +} + +;; Export specifier reference rules + +; export { default } from ... +; export { default as ... } from ... +( + (export_specifier + name:(_)@name + )@export_specifier + + (#not-eq? @name "default") +) { + + node name_push + attr (name_push) node_reference = @name + edge @export_specifier.def_to_ref -> name_push + edge name_push -> @export_specifier.source + +} + +; export { foo } from ... +; export { foo as ... } from ... +( + (export_specifier + name:(_)@name + )@export_specifier + + (#eq? @name "default") +) { + + node push_guard_default + + attr (push_guard_default) symbol_reference = "GUARD:DEFAULT", source_node = @name + edge @export_specifier.def_to_ref -> push_guard_default + edge push_guard_default -> @export_specifier.source + +} + +;; Export specifier definition rules + +; export { foo } from ... +; export { ... as foo } from ... +( [ + (export_specifier + name:(_)@alias + !alias)@export_specifier + (export_specifier + name:(_) + alias:(_)@alias)@export_specifier + ] + + (#not-eq? @alias "default") +) { + + node name_pop + + attr (name_pop) node_definition = @alias, definiens_node = @export_specifier.export_statement + edge @export_specifier.exports -> name_pop + edge name_pop -> @export_specifier.def_to_ref + +} + +; export { default } from ... +; export { ... as default } from ... +( [ + (export_specifier + name:(_)@alias + !alias)@export_specifier + (export_specifier + name:(_) + alias:(_)@alias)@export_specifier + ] + + (#eq? @alias "default") +) { + + node pop_guard_default + + attr (pop_guard_default) symbol_definition = "GUARD:DEFAULT", source_node = @alias, definiens_node = @export_specifier.export_statement + edge @export_specifier.exports -> pop_guard_default + edge pop_guard_default -> @export_specifier.def_to_ref + +} + +; simple default exports +; export default ...; + +(export_statement + value:(_)@default_expr)@export_stmt { + + edge @default_expr.before_scope -> @export_stmt.before_scope + edge @export_stmt.after_scope -> @default_expr.after_scope + +} + +; aggregated exports +; export * from "foo.js"; +(export_statement + . + source:(_)@source)@export_statement { + + edge @export_statement.after_scope -> @export_statement.before_scope + + edge @export_statement.exports -> @source.exports + +} + +; namespace exports +; export * as foo from "bar.js"; +(export_statement + (namespace_export (_)@alias) + source:(_)@source)@export_statement { + + node alias_pop + node alias_pop_dot + node source_push + node source_push_guard_exports + + edge @export_statement.after_scope -> @export_statement.before_scope + + attr (alias_pop) node_definition = @alias, definiens_node = @export_statement.export_statement + attr (alias_pop_dot) pop_symbol = "GUARD:MEMBER" + edge @export_statement.exports -> alias_pop + edge alias_pop -> alias_pop_dot + edge alias_pop_dot -> @source.exports + +} + + + +;; ██ ███ ███ ██████ ██████ ██████ ████████ ███████ +;; ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ +;; ██ ██ ████ ██ ██████ ██ ██ ██████ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ██ ██ ██ ██ ██████ ██ ██ ██ ███████ + +;; #### Import + +;; We distinguish two kinds of imports, based on the shape of the path: +;; +;; - Relative imports, whose path starts with `.` or `..`. These are resolved relative +;; to the current module, in the same package. +;; - Non-relative or bare imports, which do not start with `.`, `..`, or `/`. These are +;; resolved as package names. Note that the package definitions are introduced in the +;; Go code based on `package.json`, not in this query file. +;; +;; Import paths may include optional `.js` extensions, but behave the same regardless of +;; whether the extension is present. +;; +;; ## Limitations +;; +;; - Absolute imports, whose paths start with `/`, are not supported. +;; - Non-relative imports can resolve into a package (i.e., start with package name +;; components and then module name components inside that package). However, because +;; we don't detect source roots for JavaScript, this might not always work. For example, +;; a module `mod.js` inside a `src/` directory of package `foo` would be accessible as +;; `foo/src/mod`, while `foo/mod` is probably intended. +;; +;; ## References +;; +;; - ES6: https://nodejs.org/api/esm.html, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import +;; - CommonJS: https://nodejs.org/api/modules.html + +(import_statement)@import_stmt { + let @import_stmt.import_statement = @import_stmt +} + +[ + (import_statement source:(_)@source) + (export_statement source:(_)@source) + ( (call_expression function:(_)@_require arguments:(arguments (string)@source)) (#eq? @_require "require") ) + ( (call_expression function:(_)@_import arguments:(arguments (string)@source)) (#eq? @_import "import") ) +] { + + node source_push_guard_exports + node source_push_guard_pkg + node @source.exports + + attr (source_push_guard_exports) push_symbol = "GUARD:EXPORTS" + + scan (source-text @source) { + "^[\"']((\.|\.\.)/.*)[\"']$" { + ; relative import + let name = (replace (path-normalize (path-join (path-dir FILE_PATH) $1)) "\.js$" "") + + node module_guard_push + attr (module_guard_push) push_symbol = "GUARD:MODULE" + edge module_guard_push -> @source.pkg_push + + node source_push_end + edge source_push_end -> module_guard_push + var push_start = source_push_end + scan name { + "([^/]+)/" { + attr (push_start) push_symbol = $1 + node push_start_prev + edge push_start_prev -> push_start + set push_start = push_start_prev + } + "([^/]+)$" { + attr (push_start) push_symbol = $1 + } + } + scan $1 { + "/$" { + node push_start_prev + edge push_start_prev -> push_start + set push_start = push_start_prev + attr (push_start) push_symbol = "index" + } + } + attr (push_start) is_reference, source_node = @source + + edge source_push_guard_exports -> push_start + } + "^[\"']([^\./].*)[\"']$" { + ; package import + let name = (replace $1 "\.js$" "") + + node source_push_end + var push_start = source_push_end + scan name { + "([^/]+)/" { + attr (push_start) push_symbol = $1 + node push_start_prev + edge push_start_prev -> push_start + set push_start = push_start_prev + } + "([^/]+)$" { + attr (push_start) symbol_reference = $1, source_node = @source + } + } + + edge source_push_guard_exports -> push_start + edge source_push_end -> source_push_guard_pkg + attr (source_push_guard_pkg) push_symbol = "GUARD:PKG" + edge source_push_guard_pkg -> @source.pkg_push + } + } + + edge @source.exports -> source_push_guard_exports + +} + +; import "foo.js"; +; only used for side effects not imports. +(import_statement . source:(_))@import_stmt { + edge @import_stmt.after_scope -> @import_stmt.before_scope +} + +(import_clause)@import_clause { + node @import_clause.after_scope + node @import_clause.before_scope + node @import_clause.source +} + +; import * as name from "module-name"; +(import_clause + (namespace_import)@namespace_import)@import_clause { + + edge @namespace_import.before_scope -> @import_clause.before_scope + edge @import_clause.after_scope -> @namespace_import.after_scope + edge @namespace_import.source -> @import_clause.source +} + +(namespace_import (identifier)@imported_as)@namespace_import { + + node imported_as_pop + node imported_as_pop_dot + node @namespace_import.after_scope + node @namespace_import.before_scope + node @namespace_import.source + + edge @namespace_import.after_scope -> @namespace_import.before_scope + + attr (imported_as_pop) node_definition = @imported_as, definiens_node = @namespace_import.import_statement + attr (imported_as_pop_dot) pop_symbol = "GUARD:MEMBER" + edge imported_as_pop -> imported_as_pop_dot + edge imported_as_pop_dot -> @namespace_import.source + + edge @namespace_import.after_scope -> imported_as_pop + +} + +; import { export1 } from "module-name"; +; import { export1 as alias1 } from "module-name"; +; import { export1 , export2 } from "module-name"; +; import { export1 , export2 as alias2 , [...] } from "module-name"; + +(import_statement + (import_clause)@import_clause + source:(_)@source)@import_stmt { + + edge @import_stmt.after_scope -> @import_stmt.before_scope + + edge @import_clause.before_scope -> @import_stmt.before_scope + edge @import_stmt.hoist_point -> @import_clause.after_scope + + edge @import_clause.source -> @source.exports + +} + +(import_clause + (named_imports)@named_imports)@import_clause { + + edge @named_imports.before_scope -> @import_clause.before_scope + edge @import_clause.after_scope -> @named_imports.after_scope + edge @named_imports.source -> @import_clause.source + +} + +(named_imports)@named_imports { + node @named_imports.after_scope + node @named_imports.before_scope + node @named_imports.source +} + +(named_imports (_)* @specs)@named_imports { + if (is-empty @specs) { + edge @named_imports.after_scope -> @named_imports.before_scope + } +} + +(named_imports + (import_specifier)@import_specifier)@named_imports { + + edge @import_specifier.source -> @named_imports.source +} + +(named_imports + . + (import_specifier)@first_import)@named_imports { + + edge @first_import.before_scope -> @named_imports.before_scope +} + +(named_imports + (import_specifier)@left_import + . + (import_specifier)@right_import) { + + edge @right_import.before_scope -> @left_import.after_scope +} + +(named_imports + (import_specifier)@last_import + .)@named_imports { + + edge @named_imports.after_scope -> @last_import.after_scope +} + +;; Import specifiers have several cases: +;; - the reference into the source module can be default or named +;; - the definition from this module can be default or named. +;; The case where the definition is default instead of named is invalid. +;; +;; We model this using seperate sets of rules, two rules for the reference, +;; and one rules for the definition. The `.def_to_ref` node is used to connect +;; the two. + +(import_specifier)@import_specifier { + node @import_specifier.after_scope + node @import_specifier.before_scope + node @import_specifier.def_to_ref + node @import_specifier.source + + edge @import_specifier.after_scope -> @import_specifier.before_scope +} + +;; Import specifier reference rules + +( + (import_specifier + name:(_)@name + )@import_specifier + + (#not-eq? @name "default") +) { + + node name_push + + attr (name_push) node_reference = @name + edge name_push -> @import_specifier.source + edge @import_specifier.def_to_ref -> name_push + +} + +( + (import_specifier + name:(_)@name + )@import_specifier + + (#eq? @name "default") +) { + + node push_guard_default + + attr (push_guard_default) symbol_reference = "GUARD:DEFAULT", source_node = @name + edge push_guard_default -> @import_specifier.source + edge @import_specifier.def_to_ref -> push_guard_default + +} + +;; Import specifier definition rules + +( [ + (import_specifier + name:(_)@alias + !alias)@import_specifier + (import_specifier + name:(_) + alias:(_)@alias)@import_specifier + ] + + (#not-eq? @alias "default") +) { + + node name_pop + + attr (name_pop) node_definition = @alias, definiens_node = @import_specifier.import_statement + edge name_pop -> @import_specifier.def_to_ref + edge @import_specifier.after_scope -> name_pop + +} + +; (import_statement +; (import_clause +; (named_imports +; (import_specifier +; name:(_)@name +; alias:(_)@alias))) +; source: (_)@mod_name)@import_stmt { +; +; ; scope passes through, augmented by the identifier +; scan @mod_name { +; "\"([^/\"]+)\.js\"$" { +; attr (@mod_name.push) symbol_reference = $1, source_node = @mod_name +; } +; } +; edge @mod_name.push -> @import_stmt.pkg_push +; +; attr (@name) node_reference = @name +; attr (name_push_dot) push_symbol = "GUARD:MEMBER" +; edge name_push_dot -> @mod_name.push +; edge name -> @name_push_dot +; +; attr (@alias) node_definition = @alias +; edge @alias -> @name +; +; edge @import_stmt.after_scope -> @alias +; } + + +; TODO import defaultExport, { export1 [ , [...] ] } from "module-name"; +; TODO import defaultExport, * as name from "module-name"; +; TODO var promise = import("module-name"); + +; import defaultExport from "module-name"; +(import_clause + (identifier)@default_name)@import_clause { + + node default_name_pop + node default_name_push_guard_default + + edge @import_clause.after_scope -> @import_clause.before_scope + + attr (default_name_pop) node_definition = @default_name, definiens_node = @import_clause.import_statement + attr (default_name_push_guard_default) symbol_reference = "GUARD:DEFAULT", source_node = @default_name + edge default_name_pop -> default_name_push_guard_default + edge default_name_push_guard_default -> @import_clause.source + + edge @import_clause.after_scope -> default_name_pop + +} + + + +;; ███ ██ ██████ ██████ ███ ███ █████ ██ +;; ████ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ +;; ██ ██ ██ ██ ██ ██████ ██ ████ ██ ███████ ██ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ██ ████ ██████ ██ ██ ██ ██ ██ ██ ███████ + +;; ███████ ████████ █████ ████████ ███████ ███ ███ ███████ ███ ██ ████████ ███████ +;; ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ +;; ███████ ██ ███████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ███████ ██ ██ ██ ██ ███████ ██ ██ ███████ ██ ████ ██ ███████ + +;; #### Debugger + +(debugger_statement)@debugger_stmt { + ; scopes flow through unchanged + edge @debugger_stmt.after_scope -> @debugger_stmt.before_scope +} + + + +;; #### Expression + +(expression_statement (_)@inner)@expr_stmt { + + ; scopes flow in then back out + edge @inner.before_scope -> @expr_stmt.before_scope + edge @expr_stmt.after_scope -> @inner.after_scope +} + + + +;; #### Declarations + +;; ##### Variable Declarations + +(variable_declaration + (variable_declarator + name:(identifier)@name))@variable_decl +{ + + node @name.pop + attr (@name.pop) node_definition = @name + edge @variable_decl.after_scope -> @name.pop + attr (@variable_decl.after_scope -> @name.pop) precedence = 1 + +} + +(lexical_declaration + (variable_declarator + name:(identifier)@name))@lexical_decl +{ + + node @name.pop + attr (@name.pop) node_definition = @name + edge @lexical_decl.after_scope -> @name.pop + attr (@lexical_decl.after_scope -> @name.pop) precedence = 1 + +} + +(variable_declaration + (variable_declarator + !value))@decl { + + edge @decl.after_scope -> @decl.before_scope +} + +(lexical_declaration + (variable_declarator + !value))@decl { + + edge @decl.after_scope -> @decl.before_scope +} + +(variable_declaration + (variable_declarator + name:(identifier)@name + value:(_)@initializer))@variable_decl +{ + + edge @name.pop -> @initializer.value + edge @initializer.before_scope -> @variable_decl.before_scope + edge @variable_decl.after_scope -> @initializer.after_scope + +} + +(lexical_declaration + (variable_declarator + name:(identifier)@name + value:(_)@initializer))@lexical_decl +{ + + edge @name.pop -> @initializer.value + edge @initializer.before_scope -> @lexical_decl.before_scope + edge @lexical_decl.after_scope -> @initializer.after_scope + attr (@lexical_decl.after_scope -> @initializer.after_scope) precedence = 0 + +} + +(variable_declaration + (variable_declarator + name:[(object_pattern) (array_pattern)]@pat + value:(_)@initializer))@decl { + + edge @initializer.before_scope -> @decl.before_scope + edge @pat.before_scope -> @initializer.after_scope + edge @decl.after_scope -> @pat.after_scope + + edge @pat.covalue -> @initializer.value +} + +(lexical_declaration + (variable_declarator + name:[(object_pattern) (array_pattern)]@pat + value:(_)@initializer))@decl { + + edge @initializer.before_scope -> @decl.before_scope + edge @pat.before_scope -> @initializer.after_scope + edge @decl.after_scope -> @pat.after_scope + + edge @pat.covalue -> @initializer.value +} + + + +;; ##### Function Declarations + +(function_declaration + name:(_)@name + parameters:(_)@call_sig + body:(_)@body)@fun_decl { + + node call_sig_arguments_pop + node call_sig_arguments_push + node call_sig_this_pop + node call_sig_this_push + node @name.pop + node fun_decl_function_value + node @fun_decl.return_or_yield + node @fun_decl.value_arg_scope + node fun_decl_value_call + node fun_decl_value_drop + node fun_decl_value_return + node fun_decl_value_this + node fun_decl_value_this_guard + let @body.closure_point = @body.after_scope + + attr (@name.pop) syntax_type = "function" + + ; scope flows across the decl + edge @fun_decl.after_scope -> @fun_decl.before_scope + + ; with an augmentation for the function + attr (@name.pop) node_definition = @name + edge @fun_decl.hoist_point -> @name.pop + edge @name.pop -> fun_decl_function_value + + ; function values have drop nodes that handle closures, that points to the + ; before scope for the function + attr (fun_decl_value_drop) type = "drop_scopes" + edge fun_decl_value_drop -> @fun_decl.closure_point + + ; the call sig's before scope comes from the drop node then flows into the body + edge @call_sig.before_scope -> fun_decl_value_drop + attr (call_sig_this_pop) symbol_definition = "this", source_node = @call_sig + attr (call_sig_this_push) push_symbol = "this" + edge call_sig_this_pop -> call_sig_this_push + edge call_sig_this_push -> @fun_decl.value_arg_scope + edge @call_sig.before_scope -> call_sig_this_pop + attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig + attr (call_sig_arguments_push) push_symbol = "arguments" + edge call_sig_arguments_pop -> call_sig_arguments_push + edge call_sig_arguments_push -> @fun_decl.value_arg_scope + edge @call_sig.before_scope -> call_sig_arguments_pop + edge @body.before_scope -> @call_sig.after_scope + + + ; function values have call nodes + attr (fun_decl_value_call) pop_scoped_symbol = "()" + edge fun_decl_function_value -> fun_decl_value_call + + ; function values have return nodes which need to be visible for returns + attr (fun_decl_value_return) pop_symbol = "GUARD:RETURN" + edge fun_decl_value_call -> fun_decl_value_return + let @body.return_or_yield = fun_decl_value_return + + ; method values have this nodes which need to be visible for constructor calls + attr (fun_decl_value_this) push_symbol = "this" + attr (fun_decl_value_this_guard) pop_symbol = "GUARD:THIS" + edge fun_decl_value_call -> fun_decl_value_this_guard + edge fun_decl_value_this_guard -> fun_decl_value_this + edge fun_decl_value_this -> @body.after_scope + + ; function values have a jump node that lets params connect up to actual arguments + edge @fun_decl.value_arg_scope -> JUMP_TO_SCOPE_NODE +} + +(function_declaration + parameters: + (formal_parameters (_)@param))@fun_decl { + + node param_arg_index + + ; parameters jump to the pushed argument scope + attr (param_arg_index) push_symbol = (named-child-index @param) + edge @param.covalue -> param_arg_index + edge param_arg_index -> @fun_decl.value_arg_scope +} + + +;; ##### Generator Function Declarations + +(generator_function_declaration + name:(_)@name + parameters:(_)@call_sig + body:(_)@body)@fun_decl { + + node call_sig_arguments_pop + node call_sig_arguments_push + node call_sig_this_pop + node call_sig_this_push + node @name.pop + node fun_decl_function_value + node @fun_decl.return_or_yield + node @fun_decl.value_arg_scope + node fun_decl_value_call + node fun_decl_value_drop + node fun_decl_value_return + node fun_decl_value_this + node fun_decl_value_this_guard + let @body.closure_point = @body.after_scope + + attr (@name.pop) syntax_type = "function" + + ; scope flows across the decl + edge @fun_decl.after_scope -> @fun_decl.before_scope + + ; with an augmentation for the function + attr (@name.pop) node_definition = @name + edge @fun_decl.hoist_point -> @name.pop + edge @name.pop -> fun_decl_function_value + + ; function values have drop nodes that handle closures, that points to the + ; before scope of the declaration + attr (fun_decl_value_drop) type = "drop_scopes" + edge fun_decl_value_drop -> @fun_decl.closure_point + + + ; the call sig's before scope comes from the drop node then flows into the body + edge @call_sig.before_scope -> fun_decl_value_drop + attr (call_sig_this_pop) symbol_definition = "this", source_node = @call_sig + attr (call_sig_this_push) push_symbol = "this" + edge call_sig_this_pop -> call_sig_this_push + edge call_sig_this_push -> @fun_decl.value_arg_scope + edge @call_sig.before_scope -> call_sig_this_pop + attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig + attr (call_sig_arguments_push) push_symbol = "arguments" + edge call_sig_arguments_pop -> call_sig_arguments_push + edge call_sig_arguments_push -> @fun_decl.value_arg_scope + edge @call_sig.before_scope -> call_sig_arguments_pop + edge @body.before_scope -> @call_sig.after_scope + + ; function values have call nodes + attr (fun_decl_value_call) pop_scoped_symbol = "()" + edge fun_decl_function_value -> fun_decl_value_call + + ; function values have return nodes which need to be visible for returns + attr (fun_decl_value_return) pop_symbol = "GUARD:RETURN" + edge fun_decl_value_call -> fun_decl_value_return + let @body.return_or_yield = fun_decl_value_return + + ; method values have this nodes which need to be visible for constructor calls + attr (fun_decl_value_this) push_symbol = "this" + attr (fun_decl_value_this_guard) pop_symbol = "GUARD:THIS" + edge fun_decl_value_call -> fun_decl_value_this_guard + edge fun_decl_value_this_guard -> fun_decl_value_this + edge fun_decl_value_this -> @body.after_scope + + ; function values have a jump node that lets params connect up to actual arguments + edge @fun_decl.value_arg_scope -> JUMP_TO_SCOPE_NODE +} + +(generator_function_declaration + parameters: + (formal_parameters (_)@param))@fun_decl { + + node param_arg_index + + ; parameters jump to the pushed argument scope + attr (param_arg_index) push_symbol = (named-child-index @param) + edge @param.covalue -> param_arg_index + edge param_arg_index -> @fun_decl.value_arg_scope +} + + + +;; #### Classes + +(class_declaration + name:(_)@name + body:(_)@body)@class_decl { + + node @name.pop + node @class_decl.class_value + let @class_decl.containing_class_value = @class_decl.class_value + node guard_prototype + node @class_decl.prototype + node @class_decl.constructor + + attr (@name.pop) syntax_type = "class" + + attr (@name.pop) node_definition = @name + attr (guard_prototype) pop_symbol = "GUARD:PROTOTYPE" + edge @class_decl.after_scope -> @name.pop + edge @name.pop -> @class_decl.class_value + edge @class_decl.class_value -> guard_prototype + edge guard_prototype -> @class_decl.prototype + + edge @body.before_scope -> @class_decl.closure_point + edge @class_decl.prototype -> @body.after_scope + edge @class_decl.after_scope -> @class_decl.before_scope +} + +(class_declaration + (class_heritage (_)@name))@class_decl { + + node guard_prototype + node guard_constructor + attr (guard_prototype) push_symbol = "GUARD:PROTOTYPE" + attr (guard_constructor) push_symbol = "GUARD:CONSTRUCTOR" + edge @name.before_scope -> @class_decl.before_scope + edge @class_decl.prototype -> guard_prototype + edge guard_prototype -> @name.value + edge @class_decl.constructor -> guard_constructor + edge guard_constructor -> @name.value +} + + +(class_body)@class_body { + node @class_body.after_scope + node @class_body.before_scope +} + +(class_body (_)* @decls)@class_body { + if (is-empty @decls) { + edge @class_body.after_scope -> @class_body.before_scope + } +} + +(class_body + . + (_)@first_decl)@class_body { + + edge @first_decl.before_scope -> @class_body.before_scope +} + +(class_body + (_)@left_decl + . + (_)@right_decl) { + + edge @right_decl.before_scope -> @left_decl.after_scope +} + +(class_body + (_)@last_decl + .)@class_body { + + edge @class_body.after_scope -> @last_decl.after_scope +} + +(method_definition name:(_)@name)@method_def { + + node @name.pop + node @method_def.after_scope + node @method_def.before_scope + node @method_def.method_value + +} + +( + (method_definition + name:(_)@name)@method_def + (#eq? @name "constructor") +) { + + ; augmentation for the constructor + attr (@name.pop) symbol_definition = "GUARD:CONSTRUCTOR", source_node = @name + edge @method_def.class_value -> @name.pop + edge @name.pop -> @method_def.constructor + edge @method_def.constructor -> @method_def.method_value +} + +( + (method_definition + name:(_)@name)@method_def + (#not-eq? @name "constructor") +) { + + node name_pop_dot + ; augmentation for the method + attr (@name.pop) node_definition = @name + attr (name_pop_dot) pop_symbol = "GUARD:MEMBER" + edge @method_def.after_scope -> name_pop_dot + edge name_pop_dot -> @name.pop + edge @name.pop -> @method_def.method_value +} + +(method_definition + name:(_)@name + parameters:(_)@call_sig + body:(_)@body)@method_def { + + node call_sig_arguments_pop + node call_sig_arguments_push + node @method_def.method_value_arg_scope + node method_def_method_value_call + node method_def_method_value_drop + node method_def_method_value_return + node method_def_method_value_this + node method_def_method_value_this_guard + + attr (@name.pop) syntax_type = "method" + + ; scope flows across the decl + edge @method_def.after_scope -> @method_def.before_scope + + ; method values have drop nodes that handle closures, that points to the + ; before scope from method def + attr (method_def_method_value_drop) type = "drop_scopes" + edge method_def_method_value_drop -> @method_def.before_scope + + ; the call sig's before scope comes from the drop node then flows into the body + edge @call_sig.before_scope -> method_def_method_value_drop + attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig + attr (call_sig_arguments_push) push_symbol = "arguments" + edge call_sig_arguments_pop -> call_sig_arguments_push + edge call_sig_arguments_push -> @method_def.method_value_arg_scope + edge @call_sig.before_scope -> call_sig_arguments_pop + edge @body.before_scope -> @call_sig.after_scope + + ; method values have call nodes + attr (method_def_method_value_call) pop_scoped_symbol = "()" + edge @method_def.method_value -> method_def_method_value_call + + ; method values have return nodes which need to be visible for returns + attr (method_def_method_value_return) pop_symbol = "GUARD:RETURN" + edge method_def_method_value_call -> method_def_method_value_return + let @body.return_or_yield = method_def_method_value_return + + ; method values have this nodes which need to be visible for constructor calls + attr (method_def_method_value_this) push_symbol = "this" + attr (method_def_method_value_this_guard) pop_symbol = "GUARD:THIS" + edge method_def_method_value_call -> method_def_method_value_this_guard + edge method_def_method_value_this_guard -> method_def_method_value_this + edge method_def_method_value_this -> @body.after_scope + + ; method values have a jump node that lets params connect up to actual arguments + edge @method_def.method_value_arg_scope -> JUMP_TO_SCOPE_NODE +} + +(method_definition + parameters: + (formal_parameters (_)@param))@method_def { + + node param_arg_index + + ; parameters jump to the pushed argument scope + attr (param_arg_index) push_symbol = (named-child-index @param) + edge @param.covalue -> param_arg_index + edge param_arg_index -> @method_def.method_value_arg_scope +} + + + +(field_definition)@field_def { + node @field_def.after_scope + node @field_def.before_scope +} + +(field_definition + property:(_)@property)@field_def { + + node @property.pop + node property_pop_dot + + attr (@property.pop) node_definition = @property + attr (property_pop_dot) pop_symbol = "GUARD:MEMBER" + edge @field_def.after_scope -> property_pop_dot + edge property_pop_dot -> @property.pop +} + +(field_definition + !value)@field_def { + + edge @field_def.after_scope -> @field_def.before_scope +} + +(field_definition + property:(_)@property + value:(_)@value)@field_def { + + edge @value.before_scope -> @field_def.before_scope + edge @field_def.after_scope -> @value.after_scope + edge @property.pop -> @value.value +} + +(class_static_block body: (_) @body) @static_block { + node @static_block.before_scope + node @static_block.after_scope + + edge @body.before_scope -> @static_block.before_scope + + edge @static_block.after_scope -> @static_block.before_scope +} + + + +;; #### Statement Block + +(statement_block)@statement_block { + + node @statement_block.hoist_point + edge @statement_block.before_scope -> @statement_block.hoist_point + +} + +(statement_block (_)* @stmts)@statement_block { + if (is-empty @stmts) { + edge @statement_block.after_scope -> @statement_block.before_scope + } +} + +; statement block, first statement +(statement_block + . + (_)@first_stmt)@block { + + ; scope flows from block to first statement + edge @first_stmt.before_scope -> @block.before_scope +} + +; statement block, between statements +(statement_block + (_)@left_stmt + . + (_)@right_stmt) { + ; scope flows from left to right + edge @right_stmt.before_scope -> @left_stmt.after_scope +} + +; statement block, last statement +(statement_block + (_)@last_stmt + .)@block { + ; scope flows from last statement to block + edge @block.after_scope -> @last_stmt.after_scope +} + + + + + +;; #### If + +(if_statement + consequence:(_)@consequence)@if_stmt { + + let @if_stmt.hoist_point = @consequence.before_scope + +} + +(if_statement condition:(_)@condition)@if_stmt { + ; scopes flow from the if statement to the condition + edge @condition.before_scope -> @if_stmt.before_scope +} + +(if_statement + condition:(_)@condition + consequence:(_)@consequence)@if_stmt +{ + ; scopes flow from the condition to the consequence, then to the if statement + edge @consequence.before_scope -> @condition.after_scope + edge @if_stmt.after_scope -> @consequence.after_scope + +} + +(if_statement + condition:(_)@condition + alternative:(_)@alternative)@if_stmt +{ + ; scopes flow from the condition to the alternative, then to the if statement + edge @alternative.before_scope -> @condition.after_scope + edge @if_stmt.after_scope -> @alternative.after_scope +} + +(else_clause)@else_clause { + node @else_clause.after_scope + node @else_clause.before_scope +} + +(else_clause + . (_)@inner)@else_clause { + + let @else_clause.hoist_point = @inner.before_scope + +} + +(else_clause (_)@inner)@else_clause { + ; scopes flow in and right back out + edge @inner.before_scope -> @else_clause.before_scope + edge @else_clause.after_scope -> @inner.after_scope +} + + + + +;; #### Switch + +(switch_statement + value:(_)@value + body:(switch_body)@body)@switch_stmt +{ + ; scopes flow into the value then into the body then back out to the switch + edge @value.before_scope -> @switch_stmt.before_scope + edge @body.before_scope -> @value.after_scope + edge @switch_stmt.after_scope -> @body.after_scope +} + +(switch_body)@switch_body { + node @switch_body.after_scope + node @switch_body.before_scope + node @switch_body.hoist_point + + edge @switch_body.before_scope -> @switch_body.hoist_point +} + +(switch_body (_)* @choices)@switch_body { + if (is-empty @choices) { + edge @switch_body.after_scope -> @switch_body.before_scope + } +} + +; switch body, first choice +(switch_body + . + (_)@first_choice)@switch_body +{ + ; scopes flow from the body into the first choice + edge @first_choice.before_scope -> @switch_body.before_scope +} + +; switch body, between choices +(switch_body + (_)@left_choice + . + (_)@right_choice) +{ + ; scopes flow left to right + edge @right_choice.before_scope -> @left_choice.after_scope +} + +; switch body, last choice +(switch_body + (_)@last_choice + .)@switch_body +{ + ; scope flows out to the switch body + edge @switch_body.after_scope -> @last_choice.after_scope +} + +(switch_case)@switch_case { + node @switch_case.after_scope + node @switch_case.before_scope +} + +(switch_case (_)* @stmts)@switch_case { + if (is-empty @stmts) { + edge @switch_case.after_scope -> @switch_case.before_scope + } +} + +; switch case, non-empty statements, first statement +(switch_case + value:(_)@value + . + (_)@first_stmt)@switch_case +{ + ; scopes flow into the value then into the first statement + edge @value.before_scope -> @switch_case.before_scope + edge @first_stmt.before_scope -> @value.after_scope +} + +; switch case, non-empty statements, between statement +(switch_case + value:(_) + (_)@left_stmt + . + (_)@right_stmt) +{ + ; scopes flow left to right + edge @right_stmt.before_scope -> @left_stmt.after_scope +} + +; switch case, non-empty statements, last statement +(switch_case + value:(_) + (_)@last_stmt + .)@switch_case { + + ; scopes flow out from the last statement to the case + edge @switch_case.after_scope -> @last_stmt.after_scope +} + +(switch_default)@switch_default { + node @switch_default.after_scope + node @switch_default.before_scope +} + +(switch_default (_)* @defaults)@switch_default { + if (is-empty @defaults) { + edge @switch_default.after_scope -> @switch_default.before_scope + } +} + +; switch default, non-empty statements, first statement +(switch_default + . + (_)@first_stmt)@switch_default +{ + ; scopes flow into the first statement + edge @first_stmt.before_scope -> @switch_default.before_scope +} + +; switch default, non-empty statements, between statements +(switch_default + (_)@left_stmt + . + (_)@right_stmt) +{ + + ; scopes flow left to right + edge @right_stmt.before_scope -> @left_stmt.after_scope +} + +; switch default, non-empty statements, last statement +(switch_default + (_)@last_stmt + .)@switch_default +{ + + ; scopes flow out to the default + edge @switch_default.after_scope -> @last_stmt.after_scope +} + + + +;; #### For + +(for_statement + body:(_)@body)@for_stmt { + + let @for_stmt.hoist_point = @body.before_scope + +} + +(for_statement + initializer:(_)@initializer + condition:(_)@condition + increment:(_)@increment + body:(_)@body)@for_stmt +{ + + ; scopes flow from statement to initializer then test then body then increment + edge @initializer.before_scope -> @for_stmt.before_scope + edge @condition.before_scope -> @initializer.after_scope + edge @body.before_scope -> @condition.after_scope + edge @increment.before_scope -> @body.after_scope + + ; scopes also from from the body back into the condition + edge @condition.before_scope -> @increment.after_scope + + ; scopes also flow from condition out to statement + edge @for_stmt.after_scope -> @condition.after_scope + +} + + + +;; #### For In + +(for_in_statement + body:(_)@body)@for_in_stmt { + + let @for_in_stmt.hoist_point = @body.before_scope + +} + +(for_in_statement + left:(_)@_left + right:(_)@right + body:(_)@body)@for_in_stmt +{ + + ; scopes flow from statement to right then to body then back out + edge @right.before_scope -> @for_in_stmt.before_scope + edge @body.before_scope -> @right.after_scope + edge @for_in_stmt.after_scope -> @body.after_scope +} + +(for_in_statement + left:(identifier)@left + right:(_)@right + body:(_)@body)@_for_in_stmt +{ + + node for_in_stmt_pop + attr (for_in_stmt_pop) node_definition = @left + edge for_in_stmt_pop -> @right.value + edge @body.before_scope -> for_in_stmt_pop + +} + + + +;; #### While + +(while_statement + body:(_)@body)@while_stmt { + + let @while_stmt.hoist_point = @body.before_scope + +} + +(while_statement + condition:(_)@condition + body:(_)@body)@while_stmt +{ + + ; scopes flow from while to condition then to body then back out + edge @condition.before_scope -> @while_stmt.before_scope + edge @body.before_scope -> @condition.after_scope + edge @while_stmt.after_scope -> @body.after_scope + + ; scopes also flow back into the condition + edge @condition.before_scope -> @body.after_scope + +} + + + +;; #### Do + +(do_statement + body:(_)@body)@do_stmt { + + let @do_stmt.hoist_point = @body.before_scope + +} + +(do_statement + body:(_)@body + condition:(_)@condition)@do_stmt +{ + + ; scopes flow from statement to body then condition then back to statement + edge @body.before_scope -> @do_stmt.before_scope + edge @condition.before_scope -> @body.after_scope + edge @do_stmt.after_scope -> @condition.after_scope + + ; scopes also flow back to the body from the condition + edge @body.before_scope -> @condition.after_scope + +} + + + +;; #### Try + +(try_statement + body:(_)@body)@try_stmt +{ + + ; scopes flow into the body then back out + edge @body.before_scope -> @try_stmt.before_scope + edge @try_stmt.after_scope -> @body.after_scope + +} + +(try_statement + body:(_)@body + handler:(_)@handler)@try_stmt +{ + + ; scopes flow from body to handler then back out + edge @handler.before_scope -> @body.after_scope + edge @try_stmt.after_scope -> @handler.after_scope +} + +(try_statement + body:(_)@body + finalizer:(_)@finalizer)@_try_stmt +{ + + ; scopes flow from body to finalizer then back out + edge @finalizer.before_scope -> @body.after_scope + +} + +(try_statement + handler:(_)@handler + finalizer:(_)@finalizer)@try_stmt +{ + + ; scopes flow from handler to finalizer then back out + edge @finalizer.before_scope -> @handler.after_scope + edge @try_stmt.after_scope -> @finalizer.after_scope +} + +(catch_clause body:(_)@body)@catch_clause { + node @catch_clause.after_scope + node @catch_clause.before_scope + ; scopes flow in then back out + edge @body.before_scope -> @catch_clause.before_scope + edge @catch_clause.after_scope -> @body.after_scope +} + +(catch_clause + parameter:(identifier)@name + body:(_)@body)@_catch_clause +{ + + node catch_clause_pop + attr (catch_clause_pop) node_definition = @name + edge @body.before_scope -> catch_clause_pop + +} + +(finally_clause body:(_)@body)@finally_clause { + node @finally_clause.after_scope + node @finally_clause.before_scope + ; scopes flow in thenback out + edge @body.before_scope -> @finally_clause.before_scope + edge @finally_clause.after_scope -> @body.after_scope +} + + + +;; #### With + +(with_statement body:(_)@body)@with_stmt { + + let @with_stmt.hoist_point = @body.before_scope + +} + +(with_statement + object:(_)@object + body:(_)@body)@with_stmt +{ + + node with_stmt_push_dot + + ; scopes flow from the statement into the object then into the body then back out + edge @object.before_scope -> @with_stmt.before_scope + edge @body.before_scope -> @object.after_scope + edge @with_stmt.after_scope -> @body.after_scope + edge @with_stmt.after_scope -> @with_stmt.before_scope + attr (@with_stmt.after_scope -> @with_stmt.before_scope) precedence = 1 + + attr (with_stmt_push_dot) push_symbol = "GUARD:MEMBER" + edge with_stmt_push_dot -> @object.value + edge @body.before_scope -> with_stmt_push_dot + attr (@body.before_scope -> with_stmt_push_dot) precedence = 1 +} + + + +;; #### Break + +(break_statement)@break_stmt { + ; scopes flow through unchanged + edge @break_stmt.after_scope -> @break_stmt.before_scope +} + +(break_statement (_)@label)@break_stmt { + + node break_stmt_label_guard + node break_stmt_label_push + + attr (break_stmt_label_guard) push_symbol = "GUARD:LABEL" + attr (break_stmt_label_push) node_reference = @label + + edge break_stmt_label_push -> break_stmt_label_guard + edge break_stmt_label_guard -> @break_stmt.before_scope +} + + + +;; #### Continue + +(continue_statement)@continue_stmt { + ; scopes flow through unchanged + edge @continue_stmt.after_scope -> @continue_stmt.before_scope +} + +(continue_statement (_)@label)@continue_stmt { + + node continue_stmt_label_guard + node continue_stmt_label_push + + attr (continue_stmt_label_guard) push_symbol = "GUARD:LABEL" + attr (continue_stmt_label_push) node_reference = @label + + edge continue_stmt_label_push -> continue_stmt_label_guard + edge continue_stmt_label_guard -> @continue_stmt.before_scope +} + + + +;; #### Return + +; LATER-TODO tree sitter doesn't let us express empty returns currently +(return_statement)@return_stmt { + ; scopes flow through unchanged + edge @return_stmt.after_scope -> @return_stmt.before_scope +} + +(return_statement + (_)@returned_expr)@return_stmt { + ; scopes flow through the returned expresssion + edge @returned_expr.before_scope -> @return_stmt.before_scope + edge @return_stmt.after_scope -> @returned_expr.after_scope + + ; return statements hook up to the call node of the function value + edge @return_stmt.return_or_yield -> @returned_expr.value +} + + + +;; #### Throw + +(throw_statement (_)@thrown_expr)@throw_stmt { + ; scopes flow through the returned expresssion + edge @thrown_expr.before_scope -> @throw_stmt.before_scope + edge @throw_stmt.after_scope -> @thrown_expr.after_scope +} + + + +;; #### Empty + +(empty_statement)@empty_stmt { + ; scopes flow through unchaged + edge @empty_stmt.after_scope -> @empty_stmt.before_scope +} + + + +;; #### Labeled + +(labeled_statement + label:(_)@label + body:(_)@inner)@labeled_stmt +{ + + node labeled_stmt_label_guard + node labeled_stmt_label_pop + + attr (labeled_stmt_label_guard) pop_symbol = "GUARD:LABEL" + attr (labeled_stmt_label_pop) node_definition = @label + + ; scopes flow through the inner statement then back out + edge @inner.before_scope -> @labeled_stmt.before_scope + edge @inner.before_scope -> labeled_stmt_label_guard + edge labeled_stmt_label_guard -> labeled_stmt_label_pop + edge @labeled_stmt.after_scope -> @inner.after_scope +} + + + + + + + + + + +;; ███████ ██ ██ ██████ ██████ ███████ ███████ ███████ ██ ██████ ███ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ +;; █████ ███ ██████ ██████ █████ ███████ ███████ ██ ██ ██ ██ ██ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ███████ ██ ██ ██ ██ ██ ███████ ███████ ███████ ██ ██████ ██ ████ ███████ + +;; ## Expressions + +;; ### Attributes Defined on Expressions +;; TODO + +[ + (subscript_expression) + (member_expression) + (parenthesized_expression) + (undefined) + (this) + (super) + (number) + (string) + (template_string) + (template_substitution) + (string_fragment) + (escape_sequence) + (regex) + (true) + (false) + (null) + (sequence_expression) + (import) + (object) + (array) + (function_expression) + (arrow_function) + (generator_function) + (class) + (meta_property) + (call_expression) + (assignment_expression) + (augmented_assignment_expression) + (await_expression) + (unary_expression) + (binary_expression) + (ternary_expression) + (update_expression) + (new_expression) + (yield_expression) + (spread_element) +]@expr { + + node @expr.after_scope + node @expr.before_scope + node @expr.value + +} + +[ + (function_expression body:(_)@body) + (arrow_function body:(_)@body) + (generator_function body:(_)@body) +] { + + let @body.closure_point = @body.after_scope + +} + +;; ### Expression Queries + + +;; #### Parenthesized + +(parenthesized_expression (_)@inner)@parens { + ; scopes flow right through + edge @inner.before_scope -> @parens.before_scope + edge @parens.after_scope -> @inner.after_scope + + ; as do values + edge @parens.value -> @inner.value +} + + +;; #### Strings + +(string)@string { + ; scopes don't change + edge @string.after_scope -> @string.before_scope + + ; the value of a string is the string primitive + edge @string.value -> @string.builtins_string +} + + +;; #### Template Strings + +; template_strings w/ no substitutions +(template_string (_)* @parts)@template_string { + if (is-empty @parts) { + edge @template_string.after_scope -> @template_string.before_scope + } +} + +; nonempty template string, value +; LATER-TODO this isn't really right, but it gets flows through the template string +; which may be useful +(template_string (_)@part)@template_string { + ; the value of a template string is a template string value built from the values of its substitutions + ; attr (@template_string.value) "template_string_value" + edge @template_string.value -> @part.value +} + +; nonempty template string, first substitution +(template_string . (_)@first)@template_string { + ; scopes propagate into the first subtitution of the template string + edge @first.before_scope -> @template_string.before_scope +} + +; nonempty template string, between substitutions +(template_string + (_) @left + . + (_) @right) { + ; scopes propagate from left substitutions to right substitutions + edge @right.before_scope -> @left.after_scope +} + +; nonempty template string, last substitution +(template_string (_) @last .)@template_string { + ; scopes propagate out of the last substitution to the template string + edge @template_string.after_scope -> @last.after_scope +} + +[ + (string_fragment) + (escape_sequence) +]@part { + edge @part.after_scope -> @part.before_scope +} + +(template_substitution (_)@expr)@subst { + edge @expr.before_scope -> @subst.before_scope + edge @subst.after_scope -> @expr.after_scope + edge @subst.value -> @expr.value +} + + +;; #### Numbers + +(number)@number { + ; scopes don't change + edge @number.after_scope -> @number.before_scope + + ; the value of a number is the number primitive + edge @number.value -> @number.builtins_number +} + + +;; #### Variables + +[ + (primary_expression/identifier)@variable + (member_expression object:(identifier)@variable) +] { + ; value is a lookup, ie a push + attr (@variable.value) node_reference = @variable + edge @variable.value -> @variable.before_scope +} + + +;; #### Booleans + +(true)@true { + ; scopes don't change + edge @true.after_scope -> @true.before_scope + + ; the value of true is a boolean primitive + edge @true.value -> @true.builtins_boolean +} + +(false)@false { + ; scopes don't change + edge @false.after_scope -> @false.before_scope + + ; the value of false is a boolean primitive + edge @false.value -> @false.builtins_boolean +} + + +;; #### This + +(this)@this { + ; scopes don't change + edge @this.after_scope -> @this.before_scope + + ; this is a lookup, ie a push + attr (@this.value) symbol_reference = "this", source_node = @this + edge @this.value -> @this.before_scope + + node pop_this + attr (pop_this) symbol_definition = "this" + node guard_prototype + attr (guard_prototype) push_symbol = "GUARD:PROTOTYPE" + edge @this.value -> pop_this + edge pop_this -> guard_prototype + edge guard_prototype -> @this.containing_class_value +} + + +;; #### Super + +(super)@super { + ; scopes don't change + edge @super.after_scope -> @super.before_scope +} + + +;; #### Null + +(null)@null { + ; scopes don't change + edge @null.after_scope -> @null.before_scope + + ; the value of null is the null primitive + edge @null.value -> @null.builtins_null +} + + +;; #### Undefined + +(undefined)@undefined { + ; scopes don't change + edge @undefined.after_scope -> @undefined.before_scope + + ; the value of undefined is the undefined primitive + edge @undefined.value -> @undefined.builtins_undefined + + ; !!!! HACK: `undefined` is a perfectly cromulent name for a parameter, but the parser thinks it means this node instead. For the moment, work around the problem this causes by giving all `undefined` nodes covalues like parameters enjoy. + node @undefined.covalue +} + + +;; #### Regular Expressions + +(regex)@regex { + ; scopes don't change + edge @regex.after_scope -> @regex.before_scope + + ; the value of a regex is the Regex prototype + edge @regex.value -> @regex.builtins_Regex_prototype +} + + +;; #### Spread Elements + +(spread_element (_)@expr)@spread_elem { + ; scopes flow in then right back out + edge @expr.before_scope -> @spread_elem.before_scope + edge @spread_elem.after_scope -> @expr.after_scope +} + + +;; #### Objects + +(object)@object { + + node @object.member_pop + attr (@object.member_pop) pop_symbol = "GUARD:MEMBER" + edge @object.value -> @object.member_pop + + node @object.class_value + node @object.constructor + +} + +; empty objects +(object (_)* @entries)@object_expr { + if (is-empty @entries) { + edge @object_expr.after_scope -> @object_expr.before_scope + } +} + +; non-empty objects, scopes, first entry +(object + . + (_)@first_entry)@object_expr { + ; scopes propagate from the object to the first entry + edge @first_entry.before_scope -> @object_expr.before_scope +} + +; non-empty objects, scopes, between entries +(object + (_)@left_entry + . + (_)@right_entry +)@_object_expr { + ; scopes propagate from left entries to right entries + edge @right_entry.before_scope -> @left_entry.after_scope +} + +; non-empty objects, scopes, last entry +(object + (_)@last_entry + .)@object_expr { + ; scopes propagate from the last entry back to the object + edge @object_expr.after_scope -> @last_entry.after_scope +} + +; shorthand property identifier +(shorthand_property_identifier)@shorthand_property_identifier { + + node @shorthand_property_identifier.after_scope + node @shorthand_property_identifier.before_scope + +} + +(object (shorthand_property_identifier)@keyval)@object { + + node rhsvar_before_scope + node rhsvar_after_scope + node rhsvar_value + node key_pop + + attr (rhsvar_value) node_reference = @keyval + attr (key_pop) node_definition = @keyval + + ; scopes flow into rhsvar, and also straight across b/c they can't be modified + edge rhsvar_before_scope -> @keyval.before_scope + edge rhsvar_after_scope -> rhsvar_before_scope + edge @keyval.after_scope -> rhsvar_after_scope + + edge rhsvar_value -> rhsvar_before_scope + + ; shorthand property identifiers augment the object value with a member binding + edge key_pop -> rhsvar_value + edge @object.member_pop -> key_pop +} + +; pairs + +(computed_property_name)@computed_property_name { + node @computed_property_name.after_scope + node @computed_property_name.before_scope +} + +(computed_property_name (_)@expr)@computed_property_name { + + edge @expr.before_scope -> @computed_property_name.before_scope + edge @computed_property_name.after_scope -> @expr.after_scope + +} + +(object + (pair + key:(_)@_key + value: (_)@value)@pair)@object { + + ; pairs augment the object value with a member binding + + ; This is done differently depending on what the key is. See next rules. + ; attr @key.pop "pop" = @key, "definition" + + node @pair.key_pop + edge @pair.key_pop -> @value.value + edge @object.member_pop -> @pair.key_pop + +} + +(pair)@pair { + node @pair.after_scope + node @pair.before_scope +} + +(pair + key:(property_identifier)@key + value:(_)@value)@pair +{ + + attr (@pair.key_pop) node_definition = @key + + ; scopes flow into the value, then back to the pair + edge @value.before_scope -> @pair.before_scope + edge @pair.after_scope -> @value.after_scope + +} + +(pair + key:(string)@key + value:(_)@value)@pair +{ + + node @key.pop + attr (@key.pop) pop_symbol = (replace (source-text @key) "\"" "") + + ; scopes flow into the value, then back to the pair + edge @value.before_scope -> @pair.before_scope + edge @pair.after_scope -> @value.after_scope + +} + +(pair + key:(number)@key + value:(_)@value)@pair +{ + + attr (@pair.key_pop) node_definition = @key + + ; scopes flow into the value, then back to the pair + edge @value.before_scope -> @pair.before_scope + edge @pair.after_scope -> @value.after_scope + +} + +(pair + key:(computed_property_name)@key + value:(_)@value)@pair +{ + + ; scopes flow into the key, then out to the value, then back to the pair + edge @key.before_scope -> @pair.before_scope + edge @value.before_scope -> @key.after_scope + edge @pair.after_scope -> @value.after_scope + +} + + + +;; #### Arrays + +(array)@array_expr { + + node @array_expr.element_pop_dot + attr (@array_expr.element_pop_dot) pop_symbol = "GUARD:MEMBER" + edge @array_expr.value -> @array_expr.element_pop_dot + +} + +; empty arrays +(array (_)* @elems)@array_expr { + if (is-empty @elems) { + edge @array_expr.after_scope -> @array_expr.before_scope + } +} + +; nonempty arrays, first element +(array + . + (_)@first_element)@array_expr { + ; scopes propagate into the first element of the array + edge @first_element.before_scope -> @array_expr.before_scope +} + +; nonempty arrays, between elements +(array + (_)@left_element + . + (_)@right_element) { + ; scopes propagate from left elements to right elements + edge @right_element.before_scope -> @left_element.after_scope +} + +; nonempty arrays, last element +(array + (_)@last_element + .)@array_expr { + ; scopes propagate out of the last element to the array + edge @array_expr.after_scope -> @last_element.after_scope +} + +; elements at indices +(array (_)@element)@array_expr { + + node element_index_pop + + attr (element_index_pop) pop_symbol = (named-child-index @element) + edge @array_expr.element_pop_dot -> element_index_pop + edge element_index_pop -> @element.value + +} + + + +;; #### Formal Parameters + +(formal_parameters)@formal_params { + node @formal_params.after_scope + node @formal_params.before_scope +} + +(formal_parameters (_)* @params)@formal_params { + if (is-empty @params) { + edge @formal_params.after_scope -> @formal_params.before_scope + } +} + +; first parameter +(formal_parameters + . + (_)@first_param)@formal_params { + + edge @first_param.before_scope -> @formal_params.before_scope +} + +; between parameters +(formal_parameters + (_)@left_param + . + (_)@right_param) { + + ; scope flows left to right + edge @right_param.before_scope -> @left_param.after_scope +} + +; last parameter +(formal_parameters + (_)@last_param + .)@formal_params { + + ; scope flows from the param to the call sig + edge @formal_params.after_scope -> @last_param.after_scope +} + + + +;; #### Function Literals + +; functions with names +(function_expression + name:(_)@name + parameters:(_)@call_sig)@fun { + + node @name.pop + attr (@name.pop) syntax_type = "function" + + ; if the function has a name, this is bound the callsig's before scope + attr (@name.pop) node_definition = @name + edge @call_sig.before_scope -> @name.pop + edge @name.pop -> @fun.value +} + + +; function +(function_expression + parameters:(_)@call_sig + body:(_)@body)@fun { + + node call_sig_arguments_pop + node call_sig_arguments_push + node call_sig_this_pop + node call_sig_this_push + node @fun.return_or_yield + node @fun.value_arg_scope + node fun_value_call + node fun_value_drop + node fun_value_return + node fun_value_this + node fun_value_this_guard + + ; scope flows across the decl + edge @fun.after_scope -> @fun.before_scope + + ; function values have drop nodes that handle closures, that points to the + ; before scope from the function + attr (fun_value_drop) type = "drop_scopes" + edge fun_value_drop -> @fun.closure_point + + ; the call sig's before scope comes from the drop node, + ; then flows into the body, and includes a variable binding for "this" + edge @call_sig.before_scope -> fun_value_drop + attr (call_sig_this_pop) symbol_definition = "this", source_node = @call_sig + attr (call_sig_this_push) push_symbol = "this" + edge call_sig_this_pop -> call_sig_this_push + edge call_sig_this_push -> @fun.value_arg_scope + edge @call_sig.before_scope -> call_sig_this_pop + attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig + attr (call_sig_arguments_push) push_symbol = "arguments" + edge call_sig_arguments_pop -> call_sig_arguments_push + edge call_sig_arguments_push -> @fun.value_arg_scope + edge @call_sig.before_scope -> call_sig_arguments_pop + edge @body.before_scope -> @call_sig.after_scope + + ; function values have call nodes + attr (fun_value_call) pop_scoped_symbol = "()" + edge @fun.value -> fun_value_call + + ; function values have return nodes which need to be visible for returns + attr (fun_value_return) pop_symbol = "GUARD:RETURN" + edge fun_value_call -> fun_value_return + let @body.return_or_yield = fun_value_return + + ; function values have this nodes which need to be visible for method calls + attr (fun_value_this) push_symbol = "this" + attr (fun_value_this_guard) pop_symbol = "GUARD:THIS" + edge fun_value_call -> fun_value_this_guard + edge fun_value_this_guard -> fun_value_this + edge fun_value_this -> @body.after_scope + + ; function values have a jump node that lets params connect up to actual arguments + edge @fun.value_arg_scope -> JUMP_TO_SCOPE_NODE +} + +(function_expression + parameters: + (formal_parameters (_)@param))@fun { + + node param_arg_index + + ; parameters jump to the pushed argument scope + attr (param_arg_index) push_symbol = (named-child-index @param) + edge @param.covalue -> param_arg_index + edge param_arg_index -> @fun.value_arg_scope +} + + + +;; #### Arrow Function Literals + +; function +[ + (arrow_function + parameters:(_)@call_sig + body:(_)@body)@fun + + (arrow_function + parameter:(_)@call_sig + body:(_)@body)@fun +] { + + node @fun.return_or_yield + node @fun.value_arg_scope + node fun_value_call + node fun_value_drop + node fun_value_return + node fun_value_this + node fun_value_this_guard + + ; scope flows across the decl + edge @fun.after_scope -> @fun.before_scope + + ; function values have drop nodes that handle closures, that points to the + ; before scope from the function + attr (fun_value_drop) type = "drop_scopes" + edge fun_value_drop -> @fun.closure_point + + ; the call sig's before scope comes from the drop node then flows into the body + edge @call_sig.before_scope -> fun_value_drop + edge @body.before_scope -> @call_sig.after_scope + + ; function values have call nodes + attr (fun_value_call) pop_scoped_symbol = "()" + edge @fun.value -> fun_value_call + + ; function values have return nodes which need to be visible for returns + attr (fun_value_return) pop_symbol = "GUARD:RETURN" + edge fun_value_call -> fun_value_return + edge fun_value_return -> @fun.return_or_yield + + ; function values have this nodes which need to be visible for method calls + attr (fun_value_this) push_symbol = "this" + attr (fun_value_this_guard) pop_symbol = "GUARD:THIS" + edge fun_value_call -> fun_value_this_guard + edge fun_value_this_guard -> fun_value_this + edge fun_value_this -> @body.after_scope + + ; function values have a jump node that lets params connect up to actual arguments + edge @fun.value_arg_scope -> JUMP_TO_SCOPE_NODE +} + +; arrow functions returning exprs need special rules for getting the return value hooked up +(arrow_function + body:(expression)@return_expr) { + edge @return_expr.return_or_yield -> @return_expr.value +} + +(arrow_function + parameter:(_)@param)@fun { + node param_arg_index + node param_pop + + ; but augmented with a pop, b/c it's not a pattern + attr (param_pop) node_definition = @param + edge param_pop -> @param.covalue + edge @param.after_scope -> param_pop + + ; parameters jump to the pushed argument scope + attr (param_arg_index) push_symbol = "0" + edge @param.covalue -> param_arg_index + edge param_arg_index -> @fun.value_arg_scope +} + +(arrow_function + parameters: + (formal_parameters (_)@param))@fun { + + node param_arg_index + + ; parameters jump to the pushed argument scope + attr (param_arg_index) push_symbol = (named-child-index @param) + edge @param.covalue -> param_arg_index + edge param_arg_index -> @fun.value_arg_scope +} + + + +;; #### Generator Function Literals + +; generator functions with names +(generator_function + name:(_)@name + parameters:(_)@call_sig)@fun { + + node @name.pop + attr (@name.pop) syntax_type = "function" + + ; if the function has a name, this is bound the callsig's before scope + attr (@name.pop) node_definition = @name + edge @call_sig.before_scope -> @name.pop + edge @name.pop -> @fun.value +} + + +; generator function +(generator_function + parameters:(_)@call_sig + body:(_)@body)@fun { + + node call_sig_arguments_pop + node call_sig_arguments_push + node call_sig_this_pop + node call_sig_this_push + node @fun.return_or_yield + node @fun.value_arg_scope + node fun_value_call + node fun_value_drop + node fun_value_return + node fun_value_this + node fun_value_this_guard + + ; scope flows across the decl + edge @fun.after_scope -> @fun.before_scope + + ; function values have drop nodes that handle closures, that points to the + ; before scope from the function + attr (fun_value_drop) type = "drop_scopes" + edge fun_value_drop -> @fun.closure_point + + ; the call sig's before scope comes from the drop node, + ; then flows into the body, and includes a variable binding for "this" + edge @call_sig.before_scope -> fun_value_drop + attr (call_sig_this_pop) symbol_definition = "this", source_node = @call_sig + attr (call_sig_this_push) push_symbol = "this" + edge call_sig_this_pop -> call_sig_this_push + edge call_sig_this_push -> @fun.value_arg_scope + edge @call_sig.before_scope -> call_sig_this_pop + attr (call_sig_arguments_pop) symbol_definition = "arguments", source_node = @call_sig + attr (call_sig_arguments_push) push_symbol = "arguments" + edge call_sig_arguments_pop -> call_sig_arguments_push + edge call_sig_arguments_push -> @fun.value_arg_scope + edge @call_sig.before_scope -> call_sig_arguments_pop + edge @body.before_scope -> @call_sig.after_scope + + ; function values have call nodes + attr (fun_value_call) pop_scoped_symbol = "()" + edge @fun.value -> fun_value_call + + ; function values have return nodes which need to be visible for returns + attr (fun_value_return) pop_symbol = "GUARD:RETURN" + edge fun_value_call -> fun_value_return + let @body.return_or_yield = fun_value_return + + ; function values have this nodes which need to be visible for method calls + attr (fun_value_this) push_symbol = "this" + attr (fun_value_this_guard) pop_symbol = "GUARD:THIS" + edge fun_value_call -> fun_value_this_guard + edge fun_value_this_guard -> fun_value_this + edge fun_value_this -> @body.after_scope + + ; function values have a jump node that lets params connect up to actual arguments + edge @fun.value_arg_scope -> JUMP_TO_SCOPE_NODE +} + +(generator_function + parameters: + (formal_parameters (_)@param))@fun { + + node param_arg_index + + ; parameters jump to the pushed argument scope + attr (param_arg_index) push_symbol = (named-child-index @param) + edge @param.covalue -> param_arg_index + edge param_arg_index -> @fun.value_arg_scope +} + + +;; #### Function Calls + +; calls, functions +(call_expression + function:(_)@function + arguments:(_)@arguments)@call_expr { + + ; scopes flow from call expressions into the function + edge @function.before_scope -> @call_expr.before_scope + edge @arguments.before_scope -> @function.after_scope + edge @call_expr.after_scope -> @arguments.after_scope +} + +; calls, values +(call_expression + function:(_)@function + arguments:(_)@arguments)@call_expr { + + node arguments_arg_arguments + node arguments_arg_arguments_dot + node @arguments.arg_scope + attr (@arguments.arg_scope) is_exported + node @arguments.arg_scope_no_this + node @arguments.arg_this + node call_expr_call + node call_expr_return_guard + + ; value is a call, ie a push "()" node w/ "push-scope" @arguments + attr (call_expr_return_guard) push_symbol = "GUARD:RETURN" + attr (call_expr_call) push_scoped_symbol = "()", scope = @arguments.arg_scope + edge @call_expr.value -> call_expr_return_guard + edge call_expr_return_guard -> call_expr_call + edge call_expr_call -> @function.value + + attr (@arguments.arg_this) symbol_definition = "this", source_node = @arguments + edge @arguments.arg_scope -> @arguments.arg_this + + edge @arguments.arg_scope -> @arguments.arg_scope_no_this + + attr (arguments_arg_arguments) symbol_definition = "arguments", source_node = @arguments + attr (arguments_arg_arguments_dot) pop_symbol = "GUARD:MEMBER" + edge @arguments.arg_scope -> arguments_arg_arguments + edge arguments_arg_arguments -> arguments_arg_arguments_dot + edge arguments_arg_arguments_dot -> @arguments.arg_scope_no_this + edge arguments_arg_arguments -> @call_expr.builtins_arguments_prototype +} + +; special case to make `this` bind correctly in calls of the forms `x.f(...)` +; and `x[f](...)` +(call_expression + function:[ + (member_expression object:(_)@object) + (subscript_expression object:(_)@object) + ] + arguments:(_)@arguments) { + + edge @arguments.arg_this -> @object.value +} + + +; TODO this should eventually be removed and replaced with a version that only +; applies to the negation of (member_expression), but that's not supported by +; tree-sitter currently +(call_expression + function: (_)@_function + arguments:(_)@arguments)@call_expr { + + edge @arguments.arg_this -> @call_expr.builtins_null +} +(call_expression + arguments:(arguments (_)@arg)@arguments) { + + node arg_arg_index + attr (arg_arg_index) pop_symbol = (named-child-index @arg) + edge @arguments.arg_scope_no_this -> arg_arg_index + edge arg_arg_index -> @arg.value +} + + + +;; #### Arguments + +(arguments)@arguments { + node @arguments.after_scope + node @arguments.before_scope +} + +(arguments (_)* @args)@arguments { + if (is-empty @args) { + edge @arguments.after_scope -> @arguments.before_scope + } +} + +(arguments + . + (_)@first_arg)@arguments { + + edge @first_arg.before_scope -> @arguments.before_scope +} + +(arguments + (_)@left_arg + . + (_)@right_arg) { + + edge @right_arg.before_scope -> @left_arg.after_scope +} + +(arguments + (_)@last_arg + .)@arguments { + + edge @arguments.after_scope -> @last_arg.after_scope +} + + + +;; #### Property Access + +;; ##### Member Expressions + +(member_expression + object:(_)@object property:(_)@property)@member_expr +{ + + node member_push + node property_push + + ; scopes flow into object then back out + edge @object.before_scope -> @member_expr.before_scope + edge @member_expr.after_scope -> @object.after_scope + + ; value is a member projection on the value of the object ie. a push then push dot + attr (member_push) push_symbol = "GUARD:MEMBER" + attr (property_push) node_reference = @property + edge property_push -> member_push + edge @member_expr.value -> property_push + edge member_push -> @object.value + + ; (member_expression) nodes can occur in patterns + node @member_expr.covalue + node @member_expr.new_bindings +} + +;; ##### Subscript Expressions + +(subscript_expression + object: (_)@object + index: (_)@index)@subscript_expr { + + node @subscript_expr.index_push + node subscript_expr_push_dot + + ; scopes flow left to right + edge @object.before_scope -> @subscript_expr.before_scope + edge @index.before_scope -> @object.after_scope + edge @subscript_expr.after_scope -> @index.after_scope + + ; value is a subscript lookup, ie a push then push dot + attr (subscript_expr_push_dot) push_symbol = "GUARD:MEMBER" + edge subscript_expr_push_dot -> @object.value + + ; this is done differently depending on what the index is + edge @subscript_expr.value -> @subscript_expr.index_push + edge @subscript_expr.index_push -> subscript_expr_push_dot + + ; subscript expressions can appear in array patterns, on the left, and thus require a covalue & bindings + node @subscript_expr.covalue + node @subscript_expr.new_bindings +} + +(subscript_expression + object: (_)@_object + index: (string)@index)@subscript_expr +{ + + attr (@subscript_expr.index_push) symbol_reference = (replace (source-text @index) "[\"\']" ""), source_node = @index + +} + +(subscript_expression + object: (_)@_object + index: (number)@index)@subscript_expr +{ + + attr (@subscript_expr.index_push) node_reference = @index + +} + + + +;; #### Constructor Calls + +(new_expression + constructor:(_)@constructor + arguments:(_)@arguments)@new_expr { + + node @arguments.arg_scope + attr (@arguments.arg_scope) is_exported + node @arguments.arg_this + node constructor_constructor + node new_expr_call + node new_expr_guard_this + + edge @constructor.before_scope -> @new_expr.before_scope + edge @arguments.before_scope -> @constructor.after_scope + edge @new_expr.after_scope -> @arguments.after_scope + + attr (new_expr_call) push_scoped_symbol = "()", scope = @arguments.arg_scope + + ; we guard for constructors for the case where we have a "true" class + attr (constructor_constructor) push_symbol = "GUARD:CONSTRUCTOR" + edge new_expr_call -> constructor_constructor + edge constructor_constructor -> @constructor.value + + ; and also just go right to the value incase we have a function-as-constructor + edge new_expr_call -> @constructor.value + + + + ; value coming from the constructor call + attr (new_expr_guard_this) push_symbol = "GUARD:THIS" + edge @new_expr.value -> new_expr_guard_this + edge new_expr_guard_this -> new_expr_call + + ; value also coming from the prototype + node guard_prototype + attr (guard_prototype) push_symbol = "GUARD:PROTOTYPE" + edge @new_expr.value -> guard_prototype + edge guard_prototype -> @constructor.value + + attr (@arguments.arg_this) symbol_definition = "this", source_node = @arguments + edge @arguments.arg_scope -> @arguments.arg_this + edge @arguments.arg_this -> @new_expr.builtins_empty_object +} + +(new_expression + arguments:(arguments (_)@arg)@arguments) { + + node arg_arg_index + + attr (arg_arg_index) pop_symbol = (named-child-index @arg) + edge @arguments.arg_scope -> arg_arg_index + edge arg_arg_index -> @arg.value +} + + + +;; #### Await + +(await_expression (_)@awaited)@await_expr { + ; scopes flow into the inner expression then back out + edge @awaited.before_scope -> @await_expr.before_scope + edge @await_expr.after_scope -> @awaited.after_scope + + ; value is just propagated up + edge @await_expr.value -> @awaited.value +} + + + +;; #### Update Expressions + +(update_expression argument: (_)@argument)@update_expr { + + node update_expr_pop + ; scope propagates through the operand then is updated by the expr + edge @argument.before_scope -> @update_expr.before_scope + edge @update_expr.after_scope -> @argument.after_scope + ; LATER-TODO this isn't quite right because the update argument can't be an arbitrary expr + ; eg f(x)++ doesn't make any sense, you have to have something more like + ; (update_expression argument: (lvar)@argument) + attr (update_expr_pop) node_definition = @argument + edge @update_expr.value -> @argument.value + edge @update_expr.after_scope -> update_expr_pop + edge update_expr_pop -> @argument.value + +} + + +;; #### Binary Expressions + +(binary_expression left: (_)@left right: (_)@right)@binary_expr { + ; scopes propagate left to right through the operands unchanged by the binop itself + edge @left.before_scope -> @binary_expr.before_scope + edge @right.before_scope -> @left.after_scope + edge @binary_expr.after_scope -> @right.after_scope + + ; value is a binary op value built from the operands + ; LATER-TODO this isn't quite correct but it permits flow through the expression + ; which can be useful + ; attr (@binary_expr.value) "binary_operation_value" + edge @binary_expr.value -> @left.value + edge @binary_expr.value -> @right.value +} + + +;; #### Unary Expressions + +(unary_expression argument: (_)@argument)@unary_expr { + ; scope propagates through the operand + edge @argument.before_scope -> @unary_expr.before_scope + edge @unary_expr.after_scope -> @argument.after_scope + + ; value is a unaryop value built from the operand + ; LATER-TODO this isn't quite correct but it permits flow through the expression + ; which can be useful + ; attr (@unary_expr.value) "unary_operation_value" + edge @unary_expr.value -> @argument.value +} + + + +;; #### Assignment Expressions; + +; scopes on RHS, values +(assignment_expression + left: (_)@_left + right: (_)@right)@assignment_expr { + + ; scopes flow into the RHS then back out to the whole expr, + ; augmented (in subsequent rules) by the LHS + edge @right.before_scope -> @assignment_expr.before_scope + + ; value of the whole thing is value of the RHS + edge @assignment_expr.value -> @right.value +} + +; augmentation of scope via identifiers +(assignment_expression + left: (identifier)@name + right: (_)@right)@assignment_expr { + + node @name.pop + + ; augments the scope by adding a lookup edge, ie. a pop + attr (@name.pop) node_definition = @name + edge @assignment_expr.after_scope -> @name.pop + edge @name.pop -> @right.value + + ; ensure the scope flows through the identifier + edge @assignment_expr.after_scope -> @right.after_scope + +} + +(assignment_expression + left: [ + (member_expression) + (subscript_expression) + ]@left + right: (_)@right)@assignment_expr { + + ; scope flows from LHS into pattern then back to assignment + edge @left.before_scope -> @assignment_expr.before_scope + + ; ensure the scope flows through the identifier + edge @assignment_expr.after_scope -> @right.after_scope + +} + +; assignment to direct fields on `this` +(assignment_expression + left: (member_expression + object:(this)@_this + property:(_)@property) + right: (_)@right)@assignment_expr { + + node property_pop + node this_drop + node this_pop + node this_pop_dot + + ; HACK + attr (this_drop) type = "drop_scopes" + edge this_drop -> this_pop + attr (this_pop) pop_symbol = "this" + attr (this_pop_dot) pop_symbol = "GUARD:MEMBER" + attr (property_pop) node_definition = @property + edge @assignment_expr.after_scope -> this_drop + edge @assignment_expr.after_scope -> this_pop + edge this_pop -> this_pop_dot + edge this_pop_dot -> property_pop + edge property_pop -> @right.value +} + +; augmentation of scope via _destructuring_patterns +(assignment_expression + left: [ + (object_pattern) + (array_pattern) + (assignment_pattern) + ]@left + right: (_)@right)@assignment_expr { + + ; scope flows from LHS into pattern then back to assignment + edge @left.before_scope -> @right.after_scope + edge @assignment_expr.after_scope -> @left.after_scope + +} + + + +;; #### Augmented Assignment Expressions + +(augmented_assignment_expression + left: (_)@_left + right: (_)@right)@augmented_assignment_expr { + + ; scopes flow into the RHS then back out to the whole expr, augmented by the LHS + edge @right.before_scope -> @augmented_assignment_expr.before_scope + edge @augmented_assignment_expr.after_scope -> @right.after_scope +} + +(augmented_assignment_expression + left:(identifier)@left + right:(_)@right)@augmented_assignment_expr { + + node augmented_assignment_expr_pop + node augmented_assignment_expr_push + + ; augment the scope + attr (augmented_assignment_expr_pop) node_definition = @left + attr (augmented_assignment_expr_push) node_reference = @left + edge augmented_assignment_expr_push -> @augmented_assignment_expr.before_scope + edge augmented_assignment_expr_pop -> augmented_assignment_expr_push + edge augmented_assignment_expr_pop -> @right.value + edge @augmented_assignment_expr.after_scope -> augmented_assignment_expr_pop + +} + + +;; #### Comma Operator / Sequence Expressions + +(sequence_expression (_)* @elems)@sequence_expr { + if (is-empty @elems) { + edge @sequence_expr.after_scope -> @sequence_expr.before_scope + } +} + +(sequence_expression . (_)@first)@sequence_expr { + edge @first.before_scope -> @sequence_expr.before_scope +} + +(sequence_expression (_)@left . (_)@right) { + edge @right.before_scope -> @left.after_scope +} + +(sequence_expression (_)@last .)@sequence_expr { + edge @sequence_expr.after_scope -> @last.after_scope + edge @sequence_expr.value -> @last.value +} + + + +;; #### Ternary Expression + +(ternary_expression + condition: (_)@condition + consequence: (_)@consequence + alternative: (_)@alternative)@ternary_expr { + + ; scopes propagate into condition, then into each branch + edge @condition.before_scope -> @ternary_expr.before_scope + edge @consequence.before_scope -> @condition.after_scope + edge @alternative.before_scope -> @condition.after_scope + edge @ternary_expr.after_scope -> @consequence.after_scope + edge @ternary_expr.after_scope -> @alternative.after_scope + + ; value of the whole thing is a conditional value from the operands + edge @ternary_expr.value -> @consequence.value + edge @ternary_expr.value -> @alternative.value +} + + + +;; #### Yield + +(yield_expression (_)@yielded_expr)@yield_expr { + ; scopes flow in to the yielded expression then back out + edge @yielded_expr.before_scope -> @yield_expr.before_scope + edge @yield_expr.after_scope -> @yielded_expr.after_scope + + ; yield expressions hook up to the call node of the function value + edge @yield_expr.return_or_yield -> @yielded_expr.value +} + + + +;; #### Class Expressions + +(class + body:(_)@body)@class { + + node @class.class_value + let @class.containing_class_value = @class.class_value + node guard_prototype + node @class.prototype + node @class.constructor + attr (guard_prototype) pop_symbol = "GUARD:PROTOTYPE" + + edge @body.before_scope -> @class.closure_point + edge @class.value -> @class.class_value + edge @class.class_value -> guard_prototype + edge guard_prototype -> @class.prototype + edge @class.class_value -> @class.constructor + edge @class.prototype -> @body.after_scope + edge @class.after_scope -> @class.before_scope +} + +(class + name:(_)@name + body:(_)@body)@class { + + node @name.pop + attr (@name.pop) syntax_type = "class" + + attr (@name.pop) node_definition = @name + edge @body.before_scope -> @name.pop + edge @name.pop -> @class.value +} + +(class + (class_heritage (_)@name))@class { + + node guard_prototype + node guard_constructor + attr (guard_prototype) push_symbol = "GUARD:PROTOTYPE" + attr (guard_constructor) push_symbol = "GUARD:CONSTRUCTOR" + edge @name.before_scope -> @class.before_scope + edge @class.prototype -> guard_prototype + edge guard_prototype -> @name.value + edge @class.constructor -> guard_constructor + edge guard_constructor -> @name.value + edge @class.after_scope -> @name.after_scope +} + +(jsx_element + open_tag:(_)@open_tag + close_tag:(_)@close_tag + (_)*@children)@jsx_element { + + node @jsx_element.before_scope + node @jsx_element.after_scope + node @jsx_element.value + + edge @open_tag.before_scope -> @jsx_element.before_scope + edge @jsx_element.after_scope -> @close_tag.after_scope + + if (is-empty @children) { + edge @close_tag.before_scope -> @open_tag.after_scope + } + +} + +(jsx_element + open_tag:(_)@open_tag + . + [ + (jsx_text) + (jsx_element) + (jsx_self_closing_element) + (jsx_expression) + ]@first_child +) { + edge @first_child.before_scope -> @open_tag.after_scope +} + +(jsx_element + [ + (jsx_text) + (jsx_element) + (jsx_self_closing_element) + (jsx_expression) + ]@left_child + . + [ + (jsx_text) + (jsx_element) + (jsx_self_closing_element) + (jsx_expression) + ]@right_child +) { + edge @right_child.before_scope -> @left_child.after_scope +} + +(jsx_element + [ + (jsx_text) + (jsx_element) + (jsx_self_closing_element) + (jsx_expression) + ]@last_child + . + close_tag:(_)@close_tag +) { + edge @close_tag.before_scope -> @last_child.after_scope +} + +(jsx_text)@jsx_text { + node @jsx_text.before_scope + node @jsx_text.after_scope + + edge @jsx_text.after_scope -> @jsx_text.before_scope +} + +(jsx_opening_element)@jsx_opening_element { + + node @jsx_opening_element.before_scope + node @jsx_opening_element.after_scope + +} + +(jsx_opening_element + name:(_)@element_name)@jsx_opening_element { + + edge @element_name.before_scope -> @jsx_opening_element.before_scope + +} + +(jsx_opening_element + !name)@jsx_opening_element +{ + + edge @jsx_opening_element.after_scope -> @jsx_opening_element.before_scope + +} + +(jsx_opening_element + name:(_)@element_name + !attribute)@jsx_opening_element +{ + + edge @jsx_opening_element.after_scope -> @element_name.after_scope + +} + +(jsx_opening_element + name:(_)@element_name + . + attribute:(_)@first_attr +) { + + edge @first_attr.before_scope -> @element_name.after_scope + +} + +(jsx_opening_element + attribute:(_)@left_attr + . + attribute:(_)@right_attr +) { + + edge @right_attr.before_scope -> @left_attr.after_scope + +} + +(jsx_opening_element + attribute:(_)@last_attr + .)@jsx_opening_element +{ + + edge @jsx_opening_element.after_scope -> @last_attr.after_scope + +} + +(jsx_attribute (_) . (_)?@attr_value)@jsx_attribute { + + node @jsx_attribute.before_scope + node @jsx_attribute.after_scope + + if none @attr_value { + edge @jsx_attribute.after_scope -> @jsx_attribute.before_scope + } else { + edge @attr_value.before_scope -> @jsx_attribute.before_scope + edge @jsx_attribute.after_scope -> @attr_value.after_scope + } + +} + +(jsx_namespace_name (_) @lhs (_) @rhs)@name { + node @name.before_scope + node @name.after_scope + + edge @lhs.before_scope -> @name.before_scope + edge @rhs.before_scope -> @lhs.after_scope + edge @name.after_scope -> @name.before_scope +} + +(jsx_self_closing_element + name:(_)@element_name)@jsx_self_closing_element { + + node @jsx_self_closing_element.before_scope + node @jsx_self_closing_element.after_scope + node @jsx_self_closing_element.value + + edge @element_name.before_scope -> @jsx_self_closing_element.before_scope + +} + +(jsx_self_closing_element + !name + !attribute)@jsx_self_closing_element +{ + + edge @jsx_self_closing_element.after_scope -> @jsx_self_closing_element.before_scope + +} + +(jsx_self_closing_element + name:(_)@element_name + !attribute)@jsx_self_closing_element +{ + + edge @jsx_self_closing_element.after_scope -> @element_name.after_scope + +} + +(jsx_self_closing_element + name:(_)@element_name + . + attribute:(_)@first_attr +) { + + edge @first_attr.before_scope -> @element_name.after_scope + +} + +(jsx_self_closing_element + attribute:(_)@left_attr + . + attribute:(_)@right_attr +) { + + edge @right_attr.before_scope -> @left_attr.after_scope + +} + +(jsx_self_closing_element + attribute:(_)@last_attr + .)@jsx_self_closing_element +{ + + edge @jsx_self_closing_element.after_scope -> @last_attr.after_scope + +} + +(jsx_expression)@jsx_expression { + + node @jsx_expression.before_scope + node @jsx_expression.after_scope + +} + +(jsx_expression (_)?@child)@jsx_expression { + + if none @child { + edge @jsx_expression.after_scope -> @jsx_expression.before_scope + } else { + edge @child.before_scope -> @jsx_expression.before_scope + edge @jsx_expression.after_scope -> @child.after_scope + } + +} + +(jsx_closing_element)@jsx_closing_element +{ + + node @jsx_closing_element.before_scope + node @jsx_closing_element.after_scope + +} + +(jsx_closing_element + !name)@jsx_closing_element +{ + + edge @jsx_closing_element.after_scope -> @jsx_closing_element.before_scope + +} + +(jsx_closing_element + name:(_)@element_name)@jsx_closing_element +{ + + edge @element_name.before_scope -> @jsx_closing_element.before_scope + edge @jsx_closing_element.after_scope -> @element_name.after_scope + +} + +[ + (jsx_opening_element + name:(identifier)@element_name) + (jsx_self_closing_element + name:(identifier)@element_name) + (jsx_closing_element + name:(identifier)@element_name) +] +{ + scan (source-text @element_name) { + ; standard HTML elements + "^(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|input|ins|kbd|label|legend|li|link|main|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|search|section|select|small|source|span|strike|strong|style|sub|summary|sup|svg|table|tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)$" { + ; do nothing! + } + + ; everything else + "^.+$" { + node element_name_pop + attr (element_name_pop) node_reference = @element_name + edge element_name_pop -> @element_name.before_scope + } + } + +} + + + + +;; ██████ █████ ████████ ████████ ███████ ██████ ███ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ +;; ██████ ███████ ██ ██ █████ ██████ ██ ██ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ██ ██ ██ ██ ██ ███████ ██ ██ ██ ████ ███████ + +;; ## Patterns + +;; Patterns introduce at least two interesting problems to the task of name +;; resolution. On the one hand, patterns by themselves are rich in structure +;; and binding possibilities. On the other, they function to pull apart +;; structure in ways that we would like to make use of in resolving names. Let's +;; look at these in order. + +;; ### Binding Possibilities + +;; If names could only be bound directly either on the LHS of an assignment, or +;; as the formal parameters to a function, or in related places like as +;; arguments to increment or decrement operators, then there would be little +;; trouble saying where a name is bound. But consider a destructuring assignment +;; such as this: + +;; ``````javascript +;; let [x,y,z] = arr; +;; `````` + +;; This assignment has variable names on the LHS of an assignment, sure, but not +;; directly as the LHS. Rather they're buried down inside a pattern. Here they +;; aren't very deep, just one node below the root of the LHS, but they can be +;; arbitrarily far down: + +;; ``````javascript +;; let [x, [y, [z, w], q], r] = arr; +;; `````` + +;; On top of this, patterns in JavaScript permit default assignments, such as + +;; ``````javascript +;; let [x, y = 1] = arr; +;; `````` + +;; These default values can be the result of a computation itself containing +;; variables, such as + +;; ``````javascript +;; let [x, y = 2*z] = arr; +;; `````` + +;; Additionally, those variables referenced in the default value are evaluated +;; *after* the RHS of the assignment. In the following code, `z` is incremented +;; and becomes `2`, and then `x` is assigned to that value. The array on the RHS +;; is only one element long, so the `y` variable gets assigned the default value +;; of `2*z`, which is computed *after* the increment, and so is `2*2` or `4`. + +;; ``````javascript +;; let z = 1; +;; let [x, y = 2*z] = [z++]; +;; `````` + +;; To complicated matters further, the default value can reference the *bound +;; variables to its left*. For instance: + +;; ``````javascript +;; let [x, y = x+1] = arr; +;; `````` + +;; All of this leads to some very interesting and tricky problems for name +;; resolution. The flow of the environment is as follows: First, the environment +;; flows into the RHS of the assignment and is updated by whatever evaluations +;; happen there, then it flows out of the RHS and into the LHS, where it goes +;; through each pattern in the LHS parse tree, in a left-to-right, depth-first +;; traversal. Patterns with no default assignments do nothing to the environment +;; but patterns with a default will pass the environment to the default value, +;; where it may be updated by the evaluation, and then passed further along. +;; Each variable, whether bare or on the left of an assignment, also has to +;; extend the environment because those variables come into scope further along. + +;; ### Structure Decomposition + +;; Let's now look at how patterns decompose structure. Let's consider the effect +;; of an assignment such as this: + +;; ``````javascript +;; let [x,y] = [1,2]; +;; `````` + +;; This assignment binds `x` to `1` and `y` to `2`. It's equivalent to doing + +;; ``````javascript +;; let x = 1, y = 2; +;; `````` + +;; Except, unlike the latter, the array destructuring does not have any obvious +;; pairing up of the names with the expressions that give them their values. +;; Now, perhaps there's some clever hack we can perform for this special case +;; where the RHS is a structure like `[1,2]` where it's manifestly clear out to +;; pair things up, but of course the RHS can come from anywhere. It can come +;; from a local variable assignment: + +;; ``````javascript +;; let arr = [1,2]; +;; let [x,y] = arr; +;; `````` + +;; Or a function call: + +;; ``````javascript +;; function foo(arr) { +;; let [x,y] = arr; +;; ... +;; } +;; foo([1,2]); +;; `````` + +;; Or any other number of places. We would like *all* of these to permit at +;; least *some* amount of name resolution so that we can find that `x` is `1` +;; and `y` is `2`. This should extend also to objects, not just arrays. + +;; The approach we take is to recognize a general pattern of equivalence, which +;; the above double-let assignment is related special case. For arrays, all +;; destructuring assignments of the form + +;; ``````javascript +;; let [..., pat_i, ...] = arr; +;; `````` + +;; are equivalent to + +;; ``````javascript +;; let pat_i = arr[i]; +;; `````` + +;; For any pattern `pat` and expression `arr`. So for instance the simple case +;; of a pattern expression and an array literal + +;; ``````javascript +;; let [x,y] = [1,2]; +;; `````` + +;; is equivalent to a pair of lets + +;; ``````javascript +;; let x = [1,2][0]; +;; let y = [1,2][1]; +;; `````` + +;; Modulo any change in side effects from duplicating the array syntactically, +;; these two are equivalent and this would be a valid code transformation. Or if +;; the pattern were an embedded array destructuring like so: + +;; ``````javascript +;; let [x, [y,z]] = [1, [2,3]]; +;; `````` + +;; then this would be equivalent to + +;; ``````javascript +;; let x = [1, [2,3]][0]; +;; let [y,z] = [1, [2,3]][1]; +;; `````` + +;; And of course the second let here would similarly unfold to be equivalent to +;; a pair of assignments, giving us + +;; ``````javascript +;; let x = [1, [2,3]][0]; +;; let y = [1, [2,3]][1][0]; +;; let z = [1, [2,3]][1][1]; +;; `````` + +;; Similarly for objects, we have the following destructuring equivalence that +;; says an assignment like this: + +;; ``````javascript +;; let {..., k: pat, ...} = obj; +;; `````` + +;; is equivalent to + +;; ``````javascript +;; let pat = obj[k]; +;; `````` + +;; for all patterns `pat` and expressions `obj`. + +;; This lets us then conclude that whatever the graphs are that we generate for +;; patterns ought to be equivalent to the graphs we would general for using +;; array indexing and object indexing. Since array and object indexing are fully +;; capable of participating in name resolution, if we can achieve this +;; equivalence, patterns can also fully participate as well, and we'll be able +;; to say that the assignment `let [x,y] = [1,2]` yields the name `x` resolving +;; to `1` and `y` to `2`, as well as many many more complicated cases. + +;; As mentioned in the intro to this doc, assignments point to values. The +;; simplest way to do this, in the absence of patterns, is just to have an edge +;; from the after scope of the assignment to a pop node for the variable, +;; and then to the value node of the RHS, so for `let x = 1;` it'd be like so: + +;; `````` +;; after_scope ---> POP "x" ---> value_node_for_1 +;; `````` + +;; But the above discussion of destructuring complicates this. What we do to +;; address this is introduce the notion of a "covalue". Just as we can say that +;; an expression *has* a value, or produces a value, etc., we'll say that a +;; pattern *consumes* a value. Expressions have values going "out", while +;; patterns have values coming "in". And so like expressions have an associated +;; `value` node in the graph, patterns have an associated `covalue` node. The +;; covalue corresponds to an incoming value. + +;; We build covalues similar to how we build values. Consider the value for an +;; array such as `["foo", "bar"]`, which will be a scope node with pop nodes +;; going out to the values of the strings, like so: + +;; `````` +;; ,---> POP 0 ---> value_node_of_foo +;; value_node_of_the_array ---| +;; `---> POP 1 ---> value_node_of_bar +;; `````` + +;; Similarly, a covalue for an array pattern will also have nodes for the +;; sub-patterns and nodes for the first and second indexes. But rather than pop +;; nodes, which show you where the 0th and 1st elements are, they'll be push +;; nodes to establish the lookup of those elements. The edges will therefore +;; go the other way around. So for a pattern like `[x,y]`, we have the graph + +;; `````` +;; ,--- PUSH 0 <--- covalue_node_of_x +;; covalue_node_of_the_pattern <---| +;; `--- PUSH 1 <--- covalue_node_of_y +;; `````` + +;; For readers familiar with category theory's notion of duality, this explains +;; why these are called "covalues". The general schema here is that where values +;; have pops, covalues have pushes, and all the arrows get flipped. + +;; ### Attributes Defined on Patterns +;; TODO +[ + (assignment_pattern)@pattern + (object_pattern)@pattern + (array_pattern)@pattern + (rest_pattern)@pattern + (pair_pattern)@pattern + (pattern/property_identifier)@pattern + (object_assignment_pattern)@pattern + (shorthand_property_identifier_pattern)@pattern +] { + + node @pattern.after_scope + node @pattern.before_scope + node @pattern.covalue + node @pattern.new_bindings + +} + +;; ### Pattern Queries + +;; #### Variable Patterns + +; scope propagation through identifier patterns +(pattern/identifier)@ident_pat { + + node ident_pat_pop + + ; scope flows through, binding via a pop edge that goes to an unknown value + attr (ident_pat_pop) node_definition = @ident_pat + edge ident_pat_pop -> @ident_pat.covalue + edge @ident_pat.after_scope -> ident_pat_pop + + edge @ident_pat.new_bindings -> ident_pat_pop +} + + +;; #### Object Patterns + +(object_pattern (_)* @entries)@object_pat { + if (is-empty @entries) { + edge @object_pat.after_scope -> @object_pat.before_scope + } +} + +; scope propagation through object patterns, first entry +(object_pattern + . + (_)@first_entry)@object_pat { + + ; scope propagates from object pattern to entry + edge @first_entry.before_scope -> @object_pat.before_scope +} + +; scope propagation through object patterns, between entries +(object_pattern + (_)@left_entry + . + (_)@right_entry) { + + ; scope propagates from left entry to right entry + edge @right_entry.before_scope -> @left_entry.after_scope +} + +; scope propagation through object patterns, last entry +(object_pattern + (_)@last_entry + .)@object_pat { + + ; scope propagates out from last entry to object pattern + edge @object_pat.after_scope -> @last_entry.after_scope +} + +; covalue propagation through object patterns +(object_pattern + (_)@entry)@object_pat { + + ; covalues flow into entries unchanged + edge @entry.covalue -> @object_pat.covalue + + edge @object_pat.new_bindings -> @entry.new_bindings +} + +; object entry pair patterns +(pair_pattern + key:(_)@key + value:(_)@value_pat)@pair_pat { + + node @key.push + node key_push_dot + + ; covalues flow in dotted + attr (key_push_dot) push_symbol = "GUARD:MEMBER" + edge @value_pat.covalue -> @key.push + edge @key.push -> key_push_dot + edge key_push_dot -> @pair_pat.covalue + ; scope flows into value pattern then back out + edge @value_pat.before_scope -> @pair_pat.before_scope + edge @pair_pat.after_scope -> @value_pat.after_scope + + edge @pair_pat.new_bindings -> @value_pat.new_bindings +} + +(pair_pattern + key:(property_identifier)@key)@_pair_pattern { + + attr (@key.push) node_reference = @key +} + +(pair_pattern + key:(string)@key)@_pair_pattern { + + attr (@key.push) symbol_reference = (replace (source-text @key) "\"" ""), source_node = @key +} + +; LATER-TODO the left pattern has to be a name, it cant be another pattern +; object entry assignment patterns +(object_assignment_pattern + left:(_)@left_pat + right:(_)@right_expr)@object_assignment_pat { + + ; scope flows both THROUGH and AROUND the RHS, because it's a + ; by-passable default not a guaranteed value + + ; here we go around + edge @left_pat.before_scope -> @object_assignment_pat.before_scope + + ; and here we go through + edge @right_expr.before_scope -> @object_assignment_pat.before_scope + edge @left_pat.before_scope -> @right_expr.after_scope + + ; and in either case we come out the LHS + edge @object_assignment_pat.after_scope -> @left_pat.after_scope + + ; covalues flow both in from the outside and also from the right expression + edge @left_pat.covalue -> @object_assignment_pat.covalue + edge @left_pat.covalue -> @right_expr.value + + edge @object_assignment_pat.new_bindings -> @left_pat.new_bindings + +} + +(shorthand_property_identifier_pattern)@shorthand_prop_pat { + node pat_pop + node pat_push + node pat_push_dot + + edge @shorthand_prop_pat.after_scope -> @shorthand_prop_pat.before_scope + + attr (pat_push) node_reference = @shorthand_prop_pat + attr (pat_push_dot) push_symbol = "GUARD:MEMBER" + attr (pat_pop) node_definition = @shorthand_prop_pat + edge pat_pop -> pat_push + edge pat_push -> pat_push_dot + edge pat_push_dot -> @shorthand_prop_pat.covalue + edge @shorthand_prop_pat.after_scope -> pat_pop + + edge @shorthand_prop_pat.new_bindings -> pat_pop +} + + +;; #### Array Patterns + +(array_pattern (_)* @pats)@array_pat { + if (is-empty @pats) { + edge @array_pat.after_scope -> @array_pat.before_scope + } +} + +; scope propagation through array patterns, first element +(array_pattern + . + (_)@first_el_pat)@array_pat { + + ; scope flows into the first element + edge @first_el_pat.before_scope -> @array_pat.before_scope +} + +; scope propagation through array patterns, between element +(array_pattern + (_)@left_el_pat + . + (_)@right_el_pat) { + + ; scope flows from left to right + edge @right_el_pat.before_scope -> @left_el_pat.after_scope +} + +; scope propagation through array patterns, last element +(array_pattern + (_)@last_el_pat + .)@array_pat { + + ; scope flow out from the last element + edge @array_pat.after_scope -> @last_el_pat.after_scope +} + +; array pattern +(array_pattern)@array_pat { + node @array_pat.element_index_push_dot + edge @array_pat.element_index_push_dot -> @array_pat.covalue + attr (@array_pat.element_index_push_dot) push_symbol = "GUARD:MEMBER" +} + +; array pattern elements +(array_pattern (_)@element_pat)@array_pat { + + node element_pat_element_index_push + + attr (element_pat_element_index_push) push_symbol = (named-child-index @element_pat) + edge @element_pat.covalue -> element_pat_element_index_push + edge element_pat_element_index_push -> @array_pat.element_index_push_dot + + edge @array_pat.new_bindings -> @element_pat.new_bindings +} + + +;; #### Assignment Patterns + +; scope propagation through assignment patterns +(assignment_pattern + left:(_)@left_pat + right:(_)@right_expr)@assignment_pat { + + ; scope flows both THROUGH and AROUND the RHS, because it's a + ; by-passable default not a guaranteed value + + ; here we go around + edge @left_pat.before_scope -> @assignment_pat.before_scope + + ; and here we go through + edge @right_expr.before_scope -> @assignment_pat.before_scope + edge @left_pat.before_scope -> @right_expr.after_scope + + ; the pattern's covalue is the whole thing's, and also the RHS + edge @left_pat.covalue -> @assignment_pat.covalue + edge @left_pat.covalue -> @right_expr.value + + ; and in either case we come out the LHS + edge @assignment_pat.after_scope -> @left_pat.after_scope + + edge @assignment_pat.new_bindings -> @left_pat.new_bindings +} + + +;; #### Rest Patterns + +(rest_pattern (_)@name)@rest_pat { + node rest_pat_pop + ; scope flows through, binding via a pop edge that goes to an unknown value + + attr (rest_pat_pop) node_definition = @name + edge @rest_pat.after_scope -> @rest_pat.before_scope + edge @rest_pat.after_scope -> rest_pat_pop +} + + + + + + + + + + +;; ███████ ██████ ███████ ██████ ██ █████ ██ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ███████ ██████ █████ ██ ██ ███████ ██ +;; ██ ██ ██ ██ ██ ██ ██ ██ +;; ███████ ██ ███████ ██████ ██ ██ ██ ███████ + +;; ## Special Cases +;; +;; There are a number of annoying features that libraries make use of to +;; effectively add features to JavaScript. While they don't technically change +;; the language in any way, they're broad design patterns that are meant to be +;; used *as if* these things were more language level than not. These often make +;; it hard to do analysis without actually running code, and so instead, we +;; define some special case queries that treat these techniques as if they were +;; indeed core features of the language. +;; +;; ### Extend +;; +;; The extend method is a mass assignment of values to keys on objects and gets +;; used a bunch for building module export objects. We special case it here so +;; that we can do lookup on it because the extend method itself is dependent on +;; state and mutability, and has no good analytical explanation within the +;; Stack Graph formalism. +;; +;; Since we can't extend the actual value, but only the syntactic references to +;; it in the SG formalism, we treat extend as a kind of shadowing binder, +;; similar to how we treat `+=` or `*=`. + +( + (call_expression + function: (member_expression + object: (identifier)@object + property: (_)@_extend) + arguments: (arguments (object)@new_fields))@call_expr + (#eq? @_extend "extend") +) { + + node object_pop + attr (object_pop) node_definition = @object + edge @call_expr.after_scope -> object_pop + edge object_pop -> @new_fields.value + +} + +;; ### CommonJS-style Exports + +;; CommonJS introduced an export style for pre-ES6 JavaScript that permitted +;; modules to export functions using an exports object bound to a top-level +;; variable `exports`. For instance, to export something as `foo`, we would do: + +;; ``````javascript +;; exports.foo = 1; +;; `````` + +;; If we then imported with `require`, the exports object would have `foo` as +;; a field. Alternatively, we can also specify the entire export object, using + +;; ``````javascript +;; module.exports = my_exported_object; +;; `````` + +( + (assignment_expression + left: [ + ( ; exports.foo = ... + (member_expression + object:(_)@exports + property:(_)@property) + (#eq? @exports "exports") + ) + ( ; module.exports.foo = ... + (member_expression + object:(member_expression + object:(_)@_module + property:(_)@exports) + property:(_)@property) + (#eq? @_module "module") + (#eq? @exports "exports") + ) + ] + right: (_)@right)@assignment_expr + +) { + + node pop_default_guard + node pop_dot + node @assignment_expr.pop_name + + attr (pop_default_guard) symbol_definition = "GUARD:DEFAULT", source_node = @exports + edge @assignment_expr.exports -> pop_default_guard + + attr (pop_dot) pop_symbol = "GUARD:MEMBER" + edge pop_default_guard -> pop_dot + + attr (@assignment_expr.pop_name) node_definition = @property + attr (@assignment_expr.pop_name) definiens_node = @assignment_expr + edge pop_dot -> @assignment_expr.pop_name + edge @assignment_expr.pop_name -> @right.value + + ;; For ES6 interoperability, expose members as named exports + edge @assignment_expr.exports -> @assignment_expr.pop_name + + node detour_push + node @assignment_expr.detour_pop + + scan FILE_PATH { + + "^(.+/)?([^/]+)/index\.js$" { + let module_name = $2 + attr (detour_push) push_symbol = module_name + attr (@assignment_expr.detour_pop) symbol_definition = module_name, source_node = @assignment_expr, definiens_node = @assignment_expr + edge pop_default_guard -> detour_push + edge detour_push -> @assignment_expr.detour_pop + edge @assignment_expr.detour_pop -> @right.value + } + + "^(.+/)?([^/]+)\.js$" { + let module_name = $2 + attr (detour_push) push_symbol = module_name + attr (@assignment_expr.detour_pop) symbol_definition = module_name, source_node = @assignment_expr, definiens_node = @assignment_expr + edge pop_default_guard -> detour_push + edge detour_push -> @assignment_expr.detour_pop + edge @assignment_expr.detour_pop -> @right.value + } + + } + + node default_detour_push + node @assignment_expr.default_detour_pop + + attr (default_detour_push) push_symbol = "default" + attr (@assignment_expr.default_detour_pop) symbol_definition = "default", source_node = @assignment_expr, definiens_node = @assignment_expr + edge pop_default_guard -> default_detour_push + edge default_detour_push -> @assignment_expr.default_detour_pop + edge @assignment_expr.default_detour_pop -> @right.value + +} + +( + (assignment_expression + left: (member_expression + object:(_)@_module + property:(_)@exports) + right: (_)@right)@assignment_expr + (#eq? @_module "module") + (#eq? @exports "exports") +) { + + node @assignment_expr.pop_default_guard + node pop_dot + + attr (@assignment_expr.pop_default_guard) symbol_definition = "GUARD:DEFAULT", source_node = @exports + attr (@assignment_expr.pop_default_guard) definiens_node = @assignment_expr + edge @assignment_expr.exports -> @assignment_expr.pop_default_guard + edge @assignment_expr.pop_default_guard -> @right.value + + ;; For ES6 interoperability, expose members as named exports + attr (pop_dot) pop_symbol = "GUARD:MEMBER" + edge @assignment_expr.exports -> pop_dot + edge pop_dot -> @right.value + + node detour_push + node @assignment_expr.detour_pop + + scan FILE_PATH { + + "^(.+/)?([^/]+)/index\.js$" { + let module_name = $2 + attr (detour_push) push_symbol = module_name + attr (@assignment_expr.detour_pop) symbol_definition = module_name, source_node = @assignment_expr, definiens_node = @assignment_expr + edge @assignment_expr.pop_default_guard -> detour_push + edge detour_push -> @assignment_expr.detour_pop + edge @assignment_expr.detour_pop -> @right.value + } + + "^(.+/)?([^/]+)\.js$" { + let module_name = $2 + attr (detour_push) push_symbol = module_name + attr (@assignment_expr.detour_pop) symbol_definition = module_name, source_node = @assignment_expr, definiens_node = @assignment_expr + edge @assignment_expr.pop_default_guard -> detour_push + edge detour_push -> @assignment_expr.detour_pop + edge @assignment_expr.detour_pop -> @right.value + } + + } + + node default_detour_push + node @assignment_expr.default_detour_pop + + attr (default_detour_push) push_symbol = "default" + attr (@assignment_expr.default_detour_pop) symbol_definition = "default", source_node = @assignment_expr, definiens_node = @assignment_expr + edge @assignment_expr.pop_default_guard -> default_detour_push + edge default_detour_push -> @assignment_expr.default_detour_pop + edge @assignment_expr.default_detour_pop -> @right.value + +} + +;; ### CommonJS-style Imports + +;; Similar to exports, CommonJS also defines a way to do imports. In general, +;; these look like `require(expr)`, but in practice the expression is a string +;; constant, which is the only case we handle. + +( + (call_expression + function:(identifier)@_require + arguments:(arguments (string)@source))@call_expr + (#eq? @_require "require") +) { + + node default_guard_push + + attr (default_guard_push) symbol_reference = "GUARD:DEFAULT", source_node = @source + edge @call_expr.value -> default_guard_push + edge default_guard_push -> @source.exports + +} + +;; ### Dynamic Imports + +;; Both ES6 and CommonJS modules can be imported using an import function. +;; In general, these look like `import(expr)`, but in practice the expression +;; is a string constant, which is the only case we handle. +;; +;; The return value of the import function is an object whose properties are +;; the exports of the module. The default export is assigned to the `default` +;; property. +;; +;; The import function is async and returns a promise. Since we do not support +;; async functions and promises, we only support the case where the function +;; is called as `await import(...)`. + +( + (await_expression + (call_expression + function:(_)@_import + arguments:(arguments (string)@source)) + )@await_expr + (#eq? @_import "import") +) { + + node pop_dot + node pop_default + node push_guard_default + + attr (pop_dot) pop_symbol = "GUARD:MEMBER" + edge @await_expr.value -> pop_dot + edge pop_dot -> @source.exports + + attr (pop_default) pop_symbol = "default" + edge pop_dot -> pop_default + + attr (push_guard_default) symbol_reference = "GUARD:DEFAULT", source_node = @source + edge pop_default -> push_guard_default + edge push_guard_default -> @source.exports + +} + +;; ### ES6 and CommonJS interoperability + +;; Nodes supports some interoperability between ES6 and CommonJS modules. +;; +;; A CommonJS module can be imported in an ES6 module: +;; +;; - `import foo from "cjs_module"` binds `foo` to the value of `module.exports`. +;; - `import { foo } from "cjs_module"` binds `foo` to the value of `module.exports.foo`. +;; - `import("cjs_module")` returns (a promise to) an object with +;; - property `default` bound to the value of `module.exports`, and +;; - other properties bound to the value of those properties in `module.exports` +;; (e.g., `foo` binds `module.exports.foo`). +;; +;; A ES6 module can be import in an CommonJS module: +;; +;; - `import("es6_module")` returns (a promise to) an object with +;; - property default bound to the value of `export default`, and +;; - other properties bound to named exports (e.g., `foo` binds `export { foo }`). +;; - `require("es6_module")` is not supported. +;; +;; References: +;; +;; - https://nodejs.org/api/esm.html#interoperability-with-commonjs +;; + + + + + + + +;; ██████ ███████ ███████ ██ ███ ██ ██ ███████ ███ ██ ███████ +;; ██ ██ ██ ██ ██ ████ ██ ██ ██ ████ ██ ██ +;; ██ ██ █████ █████ ██ ██ ██ ██ ██ █████ ██ ██ ██ ███████ +;; ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +;; ██████ ███████ ██ ██ ██ ████ ██ ███████ ██ ████ ███████ + +;; ## Definiens Rules + +;; These rules explain how defined names relate to syntactic definitions +;; of various forms. Sometimes that's declared function names mapping to +;; the entire function declaration, and sometimes that's variables in an +;; assignment being mapped to the thing it's assigned to. The purpose of +;; these is not to augment stack graphs, per se, but to permit syntax +;; oriented tools that need to know about approximate call graphs, etc. + +;; ### Basic Definiens Rules + +;; These rules are all about declarations and terms that have the names +;; directly in them. + +( + (class_declaration + name:(_)@name + body:(class_body + member:(method_definition + name:(_)@_method_name)@constructor + ) + ) + + (#eq? @_method_name "constructor") +) { + + attr (@name.pop) definiens_node = @constructor + +} + +(function_declaration + name:(_)@name + parameters:(_)@_call_sig + body:(_)@_body)@fun_decl { + + attr (@name.pop) definiens_node = @fun_decl + +} + +(generator_function_declaration + name:(_)@name + parameters:(_)@_call_sig + body:(_)@_body)@fun_decl { + + attr (@name.pop) definiens_node = @fun_decl + +} + +(method_definition + name:(_)@name + parameters:(_)@_call_sig + body:(_)@_body)@method_def { + + attr (@name.pop) definiens_node = @method_def + +} + +(function_expression + name:(_)@name + parameters:(_)@_call_sig)@fun { + + attr (@name.pop) definiens_node = @fun + +} + +(generator_function + name:(_)@name + parameters:(_)@_call_sig)@fun { + + attr (@name.pop) definiens_node = @fun + +} + +( + (class + name:(_)@name + body:(class_body + member:(method_definition + name:(_)@_method_name)@constructor + ) + ) + + (#eq? @_method_name "constructor") +) { + + attr (@name.pop) definiens_node = @constructor + +} + +;; ### Assignment-like Rules + +;; These rules make up for the fact that JavaScript permits way more +;; kinds of definitions/declarations than just those that show up in +;; syntactic declarations of the thing in question. + +;; These rules are currently way less precise than we would like but +;; do provide at least some information about definiens for these +;; kinds of definitions. + +(assignment_expression + left: (identifier)@left + right: (_))@assignment_expr { + + attr (@left.pop) definiens_node = @assignment_expr + +} + +( + (assignment_expression + left: (member_expression + object:(identifier)@_object + property:(_)@left + ) + right: (_))@assignment_expr + (#not-eq? @_object "module") + (#not-eq? @left "exports") +) { + + node left_definiens_hook + node left_ignore_guard + + attr (left_ignore_guard) pop_symbol = "GUARD:GANDALF" + attr (left_definiens_hook) node_definition = @left + attr (left_definiens_hook) definiens_node = @assignment_expr + edge @assignment_expr.pkg_pop -> left_ignore_guard + edge left_ignore_guard -> left_definiens_hook +} + +( + (assignment_expression + left: (member_expression + object:(identifier)@_object + property:(_)@left + ) + right: (_))@assignment_expr + (#eq? @_object "module") + (#eq? @left "exports") +) { + + node left_definiens_hook + node left_ignore_guard + + attr (left_ignore_guard) pop_symbol = "GUARD:GANDALF" + attr (left_definiens_hook) node_definition = @left + attr (left_definiens_hook) definiens_node = @assignment_expr + edge @assignment_expr.pkg_pop -> left_ignore_guard + edge left_ignore_guard -> left_definiens_hook + +} + +(variable_declaration + (variable_declarator + name:(identifier)@name))@decl { + + attr (@name.pop) definiens_node = @decl + +} + +(lexical_declaration + (variable_declarator + name:(identifier)@name))@decl { + + attr (@name.pop) definiens_node = @decl + +} + +[ + (variable_declaration + (variable_declarator + name:(identifier)@name + value: [ + (function_expression) + (generator_function) + (arrow_function) + ])) + (lexical_declaration + (variable_declarator + name:(identifier)@name + value: [ + (function_expression) + (generator_function) + (arrow_function) + ])) + (assignment_expression + left: [ + (identifier)@name + ; (member_expression property:(_)@name) ; FIXME member expressions are references and have no .pop + ] + right: [ + (function_expression) + (generator_function) + (arrow_function) + ]) +] { + + attr (@name.pop) syntax_type = "function" + +} + +( + (assignment_expression + left: [ + ( ; exports.foo = ... + (member_expression + object:(_)@_exports + property:(_)@_property) + (#eq? @_exports "exports") + ) + ( ; module.exports.foo = ... + (member_expression + object:(member_expression + object:(_)@_module + property:(_)@_exports) + property:(_)@_property) + (#eq? @_module "module") + (#eq? @_exports "exports") + ) + ] + right: [ + (function_expression) + (generator_function) + (arrow_function) + ])@assignment_expr + +) { + + attr (@assignment_expr.pop_name) syntax_type = "function" + attr (@assignment_expr.detour_pop) syntax_type = "function" + attr (@assignment_expr.default_detour_pop) syntax_type = "function" + +} + +( + (assignment_expression + left: (member_expression + object:(_)@_module + property:(_)@_exports) + right: [ + (function_expression) + (generator_function) + (arrow_function) + ])@assignment_expr + (#eq? @_module "module") + (#eq? @_exports "exports") +) { + + attr (@assignment_expr.pop_default_guard) syntax_type = "function" + attr (@assignment_expr.detour_pop) syntax_type = "function" + attr (@assignment_expr.default_detour_pop) syntax_type = "function" + +} + +(export_statement "default" + value:[ + (function_expression) + (generator_function) + (arrow_function) + ])@export_stmt { + + attr (@export_stmt.pop_guard_default) syntax_type = "function" + attr (@export_stmt.detour_pop) syntax_type = "function" + attr (@export_stmt.default_detour_pop) syntax_type = "function" + +} + +(pair + key: (_)@name + value: [ + (function_expression) + (generator_function) + (arrow_function) + ]) { + + attr (@name.definiens_hook) syntax_type = "function" + +} + + +[ + (variable_declaration + (variable_declarator + name:(identifier)@name + value: (class))) + (lexical_declaration + (variable_declarator + name:(identifier)@name + value: (class))) + (assignment_expression + left: [ + (identifier)@name + ; (member_expression property:(_)@name) ; FIXME member expressions are references and have no .pop + ] + right: (class)) +] { + + attr (@name.pop) syntax_type = "class" + +} + +( + (assignment_expression + left: [ + ( ; exports.foo = ... + (member_expression + object:(_)@_exports + property:(_)@_property) + (#eq? @_exports "exports") + ) + ( ; module.exports.foo = ... + (member_expression + object:(member_expression + object:(_)@_module + property:(_)@_exports) + property:(_)@_property) + (#eq? @_module "module") + (#eq? @_exports "exports") + ) + ] + right: (class))@assignment_expr + +) { + + attr (@assignment_expr.pop_name) syntax_type = "class" + attr (@assignment_expr.detour_pop) syntax_type = "class" + attr (@assignment_expr.default_detour_pop) syntax_type = "class" + +} + +( + (assignment_expression + left: (member_expression + object:(_)@_module + property:(_)@_exports) + right: (class))@assignment_expr + (#eq? @_module "module") + (#eq? @_exports "exports") +) { + + attr (@assignment_expr.pop_default_guard) syntax_type = "class" + attr (@assignment_expr.detour_pop) syntax_type = "class" + attr (@assignment_expr.default_detour_pop) syntax_type = "class" + +} + +(export_statement "default" + value:(class))@export_stmt { + + attr (@export_stmt.pop_guard_default) syntax_type = "class" + attr (@export_stmt.detour_pop) syntax_type = "class" + attr (@export_stmt.default_detour_pop) syntax_type = "class" + +} + +(pair + key: (_)@name + value: (class)) { + + attr (@name.definiens_hook) syntax_type = "class" + +} + +(pair + key: (_)@name + value: (_))@pair_expr { + + node @name.definiens_hook + node name_ignore_guard + + attr (name_ignore_guard) pop_symbol = "GUARD:GANDALF" + attr (@name.definiens_hook) node_definition = @name + attr (@name.definiens_hook) definiens_node = @pair_expr + edge @pair_expr.pkg_pop -> name_ignore_guard + edge name_ignore_guard -> @name.definiens_hook + +} diff --git a/languages/tree-sitter-stack-graphs-javascript/test/base_syntax.js b/languages/tree-sitter-stack-graphs-javascript/test/base_syntax.js new file mode 100644 index 000000000..4cf3170d0 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/base_syntax.js @@ -0,0 +1,107 @@ +#!/usr/bin/node + +// foo + +export let x = 1; +export { + // x + A +}; +import "foo"; +debugger; +var x; +let x; +let x = { + get constructor() {} +}; +function foo() { } +function foo(a) { } +function foo(undefined) { } +function* foo() { } +class Foo { + #x = null; + get [/**/ foo]() {} + static {} +} +@Foo class Bar { } +@Foo.Quux class Bar { } +@Foo() class Bar { } +@Foo.Quux() class Bar { } +{ } +if (true) { } +if (true) { } else { } +if (/**/ true) /**/ { } else /**/ { } +if (true) return; +if (true) return; else return; +if (/**/ true) /**/ return; else /**/ return; +switch (x) { } +for (x; y; z) { } +for (x in xs) { } +while (x) { } +do { } while (x); +try { throw x; } catch (e) { } +with (x) { } +break; +continue; +return; +return x; +; +foo: x; + +(5); +"foo"; +`foo`; +`f${"o"}o` +5; +x; +true; +false; +this; +super; +null; +undefined; +/foo/; +({}); +{ + foo: "bar" +}; +[]; +[1, 2, 3]; +function () { return; }; +function () { return function () { }; }; +() => { }; +() => () => { }; +function* () { yield 1; }; +function (/**/) { }; +function (x /**/) { }; +function (/**/ x) { }; +foo(); +foo(bar); +foo.bar; +foo[bar]; +new Foo(); +new Foo(bar); +await foo; +x++; +--x; +1 + 1; +-2; +(x = 1); +(x[i] = 1); +([x] = 1); +({ x: y } = 1); +({/**/x: y } = 1); +({x:y.z} = 1); +x += 1; +(1, 2); +1 ? 2 : 3; +class { }; + + + <>doo + {garply} + { } + {/**/x} +; +; +; \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/array_index_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/array_index_flow.js new file mode 100644 index 000000000..88492c222 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/array_index_flow.js @@ -0,0 +1,10 @@ +let x = 1; +let arr = [0, x]; +let y = arr[0]; +let z = arr[1]; + +/**/ y; +// ^ defined: 3 + +/**/ z; +// ^ defined: 1, 4 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/assignment_destructuring_array_pattern.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/assignment_destructuring_array_pattern.js new file mode 100644 index 000000000..cab97c11d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/assignment_destructuring_array_pattern.js @@ -0,0 +1,10 @@ +let arr = [ + 1, + { + x: 2 + } +]; +let [num, obj] = arr; + +obj.x; +// ^ defined: 4 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/assignment_destructuring_object_pattern.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/assignment_destructuring_object_pattern.js new file mode 100644 index 000000000..0d1bdae26 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/assignment_destructuring_object_pattern.js @@ -0,0 +1,16 @@ +let obj = { + x: 1, + y: { + x: 2 + } +}; +let { + x: num, + y: obj2 +} = obj; + +/**/ obj2; +// ^ defined: 3, 9 + +obj2.x; +// ^ defined: 4 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_constructor_field.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_constructor_field.js new file mode 100644 index 000000000..3861e37c9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_constructor_field.js @@ -0,0 +1,9 @@ +class Foo { + constructor() { + this.bar = 5; + } +}; + +let x = new Foo(); +x.bar; +// ^ defined: 3 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_field_definitions.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_field_definitions.js new file mode 100644 index 000000000..bd59589aa --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_field_definitions.js @@ -0,0 +1,20 @@ +class Foo { + bar; + baz = 1; + static quux; + static doo = 1; +} + +let obj = new Foo(); + +obj.bar; +// ^ defined: 2 + +obj.baz; +// ^ defined: 3 + +obj.quux; +// ^ defined: 4 + +obj.doo; +// ^ defined: 5 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_fields_and_methods_not_visible_outside_class.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_fields_and_methods_not_visible_outside_class.js new file mode 100644 index 000000000..7a8101f2c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_fields_and_methods_not_visible_outside_class.js @@ -0,0 +1,14 @@ +class Foo { + bar = 1; + baz() { + + } +} + +Foo.bar; +// ^ defined: +// bar should not be visible here + +Foo.baz; +// ^ defined: +// baz should not be visible here \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_fields_and_methods_visible_in_other_methods.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_fields_and_methods_visible_in_other_methods.js new file mode 100644 index 000000000..3bed1c415 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_fields_and_methods_visible_in_other_methods.js @@ -0,0 +1,23 @@ +class Foo { + bar = 1; + baz() { } + quux() { + this.bar; + // ^ defined: 2 + + this.baz(); + // ^ defined: 3 + } +} + +(class { + bar = 1; + baz() { } + quux() { + this.bar; + // ^ defined: 14 + + this.baz(); + // ^ defined: 15 + } +}); \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_argument_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_argument_flow.js new file mode 100644 index 000000000..8392a7f73 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_argument_flow.js @@ -0,0 +1,25 @@ +let obj = { + x: 1 +}; + + + +class Foo { + // method declaration + meth_1(o) { + return o; + } + + // generator method declaration + * gen_meth_1(o) { + yield o; + } +} + +let foo = new Foo(); + +foo.meth_1(obj).x; +// ^ defined: 2 + +foo.gen_meth_1(obj).x; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_arguments_variable_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_arguments_variable_flow.js new file mode 100644 index 000000000..d21008426 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_arguments_variable_flow.js @@ -0,0 +1,25 @@ +let obj = { + x: 1 +}; + + + +class Foo { + // method declaration + meth_1() { + return arguments; + } + + // generator method declaration + * gen_meth_1() { + yield arguments; + } +} + +let foo = new Foo(); + +foo.meth_1(obj)[0].x; +// ^ defined: 2 + +foo.gen_meth_1(obj)[0].x; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_closure_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_closure_flow.js new file mode 100644 index 000000000..1b129f7d9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_closure_flow.js @@ -0,0 +1,25 @@ +let obj = { + x: 1 +}; + + + +class Foo { + // method declaration + meth_1() { + return obj; + } + + // generator method declaration + * gen_meth_1() { + yield obj; + } +} + +let foo = new Foo(); + +foo.meth_1().x; +// ^ defined: 2 + +foo.gen_meth_1().x; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_constant_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_constant_flow.js new file mode 100644 index 000000000..05b73e602 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_method_call_constant_flow.js @@ -0,0 +1,23 @@ +class Foo { + // method declaration + meth_1() { + return { + x: 1 + }; + } + + // generator method declaration + * gen_meth_1() { + yield { + x: 1 + }; + } +} + +let foo = new Foo(); + +foo.meth_1().x; +// ^ defined: 5 + +foo.gen_meth_1().x; +// ^ defined: 12 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_methods_visible_on_instances.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_methods_visible_on_instances.js new file mode 100644 index 000000000..d02aa0823 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_methods_visible_on_instances.js @@ -0,0 +1,9 @@ +class Foo { + bar() { + + } +} + +let obj = new Foo(); +obj.bar; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_super_reference_to_method.js.skip b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_super_reference_to_method.js.skip new file mode 100644 index 000000000..31b2628ab --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_super_reference_to_method.js.skip @@ -0,0 +1,15 @@ +class Foo { + bar() { + + } +} + +class Baz extends Foo { + bar() { + + } + quux() { + super.bar; + // ^ defined: 2 + } +} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_superclass_field_definition.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_superclass_field_definition.js new file mode 100644 index 000000000..12f894d72 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_superclass_field_definition.js @@ -0,0 +1,22 @@ +class Foo { + bar; + baz = 1; + static quux; + static doo = 1; +} + +class Garply extends Foo { } + +let obj = new Garply(); + +obj.bar; +// ^ defined: 2 + +obj.baz; +// ^ defined: 3 + +obj.quux; +// ^ defined: 4 + +obj.doo; +// ^ defined: 5 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_superclass_methods_visible_on_instances.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_superclass_methods_visible_on_instances.js new file mode 100644 index 000000000..3cfe06241 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_superclass_methods_visible_on_instances.js @@ -0,0 +1,11 @@ +class Foo { + bar() { + + } +} + +class Baz extends Foo { } + +let obj = new Baz(); +obj.bar; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_this_field_and_method.js.skip b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_this_field_and_method.js.skip new file mode 100644 index 000000000..bf891fa27 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_declaration_this_field_and_method.js.skip @@ -0,0 +1,12 @@ +class Foo { + bar; + baz() { + + } + quux() { + this.bar; + // ^ defined: 2 + this.baz; + // ^ defined: 3 + } +} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_constructor_field.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_constructor_field.js new file mode 100644 index 000000000..9caaf5ada --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_constructor_field.js @@ -0,0 +1,9 @@ +let Foo = class { + constructor() { + this.bar = 5; + } +}; + +let x = new Foo(); +x.bar; +// ^ defined: 3 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_field_definition.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_field_definition.js new file mode 100644 index 000000000..38d00377f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_field_definition.js @@ -0,0 +1,20 @@ +let Foo = class { + bar; + baz = 1; + static quux; + static doo = 1; +}; + +let obj = new Foo(); + +obj.bar; +// ^ defined: 2 + +obj.baz; +// ^ defined: 3 + +obj.quux; +// ^ defined: 4 + +obj.doo; +// ^ defined: 5 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_fields_and_methods_not_visible_outside_class.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_fields_and_methods_not_visible_outside_class.js new file mode 100644 index 000000000..4b02b50be --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_fields_and_methods_not_visible_outside_class.js @@ -0,0 +1,14 @@ +let Foo = class { + bar = 1; + baz() { + + } +}; + +Foo.bar; +// ^ defined: +// bar should not be visible here + +Foo.baz; +// ^ defined: +// baz should not be visible here \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_argument_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_argument_flow.js new file mode 100644 index 000000000..06725bc9d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_argument_flow.js @@ -0,0 +1,25 @@ +let obj = { + x: 1 +}; + + + +let Foo = class { + // method declaration + meth_1(o) { + return o; + } + + // generator method declaration + * gen_meth_1(o) { + yield o; + } +}; + +let foo = new Foo(); + +foo.meth_1(obj).x; +// ^ defined: 2 + +foo.gen_meth_1(obj).x; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_arguments_variable_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_arguments_variable_flow.js new file mode 100644 index 000000000..6a04b5900 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_arguments_variable_flow.js @@ -0,0 +1,25 @@ +let obj = { + x: 1 +}; + + + +let Foo = class { + // method declaration + meth_1() { + return arguments; + } + + // generator method declaration + * gen_meth_1() { + yield arguments; + } +}; + +let foo = new Foo(); + +foo.meth_1(obj)[0].x; +// ^ defined: 2 + +foo.gen_meth_1(obj)[0].x; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_closure_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_closure_flow.js new file mode 100644 index 000000000..a155cab18 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_closure_flow.js @@ -0,0 +1,25 @@ +let obj = { + x: 1 +}; + + + +let Foo = class { + // method declaration + meth_1() { + return obj; + } + + // generator method declaration + * gen_meth_1() { + yield obj; + } +}; + +let foo = new Foo(); + +foo.meth_1().x; +// ^ defined: 2 + +foo.gen_meth_1().x; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_constant_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_constant_flow.js new file mode 100644 index 000000000..0177a7e94 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_method_call_constant_flow.js @@ -0,0 +1,23 @@ +let Foo = class { + // method declaration + meth_1() { + return { + x: 1 + }; + } + + // generator method declaration + * gen_meth_1() { + yield { + x: 1 + }; + } +}; + +let foo = new Foo(); + +foo.meth_1().x; +// ^ defined: 5 + +foo.gen_meth_1().x; +// ^ defined: 12 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_methods_visible_on_instances.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_methods_visible_on_instances.js new file mode 100644 index 000000000..6137bc781 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_methods_visible_on_instances.js @@ -0,0 +1,9 @@ +let Foo = class { + bar() { + + } +}; + +let obj = new Foo(); +obj.bar; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_super_reference_to_method.js.skip b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_super_reference_to_method.js.skip new file mode 100644 index 000000000..a9edf537b --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_super_reference_to_method.js.skip @@ -0,0 +1,15 @@ +let Foo = class { + bar() { + + } +}; + +let Bar = class extends Foo { + bar() { + + } + quux() { + super.bar; + // ^ defined: 2 + } +}; \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_superclass_field_definition.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_superclass_field_definition.js new file mode 100644 index 000000000..69cf71afd --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_superclass_field_definition.js @@ -0,0 +1,22 @@ +let Foo = class { + bar; + baz = 1; + static quux; + static doo = 1; +}; + +let Garply = class extends Foo { }; + +let obj = new Garply(); + +obj.bar; +// ^ defined: 2 + +obj.baz; +// ^ defined: 3 + +obj.quux; +// ^ defined: 4 + +obj.doo; +// ^ defined: 5 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_superclass_methods_visible_on_instances.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_superclass_methods_visible_on_instances.js new file mode 100644 index 000000000..305302523 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_superclass_methods_visible_on_instances.js @@ -0,0 +1,11 @@ +let Foo = class { + bar() { + + } +}; + +let Baz = class extends Foo { }; + +let obj = new Baz(); +obj.bar; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_this_field_and_method.js.skip b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_this_field_and_method.js.skip new file mode 100644 index 000000000..ba8fd3879 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/class_expression_this_field_and_method.js.skip @@ -0,0 +1,12 @@ +let Foo = class { + bar; + baz() { + + } + quux() { + this.bar; + // ^ defined: 2 + this.baz; + // ^ defined: 3 + } +}; \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_argument_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_argument_flow.js new file mode 100644 index 000000000..e9f45c3bd --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_argument_flow.js @@ -0,0 +1,74 @@ +let obj = { + x: 1 +}; + + + +// function declaration +function func_1(o) { + return o; +} + +func_1(obj).x; +// ^ defined: 2 + + + +// generator function declaration +function* gen_func_1(o) { + yield o; +} + +gen_func_1(obj).x; +// ^ defined: 2 + + + +// assigned function +let func_2 = function (o) { + return o; +} + +func_2(obj).x; +// ^ defined: 2 + + + +// assigned generator function +let gen_func_2 = function* (o) { + yield o; +} +gen_func_2(obj).x; +// ^ defined: 2 + + + +// assigned single-param expression-body arrow function +let func_3 = o => o; + +func_3(obj).x; +// ^ defined: 2 + + + +// assigned multi-param expression-body arrow function +let func_4 = (o, p) => o; + +func_4(obj, 1).x; +// ^ defined: 2 + + + +// assigned single-param statement-body arrow function +let func_5 = o => { return o; }; + +func_5(obj).x; +// ^ defined: 2 + + + +// assigned multi-param statement-body arrow function +let func_6 = (o, p) => { return o; }; + +func_6(obj, 1).x; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_arguments_variable_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_arguments_variable_flow.js new file mode 100644 index 000000000..0b89b5b2d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_arguments_variable_flow.js @@ -0,0 +1,79 @@ +let obj = { + x: 1 +}; + + + +// function declaration +function func_1() { + return arguments; +} + +func_1(obj)[0].x; +// ^ defined: 2 + + + +// generator function declaration +function* gen_func_1() { + yield arguments; +} + +gen_func_1(obj)[0].x; +// ^ defined: 2 + + + +// assigned function +let func_2 = function () { + return arguments; +}; + +func_2(obj)[0].x; +// ^ defined: 2 + + + +// assigned generator function +let gen_func_2 = function* () { + yield arguments; +}; + +gen_func_2(obj)[0].x; +// ^ defined: 2 + + + +// assigned single-param expression-body arrow function +let func_3 = o => arguments; + +func_3(obj)[0].x; +// ^ defined: +// arrow functions have no `arguments`! + + + +// assigned multi-param expression-body arrow function +let func_4 = () => arguments; + +func_4(obj, 1)[0].x; +// ^ defined: +// arrow functions have no `arguments`! + + + +// assigned single-param statement-body arrow function +let func_5 = o => { return arguments; }; + +func_5(obj)[0].x; +// ^ defined: +// arrow functions have no `arguments`! + + + +// assigned multi-param statement-body arrow function +let func_6 = () => { return arguments; }; + +func_6(obj, 1)[0].x; +// ^ defined: +// arrow functions have no `arguments`! \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_closure_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_closure_flow.js new file mode 100644 index 000000000..4867c77d2 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_closure_flow.js @@ -0,0 +1,74 @@ +let obj = { + x: 1 +}; + + + +// function declaration +function func_1() { + return obj; +} + +func_1().x; +// ^ defined: 2 + + + +// generator function declaration +function* gen_func_1() { + yield obj; +} + +gen_func_1().x; +// ^ defined: 2 + + + +// assigned function +let func_2 = function () { + return obj; +} + +func_2().x; +// ^ defined: 2 + + + +// assigned generator function +let gen_func_2 = function* () { + yield obj; +} +gen_func_2().x; +// ^ defined: 2 + + + +// assigned single-param expression-body arrow function +let func_3 = y => obj; + +func_3().x; +// ^ defined: 2 + + + +// assigned multi-param expression-body arrow function +let func_4 = () => obj; + +func_4().x; +// ^ defined: 2 + + + +// assigned single-param statement-body arrow function +let func_5 = y => { return obj; }; + +func_5().x; +// ^ defined: 2 + + + +// assigned multi-param statement-body arrow function +let func_6 = () => { return obj; }; + +func_6().x; +// ^ defined: 2 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_constant_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_constant_flow.js new file mode 100644 index 000000000..93fcaa84e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_constant_flow.js @@ -0,0 +1,88 @@ +// function declaration +function func_1() { + return { + x: 1 + }; +} + +func_1().x; +// ^ defined: 4 + + + +// generator function declaration +function* gen_func_1() { + yield { + x: 1 + }; +} + +gen_func_1().x; +// ^ defined: 16 + + + +// assigned function +let func_2 = function () { + return { + x: 1 + }; +} + +func_2().x; +// ^ defined: 28 + + + +// assigned generator function +let gen_func_2 = function* () { + yield { + x: 1 + }; +} +gen_func_2().x; +// ^ defined: 40 + + + +// assigned single-param expression-body arrow function +let func_3 = o => ({ + x: 1 +}); + +func_3(1).x; +// ^ defined: 50 + + + +// assigned multi-param expression-body arrow function +let func_4 = () => ({ + x: 1 +}); + +func_4().x; +// ^ defined: 60 + + + +// assigned single-param statement-body arrow function +let func_5 = o => { + return { + x: 1 + }; +}; + +func_5(1).x; +// ^ defined: 71 + + + +// assigned multi-param statement-body arrow function +let func_6 = () => { + return { + x: 1 + }; +}; + +func_6().x; +// ^ defined: 83 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_this_flow.js.skip b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_this_flow.js.skip new file mode 100644 index 000000000..f3854b9ad --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_this_flow.js.skip @@ -0,0 +1,16 @@ +function foo() { + return this; +} + +foo().x; +// ^ defined: + +foo.apply({ + x: 1 +}).x +// ^ defined: 9 + +foo.call({ + x: 1 +}).x +// ^ defined: 14 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_this_keyword_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_this_keyword_flow.js new file mode 100644 index 000000000..bd5e41c66 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_call_this_keyword_flow.js @@ -0,0 +1,22 @@ +function foo() { + this.x = 1; +} + +let obj = new foo(); + +obj.x; +// ^ defined: 2 + + + +function bar(y) { + this.z = y; +} + +let obj_2 = new bar({ + w: 1 +}); + +obj_2.z.w; +// ^ defined: 17 +// ^ defined: 12, 13 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_closure_mutation.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_closure_mutation.js new file mode 100644 index 000000000..4b8c1a200 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/function_closure_mutation.js @@ -0,0 +1,43 @@ +let x = 1; + +function foo() { + /**/ x; + // ^ defined: 1, 38 +} + +function* foo() { + /**/ x; + // ^ defined: 1, 38 +} + +function () { + /**/ x; + // ^ defined: 1, 38 +}; + +function* () { + /**/ x; + // ^ defined: 1, 38 +}; + +() => x; +// ^ defined: 1, 38 + +() => { + /**/ x; + // ^ defined: 1, 38 +}; + +class C { + foo() { + /**/ x; + // ^ defined: 1, 38 + } +} + +x = 2; + +(function bar() { + /**/ bar; + // ^ defined: 40 +}); \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/member_object_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/member_object_flow.js new file mode 100644 index 000000000..b8ec9ce73 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/member_object_flow.js @@ -0,0 +1,7 @@ +let x = 0; +let obj = { + foo: x +}; + +obj.foo; +// ^ defined: 1, 3 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/object_computed_property_name_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/object_computed_property_name_flow.js new file mode 100644 index 000000000..c9ee50c43 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/object_computed_property_name_flow.js @@ -0,0 +1,8 @@ +let x = 0; +let obj = { + ["foo"]: x +}; +let y = obj["foo"]; + +/**/ y; +// TODO defined: 1, 3, 5 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/object_shorthand_property_identifier_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/object_shorthand_property_identifier_flow.js new file mode 100644 index 000000000..77c85e823 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/object_shorthand_property_identifier_flow.js @@ -0,0 +1,8 @@ +let x = 1; + +let obj = { + x +}; + +obj.x; +// ^ defined: 1, 4 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/subscript_array_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/subscript_array_flow.js new file mode 100644 index 000000000..21071da2f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/subscript_array_flow.js @@ -0,0 +1,6 @@ +let x = 0; +let arr = [0, x]; +let x2 = arr[1]; + +/**/ x2; +// ^ defined: 1, 3 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/subscript_object_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/subscript_object_flow.js new file mode 100644 index 000000000..c3d5fc209 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/subscript_object_flow.js @@ -0,0 +1,8 @@ +let x = 0; +let obj = { + foo: x +}; +let y = obj["foo"]; + +/**/ y; +// ^ defined: 1, 3, 5 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/with_statement_binding_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/with_statement_binding_flow.js new file mode 100644 index 000000000..15ab7f6c3 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/with_statement_binding_flow.js @@ -0,0 +1,4 @@ +with ({ x: 1 }) { + /**/ x; + // ^ defined: 1 +} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/with_statement_scope_shadowing_flow.js b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/with_statement_scope_shadowing_flow.js new file mode 100644 index 000000000..d6b31f760 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/computation_flow/with_statement_scope_shadowing_flow.js @@ -0,0 +1,9 @@ +let x = 1; + +with ({ x: 2 }) { + /**/ x; + // ^ defined: 3 +} + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/array.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/array.js new file mode 100644 index 000000000..c80b26d79 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/array.js @@ -0,0 +1,20 @@ +let x = 1; + +// Flow in + +[0, x]; +// ^ defined: 1 + +// Flow out + +[y = 1, + 0, y]; +// ^ defined: 10 + +/**/ y; +// ^ defined: 10 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/arrow_function.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/arrow_function.js new file mode 100644 index 000000000..8aa55f72b --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/arrow_function.js @@ -0,0 +1,33 @@ +// +// READ ME +// +// This test does NOT test complex arguments. See the `binding` test dir. +// + +let x = 1; + +// Flow In + +() => x; +// ^ defined: 7 + +() => { x }; +// ^ defined: 7 + +// Flow Out + +() => y = 1; + +/**/ y; +// ^ defined: +// y should not be defined here + +// Flow Around + +/**/ x; +// ^ defined: 7 + +// Flow In from Arg +y => + y + 1; +// ^ defined: 31 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/assignment_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/assignment_expression.js new file mode 100644 index 000000000..d03c690cc --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/assignment_expression.js @@ -0,0 +1,17 @@ +let x = 1; +let y = 2; +let z = 3; + +// Flow in +z = x; +// ^ defined: 1 + +// Flow around, update, and out + +x = (y = 3); + +/**/ x; +// ^ defined: 1, 11 + +/****/ y; +// ^ defined: 2, 11 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/augmented_assignment_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/augmented_assignment_expression.js new file mode 100644 index 000000000..b8c31a7f8 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/augmented_assignment_expression.js @@ -0,0 +1,14 @@ +let x = 1; +let y = 2; +let z = 3; + +// Flow in +z += x; +// ^ defined: 1 + +// Flow around and out + +y += 1; + +/**/ y; +// ^ defined: 2, 11 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/await.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/await.js new file mode 100644 index 000000000..0a0d43e06 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/await.js @@ -0,0 +1,17 @@ +let x = 1; + +// Flow in +await x; +// ^ defined: 1 + +// Flow around + +/**/ x; +// ^ defined: 1 + +// Flow out + +await y = 1; + +/**/ y; +// ^ defined: 14 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/binary_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/binary_expression.js new file mode 100644 index 000000000..88da3d14a --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/binary_expression.js @@ -0,0 +1,18 @@ +let x = 1; + +// Flow in +/**/ x + x; +// ^ defined: 1 +// ^ defined: 1 + +// Flow around + +/**/ x; +// ^ defined: 1 + +// Flow out + +x + (y = 1); + +/**/ y; +// ^ defined: 15 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/booleans.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/booleans.js new file mode 100644 index 000000000..bf227b43c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/booleans.js @@ -0,0 +1,9 @@ +let x = 1; + +// Flow around + +true; +false; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/call_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/call_expression.js new file mode 100644 index 000000000..c659699ea --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/call_expression.js @@ -0,0 +1,24 @@ +let x = 1; +let f = 2; + +// Flow in +/**/ f(x); +// ^ defined: 1 +// ^ defined: 2 + +// Flow around + +/**/ x; +// ^ defined: 1 + +// Flow out + +(y = 1)( + z = 2 +); + +/**/ y; +// ^ defined: 16 + +/**/ z; +// ^ defined: 17 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/class.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/class.js new file mode 100644 index 000000000..5786cffe5 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/class.js @@ -0,0 +1,33 @@ +let x = 1; + +// Flow In + +(class extends x { + // ^ defined: 1 + z = x; + // ^ defined: 1 + + bar() { + /**/ x; + // ^ defined: 1 + + /**/ z; + // ^ defined: + // z should not be defined here + } +}); + +// Flow Out + +(class { + y = 1; +}); + +/**/ y; +// ^ defined: +// y should not be defined here + +// Flow Around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/comma.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/comma.js new file mode 100644 index 000000000..940482749 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/comma.js @@ -0,0 +1,23 @@ +let x = 1; + +// Flow in + +(1, x); +// ^ defined: 1 + +(y = 1, y); +// ^ defined: 8 + +// Flow out + +(1, z = 5); + +/**/ y; +// ^ defined: 8 +/**/ z; +// ^ defined: 13 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/function.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/function.js new file mode 100644 index 000000000..29061f3b0 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/function.js @@ -0,0 +1,31 @@ +// +// READ ME +// +// This test does NOT test complex arguments. See the `binding` test dir. +// + +let x = 1; + +// Flow In + +(function () { x; }); +// ^ defined: 7 + +// Flow Out + +(function () { y = 1; }); + +/**/ y; +// ^ defined: +// y should not be defined here + +// Flow Around + +/**/ x; +// ^ defined: 7 + +// Flow In from Arg +(function (y) { + /**/ y; + // ^ defined: 28 +}); \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/generator_function.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/generator_function.js new file mode 100644 index 000000000..5e11f4664 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/generator_function.js @@ -0,0 +1,31 @@ +// +// READ ME +// +// This test does NOT test complex arguments. See the `binding` test dir. +// + +let x = 1; + +// Flow In + +(function* () { x; }); +// ^ defined: 7 + +// Flow Out + +(function* () { y = 1; }); + +/**/ y; +// ^ defined: +// y should not be defined here + +// Flow Around + +/**/ x; +// ^ defined: 7 + +// Flow In from Arg +(function* (y) { + /**/ y; + // ^ defined: 28 +}); \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/member_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/member_expression.js new file mode 100644 index 000000000..438cf9dd6 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/member_expression.js @@ -0,0 +1,22 @@ +let x = 1; + +// Flow in + +/**/ x.foo; +// ^ defined: 1 + +// Flow out + +(y = 1).foo; + +/**/ y; +// ^ defined: 10 + +// Flow around + +/**/ x; +// ^ defined: 1 + +// Optional chain +/**/ x?.foo +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/new_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/new_expression.js new file mode 100644 index 000000000..2a3655759 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/new_expression.js @@ -0,0 +1,24 @@ +let x = 1; +let f = 2; + +// Flow in +/**/ new f(x); +// ^ defined: 1 +// ^ defined: 2 + +// Flow around + +/**/ x; +// ^ defined: 1 + +// Flow out + +new (y = 1)( + z = 2 +); + +/**/ y; +// ^ defined: 16 + +/**/ z; +// ^ defined: 17 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/null.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/null.js new file mode 100644 index 000000000..f13eb4a5f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/null.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow around + +null; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/number.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/number.js new file mode 100644 index 000000000..955646910 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/number.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow around + +5; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/object.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/object.js new file mode 100644 index 000000000..feb199395 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/object.js @@ -0,0 +1,23 @@ +let x = 1; + +// Flow in + +{ + /**/[x]: x + // ^ defined: 1 + // ^ defined: 1 +}; + +// Flow out + +{ + [y = 0]: + /**/ (y, z = 0) + // ^ defined: 14 +}; + +/**/ y; +// ^ defined: 14 + +/**/ z; +// ^ defined: 15 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/parenthesized_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/parenthesized_expression.js new file mode 100644 index 000000000..07a787792 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/parenthesized_expression.js @@ -0,0 +1,17 @@ +let x = 1; + +// Flow in +/**/ (x); +// ^ defined: 1 + +// Flow out + +(y = 1); + +/**/ y; +// ^ defined: 9 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/regex.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/regex.js new file mode 100644 index 000000000..09fd564f1 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/regex.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow around + +/foo/; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/string.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/string.js new file mode 100644 index 000000000..db207e4c4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/string.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow around + +"foo"; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/subscript_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/subscript_expression.js new file mode 100644 index 000000000..e63083d83 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/subscript_expression.js @@ -0,0 +1,24 @@ +let x = 1; + +// Flow in + +/**/ x[x]; +// ^ defined: 1 +// ^ defined: 1 + +// Flow out + +(y = 1)[ + z = 1 +]; + +/**/ y; +// ^ defined: 11 + +/**/ z; +// ^ defined: 12 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/super.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/super.js new file mode 100644 index 000000000..e3317db0d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/super.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow around + +super; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/template_strings.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/template_strings.js new file mode 100644 index 000000000..f8bfa0837 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/template_strings.js @@ -0,0 +1,20 @@ +let x = 1; + +// Flow in + +`template ${x} string`; +// ^ defined: 1 + +`template ${y = 1} ${y} string`; +// ^ defined: 8 + + +// Flow out + +/**/ y; +// ^ defined: 8 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/ternary_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/ternary_expression.js new file mode 100644 index 000000000..5e22add80 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/ternary_expression.js @@ -0,0 +1,31 @@ +let x = 1; + +// Flow in + +/**/ x ? x : x; +// ^ defined: 1 +// ^ defined: 1 +// ^ defined: 1 + +// Flow out + +(y = 1) ? + /**/ (y, z = 1) : + // ^ defined: 12 + /**/ (y, w = 1); + // ^ defined: 12 + + +/**/ y; +// ^ defined: 12 + +/**/ z; +// ^ defined: 13 + +/**/ w; +// ^ defined: 15 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/this.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/this.js new file mode 100644 index 000000000..bf8cc6420 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/this.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow around + +this; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/unary_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/unary_expression.js new file mode 100644 index 000000000..0dbb71814 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/unary_expression.js @@ -0,0 +1,18 @@ +let x = 1; + +// Flow in + +/**/ -x; +// ^ defined: 1 + +// Flow out + +-(y = 1); + +/**/ y; +// ^ defined: 10 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/undefined.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/undefined.js new file mode 100644 index 000000000..219678bce --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/undefined.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow around + +undefined; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/update_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/update_expression.js new file mode 100644 index 000000000..2197e5571 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/update_expression.js @@ -0,0 +1,18 @@ +let x = 1; + +// Flow in + +/**/ x++; +// ^ defined: 1 + +// Flow out + +--(y = 1); + +/**/ y; +// ^ defined: 10 + +// Flow around and update + +/**/ x; +// ^ defined: 1, 5 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/variable.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/variable.js new file mode 100644 index 000000000..05ba7b308 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/variable.js @@ -0,0 +1,11 @@ +let x = 1; + +// Flow in + +/**/ x; +// ^ defined: 1 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/expressions/yield_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/expressions/yield_expression.js new file mode 100644 index 000000000..77fa2c453 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/expressions/yield_expression.js @@ -0,0 +1,20 @@ +function* f() { + let x = 1; + + // Flow in + + yield x; + // ^ defined: 2 + + // Flow out + + yield y = 1; + + /**/ y; + // ^ defined: 11 + + // Flow around + + /**/ x; + // ^ defined: 2 +} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_core.js b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_core.js new file mode 100644 index 000000000..fc0674833 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_core.js @@ -0,0 +1,48 @@ +// The core of JSX tests here verify the behavior of the following node types: +// jsx_element +// jsx_identifier +// jsx_attribute +// jsx_expression +// jsx_opening_element +// jsx_closing_element +// There is no real way to avoid testing all of these at once, +// and so we don't even try to. + +let x = 1; + +// Flow In + +const el = {x}; +// ^ defined: 11 +// ^ defined: 11 + +const el2 = +// ^ defined: 11 +// ^ defined: 11 + +// Flow Out + +const el = + {z = 3} +; + +/**/ y; +// ^ defined: 25 + +/**/ z; +// ^ defined: 26 + +// Flow Across + +const el = + {w}; +// ^ defined: 37 + +const el = ; +// ^ defined: 41 + +// Flow Around + +/**/ x; +// ^ defined: 11 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_fragment.js b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_fragment.js new file mode 100644 index 000000000..02d5f5d68 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_fragment.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow Around + +const el = <>; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_namespace_name.js b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_namespace_name.js new file mode 100644 index 000000000..ec59c3dca --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_namespace_name.js @@ -0,0 +1,21 @@ +let x = 1; + +// Flow Around + +const el = ; + +/**/ x; +// ^ defined: 1 + +// Flow In + +let foo = { + bar: { + baz: 1 + } +}; + +const el2 = ; +// ^ defined: 12 +// ^ defined: 13 +// ^ defined: 14 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_self_closing_element.js b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_self_closing_element.js new file mode 100644 index 000000000..937ca0970 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_self_closing_element.js @@ -0,0 +1,37 @@ +// The core of JSX tests here verify the behavior of the following node types: +// jsx_element +// jsx_identifier +// jsx_attribute +// jsx_expression +// jsx_opening_element +// jsx_closing_element +// There is no real way to avoid testing all of these at once, +// and so we don't even try to. + +let x = 1; + +// Flow In + +const el = ; +// ^ defined: 11 + +const el2 = +// ^ defined: 11 + +// Flow Out + +const el = ; + +/**/ y; +// ^ defined: 23 + +// Flow Across + +const el = ; +// ^ defined: 30 + +// Flow Around + +/**/ x; +// ^ defined: 11 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_text.js b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_text.js new file mode 100644 index 000000000..0cc359765 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/jsx/jsx_text.js @@ -0,0 +1,8 @@ +let x = 1; + +// Flow Around + +const el = bar; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/binding/default_arguments_from_enclosing_scope.js b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/default_arguments_from_enclosing_scope.js new file mode 100644 index 000000000..eaf4ee768 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/default_arguments_from_enclosing_scope.js @@ -0,0 +1,8 @@ +let x = { bar: 0 }; + +function foo(y = x) { + return y; +} + +foo({ bar: 1 }).bar; +// ^ defined: 1, 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/binding/default_arguments_from_other_arguments.js b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/default_arguments_from_other_arguments.js new file mode 100644 index 000000000..149ce62fb --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/default_arguments_from_other_arguments.js @@ -0,0 +1,10 @@ +function foo(x, y = x) { + return y; +} + +foo({ bar: 1 }).bar; +// ^ defined: 5 + +foo({}, + { bar: 2 }).bar; +// ^ defined: 9 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments.js b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments.js new file mode 100644 index 000000000..4fcaa1d7e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments.js @@ -0,0 +1,19 @@ +let obj = { x: 1, y: { z: 2 } }; +let { x: xval, y: yval } = obj; +let x = xval; +// ^ defined: 1, 2 + +let y = yval; +// ^ defined: 1, 2 + + y.z; +// ^ defined: 1 + +let [w, + q] = [1,2]; + + w; +// ^ defined: 12 + + q; +// ^ defined: 13 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments_with_defaults_from_enclosing_scope.js b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments_with_defaults_from_enclosing_scope.js new file mode 100644 index 000000000..f578947bd --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments_with_defaults_from_enclosing_scope.js @@ -0,0 +1,5 @@ +let x = { foo: 0 }; +let [y, z = x] = [1]; + + z.foo; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments_with_defaults_from_other_variables.js b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments_with_defaults_from_other_variables.js new file mode 100644 index 000000000..a7925a9f4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_assignments_with_defaults_from_other_variables.js @@ -0,0 +1,6 @@ +let [x, y = x] = [ // newline here distinguishes the object value from the vars + { foo: 2 } +]; + + y.foo; +// ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_parameters.js b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_parameters.js new file mode 100644 index 000000000..6034f1f0e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/destructuring_parameters.js @@ -0,0 +1,7 @@ +function foo({ x: xval }) { + return xval; +// ^ defined: 1 +} + +foo({ x: { y: 1 } }).y; +// ^ defined: 6 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/binding/object_assignment_patterns.js b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/object_assignment_patterns.js new file mode 100644 index 000000000..3529733c9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/binding/object_assignment_patterns.js @@ -0,0 +1,4 @@ +let { x = 1 } = { "x": 5 }; + + x; +// ^ defined: 1, 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2141.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2141.js new file mode 100644 index 000000000..c37b95267 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2141.js @@ -0,0 +1,14 @@ +function bark() { + console.log("Bark!"); +} + +function meow() { + console.log("Meow!"); +} + +function speak() { + bark(); +// ^ defined: 1 + meow(); +// ^ defined: 5 +} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2227.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2227.js new file mode 100644 index 000000000..c6dd885cd --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2227.js @@ -0,0 +1,17 @@ +class Cat { + constructor(name, age) { + this.name = name; + this.age = age; + } + + name() { + return this.name; + } + + age() { + return this.age + } +} + +Cat; +//^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2291.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2291.js new file mode 100644 index 000000000..e8da2831c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/2291.js @@ -0,0 +1,28 @@ +// need this to make the tests stop complaining about non-existing test cases +x = 1; +_ = x; +// ^ defined: 2 + +f = function () { }; +f = function* () { }; +f = () => 1; + +var f = function () { }; +var f = function* () { }; +var f = () => 1; + +let f = function () { }; +let f = function* () { }; +let f = () => 1; + +f.p = function () { }; +f.p = function* () { }; +f.p = () => 1; + +// related assignment-like things: + +{ + f: function () { }, + f: function* () { }, + f: () => 1 +}; \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_default_export_with_ES6_default_import.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_default_export_with_ES6_default_import.js new file mode 100644 index 000000000..5cecaa6f4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_default_export_with_ES6_default_import.js @@ -0,0 +1,12 @@ +/*--- path: a.js ---*/ + +const f = 5; + +module.exports = f; + +/*--- path: b.js ---*/ + +import g from './a.js'; + +/**/ g; +// ^ defined: 3, 5, 9 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_default_import_of_functions.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_default_import_of_functions.js new file mode 100644 index 000000000..62f351e66 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_default_import_of_functions.js @@ -0,0 +1,15 @@ +/*--- path: a.js ---*/ +module.exports = function () { + +}; + +/*--- path: b.js ---*/ +let mod = require("./a.js"); + +/**/ mod; +// ^ defined: 2, 7 + +class Quux { + bar() { + } +} diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_default_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_default_alias_default.js new file mode 100644 index 000000000..97e1f028e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_default_alias_default.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +module.exports = 1; + +/*--- path: b.js ---*/ + +module.exports = require("./a.js"); + +/*--- path: c.js ---*/ + +let bar = require("./b.js"); + +/**/ bar; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_default_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_default_alias_nondefault.js new file mode 100644 index 000000000..9c7d90fc2 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_default_alias_nondefault.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +module.exports = 1; + +/*--- path: b.js ---*/ + +exports.foo = require("./a.js"); + +/*--- path: c.js ---*/ + +let { foo } = require("./b.js"); + +/**/ foo; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_nondefault_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_nondefault_alias_default.js new file mode 100644 index 000000000..8b99bd45f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/export_with_name_nondefault_alias_default.js @@ -0,0 +1,15 @@ +/*--- path: a.js ---*/ + +exports.foo = 1; + +/*--- path: b.js ---*/ + +let { foo } = require("./a.js"); +module.exports = foo; + +/*--- path: c.js ---*/ + +let bar = require("./b.js"); + +/**/ bar; +// ^ defined: 3, 7, 8, 12 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/import_with_name_default_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/import_with_name_default_alias_nondefault.js new file mode 100644 index 000000000..d9052a27f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/CommonJS_imports_and_exports_using_defaults/import_with_name_default_alias_nondefault.js @@ -0,0 +1,10 @@ +/*--- path: a.js ---*/ + +module.exports = 1; + +/*--- path: b.js ---*/ + +let foo = require("./a.js"); + +/**/ foo; +// ^ defined: 3, 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_function_with_name_default_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_function_with_name_default_alias_default.js new file mode 100644 index 000000000..bf0ff8f1e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_function_with_name_default_alias_default.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +export default function foo() { }; + +/*--- path: b.js ---*/ + +export { default as default } from "./a.js"; + +/*--- path: c.js ---*/ + +import bar from "./b.js"; + +/**/ bar; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_default_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_default_alias_default.js new file mode 100644 index 000000000..2dcd4f19f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_default_alias_default.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +export default 1; + +/*--- path: b.js ---*/ + +export { default as default } from "./a.js"; + +/*--- path: c.js ---*/ + +import bar from "./b.js"; + +/**/ bar; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_default_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_default_alias_nondefault.js new file mode 100644 index 000000000..108abcf58 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_default_alias_nondefault.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +export default 1; + +/*--- path: b.js ---*/ + +export { default as foo } from "./a.js"; + +/*--- path: c.js ---*/ + +import { foo } from "./b.js"; + +/**/ foo; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_nondefault_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_nondefault_alias_default.js new file mode 100644 index 000000000..3af922d65 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_name_nondefault_alias_default.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +export let foo = 1; + +/*--- path: b.js ---*/ + +export { foo as default } from "./a.js"; + +/*--- path: c.js ---*/ + +import bar from "./b.js"; + +/**/ bar; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_unaliased_name_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_unaliased_name_default.js new file mode 100644 index 000000000..7f4853703 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/export_with_unaliased_name_default.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +export default 1; + +/*--- path: b.js ---*/ + +export { default } from "./a.js"; + +/*--- path: c.js ---*/ + +import foo from "./b.js"; + +/**/ foo; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/import_with_name_default_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/import_with_name_default_alias_nondefault.js new file mode 100644 index 000000000..d34fd8898 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ES6_imports_and_exports_using_the_name_or_alias_default/import_with_name_default_alias_nondefault.js @@ -0,0 +1,10 @@ +/*--- path: a.js ---*/ + +export default 1; + +/*--- path: b.js ---*/ + +import { default as foo } from "./a.js"; + +/**/ foo; +// ^ defined: 3, 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/array_assignment_interrupts_scope.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/array_assignment_interrupts_scope.js new file mode 100644 index 000000000..a71218475 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/array_assignment_interrupts_scope.js @@ -0,0 +1,10 @@ +let bar = {}; + +/**/ bar; +// ^ defined: 1 + +/**/ bar["one"] = 1; +// ^ defined: 1 + +/**/ bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/if_statement_path_blowup.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/if_statement_path_blowup.js new file mode 100644 index 000000000..98f54e4ab --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/if_statement_path_blowup.js @@ -0,0 +1,37 @@ +let x = 42; + +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } +if (true) { 1 } else { -1 } + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/string_property_interrupts_scope.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/string_property_interrupts_scope.js new file mode 100644 index 000000000..a64352e75 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/string_property_interrupts_scope.js @@ -0,0 +1,6 @@ +let x = 42; + +let foo = { "strict": true }; + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ternary_expression_path_blowup.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ternary_expression_path_blowup.js new file mode 100644 index 000000000..b0185ce87 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/ternary_expression_path_blowup.js @@ -0,0 +1,37 @@ +let x = 42; + +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; +true ? 1 : -1; + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/variable_resolves_to_module.js b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/variable_resolves_to_module.js new file mode 100644 index 000000000..e829ab7ff --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/bug_regressions/variable_resolves_to_module.js @@ -0,0 +1,4 @@ +/* --- path: debug.js --- */ + +/**/ debug(42); +// ^ defined: diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_declaration.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_declaration.js new file mode 100644 index 000000000..3504188bf --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_declaration.js @@ -0,0 +1,6 @@ + class Foo { + + } + + Foo; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression.js new file mode 100644 index 000000000..4abc8b566 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression.js @@ -0,0 +1,10 @@ +let Bar = + class Foo { + constructor() { + Foo; +// ^ defined: 2 + } + }; + + Bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression_superclasses.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression_superclasses.js new file mode 100644 index 000000000..c27cf9be2 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression_superclasses.js @@ -0,0 +1,15 @@ +let Foo = class { + constructor() { + this.x = 5; + } +} + +let Bar = class extends Foo { + constructor() { + + } +} + +let bar = new Bar(); +bar.x +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression_this_field_access.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression_this_field_access.js new file mode 100644 index 000000000..b68554283 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/class_expression_this_field_access.js @@ -0,0 +1,19 @@ + let Foo = class { + constructor() { + this.bar = 5; + } + }; + + let x = new Foo(); + x.bar; +// ^ defined: 3 + +let Bar = class { + constructor(x) { + this.field = x; + } +} + +let bar = new Bar({ baz: 5 }); +bar.field.baz +// ^ defined: 17 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/field_declaration.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/field_declaration.js new file mode 100644 index 000000000..28c0cf434 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/field_declaration.js @@ -0,0 +1,10 @@ +class Foo { + bar; + baz = 5; +} + +let x = new Foo(); + x.bar; +// ^ defined: 2 + x.baz; +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/function_class_this_field_access.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/function_class_this_field_access.js new file mode 100644 index 000000000..f48af3a96 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/function_class_this_field_access.js @@ -0,0 +1,15 @@ + function Foo() { + this.bar = 5; + } + + let x = new Foo(); + x.bar; +// ^ defined: 2 + +function Bar(x) { + this.field = x; +} + +let bar = new Bar({ baz: 5 }); +bar.field.baz +// ^ defined: 13 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/method_call_this_argument.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/method_call_this_argument.js new file mode 100644 index 000000000..1a4d93962 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/method_call_this_argument.js @@ -0,0 +1,22 @@ +let x = { + foo: { baz: 5 }, + bar: function () { + return this.foo; + } +}; + +x.bar().baz; +// ^ defined: 2 + +x["bar"]().baz; +// ^ defined: 2 + +let y = [ + { baz: 5 }, + function () { + return this[0]; + } +]; + +y[1]().baz; +// ^ defined: 15 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/method_definition.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/method_definition.js new file mode 100644 index 000000000..5166f5b78 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/method_definition.js @@ -0,0 +1,12 @@ +class Foo { + bar(x) { + return x; + } +} + +let obj = { field: 5 }; +let y = new Foo(); + y.bar; +// ^ defined: 2 +y.bar(obj).field +// ^ defined: 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/superclasses.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/superclasses.js new file mode 100644 index 000000000..82ac7b32d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/superclasses.js @@ -0,0 +1,15 @@ +class Foo { + constructor() { + this.x = 5; + } +} + +class Bar extends Foo { + constructor() { + + } +} + +let bar = new Bar(); +bar.x +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/this_field_access.js b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/this_field_access.js new file mode 100644 index 000000000..a43d41473 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/classes_and_instances/this_field_access.js @@ -0,0 +1,19 @@ + class Foo { + constructor() { + this.bar = 5; + } + } + + let x = new Foo(); + x.bar; +// ^ defined: 3 + +class Bar { + constructor(x) { + this.field = x; + } +} + +let bar = new Bar({ baz: 5 }); +bar.field.baz +// ^ defined: 17 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/arrays.js b/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/arrays.js new file mode 100644 index 000000000..52c6dc6fb --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/arrays.js @@ -0,0 +1,5 @@ +let arr = [{ x: 1 }, + { x: 2 }]; +let one = arr[1].x; +// ^ defined: 1 +// ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/object_extend_method.js b/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/object_extend_method.js new file mode 100644 index 000000000..f062c125d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/object_extend_method.js @@ -0,0 +1,15 @@ +var obj = { + x: 1, + y: 2 +}; + +obj.extend({ + z: 3 +}); + +let x = obj.x; +// ^ defined: 2 +let y = obj.y; +// ^ defined: 3 +let z = obj.z; +// ^ defined: 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/objects.js b/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/objects.js new file mode 100644 index 000000000..6d4bb76b5 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/compound_literals/objects.js @@ -0,0 +1,24 @@ +let obj = { + x: 1, y: 2, 0: "z" }; + +let obj_x = obj.x; +// ^ defined: 1 +// ^ defined: 2 +let x = obj_x; +// ^ defined: 4, 2 + +let obj_y = obj["y"]; +// ^ defined: 1 +// ^ defined: 2 +let y = obj_y; +// ^ defined: 10, 2 + +let obj_z = obj[0]; +// ^ defined: 1 +// ^ defined: 2 +let z = obj_z; +// ^ defined: 16, 2 + +let obj2 = { x, y }; +// ^ defined: 7, 4, 2 +// ^ defined: 13, 10, 2 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/dowhile.js b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/dowhile.js new file mode 100644 index 000000000..869361d0a --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/dowhile.js @@ -0,0 +1,10 @@ +let x = 1; + +do { + x = x * 2; + // ^ defined: 1, 4, 6 +} while (x--); +// ^ defined: 1, 4, 6 + +const y = x; +// ^ defined: 1, 4, 6 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/for.js b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/for.js new file mode 100644 index 000000000..aaca4ded4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/for.js @@ -0,0 +1,19 @@ +var x = 1; + +for (let y = 2; + x + y < 2; + //^ defined: 1, 7, 13 + // ^ defined: 3, 7, 14 + y++, x--) { + //^ defined: 3, 7, 14 + // ^ defined: 1, 7, 13 + alert(x + y); + // ^ defined: 1, 7, 13 + // ^ defined: 3, 7, 14 + x = 2; + y = 3; +} + +const z = x - y; +// ^ defined: 1, 7, 13 +// ^ defined: 3, 7, 14 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/forin.js b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/forin.js new file mode 100644 index 000000000..2dddccdbf --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/forin.js @@ -0,0 +1,11 @@ +var xs = [0,1,2]; + +for (x in xs) { + // ^ defined: 1 + alert(x); + // ^ defined: 1, 3 + var y = 0; +} + +let z = y; +// ^ defined: 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/if.js b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/if.js new file mode 100644 index 000000000..e14c5ba40 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/if.js @@ -0,0 +1,14 @@ +let x = 0; + +if (true) { + var y = x; + // ^ defined: 1 +} else if (true) { + var y = x+1; + // ^ defined: 1 +} else { + var y = x-2; +} + +const z = y; +// ^ defined: 1, 4, 7, 10 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/try.js b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/try.js new file mode 100644 index 000000000..7d3fb8b2f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/try.js @@ -0,0 +1,22 @@ +var x = 1; + +try { + var y = 2; + alert(x); + // ^ defined: 1 +} catch (e) { + const z = 3; + alert(x, y, e); + // ^ defined: 1 + // ^ defined: 4 + // ^ defined: 7 +} finally { + let w = 4; + alert(x, y, z); + // ^ defined: 1 + // ^ defined: 4 + // ^ defined: 8 +} + +alert(w); +// ^ defined: 14 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/while.js b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/while.js new file mode 100644 index 000000000..e843e680f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/while.js @@ -0,0 +1,10 @@ +let x = 1; + +while (x--) { + // ^ defined: 1, 3, 5 + x = x * 2; + // ^ defined: 1, 3, 5 +} + +const y = x; +// ^ defined: 1, 3, 5 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/with.js b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/with.js new file mode 100644 index 000000000..d356bfcdf --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/control_flow_statements/with.js @@ -0,0 +1,13 @@ +with ({ x: 1, y: 2 }) { + x + y; +// ^ defined: 1 +// ^ defined: 1 +} + +let obj = { z: 3, + w: 4 }; +with (obj) { + z + w; +// ^ defined: 7 +// ^ defined: 8 +} diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arguments_variable.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arguments_variable.js new file mode 100644 index 000000000..daf8ed4f3 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arguments_variable.js @@ -0,0 +1,22 @@ +let x = { foo: 1 }; + +function f() { + return arguments; +} + +f(x)[0].foo; +// ^ defined: 1 + +function* g() { + return arguments; +} + +g(x)[0].foo; +// ^ defined: 1 + +let h = function () { + return arguments; +}; + +h(x)[0].foo; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_param_lists_and_block_bodies.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_param_lists_and_block_bodies.js new file mode 100644 index 000000000..282007779 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_param_lists_and_block_bodies.js @@ -0,0 +1,13 @@ +let a = { bar: 1 }; +let b = { quux: 2 }; + +let foo = (x) => { + b.quux; +// ^ defined: 2 +// ^ defined: 2 + return x; +// ^ defined: 4 +}; + +foo(a).bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_param_lists_and_expr_bodies.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_param_lists_and_expr_bodies.js new file mode 100644 index 000000000..01b68607d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_param_lists_and_expr_bodies.js @@ -0,0 +1,11 @@ +let a = { bar: 1 }; +let b = { quux: 2 }; + +let foo = (x) => + (b.quux, x); +// ^ defined: 2 +// ^ defined: 2 +// ^ defined: 4 + +foo(a).bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_single_param_and_block_bodies.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_single_param_and_block_bodies.js new file mode 100644 index 000000000..cb12d62df --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_single_param_and_block_bodies.js @@ -0,0 +1,13 @@ +let a = { bar: 1 }; +let b = { quux: 2 }; + +let foo = x => { + b.quux; +// ^ defined: 2 +// ^ defined: 2 + return x; +// ^ defined: 4 +}; + +foo(a).bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_single_param_and_expr_bodies.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_single_param_and_expr_bodies.js new file mode 100644 index 000000000..85af929dc --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/arrow_function_values_with_single_param_and_expr_bodies.js @@ -0,0 +1,11 @@ +let a = { bar: 1 }; +let b = { quux: 2 }; + +let foo = x => + (b.quux, x); +// ^ defined: 2 +// ^ defined: 2 +// ^ defined: 4 + +foo(a).bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/call_arguments.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/call_arguments.js new file mode 100644 index 000000000..9ee96ece9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/call_arguments.js @@ -0,0 +1,7 @@ +let x = 1; +let y = 2; + +let z = foo(x, y++, y); +// ^ defined: 1 +// ^ defined: 2 +// ^ defined: 2, 4 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/function_declarations.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/function_declarations.js new file mode 100644 index 000000000..8f1078539 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/function_declarations.js @@ -0,0 +1,13 @@ +let a = { bar: 1 }; +let b = { quux: 2 }; + +function foo(x) { + b.quux; +// ^ defined: 2 +// ^ defined: 2 + return x; +// ^ defined: 4 +} + +foo(a).bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/function_values.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/function_values.js new file mode 100644 index 000000000..dc42375f3 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/function_values.js @@ -0,0 +1,15 @@ +let a = { bar: 1 }; +let b = { quux: 2 }; + +let foo = function bar(x) { + b.quux; +// ^ defined: 2 +// ^ defined: 2 + bar; +// ^ defined: 4 + return x; +// ^ defined: 4 +}; + +foo(a).bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/generator_function_declarations.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/generator_function_declarations.js new file mode 100644 index 000000000..781867bf6 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/generator_function_declarations.js @@ -0,0 +1,13 @@ +let a = { bar: 1 }; +let b = { quux: 2 }; + +function* foo(x) { + b.quux; +// ^ defined: 2 +// ^ defined: 2 + yield x; +// ^ defined: 4 +} + +foo(a).bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/generator_function_values.js b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/generator_function_values.js new file mode 100644 index 000000000..10894dcb3 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/functions_and_calls/generator_function_values.js @@ -0,0 +1,15 @@ +let a = { bar: 1 }; +let b = { quux: 2 }; + +let foo = function* bar(x) { + b.quux; +// ^ defined: 2 +// ^ defined: 2 + bar; +// ^ defined: 4 + yield x; +// ^ defined: 4 +}; + +foo(a).bar; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/basic_functions.js b/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/basic_functions.js new file mode 100644 index 000000000..69382ca19 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/basic_functions.js @@ -0,0 +1,8 @@ +/**/ foo; +// ^ defined: 7 + +/**/ bar; +// ^ defined: 8 + +function foo() { } +function* bar() { } \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/functions_in_subscopes.js b/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/functions_in_subscopes.js new file mode 100644 index 000000000..a6c25ada4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/functions_in_subscopes.js @@ -0,0 +1,163 @@ +/**/ f1; +// ^ defined: +/**/ f2; +// ^ defined: +/**/ f3; +// ^ defined: +/**/ f4; +// ^ defined: +/**/ f5; +// ^ defined: +/**/ f6; +// ^ defined: +/**/ f7; +// ^ defined: +/**/ f8; +// ^ defined: +/**/ f9; +// ^ defined: +/**/ f10; +// ^ defined: +/**/ f11; +// ^ defined: +/**/ f12; +// ^ defined: +/**/ f13; +// ^ defined: +/**/ f14; +// ^ defined: +/**/ f15; +// ^ defined: +/**/ f16; +// ^ defined: +/**/ f17; +// ^ defined: + +while (x) { + /**/ f1; + // ^ defined: 40 + + function f1() { } +} + +for (let x = 0; x < 10; x++) { + /**/ f2; + // ^ defined: 47 + + function f2() { } +} + +if (x) { + /**/ f3; + // ^ defined: 54 + + function f3() { } +} else { + /**/ f4; + // ^ defined: 59 + + function f4() { } +} + +for (x in y) { + /**/ f5; + // ^ defined: 66 + + function f5() { } +} + +do { + /**/ f6; + // ^ defined: 73 + + function f6() { } +} while (x); + +try { + /**/ f7; + // ^ defined: 80 + + function f7() { } +} catch { + /**/ f8; + // ^ defined: 85 + + function f8() { } +} finally { + /**/ f9; + // ^ defined: 90 + + function f9() { } +} + +with (x) { + /**/ f10; + // ^ defined: 97 + + function f10() { } +} + +switch (x) { + case 0: + /**/ f11; + // ^ defined: 105 + + function f11() { } +} + +{ + /**/ f12; + // ^ defined: 112 + + function f12() { } +} + +function foo() { + /**/ f13; + // ^ defined: 119 + + function f13() { } +} + +function* foo() { + /**/ f14; + // ^ defined: 126 + + function f14() { } +} + +(function () { + /**/ f15; + // ^ defined: 133 + + function f15() { } +}); + +(function* () { + /**/ f16; + // ^ defined: 140 + + function f16() { } +}); + +(() => { + /**/ f17; + // ^ defined: 147 + + function f17() { } +}); + +// Some tests of bare single-statement bodies + +while (x) function f1() { } + +for (let x = 0; x < 10; x++) function f2() { } + +if (x) function f3() { } +else function f4() { } + +for (x in y) function f5() { } + +do function f6() { } while (x); + +with (x) function f10() { } diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/imports.js b/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/imports.js new file mode 100644 index 000000000..1045e5512 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/hoisting/imports.js @@ -0,0 +1,10 @@ +/*--- path: a.js ---*/ + +export let foo = 1; + +/*--- path: b.js ---*/ + +/**/ bar; +// ^ defined: 3, 10 + +import { foo as bar } from "./a.js"; \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/export_with_name_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/export_with_name_default.js new file mode 100644 index 000000000..cb450bf73 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/export_with_name_default.js @@ -0,0 +1,11 @@ +/*--- path: index.js ---*/ + +export default function foo() { } + +/*--- path: index2.js ---*/ + +let bar = await import("./index.js"); + +/**/ bar.default; +// ^ defined: 7 +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/export_with_name_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/export_with_name_nondefault.js new file mode 100644 index 000000000..6c4ed5062 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/export_with_name_nondefault.js @@ -0,0 +1,10 @@ +/*--- path: index.js ---*/ + +export function foo() { } + +/*--- path: index2.js ---*/ + +let { foo } = await import("./index.js"); + +/**/ foo; +// ^ defined: 3, 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/field_on_default_object_import_of_exported_default_object.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/field_on_default_object_import_of_exported_default_object.js new file mode 100644 index 000000000..2e3c427c8 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/field_on_default_object_import_of_exported_default_object.js @@ -0,0 +1,12 @@ +/*--- path: index.js ---*/ + +export default { + foo: 1 +}; + +/*--- path: index2.js ---*/ + +let mod = await import("./index.js"); + +mod.default.foo; +// ^ defined: 4 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/field_on_default_object_import_of_exported_nondefault_name.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/field_on_default_object_import_of_exported_nondefault_name.js new file mode 100644 index 000000000..9bc1a8120 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/field_on_default_object_import_of_exported_nondefault_name.js @@ -0,0 +1,10 @@ +/*--- path: index.js ---*/ + +export function foo() { } + +/*--- path: index2.js ---*/ + +let mod = await import("./index.js"); + +mod.foo; +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/import_with_name_default_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/import_with_name_default_alias_nondefault.js new file mode 100644 index 000000000..2c12f935f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/import_with_name_default_alias_nondefault.js @@ -0,0 +1,16 @@ +/*--- path: a.js ---*/ + +export default function foo() { } + +/*--- path: index.js ---*/ + +import { default as bar } from "./a.js"; + +export { bar }; + +/*--- path: index2.js ---*/ + +let { bar } = await import("./index.js"); + +/**/ bar; +// ^ defined: 3, 7, 9, 13 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_default_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_default_alias_default.js new file mode 100644 index 000000000..cee2ee151 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_default_alias_default.js @@ -0,0 +1,15 @@ +/*--- path: a.js ---*/ + +export default function foo() { } + +/*--- path: index.js ---*/ + +export { default as default } from "./a.js"; + +/*--- path: index2.js ---*/ + +let bar = await import("./index.js"); + +/**/ bar.default; +// ^ defined: 11 +// ^ defined: 3, 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_default_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_default_alias_nondefault.js new file mode 100644 index 000000000..e99346fa5 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_default_alias_nondefault.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +export default function foo() { } + +/*--- path: index.js ---*/ + +export { default as bar } from "./a.js"; + +/*--- path: index2.js ---*/ + +let { bar } = await import("./index.js"); + +/**/ bar; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_nondefault_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_nondefault_alias_default.js new file mode 100644 index 000000000..99e922f17 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_nondefault_alias_default.js @@ -0,0 +1,15 @@ +/*--- path: a.js ---*/ + +export function foo() { } + +/*--- path: index.js ---*/ + +export { foo as default } from "./a.js"; + +/*--- path: index2.js ---*/ + +let bar = await import("./index.js"); + +/**/ bar.default; +// ^ defined: 11 +// ^ defined: 3, 7 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_nondefault_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_nondefault_alias_nondefault.js new file mode 100644 index 000000000..a806336b8 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_name_nondefault_alias_nondefault.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +export function foo() { } + +/*--- path: index.js ---*/ + +export { foo as bar } from "./a.js"; + +/*--- path: index2.js ---*/ + +let { bar } = await import("./index.js"); + +/**/ bar; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_unaliased_name.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_unaliased_name.js new file mode 100644 index 000000000..18bbc303c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_ES6_export/reexport_with_unaliased_name.js @@ -0,0 +1,14 @@ +/*--- path: a.js ---*/ + +export function foo() { } + +/*--- path: index.js ---*/ + +export { foo } from "./a.js"; + +/*--- path: index2.js ---*/ + +let { foo } = await import("./index.js"); + +/**/ foo; +// ^ defined: 3, 7, 11 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_default_object.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_default_object.js new file mode 100644 index 000000000..8bfed4d87 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_default_object.js @@ -0,0 +1,12 @@ +/*--- path: index.js ---*/ + +module.exports = { + foo: 1 +}; + +/*--- path: index2.js ---*/ + +let mod = require("./index.js"); + +mod.foo; +// ^ defined: 4 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_default_object_field.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_default_object_field.js new file mode 100644 index 000000000..16d60547a --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_default_object_field.js @@ -0,0 +1,10 @@ +/*--- path: a.js ---*/ + +module.exports.foo = 1; + +/*--- path: b.js ---*/ + +let mod = require("./a.js"); + +mod.foo; +// ^ defined: 3 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_nondefault_name.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_nondefault_name.js new file mode 100644 index 000000000..a699fc2a5 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/CommonJS_import_export/field_on_default_object_import_of_exported_nondefault_name.js @@ -0,0 +1,10 @@ +/*--- path: index.js ---*/ + +exports.foo = function () { }; + +/*--- path: index2.js ---*/ + +let mod = require("./index.js"); + +mod.foo; +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/export_with_name_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/export_with_name_default.js new file mode 100644 index 000000000..70afda69a --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/export_with_name_default.js @@ -0,0 +1,18 @@ +/*--- path: index.js ---*/ + +module.exports = function foo() { }; + +/*--- path: index2.js ---*/ + +import bar from "./index.js"; + +/**/ bar; +// ^ defined: 3, 7 + +/*--- path: index3.js ---*/ + +let bar = await import("./index.js"); + +/**/ bar.default; +// ^ defined: 14 +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/export_with_name_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/export_with_name_nondefault.js new file mode 100644 index 000000000..a1ff71b5d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/export_with_name_nondefault.js @@ -0,0 +1,17 @@ +/*--- path: index.js ---*/ + +exports.foo = function () { }; + +/*--- path: index2.js ---*/ + +import { foo } from "./index.js"; + +/**/ foo; +// ^ defined: 3, 7 + +/*--- path: index3.js ---*/ + +let { foo } = await import("./index.js"); + +/**/ foo; +// ^ defined: 3, 14 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_default_object.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_default_object.js new file mode 100644 index 000000000..c05ea891d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_default_object.js @@ -0,0 +1,12 @@ +/*--- path: index.js ---*/ + +module.exports = { + foo: 1 +}; + +/*--- path: index2.js ---*/ + +import mod from "./index.js"; + +mod.foo; +// ^ defined: 4 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_default_object_field.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_default_object_field.js new file mode 100644 index 000000000..b3b8c5456 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_default_object_field.js @@ -0,0 +1,10 @@ +/*--- path: a.js ---*/ + +module.exports.foo = 1; + +/*--- path: b.js ---*/ + +import { foo } from "./a.js"; + +/**/ foo; +// ^ defined: 3, 7 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_nondefault_name.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_nondefault_name.js new file mode 100644 index 000000000..a091abeaf --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/field_on_default_object_import_of_exported_nondefault_name.js @@ -0,0 +1,10 @@ +/*--- path: index.js ---*/ + +exports.foo = function () { }; + +/*--- path: index2.js ---*/ + +import mod from "./index.js"; + +mod.foo; +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/import_with_name_default_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/import_with_name_default_alias_nondefault.js new file mode 100644 index 000000000..a059a5a81 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/import_with_name_default_alias_nondefault.js @@ -0,0 +1,22 @@ +/*--- path: a.js ---*/ + +module.exports = function foo() { }; + +/*--- path: index.js ---*/ + +let mod = require("./a.js"); +exports.bar = mod; + +/*--- path: index2.js ---*/ + +import { bar } from "./index.js"; + +/**/ bar; +// ^ defined: 3, 7, 8, 12 + +/*--- path: index3.js ---*/ + +let { bar } = await import("./index.js"); + +/**/ bar; +// ^ defined: 3, 7, 8, 19 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_default_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_default_alias_default.js new file mode 100644 index 000000000..3275812e4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_default_alias_default.js @@ -0,0 +1,23 @@ +/*--- path: a.js ---*/ + +module.exports = function foo() { }; + +/*--- path: index.js ---*/ + +let mod = require("./a.js"); +module.exports = mod; + +/*--- path: index2.js ---*/ + +import bar from "./index.js"; + +/**/ bar; +// ^ defined: 3, 7, 8, 12 + +/*--- path: index3.js ---*/ + +let bar = await import("./index.js"); + +/**/ bar.default; +// ^ defined: 19 +// ^ defined: 3, 7, 8 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_default_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_default_alias_nondefault.js new file mode 100644 index 000000000..a059a5a81 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_default_alias_nondefault.js @@ -0,0 +1,22 @@ +/*--- path: a.js ---*/ + +module.exports = function foo() { }; + +/*--- path: index.js ---*/ + +let mod = require("./a.js"); +exports.bar = mod; + +/*--- path: index2.js ---*/ + +import { bar } from "./index.js"; + +/**/ bar; +// ^ defined: 3, 7, 8, 12 + +/*--- path: index3.js ---*/ + +let { bar } = await import("./index.js"); + +/**/ bar; +// ^ defined: 3, 7, 8, 19 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_nondefault_alias_default.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_nondefault_alias_default.js new file mode 100644 index 000000000..cc1722f16 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_nondefault_alias_default.js @@ -0,0 +1,23 @@ +/*--- path: a.js ---*/ + +exports.foo = function () { }; + +/*--- path: index.js ---*/ + +let mod = require("./a.js"); +module.exports = mod.foo + +/*--- path: index2.js ---*/ + +import bar from "./index.js"; + +/**/ bar; +// ^ defined: 3, 8, 12 + +/*--- path: index3.js ---*/ + +let bar = await import("./index.js"); + +/**/ bar.default; +// ^ defined: 19 +// ^ defined: 3, 8 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_nondefault_alias_nondefault.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_nondefault_alias_nondefault.js new file mode 100644 index 000000000..6963f7c5e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_name_nondefault_alias_nondefault.js @@ -0,0 +1,22 @@ +/*--- path: a.js ---*/ + +exports.foo = function () { }; + +/*--- path: index.js ---*/ + +let mod = require("./a.js"); +exports.bar = mod.foo; + +/*--- path: index2.js ---*/ + +import { bar } from "./index.js"; + +/**/ bar; +// ^ defined: 3, 8, 12 + +/*--- path: index3.js ---*/ + +let { bar } = await import("./index.js"); + +/**/ bar; +// ^ defined: 3, 8, 19 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_unaliased_name.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_unaliased_name.js new file mode 100644 index 000000000..1dd099e14 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_CommonJS_export/reexport_with_unaliased_name.js @@ -0,0 +1,21 @@ +/*--- path: a.js ---*/ + +exports.foo = function () { }; +/*--- path: index.js ---*/ + +let mod = require("./a.js"); +exports.foo = mod.foo; + +/*--- path: index2.js ---*/ + +import { foo } from "./index.js"; + +/**/ foo; +// ^ defined: 3, 7, 11 + +/*--- path: index3.js ---*/ + +let { foo } = await import("./index.js"); + +/**/ foo; +// ^ defined: 3, 7, 18 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_export/field_on_default_object_import_of_exported_default_object.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_export/field_on_default_object_import_of_exported_default_object.js new file mode 100644 index 000000000..ed6a119c7 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_export/field_on_default_object_import_of_exported_default_object.js @@ -0,0 +1,12 @@ +/*--- path: index.js ---*/ + +export default { + foo: 1 +}; + +/*--- path: index2.js ---*/ + +import mod from "./index.js"; + +mod.foo; +// ^ defined: 4 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_export/field_on_default_object_import_of_exported_nondefault_name.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_export/field_on_default_object_import_of_exported_nondefault_name.js new file mode 100644 index 000000000..b8cf8d0c9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/ES6_import_export/field_on_default_object_import_of_exported_nondefault_name.js @@ -0,0 +1,10 @@ +/*--- path: index.js ---*/ + +export function foo() { } + +/*--- path: index2.js ---*/ + +import mod from "./index.js"; + +mod.foo; +// ^ defined: diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/commonjs_imports_and_exports.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/commonjs_imports_and_exports.js new file mode 100644 index 000000000..05db804f8 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/commonjs_imports_and_exports.js @@ -0,0 +1,17 @@ +/*--- path: a.js ---*/ + +exports.foo = 1; + +module.exports = { + /**/ bar: 2 +}; + +/*--- path: b.js ---*/ + +const mod1 = require("./a.js"); + +mod1.foo; +// ^ defined: 3 + +mod1.bar; +// ^ defined: 6 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/es6_imports_and_exports.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/es6_imports_and_exports.js new file mode 100644 index 000000000..ab78f0cb1 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/es6_imports_and_exports.js @@ -0,0 +1,263 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Basic Exports and Imports +// +//////////////////////////////////////////////////////////////////////////////// + +/*--- path: a_basic.js ---*/ + +// Direct exports of declarations +export let a; +export let b = 1; +export function c() { } +export class D { } + +// Export list +let e = 2; +export { e }; + +// Renaming exports +let f = 3; +export { f as g }; + +// Exporting destructured assignments with renaming +export const { "k0": h, "k1": i } = { "k0": 4, "k1": 5 }; +export const [j, + k] = [1,2]; +export const { "a": [l] } = { "a": [1] }; +export const { m = 1 } = { "m": 2 }; + +/*--- path: b_basic_0.js ---*/ + +import { a, b as b2 } from "./a_basic.js"; + + a; +// ^ defined: 10, 32 + + b2; +// ^ defined: 11, 32 + +/*--- path: b_basic_1.js ---*/ + +import * as mod from "./a_basic.js"; + + mod; +// ^ defined: 42 + + mod.a; +// ^ defined: 10 + + mod.b; +// ^ defined: 11 + + mod.c; +// ^ defined: 12 + + mod.D; +// ^ defined: 13 + + mod.e; +// ^ defined: 16, 17 + + mod.f; +// ^ defined: + + mod.g; +// ^ defined: 20, 21 + + mod.h; +// ^ defined: 24 + + mod.i; +// ^ defined: 24 + + mod.j; +// ^ defined: 25 + + mod.k; +// ^ defined: 26 + + mod.l; +// ^ defined: 27 + + mod.m; +// ^ defined: 28 + + +//////////////////////////////////////////////////////////////////////////////// +// +// Default exports +// +//////////////////////////////////////////////////////////////////////////////// + +/*--- path: a_default_0.js ---*/ + +let n = 6; +export default n; + + +/*--- path: a_default_1.js ---*/ + +export default function () { } + + +/*--- path: a_default_2.js ---*/ + +export default function* () { } + + +/*--- path: a_default_3.js ---*/ +export default class { } + + +/*--- path: a_default_4.js ---*/ + +let o = 7; + +export { o as default }; + + +/*--- path: b_default.js ---*/ + +import p from "./a_default_0.js" +import q from "./a_default_1.js" +import r from "./a_default_2.js" +import s from "./a_default_3.js" + + p; +// ^ defined: 95, 96, 122 + + q; +// ^ defined: 101, 123 + + r; +// ^ defined: 106, 124 + + s; +// ^ defined: 110, 125 + + +//////////////////////////////////////////////////////////////////////////////// +// +// Aggregating Modules +// +//////////////////////////////////////////////////////////////////////////////// + +/*--- path: b_aggregating_0.js ---*/ + +export * from "./a_basic.js"; + + +/*--- path: b_aggregating_1.js ---*/ + +export * as t from "./a_basic.js"; + + +/*--- path: b_aggregating_2.js ---*/ + +export { c, D as D1 } from "./a_basic.js"; + + +/*--- path: b_aggregating_3.js ---*/ + +export { e as default } from "./a_basic.js"; + + +/*--- path: c_aggregating.js ---*/ + +import { a } from "./b_aggregating_0.js"; + + a; +// ^ defined: 10, 168 + + b; +// ^ defined: + +import { t } from "./b_aggregating_1.js"; + + t.a; +// ^ defined: 153, 176 +// ^ defined: 10 + +import { c, D1 } from "./b_aggregating_2.js"; + + c; +// ^ defined: 12, 158, 182 + + D1; +// ^ defined: 13, 158, 182 + +import e2 from "./b_aggregating_3.js"; + + e2; +// ^ defined: 16, 17, 163, 190 + +//////////////////////////////////////////////////////////////////////////////// +// +// Imports from super and subdirectories +// +//////////////////////////////////////////////////////////////////////////////// + +/*--- path: dirs/foo.js ---*/ + +export let x = 42; + +/*--- path: dirs/bar/baz.js ---*/ + +export let x = 42; + +/*--- path: dirs/test_0.js ---*/ + +import { x } from "./foo" +// ^ defined: 203 + +/*--- path: dirs/test_1.js ---*/ + +import { x } from "./bar/baz" +// ^ defined: 207 + +/*--- path: dirs/bar/test_2.js ---*/ + +import { x } from "../foo" +// ^ defined: 203 + +//////////////////////////////////////////////////////////////////////////////// +// +// Import using `import()` function +// +//////////////////////////////////////////////////////////////////////////////// + +/*--- path: b_function_1.js ---*/ + +const mod = await import("./a_basic.js"); + + mod; +// ^ defined: 232 + + mod.a; +// ^ defined: 10 + +//////////////////////////////////////////////////////////////////////////////// +// +// Import directory with `index.js` +// +//////////////////////////////////////////////////////////////////////////////// + +/*--- path: dir_imports/foo/index.js ---*/ + +export let x = 42; + +/*--- path: dir_imports/test_0.js ---*/ + +import { x } from "./foo/" +// ^ defined: 248 + +/*--- path: dir_imports/foo/test_1.js ---*/ + +import { x } from "./" +// ^ defined: 248 + +/*--- path: dir_imports/bar/test_2.js ---*/ + +import { x } from "../foo/" +// ^ defined: 248 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/import_function.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/import_function.js new file mode 100644 index 000000000..02b9de496 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/import_function.js @@ -0,0 +1,20 @@ +/*--- path: a.js ---*/ + +exports.foo = 2; +module.exports = 1; + +/*--- path: b.js ---*/ + +let mod = await import("./a.js"); + +mod.foo; +// ^ defined: 3 + +mod.default; +// ^ defined: 3, 4 +// !!!! TODO 3 is here because the `exports.foo` on line 3 also defines +// the default object. this is a current limitation of the import/export +// system to support CommonJS behavior + +mod.default.foo; +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/variable_visible_through_nonbinding_import.js b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/variable_visible_through_nonbinding_import.js new file mode 100644 index 000000000..8ce998c2c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/imports_and_exports/variable_visible_through_nonbinding_import.js @@ -0,0 +1,6 @@ +let x = 42; + +import "./foo"; + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/packages/can-access-package-non-main-explicilty.js b/languages/tree-sitter-stack-graphs-javascript/test/old/packages/can-access-package-non-main-explicilty.js new file mode 100644 index 000000000..820c0368e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/packages/can-access-package-non-main-explicilty.js @@ -0,0 +1,39 @@ +/* --- path: acme_foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +{ + "name": "@acme/foo", + "version": "1.0", + "main": "./api" +} + +/* --- path: acme_foo/api.js --- */ +/* --- global: FILE_PATH=api.js --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +export let x; + +/* --- path: acme_foo/core.js --- */ +/* --- global: FILE_PATH=core.js --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +export let x; + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{ + "name": "bar", + "dependencies": { + "@acme/foo": "1" + } +} + +/* --- path: bar/app.js --- */ +/* --- global: FILE_PATH=app.js --- */ +/* --- global: PROJECT_NAME=bar --- */ + +import { x } from "@acme/foo/core" +// ^ defined: 21 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-does-not-export-non-main.js b/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-does-not-export-non-main.js new file mode 100644 index 000000000..cea744788 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-does-not-export-non-main.js @@ -0,0 +1,32 @@ +/* --- path: foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=foo --- */ + +{ + "name": "foo", + "version": "1.0" +} + +/* --- path: foo/impl.js --- */ +/* --- global: FILE_PATH=impl.js --- */ +/* --- global: PROJECT_NAME=foo --- */ + +export let x; + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{ + "name": "bar", + "dependencies": { + "foo": "1" + } +} + +/* --- path: bar/app.js --- */ +/* --- global: FILE_PATH=app.js --- */ +/* --- global: PROJECT_NAME=bar --- */ + +import { x } from "foo" +// ^ defined: diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-exports-explicit-main.js b/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-exports-explicit-main.js new file mode 100644 index 000000000..14ec28812 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-exports-explicit-main.js @@ -0,0 +1,39 @@ +/* --- path: acme_foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +{ + "name": "@acme/foo", + "version": "1.0", + "main": "./api" +} + +/* --- path: acme_foo/api.js --- */ +/* --- global: FILE_PATH=api.js --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +export let x; + +/* --- path: acme_foo/core.js --- */ +/* --- global: FILE_PATH=core.js --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +export let x; + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{ + "name": "bar", + "dependencies": { + "@acme/foo": "1" + } +} + +/* --- path: bar/app.js --- */ +/* --- global: FILE_PATH=app.js --- */ +/* --- global: PROJECT_NAME=bar --- */ + +import { x } from "@acme/foo" +// ^ defined: 15 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-main-defaults-to-index.js b/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-main-defaults-to-index.js new file mode 100644 index 000000000..c06d0d625 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/packages/package-main-defaults-to-index.js @@ -0,0 +1,38 @@ +/* --- path: foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=foo --- */ + +{ + "name": "foo", + "version": "1.0" +} + +/* --- path: foo/index.js --- */ +/* --- global: FILE_PATH=index.js --- */ +/* --- global: PROJECT_NAME=foo --- */ + +export let x; + +/* --- path: foo/impl.js --- */ +/* --- global: FILE_PATH=impl.js --- */ +/* --- global: PROJECT_NAME=foo --- */ + +export let x; + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{ + "name": "bar", + "dependencies": { + "foo": "1" + } +} + +/* --- path: bar/app.js --- */ +/* --- global: FILE_PATH=app.js --- */ +/* --- global: PROJECT_NAME=bar --- */ + +import { x } from "foo" +// ^ defined: 14 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/comma.js b/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/comma.js new file mode 100644 index 000000000..58a51610e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/comma.js @@ -0,0 +1,7 @@ +let x = 1; +let y = (1, x); +// ^ defined: 1 + +let y = (1, x = 5); +let z = x; +// ^ defined: 1, 5 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/template_strings.js b/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/template_strings.js new file mode 100644 index 000000000..099cd1d37 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/template_strings.js @@ -0,0 +1,4 @@ +let x = 1; + +let y = `template ${ x } string`; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/ternary.js b/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/ternary.js new file mode 100644 index 000000000..4c7947e5a --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/ternary.js @@ -0,0 +1,4 @@ +let x = 1; +let y = true ? x++ : 2; +let z = x; +// ^ defined: 1, 2 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/variables.js b/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/variables.js new file mode 100644 index 000000000..394da591f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/old/simple_expressions/variables.js @@ -0,0 +1,9 @@ +let x = 1; +const y = 2; + +var z = x + y; +// ^ defined: 1 +// ^ defined: 2 + +let w = z; +// ^ defined: 1, 2, 4 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/patterns/array_pattern.js b/languages/tree-sitter-stack-graphs-javascript/test/patterns/array_pattern.js new file mode 100644 index 000000000..c3391b10f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/patterns/array_pattern.js @@ -0,0 +1,33 @@ +let x = 1; + +// Flow In + +let [y = x] = arr; +// ^ defined: 1 +// have to use assignment patterns here to get flow in + +// Flow Out + +let [ + z +] = arr; + +/**/ z; +// ^ defined: 12 + +// Flow Around + +/**/ x; +// ^ defined: 1 + +// Flow In From RHS + +let [w = x] = x++; +// ^ defined: 1, 25 +// have to use assignment patterns here to get flow out + +// Flow Into Subsequent Patterns From Earlier Patterns + +let [q, + r = q] = arr; + // ^ defined: 31 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/patterns/assignment_pattern.js b/languages/tree-sitter-stack-graphs-javascript/test/patterns/assignment_pattern.js new file mode 100644 index 000000000..0b8f402fb --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/patterns/assignment_pattern.js @@ -0,0 +1,28 @@ +let x = 1; + +// Flow In + +[y = x] = 2; +// ^ defined: 1 +// have to use array patterns or something else here to get a pattern +// on the LHS that can contain an assignment pattern + +// Flow Out + +[z = 1] = 2; +/**/ z; +// ^ defined: 12 +// have to use array patterns or something else here to get a pattern +// on the LHS that can contain an assignment pattern + +// Flow Around + +/**/ x; +// ^ defined: 1 + +// Flow In From RHS + +[w = x] = x++; +// ^ defined: 1, 25 +// have to use array patterns or something else here to get a pattern +// on the LHS that can contain an assignment pattern \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/patterns/function_declaration_formal_parameter_patterns.js b/languages/tree-sitter-stack-graphs-javascript/test/patterns/function_declaration_formal_parameter_patterns.js new file mode 100644 index 000000000..7be165815 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/patterns/function_declaration_formal_parameter_patterns.js @@ -0,0 +1,16 @@ +let x = 1; + +// Flow In and Out To Body + +function foo(y = x, [z] = arr) { + // ^ defined: 1 + /**/ y; + // ^ defined: 5, 1 + /**/ z; + // ^ defined: 5 +} + +// Flow Out + +/**/ z; +// ^ defined: \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/patterns/method_declaration_formal_parameter_patterns.js b/languages/tree-sitter-stack-graphs-javascript/test/patterns/method_declaration_formal_parameter_patterns.js new file mode 100644 index 000000000..0d5082948 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/patterns/method_declaration_formal_parameter_patterns.js @@ -0,0 +1,18 @@ +let x = 1; + +// Flow In and Out To Body + +class Foo { + foo(y = x, [z] = arr) { + // ^ defined: 1 + /**/ y; + // ^ defined: 6, 1 + /**/ z; + // ^ defined: 6 + } +} + +// Flow Out + +/**/ z; +// ^ defined: \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/patterns/object_pattern.js b/languages/tree-sitter-stack-graphs-javascript/test/patterns/object_pattern.js new file mode 100644 index 000000000..c71c5ccf5 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/patterns/object_pattern.js @@ -0,0 +1,47 @@ +let x = 1; + +// Flow In + +let { z: y = x } = obj; +// ^ defined: 1 +// have to use assignment patterns here to get flow in + +// Flow Out + +let { + z: w +} = obj; + +/**/ w; +// ^ defined: 12 + +// Flow Around + +/**/ x; +// ^ defined: 1 + +// Flow In From RHS + +let { z: q = r } = + // ^ defined: 27 + r++; + +// Flow Into Subsequent Patterns From Earlier Patterns + +let { z: s, + z: t = s } = obj; +// ^ defined: 31 + +// Flow Out From Shorthand Property Identifier Pattern + +let { u } = obj; + +/**/ u; +// ^ defined: 37 + +// Flow Out From Object Assignment Pattern + +let { v = 1 } = obj; + +/**/ v; +// ^ defined: 44 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/class_declaration.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/class_declaration.js new file mode 100644 index 000000000..57cd8d5e2 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/class_declaration.js @@ -0,0 +1,33 @@ +let x = 1; + +// Flow In + +class Foo extends x { + // ^ defined: 1 + z = x; + // ^ defined: 1 + + bar() { + /**/ x; + // ^ defined: 1 + + /**/ z; + // ^ defined: + // z should not be defined here + } +} + +// Flow Out + +class Baz { + y = 1; +} + +/**/ y; +// ^ defined: +// y should not be defined here + +// Flow Around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/debugger_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/debugger_statement.js new file mode 100644 index 000000000..68839e773 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/debugger_statement.js @@ -0,0 +1,8 @@ +let x = 1; + +debugger; + +// Flow around + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/do_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/do_statement.js new file mode 100644 index 000000000..be81e9e2a --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/do_statement.js @@ -0,0 +1,25 @@ +let x = 1; + +// Flow in + +do { + /**/ x; + // ^ defined: 1 + /**/ y; + // ^ defined: 11 + z = 1; +} while ((x, y = 5)); +// ^ defined: 1 + +// Flow out + +/**/ y; +// ^ defined: 11 + +/**/ z; +// ^ defined: 10 + +// Flow around + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/empty_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/empty_statement.js new file mode 100644 index 000000000..a12d041a7 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/empty_statement.js @@ -0,0 +1,8 @@ +let x = 1; + +; + +// Flow around + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/export_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/export_statement.js new file mode 100644 index 000000000..a954a1f34 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/export_statement.js @@ -0,0 +1,31 @@ +let x = 1; + +// Flow In + +export { x }; +// ^ defined: 1 +export { x as _ }; +// ^ defined: 1 +export let _ = x; +// ^ defined: 1 +export function f() { + /**/ x; + // ^ defined: 1 +}; +export default function () { + /**/ x; + // ^ defined: 1 +}; + +// Flow Out + +export { _ as y }; + +/**/ y; +// ^ defined: +// y should not be defined here + +// Flow Around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/for_in_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/for_in_statement.js new file mode 100644 index 000000000..eac6898a8 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/for_in_statement.js @@ -0,0 +1,25 @@ +let x = 1; + +// Flow in + +for (let y in (x, 1)) { + // ^ defined: 1 + /**/ x; + // ^ defined: 1 + /**/ y; + // ^ defined: 5 + z = 1; +} + +// Flow out + +/**/ y; +// ^ defined: 5 + +/**/ z; +// ^ defined: 11 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/for_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/for_statement.js new file mode 100644 index 000000000..7cdc5e19f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/for_statement.js @@ -0,0 +1,31 @@ +let x = 1; + +// Flow in + +for (let y = (x, 1); + // ^ defined: 1 + /**************/ x < y; + // ^ defined: 1 + // ^ defined: 5, 10 + /*********************/ y = (x, y)) { + // ^ defined: 1 + // ^ defined: 5, 10 + /**/ x; + // ^ defined: 1 + /**/ y; + // ^ defined: 5, 10 + z = 1; +} + +// Flow out + +/**/ y; +// ^ defined: 5, 10 + +/**/ z; +// ^ defined: 17 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/function_declaration.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/function_declaration.js new file mode 100644 index 000000000..a3094f1a5 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/function_declaration.js @@ -0,0 +1,35 @@ +// +// READ ME +// +// This test does NOT test complex arguments. See the `binding` test dir. +// + +let x = 1; + +// Flow In + +function foo() { + /**/ x; + // ^ defined: 7 +} + +// Flow Out + +function bar() { + y = 1; +} + +/**/ y; +// ^ defined: +// y should not be defined here + +// Flow Around + +/**/ x; +// ^ defined: 7 + +// Flow In from Arg +function baz(y) { + /**/ y; + // ^ defined: 32 +} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/generator_function_declaration.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/generator_function_declaration.js new file mode 100644 index 000000000..2bd9f050f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/generator_function_declaration.js @@ -0,0 +1,35 @@ +// +// READ ME +// +// This test does NOT test complex arguments. See the `binding` test dir. +// + +let x = 1; + +// Flow In + +function* foo() { + /**/ x; + // ^ defined: 7 +} + +// Flow Out + +function* bar() { + y = 1; +} + +/**/ y; +// ^ defined: +// y should not be defined here + +// Flow Around + +/**/ x; +// ^ defined: 7 + +// Flow In from Arg +function* baz(y) { + /**/ y; + // ^ defined: 32 +} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/hash_bang_line.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/hash_bang_line.js new file mode 100644 index 000000000..babd2b2dd --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/hash_bang_line.js @@ -0,0 +1,5 @@ +#! foo +let x = 1; + +/**/ x; +// ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/if_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/if_statement.js new file mode 100644 index 000000000..c278bba7b --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/if_statement.js @@ -0,0 +1,30 @@ +let x = 1; + +// Flow in + +// TODO NOW add flow into the condition too +if (/**/ x) { + // ^ defined: 1 + /**/ x; + // ^ defined: 1 + y = 2; +} else if (x) { + // ^ defined: 1 + /**/ x; + // ^ defined: 1 + y = 2; +} else { + /**/ x; + // ^ defined: 1 + y = 2; +} + +// Flow out + +/**/ y; +// ^ defined: 10, 15, 19 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/import_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/import_statement.js new file mode 100644 index 000000000..c3370b624 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/import_statement.js @@ -0,0 +1,25 @@ +let x = 1; + +// Flow Out + +import * as y from "mod"; +import { z } from "mod"; +import { y as w } from "mod"; +import q from "mod"; + +/**/ y; +// ^ defined: 5 + +/**/ z; +// defined: 6 + +/**/ w; +// defined: 7 + +/**/ q; +// defined: 8 + +// Flow Around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/label_continue_break_statements.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/label_continue_break_statements.js new file mode 100644 index 000000000..f205dcefe --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/label_continue_break_statements.js @@ -0,0 +1,15 @@ +let x = 1; + +outer: while (t) { + while (t) { + break outer; + // ^ defined: 3 + } + continue outer; + // ^ defined: 3 +} + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/label_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/label_statement.js new file mode 100644 index 000000000..5ebc6df3c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/label_statement.js @@ -0,0 +1,16 @@ +let x = 1; + +// Flow in + +label: x, y = 1; +// ^ defined: 1 + +// Flow out + +/**/ y; +// ^ defined: 5 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/lexical_declaration_basic_no_destructuring.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/lexical_declaration_basic_no_destructuring.js new file mode 100644 index 000000000..de4720de3 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/lexical_declaration_basic_no_destructuring.js @@ -0,0 +1,18 @@ +let x = 1; +/**/ x; +// ^ defined: 1 + +// Flow in +let y = x; +// ^ defined: 1 + +// Flow around + +/**/ x; +// ^ defined: 1 + +// Shadowing + +let x = 2; +/**/ x; +// ^ defined: 16 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/return_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/return_statement.js new file mode 100644 index 000000000..4bec4d573 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/return_statement.js @@ -0,0 +1,20 @@ +function* f() { + let x = 1; + + // Flow in + + return x; + // ^ defined: 2 + + // Flow out + + return y = 1; + + /**/ y; + // ^ defined: 11 + + // Flow around + + /**/ x; + // ^ defined: 2 +} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/statement_block.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/statement_block.js new file mode 100644 index 000000000..19f70a1b2 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/statement_block.js @@ -0,0 +1,21 @@ +let x = 1; + +// Flow in +{ + /**/ x; + // ^ defined: 1 +} + +// Flow around + +/**/ x; +// ^ defined: 1 + +// Flow out + +{ + let y = 1; +} + +/**/ y; +// ^ defined: 17 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/switch_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/switch_statement.js new file mode 100644 index 000000000..a40f7f2e9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/switch_statement.js @@ -0,0 +1,25 @@ +let x = 1; + +// Flow in + +switch (x) { + // ^ defined: 1 + case 0: + /**/ x; + // ^ defined: 1 + y = 2; + default: + /**/ x; + // ^ defined: 1 + y = 2; +} + +// Flow out + +/**/ y; +// ^ defined: 10, 14 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/throw_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/throw_statement.js new file mode 100644 index 000000000..01d9c2e2d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/throw_statement.js @@ -0,0 +1,15 @@ +let x = 1; + +// Flow In +throw x, y = 1; +// ^ defined: 1 + +// Flow Out + +/**/ y; +// ^ defined: 4 + +// Flow around + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/try_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/try_statement.js new file mode 100644 index 000000000..0b6613243 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/try_statement.js @@ -0,0 +1,31 @@ +let x = 1; + +// Flow in + +try { + /**/ x; + // ^ defined: 1 + y = 1; +} catch (e) { + /**/ e + // ^ defined: 9 + /**/ y; + // ^ defined: 8 + y = 1; +} finally { + /**/ x; + // ^ defined: 1 + /**/ y; + // ^ defined: 8, 14 + y = 1; +} + +// Flow out + +/**/ y; +// ^ defined: 8, 14, 20 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/variable_declaration_basic_no_destructuring.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/variable_declaration_basic_no_destructuring.js new file mode 100644 index 000000000..7cc45c1c7 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/variable_declaration_basic_no_destructuring.js @@ -0,0 +1,18 @@ +var x = 1; +/**/ x; +// ^ defined: 1 + +// Flow in +var y = x; +// ^ defined: 1 + +// Flow around + +/**/ x; +// ^ defined: 1 + +// Shadowing + +var x = 2; +/**/ x; +// ^ defined: 16 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/while_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/while_statement.js new file mode 100644 index 000000000..6df4acafe --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/while_statement.js @@ -0,0 +1,25 @@ +let x = 1; + +// Flow in + +while ((x, y = 5)) { + // ^ defined: 1 + /**/ x; + // ^ defined: 1 + /**/ y; + // ^ defined: 5 + z = 1; +} + +// Flow out + +/**/ y; +// ^ defined: 5 + +/**/ z; +// ^ defined: 11 + +// Flow around + +/**/ x; +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-javascript/test/statements/with_statement.js b/languages/tree-sitter-stack-graphs-javascript/test/statements/with_statement.js new file mode 100644 index 000000000..8b11e2de9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-javascript/test/statements/with_statement.js @@ -0,0 +1,25 @@ +let x = 1; + +// Flow in + +with ((y = 1, x)) { + // ^ defined: 1 + /**/ x; + // ^ defined: 1 + /**/ y; + // ^ defined: 5 + z = 1; +} + +// Flow out + +/**/ y; +// ^ defined: 5 + +/**/ z; +// ^ defined: 11 + +// Flow around + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-python/.gitignore b/languages/tree-sitter-stack-graphs-python/.gitignore new file mode 100644 index 000000000..faf6459fb --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/.gitignore @@ -0,0 +1,3 @@ +*.html +/Cargo.lock +/target diff --git a/languages/tree-sitter-stack-graphs-python/CHANGELOG.md b/languages/tree-sitter-stack-graphs-python/CHANGELOG.md new file mode 100644 index 000000000..f21aecc9f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog for tree-sitter-stack-graphs-python + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## v0.3.0 -- 2024-12-12 + +- The `tree-sitter-stack-graphs` dependency is updated to version 0.10. + +- The `tree-sitter-python` dependency is updated to version 0.23.5. + +## v0.2.0 -- 2024-07-09 + +### Added + +- Added support for root paths. This fixes import problems when indexing using absolute directory paths. + +### Fixed + +- Fixed crash for lambdas with parameters. +- Fixed crash for nested functions definitions. + +### Removed + +- The `FILE_PATH_VAR` constant has been replaced in favor of `tree_sitter_stack_graphs::FILE_PATH_VAR`. + +## v0.1.0 -- 2024-03-06 + +Initial release. diff --git a/languages/tree-sitter-stack-graphs-python/Cargo.toml b/languages/tree-sitter-stack-graphs-python/Cargo.toml new file mode 100644 index 000000000..afdfe6091 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "tree-sitter-stack-graphs-python" +version = "0.3.0" +description = "Stack graphs definition for Python using tree-sitter-python" +readme = "README.md" +keywords = ["tree-sitter", "stack-graphs", "python"] +authors = [ + "GitHub ", +] +license = "MIT OR Apache-2.0" +edition = "2018" + +[[bin]] +name = "tree-sitter-stack-graphs-python" +path = "rust/bin.rs" +required-features = ["cli"] + +[lib] +path = "rust/lib.rs" +test = false + +[[test]] +name = "test" +path = "rust/test.rs" +harness = false + +[features] +cli = ["anyhow", "clap", "tree-sitter-stack-graphs/cli"] + +[dependencies] +anyhow = { version = "1.0", optional = true } +clap = { version = "4", optional = true, features = ["derive"] } +tree-sitter-stack-graphs = { version = "0.10", path = "../../tree-sitter-stack-graphs" } # explicit version is required to be able to publish crate +tree-sitter-python = "=0.23.5" + +[dev-dependencies] +anyhow = "1.0" +tree-sitter-stack-graphs = { path = "../../tree-sitter-stack-graphs", features = ["cli"] } diff --git a/languages/tree-sitter-stack-graphs-python/README.md b/languages/tree-sitter-stack-graphs-python/README.md new file mode 100644 index 000000000..7ec9ded72 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/README.md @@ -0,0 +1,138 @@ +# tree-sitter-stack-graphs definition for Python + +This project defines tree-sitter-stack-graphs rules for Python using the [tree-sitter-python][] grammar. + +[tree-sitter-python]: https://crates.io/crates/tree-sitter-python + +- [API documentation](https://docs.rs/tree-sitter-stack-graphs-python/) +- [Release notes](https://github.com/github/stack-graphs/blob/main/languages/tree-sitter-stack-graphs-python/CHANGELOG.md) + +## Using the API + +To use this library, add the following to your `Cargo.toml`: + +```toml +[dependencies] +tree-sitter-stack-graphs-python = "0.3" +``` + +Check out our [documentation](https://docs.rs/tree-sitter-stack-graphs-python/*/) for more details on how to use this library. + +## Using the Command-line Program + +The command-line program for `tree-sitter-stack-graphs-python` lets you do stack graph based analysis and lookup from the command line. + +The CLI can be run as follows: + +1. _(Installed)_ Install the CLI using Cargo as follows: + + ```sh + cargo install --features cli tree-sitter-stack-graphs-python + ``` + + After this, the CLI should be available as `tree-sitter-stack-graphs-python`. + +2. _(From source)_ Instead of installing the CLI, it can also be run directly from the crate directory, as a replacement for a `tree-sitter-stack-graphs-python` invocation, as follows: + + ```sh + cargo run --features cli -- + ``` + +The basic CLI workflow for the command-line program is to index source code and issue queries against the resulting database: + +1. Index a source folder as follows: + + ```sh + tree-sitter-stack-graphs-python index SOURCE_DIR + ``` + + _Indexing will skip any files that have already be indexed. To force a re-index, add the `-f` flag._ + + To check the status if a source folder, run: + + ```sh + tree-sitter-stack-graphs-python status SOURCE_DIR + ``` + + To clean the database and start with a clean slate, run: + + ```sh + tree-sitter-stack-graphs-python clean + ``` + + _Pass the `--delete` flag to not just empty the database, but also delete it. This is useful to resolve `unsupported database version` errors that may occur after a version update._ + +2. Run a query to find the definition(s) for a reference on a given line and column, run: + + ```sh + tree-sitter-stack-graphs-python query definition SOURCE_PATH:LINE:COLUMN + ``` + + Resulting definitions are printed, including a source line if the source file is available. + +Discover all available commands and flags by passing the `-h` flag to the CLI directly, or to any of the subcommands. + +## Development + +The project is written in Rust, and requires a recent version installed. Rust can be installed and updated using [rustup][]. + +[rustup]: https://rustup.rs/ + +The project is organized as follows: + +- The stack graph rules are defined in `src/stack-graphs.tsg`. +- Builtins sources and configuration are defined in `src/builtins.it` and `builtins.cfg` respectively. +- Tests are put into the `test` directory. + +### Running Tests + +Run the tests as follows: + +```sh +cargo test +``` + +The project consists of a library and a CLI. By default, running `cargo` only applies to the library. To run `cargo` commands on the CLI as well, add `--features cli` or `--all-features`. + +Run the CLI from source as follows: + +```sh +cargo run --features cli -- ARGS +``` + +Sources are formatted using the standard Rust formatted, which is applied by running: + +```sh +cargo fmt +``` + +### Writing TSG + +The stack graph rules are written in [tree-sitter-graph][]. Checkout the [examples][], +which contain self-contained TSG rules for specific language features. A VSCode +[extension][] is available that provides syntax highlighting for TSG files. + +[tree-sitter-graph]: https://github.com/tree-sitter/tree-sitter-graph +[examples]: https://github.com/github/stack-graphs/blob/main/tree-sitter-stack-graphs/examples/ +[extension]: https://marketplace.visualstudio.com/items?itemName=tree-sitter.tree-sitter-graph + +Parse and test a single file by executing the following commands: + +```sh +cargo run --features cli -- parse FILES... +cargo run --features cli -- test TESTFILES... +``` + +Generate a visualization to debug failing tests by passing the `-V` flag: + +```sh +cargo run --features cli -- test -V TESTFILES... +``` + +To generate the visualization regardless of test outcome, execute: + +```sh +cargo run --features cli -- test -V --output-mode=always TESTFILES... +``` + +Go to for links to examples and documentation. diff --git a/languages/tree-sitter-stack-graphs-python/rust/bin.rs b/languages/tree-sitter-stack-graphs-python/rust/bin.rs new file mode 100644 index 000000000..dc9a6772c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/rust/bin.rs @@ -0,0 +1,32 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use anyhow::anyhow; +use clap::Parser; +use tree_sitter_stack_graphs::cli::database::default_user_database_path_for_crate; +use tree_sitter_stack_graphs::cli::provided_languages::Subcommands; +use tree_sitter_stack_graphs::NoCancellation; + +fn main() -> anyhow::Result<()> { + let lc = match tree_sitter_stack_graphs_python::try_language_configuration(&NoCancellation) { + Ok(lc) => lc, + Err(err) => { + eprintln!("{}", err.display_pretty()); + return Err(anyhow!("Language configuration error")); + } + }; + let cli = Cli::parse(); + let default_db_path = default_user_database_path_for_crate(env!("CARGO_PKG_NAME"))?; + cli.subcommand.run(default_db_path, vec![lc]) +} + +#[derive(Parser)] +#[clap(about, version)] +pub struct Cli { + #[clap(subcommand)] + subcommand: Subcommands, +} diff --git a/languages/tree-sitter-stack-graphs-python/rust/lib.rs b/languages/tree-sitter-stack-graphs-python/rust/lib.rs new file mode 100644 index 000000000..332250511 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/rust/lib.rs @@ -0,0 +1,45 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use tree_sitter_stack_graphs::loader::LanguageConfiguration; +use tree_sitter_stack_graphs::loader::LoadError; +use tree_sitter_stack_graphs::CancellationFlag; + +/// The stack graphs tsg source for this language. +pub const STACK_GRAPHS_TSG_PATH: &str = "src/stack-graphs.tsg"; +/// The stack graphs tsg source for this language. +pub const STACK_GRAPHS_TSG_SOURCE: &str = include_str!("../src/stack-graphs.tsg"); + +/// The stack graphs builtins configuration for this language. +pub const STACK_GRAPHS_BUILTINS_CONFIG: &str = include_str!("../src/builtins.cfg"); +/// The stack graphs builtins path for this language +pub const STACK_GRAPHS_BUILTINS_PATH: &str = "src/builtins.py"; +/// The stack graphs builtins source for this language. +pub const STACK_GRAPHS_BUILTINS_SOURCE: &str = include_str!("../src/builtins.py"); + +pub fn language_configuration(cancellation_flag: &dyn CancellationFlag) -> LanguageConfiguration { + try_language_configuration(cancellation_flag).unwrap_or_else(|err| panic!("{}", err)) +} + +pub fn try_language_configuration( + cancellation_flag: &dyn CancellationFlag, +) -> Result { + LanguageConfiguration::from_sources( + tree_sitter_python::LANGUAGE.into(), + Some(String::from("source.py")), + None, + vec![String::from("py")], + STACK_GRAPHS_TSG_PATH.into(), + STACK_GRAPHS_TSG_SOURCE, + Some(( + STACK_GRAPHS_BUILTINS_PATH.into(), + STACK_GRAPHS_BUILTINS_SOURCE, + )), + Some(STACK_GRAPHS_BUILTINS_CONFIG), + cancellation_flag, + ) +} diff --git a/languages/tree-sitter-stack-graphs-python/rust/test.rs b/languages/tree-sitter-stack-graphs-python/rust/test.rs new file mode 100644 index 000000000..c81edf0d4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/rust/test.rs @@ -0,0 +1,23 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use anyhow::anyhow; +use std::path::PathBuf; +use tree_sitter_stack_graphs::ci::Tester; +use tree_sitter_stack_graphs::NoCancellation; + +fn main() -> anyhow::Result<()> { + let lc = match tree_sitter_stack_graphs_python::try_language_configuration(&NoCancellation) { + Ok(lc) => lc, + Err(err) => { + eprintln!("{}", err.display_pretty()); + return Err(anyhow!("Language configuration error")); + } + }; + let test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test"); + Tester::new(vec![lc], vec![test_path]).run() +} diff --git a/languages/tree-sitter-stack-graphs-python/src/builtins.cfg b/languages/tree-sitter-stack-graphs-python/src/builtins.cfg new file mode 100644 index 000000000..d685061be --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/src/builtins.cfg @@ -0,0 +1 @@ +[globals] diff --git a/languages/tree-sitter-stack-graphs-python/src/builtins.py b/languages/tree-sitter-stack-graphs-python/src/builtins.py new file mode 100644 index 000000000..e69de29bb diff --git a/languages/tree-sitter-stack-graphs-python/src/stack-graphs.scm b/languages/tree-sitter-stack-graphs-python/src/stack-graphs.scm new file mode 100644 index 000000000..380af7d95 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/src/stack-graphs.scm @@ -0,0 +1,779 @@ +;; -*- coding: utf-8 -*- +;; ------------------------------------------------------------------------------------------------ +;; Copyright © 2023, stack-graphs authors. +;; Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +;; Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +;; ------------------------------------------------------------------------------------------------ + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; LEGACY DEFINITION! INCLUDED FOR REFERENCE ONLY! ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Modules and Imports +;--------------------- + +(module) @mod +{ + var module_def = @mod.file_def + attr module_def "no_span" + var module_ref = @mod.file_ref + attr module_ref "no_span" + var parent_module_def = module_def + var parent_module_ref = module_ref + var grandparent_module_ref = module_ref + + scan filepath { + "([^/]+)/" + { + edge module_def -> module_def.dot + edge module_def.dot -> module_def.next_def + edge module_ref.next_ref -> module_ref.dot + edge module_ref.dot -> module_ref + + attr module_def "pop" = $1 + attr module_def.dot "pop" = "." + attr module_ref "push" = $1 + attr module_ref.dot "push" = "." + + set grandparent_module_ref = parent_module_ref + set parent_module_def = module_def + set parent_module_ref = module_ref + set module_ref = module_ref.next_ref + set module_def = module_def.next_def + attr module_def "no_span" + attr module_ref "no_span" + } + + "__init__\\.py$" + { + attr parent_module_def "definition" + } + + "([^/]+)$" + { + edge module_def -> module_def.dot + edge module_def.dot -> module_def.next_def + + attr module_def "definition", "pop" = (replace $1 "\\.py" "") + attr module_def.dot "pop" = "." + + set module_def = module_def.next_def + attr module_def "no_span" + attr module_ref "no_span" + } + } + + edge root -> @mod.file_def + edge @mod.file_ref -> root + edge module_def -> @mod.after_scope + + edge @mod.before_scope -> @mod.global_dot + edge @mod.global -> root + attr @mod.global "push" = "" + + edge @mod.global_dot -> @mod.global + attr @mod.global_dot "push" = "." + + var @mod::parent_module = parent_module_ref + var @mod::grandparent_module = grandparent_module_ref + var @mod::bottom = @mod.after_scope + var @mod::global = @mod.global + var @mod::global_dot = @mod.global_dot +} + +(import_statement + name: (dotted_name + . (identifier) @root_name)) @stmt +{ + edge @stmt.after_scope -> @root_name.def, "precedence" = 1 + edge @root_name.ref -> root + attr @root_name.ref "push", "reference" + attr @root_name.def "pop", "definition" +} + +(import_statement + name: (aliased_import + (dotted_name . (identifier) @root_name))) @stmt +{ + edge @stmt.after_scope -> @root_name.def + edge @root_name.ref -> root + attr @root_name.ref "push", "reference" +} + +(import_from_statement + module_name: (dotted_name + . (identifier) @prefix_root_name)) @stmt +{ + edge @prefix_root_name.ref -> root + attr @prefix_root_name.ref "push", "reference" +} + +(import_from_statement + name: (dotted_name + . (identifier) @import_root_name)) @stmt +{ + edge @stmt.after_scope -> @import_root_name.def, "precedence" = 1 + edge @import_root_name.ref -> @import_root_name.ref_dot + attr @import_root_name.def "pop", "definition" + attr @import_root_name.ref "push", "reference" + attr @import_root_name.ref_dot "push" = "." +} + +(import_from_statement + name: (aliased_import + (dotted_name + . (identifier) @import_root_name))) @stmt +{ + edge @import_root_name.ref -> @import_root_name.ref_dot + attr @import_root_name.ref "push", "reference" + attr @import_root_name.ref_dot "push" = "." +} + +(import_from_statement + module_name: [ + (dotted_name (identifier) @prefix_leaf_name .) + (relative_import (dotted_name (identifier) @prefix_leaf_name .)) + (relative_import (import_prefix) @prefix_leaf_name .) + ] + name: [ + (dotted_name + . (identifier) @import_root_name) + (aliased_import + (dotted_name + . (identifier) @import_root_name)) + ]) +{ + edge @import_root_name.ref_dot -> @prefix_leaf_name.ref +} + +[ + (import_from_statement + (aliased_import + name: (dotted_name (identifier) @name .) + alias: (identifier) @alias)) + (import_statement + (aliased_import + name: (dotted_name (identifier) @name .) + alias: (identifier) @alias)) +] @stmt +{ + edge @stmt.after_scope -> @alias + edge @alias -> @name.ref + attr @alias "pop", "definition" +} + +[ + (import_statement + name: (dotted_name + (identifier) @leaf_name .)) + (import_from_statement + name: (dotted_name + (identifier) @leaf_name .)) +] +{ + attr @leaf_name.def "pop", "definition" + attr @leaf_name.ref "push", "reference" + edge @leaf_name.def -> @leaf_name.ref +} + +(relative_import + (import_prefix) @prefix + (#eq? @prefix ".")) @import +{ + edge @prefix.ref -> @import::parent_module +} + +(relative_import + (import_prefix) @prefix + (#eq? @prefix "..")) @import +{ + edge @prefix.ref -> @import::grandparent_module +} + +(relative_import + (import_prefix) @prefix + (dotted_name + . (identifier) @name)) +{ + attr @name.ref "push", "reference" + attr @name.ref_dot "push" = "." + edge @name.ref -> @name.ref_dot + edge @name.ref_dot -> @prefix.ref +} + +[ + (import_from_statement + module_name: (relative_import + (dotted_name + (identifier) @parent_name + . + (identifier) @child_name))) + (import_from_statement + module_name: (dotted_name + (identifier) @parent_name + . + (identifier) @child_name)) +] +{ + attr @child_name.ref "push", "reference" + attr @child_name.ref_dot "push" = "." + edge @child_name.ref -> @child_name.ref_dot + edge @child_name.ref_dot -> @parent_name.ref +} + +(import_from_statement + module_name: (dotted_name + . (identifier) @root_name)) +{ + attr @root_name.ref "push", "reference" + edge @root_name.ref -> root +} + +(import_from_statement + module_name: (dotted_name + (identifier) @leaf_name .) + (wildcard_import) @star) @stmt +{ + edge @stmt.after_scope -> @star.ref_dot, "precedence" = 1 + edge @star.ref_dot -> @leaf_name.ref + attr @star.ref_dot "push" = "." +} + +[ + (import_statement + name: (dotted_name + (identifier) @parent_name + . + (identifier) @child_name)) + (import_from_statement + name: (dotted_name + (identifier) @parent_name + . + (identifier) @child_name)) + (import_from_statement + name: (aliased_import + name: (dotted_name + (identifier) @parent_name + . + (identifier) @child_name))) +] +{ + edge @child_name.ref -> @child_name.ref_dot + edge @child_name.ref_dot -> @parent_name.ref + edge @parent_name.def -> @parent_name.def_dot + edge @parent_name.def_dot -> @child_name.def + attr @child_name.def "pop", "definition" + attr @child_name.ref "push","reference" + attr @parent_name.def_dot "pop" = "." + attr @child_name.ref_dot "push" = "." +} + +;-------- +; Scopes +;-------- + +[ + (module (_) @last_stmt .) + (block (_) @last_stmt .) +] @block +{ + edge @block.after_scope -> @last_stmt.after_scope +} + +[ + (module (_) @stmt1 . (_) @stmt2) + (block (_) @stmt1 . (_) @stmt2) +] +{ + edge @stmt2.before_scope -> @stmt1.after_scope +} + +[ + (module (_) @stmt) + (block (_) @stmt) +] +{ + edge @stmt.after_scope -> @stmt.before_scope + let @stmt::local_scope = @stmt.before_scope +} + +[ + (block . (_) @stmt) + (module . (_) @stmt) +] @block +{ + edge @stmt.before_scope -> @block.before_scope +} + +(block (_) @stmt . ) @block +{ + edge @block.after_scope -> @stmt.after_scope +} + +(function_definition (block) @block) +{ + edge @block.before_scope -> @block::local_scope +} + +[ + (while_statement (block) @block) + (if_statement (block) @block) + (with_statement (block) @block) + (try_statement (block) @block) + (for_statement (block) @block) + (_ [ + (else_clause (block) @block) + (elif_clause (block) @block) + (except_clause (block) @block) + (finally_clause (block) @block) + ]) +] @stmt +{ + edge @block.before_scope -> @block::local_scope + edge @stmt.after_scope -> @block.after_scope +} + +(match_statement (case_clause) @block) @stmt +{ + let @block::local_scope = @block.before_scope + edge @block.before_scope -> @stmt.before_scope + edge @stmt.after_scope -> @block.after_scope +} + +[ + (for_statement) + (while_statement) +] @stmt +{ + edge @stmt.before_scope -> @stmt.after_scope +} + +;------------- +; Definitions +;------------- + +[ + (assignment + left: (_) @pattern + right: (_) @value) + (with_item + value: + (as_pattern + (_) @value + alias: (as_pattern_target (_) @pattern))) +] +{ + edge @pattern.input -> @value.output +} + +(function_definition + name: (identifier) @name + parameters: (parameters) @params + body: (block) @body) @func +{ + attr @name "definiens" = @func + edge @func.after_scope -> @name + edge @name -> @func.call + edge @func.call -> @func.return_value + edge @body.before_scope -> @params.after_scope + edge @body.before_scope -> @func.drop_scope + edge @func.drop_scope -> @func::bottom + attr @func.drop_scope "drop" + attr @name "pop", "definition" + attr @func.call "pop" = "()", "pop-scope" + attr @params.before_scope "jump-to" + attr @func.return_value "endpoint" + let @func::function_returns = @func.return_value + + ; Prevent functions defined inside of method bodies from being treated like methods + let @body::class_self_scope = nil + let @body::class_member_attr_scope = nil +} + +;; +;; BEGIN BIG GNARLY DISJUNCTION +;; +;; The following pair of rules is intended to capture the following behavior: +;; +;; If a function definition is used to define a method, by being inside a class +;; definition, then we make its syntax type `method`. Otherwise, we make it's +;; syntax type `function`. Unfortunately, because of the limitations on negation +;; and binding in tree sitter queries, we cannot negate `class_definition` or +;; similar things directly. Instead, we have to manually push the negation down +;; to form the finite disjunction it corresponds to. +;; + +[ + (class_definition (block (decorated_definition (function_definition name: (_)@name)))) + (class_definition (block (function_definition name: (_)@name))) +] +{ + attr @name "syntax_type" = "method" +} + +[ + (module (decorated_definition (function_definition name: (_)@name))) + (module (function_definition name: (_)@name)) + + (if_statement (block (decorated_definition (function_definition name: (_)@name)))) + (if_statement (block (function_definition name: (_)@name))) + + (elif_clause (block (decorated_definition (function_definition name: (_)@name)))) + (elif_clause (block (function_definition name: (_)@name))) + + (else_clause (block (decorated_definition (function_definition name: (_)@name)))) + (else_clause (block (function_definition name: (_)@name))) + + (case_clause (block (decorated_definition (function_definition name: (_)@name)))) + (case_clause (block (function_definition name: (_)@name))) + + (for_statement (block (decorated_definition (function_definition name: (_)@name)))) + (for_statement (block (function_definition name: (_)@name))) + + (while_statement (block (decorated_definition (function_definition name: (_)@name)))) + (while_statement (block (function_definition name: (_)@name))) + + (try_statement (block (decorated_definition (function_definition name: (_)@name)))) + (try_statement (block (function_definition name: (_)@name))) + + (except_clause (block (decorated_definition (function_definition name: (_)@name)))) + (except_clause (block (function_definition name: (_)@name))) + + (finally_clause (block (decorated_definition (function_definition name: (_)@name)))) + (finally_clause (block (function_definition name: (_)@name))) + + (with_statement (block (decorated_definition (function_definition name: (_)@name)))) + (with_statement (block (function_definition name: (_)@name))) + + (function_definition (block (decorated_definition (function_definition name: (_)@name)))) + (function_definition (block (function_definition name: (_)@name))) +] +{ + attr @name "syntax_type" = "function" +} + +;; +;; END BIG GNARLY DISJUNCTION +;; + +(function_definition + parameters: (parameters + . (identifier) @param) + body: (block) @body) +{ + edge @param.input -> @param::class_self_scope + edge @param::class_member_attr_scope -> @param.output + edge @param.output -> @body.after_scope + attr @param.output "push" +} + +(parameter/identifier) @param +{ + attr @param.input "definition", "pop" + attr @param.param_name "push" + edge @param.input -> @param.param_index + edge @param.input -> @param.param_name +} + +[ + (parameter/default_parameter + name: (identifier) @name + value: (_) @value) @param + (parameter/typed_default_parameter + name: (_) @name + value: (_) @value) @param +] +{ + attr @name "definition", "pop" + attr @param.param_name "push" = @name + edge @name -> @param.param_name + edge @name -> @param.param_index + edge @param.input -> @name + edge @name -> @value.output +} + +[ + (parameter/typed_parameter + . (_) @name) @param + (parameter/list_splat_pattern + (_) @name) @param + (parameter/dictionary_splat_pattern + (_) @name) @param +] +{ + attr @name "definition", "pop" + attr @param.param_name "push" = @name + edge @name -> @param.param_name + edge @name -> @param.param_index + edge @param.input -> @name +} + +[ + (pattern_list (_) @pattern) + (tuple_pattern (_) @pattern) +] @list +{ + let statement_scope = @list::local_scope + let @pattern::local_scope = @pattern.pattern_before_scope + edge statement_scope -> @pattern::local_scope, "precedence" = (+ 1 (child-index @pattern)) + + edge @pattern.pattern_index -> @list.input + edge @pattern.input -> @pattern.pattern_index + attr @pattern.pattern_index "push" = (child-index @pattern) +} + +(parameters + (_) @param) @params +{ + attr @param.param_index "push" = (child-index @param) + edge @param.param_index -> @params.before_scope + edge @params.after_scope -> @param.input + edge @param.param_name -> @params.before_scope +} + +(return_statement (_) @expr) @stmt +{ + edge @stmt::function_returns -> @expr.output +} + +(class_definition + name: (identifier) @name) @class +{ + attr @name "definiens" = @class + attr @name "syntax_type" = "class" + edge @class.parent_scope -> @class::class_parent_scope + edge @class.parent_scope -> @class::local_scope + edge @class.after_scope -> @name + edge @name -> @class.call + edge @name -> @class.dot + edge @class.dot -> @class.members + edge @class.call -> @class.call_drop + edge @class.call_drop -> @class.self_scope + edge @class.self_scope -> @class.super_scope + edge @class.self_scope -> @class.self_dot + edge @class.self_dot -> @class.members + edge @class.members -> @class.member_attrs + attr @class.call "pop" = "()", "pop-scope" + attr @class.call_drop "drop" + attr @class.dot "pop" = "." + attr @class.self_dot "pop" = "." + attr @name "pop", "definition" + attr @class.member_attrs "push" = "." + attr @class.self_scope "endpoint" + let @class::super_scope = @class.super_scope + let @class::class_parent_scope = @class.parent_scope + let @class::class_self_scope = @class.call_drop + let @class::class_member_attr_scope = @class.member_attrs +} + +(class_definition + body: (block + (_) @last_stmt .) @body) @class +{ + edge @class.members -> @last_stmt.after_scope +} + +(class_definition + superclasses: (argument_list + (_) @superclass)) @class +{ + edge @class.super_scope -> @superclass.output +} + +(decorated_definition + definition: (_) @def) @stmt +{ + edge @def.before_scope -> @stmt.before_scope + edge @stmt.after_scope -> @def.after_scope +} + +(case_clause + pattern: (_) @pattern + consequence: (_) @consequence) @clause +{ + edge @consequence.before_scope -> @pattern.new_bindings + edge @consequence.before_scope -> @clause.before_scope + edge @clause.after_scope -> @consequence.after_scope +} + +;------------- +; Expressions +;------------- + +(call + function: (_) @fn + arguments: (argument_list) @args) @call +{ + edge @call.output -> @call.output_args + edge @call.output_args -> @fn.output + attr @call.output_args "push" = "()", "push-scope" = @args +} + +(call + function: (attribute + object: (_) @receiver) + arguments: (argument_list + (expression) @arg) @args) +{ + edge @args -> @arg.arg_index + edge @receiver -> @receiver.arg_index + + attr @receiver.arg_index "pop" = "0" + edge @receiver.arg_index -> @receiver.output + + attr @arg.arg_index "pop" = (+ 1 (child-index @arg)) + edge @arg.arg_index -> @arg.output +} + +(call + arguments: (argument_list + (keyword_argument + name: (identifier) @name + value: (_) @val) @arg) @args) @call +{ + edge @args -> @arg.arg_name + attr @arg.arg_name "pop" = @name + edge @arg.arg_name -> @val.output +} + +(argument_list + (expression) @arg) @args +{ + edge @args -> @arg.arg_index + attr @arg.arg_index "pop" = (child-index @arg) + edge @arg.arg_index -> @arg.output +} + +( + (call + function: (identifier) @fn-name) @call + (#eq? @fn-name "super") +) +{ + edge @call.output -> @call::super_scope +} + +[ + (tuple (_) @element) + (expression_list (_) @element) +] @tuple +{ + edge @tuple.output -> @element.el_index + attr @element.el_index "pop" = (child-index @element) + edge @element.el_index -> @element.output + + edge @tuple.new_bindings -> @element.new_bindings +} + +(attribute + object: (_) @object + attribute: (identifier) @name) @expr +{ + edge @expr.output -> @name.output + edge @name.output -> @expr.output_dot + edge @expr.output_dot -> @object.output + edge @object.input -> @expr.input_dot + edge @expr.input_dot -> @name.input + edge @name.input -> @expr.input + attr @expr.output_dot "push" = "." + attr @expr.input_dot "pop" = "." + attr @name.input "pop" + attr @name.output "push" +} + +(pattern/attribute + attribute: (identifier) @name) +{ + attr @name.input "definition" +} + +(primary_expression/attribute + attribute: (identifier) @name) +{ + attr @name.output "reference" +} + +(primary_expression/identifier) @id +{ + edge @id.output -> @id::local_scope + edge @id.output -> @id::class_parent_scope + edge @id::local_scope -> @id.input + attr @id.input "pop" + attr @id.output "push", "reference" + + attr @id.new_binding_pop "pop", "definition" + edge @id.new_bindings -> @id.new_binding_pop +} + +(pattern/identifier) @id +{ + edge @id.output -> @id::local_scope + edge @id.output -> @id::class_parent_scope + edge @id::local_scope -> @id.input, "precedence" = 1 + attr @id.input "pop", "definition" + attr @id.output "push" + + attr @id.new_binding_pop "pop", "definition" + edge @id.new_bindings -> @id.new_binding_pop +} + +(as_pattern + (expression) @value + alias: (as_pattern_target (primary_expression/identifier) @id)) @as_pattern +{ + edge @id.output -> @id::local_scope + edge @id.output -> @id::class_parent_scope + edge @id::local_scope -> @id.input, "precedence" = 1 + attr @id.input "pop", "definition" + attr @id.output "push" + + edge @as_pattern.new_bindings -> @value.new_bindings + edge @as_pattern.new_bindings -> @id.new_bindings +} + +(list) @list +{ + edge @list.output -> @list.called + edge @list.called -> @list::global_dot + attr @list.called "push" = "list" +} + +(list (_) @el) @list +{ + edge @list.new_bindings -> @el.new_bindings +} + +(dictionary (pair) @pair) @dict +{ + edge @dict.new_bindings -> @pair.new_bindings +} + +(pair + value: (_) @value) @pair +{ + edge @pair.new_bindings -> @value.new_bindings +} + +(set (_) @el) @set +{ + edge @set.new_bindings -> @el.new_bindings +} + +(list_splat (_) @splatted) @splat +{ +attr @splat.new_bindings_pop "pop" = @splatted, "definition" +edge @splat.new_bindings -> @splat.new_bindings_pop +} + +(binary_operator + (_) @left + (_) @right) @binop +{ + edge @binop.new_bindings -> @left.new_bindings + edge @binop.new_bindings -> @right.new_bindings +} + +(case_pattern (_) @expr) @pat +{ + edge @pat.new_bindings -> @expr.new_bindings +} diff --git a/languages/tree-sitter-stack-graphs-python/src/stack-graphs.tsg b/languages/tree-sitter-stack-graphs-python/src/stack-graphs.tsg new file mode 100644 index 000000000..5fb407ae8 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/src/stack-graphs.tsg @@ -0,0 +1,1377 @@ +;; -*- coding: utf-8 -*- +;; ------------------------------------------------------------------------------------------------ +;; Copyright © 2023, stack-graphs authors. +;; Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +;; Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +;; ------------------------------------------------------------------------------------------------ + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Stack graphs definition for Python +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Global Variables +;; ^^^^^^^^^^^^^^^^ + +global FILE_PATH +global ROOT_PATH = "" +global ROOT_NODE +global JUMP_TO_SCOPE_NODE + +;; Attribute Shorthands +;; ^^^^^^^^^^^^^^^^^^^^ + +attribute node_definition = node => type = "pop_symbol", node_symbol = node, is_definition +attribute node_reference = node => type = "push_symbol", node_symbol = node, is_reference +attribute pop_node = node => type = "pop_symbol", node_symbol = node +attribute pop_scoped_node = node => type = "pop_scoped_symbol", node_symbol = node +attribute pop_scoped_symbol = symbol => type = "pop_scoped_symbol", symbol = symbol +attribute pop_symbol = symbol => type = "pop_symbol", symbol = symbol +attribute push_node = node => type = "push_symbol", node_symbol = node +attribute push_scoped_node = node => type = "push_scoped_symbol", node_symbol = node +attribute push_scoped_symbol = symbol => type = "push_scoped_symbol", symbol = symbol +attribute push_symbol = symbol => type = "push_symbol", symbol = symbol +attribute scoped_node_definition = node => type = "pop_scoped_symbol", node_symbol = node, is_definition +attribute scoped_node_reference = node => type = "push_scoped_symbol", node_symbol = node, is_reference +attribute symbol_definition = symbol => type = "pop_symbol", symbol = symbol, is_definition +attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, is_reference + +attribute node_symbol = node => symbol = (source-text node), source_node = node + +;; Nodes +;; ^^^^^ + +(module) @node { + node @node.after_scope + node @node.before_scope +} + +[ + ; _statement + ; _simple_statement + (future_import_statement) + (import_statement) + (import_from_statement) + (print_statement) + (assert_statement) + (expression_statement) + (return_statement) + (delete_statement) + (raise_statement) + (pass_statement) + (break_statement) + (continue_statement) + (global_statement) + (nonlocal_statement) + (exec_statement) + (type_alias_statement) + ; _compound_statement + (if_statement) + (for_statement) + (while_statement) + (try_statement) + (with_statement) + (function_definition) + (class_definition) + (decorated_definition) + (match_statement) + ; block + (block) + ; statement clauses + (if_clause) + (elif_clause) + (else_clause) + (except_group_clause) + (except_clause) + (finally_clause) + (with_clause) + (case_clause) +] @node { + node @node.after_scope + node @node.before_scope +} + +[ + (parameters) + (lambda_parameters) +] @node { + node @node.after_scope + node @node.before_scope +} + +[ + (identifier) +] @node { + node @node.def + node @node.def_dot + node @node.ref + node @node.ref_dot +} + +[ + (dotted_name) + (aliased_import) + (relative_import) + (wildcard_import) + (import_prefix) +] @node { + node @node.after_scope + node @node.before_scope + node @node.def + node @node.ref +} + +[ + ; expressions + (comparison_operator) + (not_operator) + (boolean_operator) + (lambda) + ;(primary_expression) ; unfolded below + (conditional_expression) + (named_expression) + (as_pattern) + ; primary_expression + (await) + (binary_operator) + (identifier) + ;(keyword_identifier) ; invalid query pattern? + (string) + (concatenated_string) + (integer) + (float) + (true) + (false) + (none) + (unary_operator) + (attribute) + (subscript) + (call) + (list) + (list_comprehension) + (dictionary) + (dictionary_comprehension) + (set) + (set_comprehension) + (tuple) + (pair) + (parenthesized_expression) + (generator_expression) + (ellipsis) + (list_splat) + + ; expression list + (expression_list) + + ; pattern + (pattern/identifier) + ;(keyword_identifier) ; invalid query pattern? + ;(subscript) + ;(attribute) + (list_splat_pattern) + (tuple_pattern) + (list_pattern) + ; _simple_patterns + (class_pattern) + (splat_pattern) + (union_pattern) + ;(list_pattern) ; already in pattern + ;(tuple_pattern) ; already in pattern + (dict_pattern) + ;(string) ; already in primary_expression + ;(concatenated_string) ; already in primary_expression + ;(true) ; already in primary_expression + ;(false) ; already in primary_expression + ;(none) ; already in primary_expression + ;(integer) ; already in primary_expression + ;(float) ; already in primary_expression + (complex_pattern) + (dotted_name) + ; _as_attern + (as_pattern) + ; keyword pattern + (keyword_pattern) + ; case pattern + (case_pattern) + ; with item + (with_item) + + ; pattern list + (pattern_list) + + ; parameter + ;(identifier) ; already in expressions + (typed_parameter) + (default_parameter) + (typed_default_parameter) + ;(list_splat_pattern) ; already in patterns + ;(tuple_pattern) ; already in patterns + (keyword_separator) + (positional_separator) + (dictionary_splat_pattern) + + ; parameters + (parameters) +] @node { + node @node.input + node @node.new_bindings + node @node.output +} + +(comment) @node { + node @node.after_scope + node @node.before_scope + node @node.def + node @node.def_dot + node @node.input + node @node.new_bindings + node @node.output + node @node.ref + node @node.ref_dot +} + +;; Inherited Variables +;; ^^^^^^^^^^^^^^^^^^^ + +inherit .bottom +inherit .class_member_attr_scope +inherit .class_parent_scope +inherit .class_self_scope +inherit .class_super_scope +inherit .function_returns +inherit .global +inherit .global_dot +inherit .grandparent_module +inherit .local_scope +inherit .parent_module + +;; +;; # # +;; ## ## #### ##### # # # ###### #### +;; # # # # # # # # # # # # # +;; # # # # # # # # # # ##### #### +;; # # # # # # # # # # # +;; # # # # # # # # # # # # +;; # # #### ##### #### ###### ###### #### +;; +;; Modules + +(module) @mod +{ + node mod_file_def + node mod_file_ref + + var module_def = mod_file_def + + node parent_module_def_node + var parent_module_def = parent_module_def_node + + var module_ref = mod_file_ref + + node parent_module_ref_node + var parent_module_ref = parent_module_ref_node + + node grandparent_module_ref_node + var grandparent_module_ref = grandparent_module_ref_node + + ; get the file path relative to the root path + let rel_path = (replace FILE_PATH ROOT_PATH "") + scan rel_path { + "([^/]+)/" + { + node def_dot + attr (def_dot) pop_symbol = "." + node next_def + ; + edge module_def -> def_dot + edge def_dot -> next_def + ; + attr (module_def) pop_symbol = $1 + ; + set parent_module_def = module_def + set module_def = next_def + + node ref_dot + attr (ref_dot) push_symbol = "." + node next_ref + ; + edge next_ref -> ref_dot + edge ref_dot -> module_ref + ; + attr (module_ref) push_symbol = $1 + ; + set grandparent_module_ref = parent_module_ref + set parent_module_ref = module_ref + set module_ref = next_ref + } + + "__init__\.py$" + { + attr (parent_module_def) is_definition, source_node = @mod, empty_source_span + } + + "([^/]+)\.py$" + { + node def_dot + attr (def_dot) pop_symbol = "." + node next_def + ; + edge module_def -> def_dot + edge def_dot -> next_def + ; + attr (module_def) pop_symbol = $1, is_definition, source_node = @mod, empty_source_span + ; + set module_def = next_def + } + } + + edge ROOT_NODE -> mod_file_def + edge mod_file_ref -> ROOT_NODE + edge module_def -> @mod.after_scope + + node global + node global_dot + + edge @mod.before_scope -> global_dot + edge global -> ROOT_NODE + attr (global) push_symbol = "" + + edge global_dot -> global + attr (global_dot) push_symbol = "." + + let @mod.parent_module = parent_module_ref + let @mod.grandparent_module = grandparent_module_ref + let @mod.bottom = @mod.after_scope + let @mod.global = global + let @mod.global_dot = global_dot + + ;; add a dummy nodes for inherited variables + node @mod.class_member_attr_scope + node @mod.class_parent_scope + node @mod.class_self_scope + node @mod.class_super_scope + node @mod.function_returns + node @mod.local_scope +} + +;; +;; ### +;; # # # ##### #### ##### ##### #### +;; # ## ## # # # # # # # # +;; # # ## # # # # # # # # #### +;; # # # ##### # # ##### # # +;; # # # # # # # # # # # +;; ### # # # #### # # # #### +;; +;; Imports + +;; Import References +;; ^^^^^^^^^^^^^^^^^ + +;;;; Dotted Names +;; +;; (dotted_name).ref node to connect to to use the reference +;; (dotted_name).before_scope node to connect from to ensure the reference resolves + +;; all names are references +[ + (import_statement name: (dotted_name (identifier) @name)) + (future_import_statement name: (dotted_name (identifier) @name)) + (import_from_statement name: (dotted_name (identifier) @name)) + (import_statement name: (aliased_import name: (dotted_name (identifier) @name))) + (future_import_statement name: (aliased_import name: (dotted_name (identifier) @name))) + (import_from_statement name: (aliased_import name: (dotted_name (identifier) @name))) + (import_from_statement module_name: (dotted_name (identifier) @name)) + (import_from_statement module_name: (relative_import (dotted_name (identifier) @name))) +] { + attr (@name.ref) node_reference = @name +} + +;; references are chained +[ + (import_statement name: (dotted_name (identifier) @left . (identifier) @right)) + (future_import_statement name: (dotted_name (identifier) @left . (identifier) @right)) + (import_from_statement name: (dotted_name (identifier) @left . (identifier) @right)) + (import_statement name: (aliased_import name: (dotted_name (identifier) @left . (identifier) @right))) + (future_import_statement name: (aliased_import name: (dotted_name (identifier) @left . (identifier) @right))) + (import_from_statement name: (aliased_import name: (dotted_name (identifier) @left . (identifier) @right))) + (import_from_statement module_name: (dotted_name (identifier) @left . (identifier) @right)) + (import_from_statement module_name: (relative_import (dotted_name (identifier) @left . (identifier) @right))) +] { + node push_dot + attr (push_dot) push_symbol = "." + + edge @right.ref -> push_dot + edge push_dot -> @left.ref +} + +;; lookup first reference +[ + (import_statement name: (dotted_name . (identifier) @first) @dotted) + (future_import_statement name: (dotted_name . (identifier) @first) @dotted) + (import_from_statement name: (dotted_name . (identifier) @first) @dotted) + (import_statement name: (aliased_import name: (dotted_name . (identifier) @first) @dotted)) + (future_import_statement name: (aliased_import name: (dotted_name . (identifier) @first) @dotted)) + (import_from_statement name: (aliased_import name: (dotted_name . (identifier) @first) @dotted)) + (import_from_statement module_name: (dotted_name . (identifier) @first) @dotted) + (import_from_statement module_name: (relative_import (dotted_name . (identifier) @first) @dotted)) +] { + edge @first.ref -> @dotted.before_scope +} + +;; expose last reference +[ + (import_statement name: (dotted_name (identifier) @last .) @dotted) + (future_import_statement name: (dotted_name (identifier) @last .) @dotted) + (import_from_statement name: (dotted_name (identifier) @last .) @dotted) + (import_statement name: (aliased_import name: (dotted_name (identifier) @last .) @dotted)) + (future_import_statement name: (aliased_import name: (dotted_name (identifier) @last .) @dotted)) + (import_from_statement name: (aliased_import name: (dotted_name (identifier) @last .) @dotted)) + (import_from_statement module_name: (dotted_name (identifier) @last .) @dotted) + (import_from_statement module_name: (relative_import (dotted_name (identifier) @last .) @dotted)) +] { + edge @dotted.ref -> @last.ref +} + +;;;; Aliased Import +;; +;; An aliased import behaves like its wrapped dotted_name as a reference +;; +;; (aliased_import).ref node to connect to, to use the reference +;; (aliased_import).before_scope node to connect from to ensure the reference resolves + +(aliased_import name: (dotted_name) @dotted) @aliased { + edge @aliased.ref -> @dotted.ref + edge @dotted.before_scope -> @aliased.before_scope +} + +;;;; Relative Import +;; +;; A relative import behaves like its wrapped dotted_name as a reference +;; +;; (relative_import).ref node to connect to, to use the reference +;; (relative_import).before_scope node to connect from to ensure the reference resolves + +(relative_import (import_prefix) . (dotted_name) @dotted) @relative { + edge @relative.ref -> @dotted.ref + + node push_dot + attr (push_dot) push_symbol = "." + + edge @dotted.before_scope -> push_dot + edge push_dot -> @relative.before_scope +} + +(relative_import (import_prefix) .) @relative { + edge @relative.ref -> @relative.before_scope +} + +;;;; Wildcard Import +;; +;; A wildcard import simply passes through +;; +;; (wildcard_import).ref node to connect to, to use the reference +;; (wildcard_import).before_scope node to connect from to ensure the reference resolves + +(wildcard_import) @wildcard { + edge @wildcard.ref -> @wildcard.before_scope +} + +;;;; Import from +;; +;; The imported references are resolved in the from module + +[ + (import_from_statement module_name: (_) @left name: (_) @right) + (import_from_statement module_name: (_) @left (wildcard_import) @right) +] { + node push_dot + attr (push_dot) push_symbol = "." + + edge @right.before_scope -> push_dot + edge push_dot -> @left.ref +} + +;;;; Non-relative Imports +;; +;; Non-relative imports are resolved in the root scope + +[ + (import_statement name: (_) @name) + (import_from_statement module_name: (dotted_name) @name) +] { + edge @name.before_scope -> ROOT_NODE +} + +;;;; Relative Imports +;; +;; Relative imports are resolved in scopes related to the current module + +;; . imports resolve in parent module scope +(import_from_statement module_name: (relative_import (import_prefix) @prefix (#eq? @prefix ".")) @relative) { + edge @relative.before_scope -> @prefix.parent_module +} + +;; .. imports resolve in grandparent module scope +(import_from_statement module_name: (relative_import (import_prefix) @prefix (#eq? @prefix "..")) @relative) { + edge @relative.before_scope -> @prefix.grandparent_module +} + +;;;; Future Imports +;; +;; We don't know the future, so we cannot connect references of future imports anywhere. Maybe one day? + +;; Import Definitions +;; ^^^^^^^^^^^^^^^^^^ + +;;;; Dotted Names & Aliased Imports +;; +;; (dotted_name).after_scope node to connect to, to expose the definition +;; (dotted_name).def node to connect from, to give definition content + +;; unaliased names and aliases are definitions +[ + (import_statement name: (dotted_name (identifier) @name)) + (future_import_statement name: (dotted_name (identifier) @name)) + (import_from_statement name: (dotted_name (identifier) @name)) + (import_statement name: (aliased_import alias: (identifier) @name)) + (future_import_statement name: (aliased_import alias: (identifier) @name)) + (import_from_statement name: (aliased_import alias: (identifier) @name)) +] { + attr (@name.def) node_definition = @name +} + +;; definitions are chained +[ + (import_statement name: (dotted_name (identifier) @left . (identifier) @right)) + (future_import_statement name: (dotted_name (identifier) @left . (identifier) @right)) + (import_from_statement name: (dotted_name (identifier) @left . (identifier) @right)) +] { + node pop_dot + attr (pop_dot) pop_symbol = "." + + edge @left.def -> pop_dot + edge pop_dot -> @right.def +} + +;; connect last definition +[ + (import_statement name: (dotted_name (identifier) @last .) @outer) + (future_import_statement name: (dotted_name (identifier) @last .) @outer) + (import_from_statement name: (dotted_name (identifier) @last .) @outer) + (import_statement name: (aliased_import alias: (identifier) @last ) @outer) + (future_import_statement name: (aliased_import alias: (identifier) @last ) @outer) + (import_from_statement name: (aliased_import alias: (identifier) @last ) @outer) +] { + edge @last.def -> @outer.def +} + +;; expose first definition +[ + (import_statement name: (dotted_name . (identifier) @first) @outer) + (future_import_statement name: (dotted_name . (identifier) @first) @outer) + (import_from_statement name: (dotted_name . (identifier) @first) @outer) + (import_statement name: (aliased_import alias: (identifier) @first ) @outer) + (future_import_statement name: (aliased_import alias: (identifier) @first ) @outer) + (import_from_statement name: (aliased_import alias: (identifier) @first ) @outer) +] { + edge @outer.after_scope -> @first.def +} + +;;;; Wildcard Import +;; +;; Wildcard imports simply pass through + +(wildcard_import) @wildcard { + edge @wildcard.after_scope -> @wildcard.def +} + +;;;; Import Definitions -> References +;; +;; The definitions introduced by imports are connected to the corresponding references + +[ + (import_statement name: (_) @name) + (future_import_statement name: (_) @name) + (import_from_statement name: (_) @name) + (import_from_statement (wildcard_import) @name) +] { + edge @name.def -> @name.ref +} + +;;;; Imports +;; +;; The definitions introduced by imports are visible after the import statement + +[ + (import_statement name: (_) @name) + (future_import_statement name: (_) @name) + (import_from_statement name: (_) @name) + (import_from_statement (wildcard_import) @name) +] @stmt { + edge @stmt.after_scope -> @name.after_scope + attr (@stmt.after_scope -> @name.after_scope) precedence = 1 +} + +;; +;; ###### +;; # # # #### #### # # #### +;; # # # # # # # # # # +;; ###### # # # # #### #### +;; # # # # # # # # # +;; # # # # # # # # # # # +;; ###### ###### #### #### # # #### +;; +;; Blocks + +[ + (module (_) @last_stmt .) + (block (_) @last_stmt .) +] @block +{ + edge @block.after_scope -> @last_stmt.after_scope +} + +[ + (module (_) @stmt1 . (_) @stmt2) + (block (_) @stmt1 . (_) @stmt2) +] +{ + edge @stmt2.before_scope -> @stmt1.after_scope +} + +[ + (module (_) @stmt) + (block (_) @stmt) +] +{ + edge @stmt.after_scope -> @stmt.before_scope + let @stmt.local_scope = @stmt.before_scope +} + +[ + (block . (_) @stmt) + (module . (_) @stmt) +] @block +{ + edge @stmt.before_scope -> @block.before_scope +} + +(function_definition (block) @block) +{ + edge @block.before_scope -> @block.local_scope +} + +[ + (while_statement (block) @block) + (if_statement (block) @block) + (with_statement (block) @block) + (try_statement (block) @block) + (for_statement (block) @block) + (_ [ + (else_clause (block) @block) + (elif_clause (block) @block) + (except_clause (block) @block) + (finally_clause (block) @block) + ]) +] @stmt +{ + edge @block.before_scope -> @block.local_scope + edge @stmt.after_scope -> @block.after_scope +} + +(match_statement body: (_) @block) @stmt +{ + let @block.local_scope = @block.before_scope + edge @block.before_scope -> @stmt.before_scope + edge @stmt.after_scope -> @block.after_scope +} + +[ + (for_statement) + (while_statement) +] @stmt +{ + edge @stmt.before_scope -> @stmt.after_scope +} + +;; +;; ##### +;; # # ##### ## ##### ###### # # ###### # # ##### #### +;; # # # # # # ## ## # ## # # # +;; ##### # # # # ##### # ## # ##### # # # # #### +;; # # ###### # # # # # # # # # # +;; # # # # # # # # # # # ## # # # +;; ##### # # # # ###### # # ###### # # # #### +;; +;; Statements + +;;;; Simple Statements + +(print_statement) {} + +(assert_statement) {} + +(expression_statement) {} + +(return_statement (_) @expr) @stmt +{ + edge @stmt.function_returns -> @expr.output +} + +(delete_statement) {} + +(raise_statement) {} + +(pass_statement) {} + +(break_statement) {} + +(continue_statement) {} + +(global_statement) {} + +(nonlocal_statement) {} + +(exec_statement) {} + +(type_alias_statement) {} + +;;;; Compound Statements + +(if_statement) {} + +(if_clause) {} + +(elif_clause) {} + +(else_clause) {} + +(for_statement) {} + +(while_statement) {} + +(try_statement) {} + +(except_group_clause) {} + +(except_clause) {} + +(finally_clause) {} + +(with_statement) {} + +(with_clause) {} + +[ + (function_definition + parameters: (_) @params + body: (_) @body + ) @func + (lambda + parameters: (_) @params + body: (_) @body + )@func +] { + node @func.call + node return_value + node drop_scope + + edge @func.call -> return_value + edge @body.before_scope -> @params.after_scope + edge @body.before_scope -> drop_scope + edge drop_scope -> @func.bottom + attr (drop_scope) type = "drop_scopes" + attr (@func.call) pop_scoped_symbol = "()" + edge @params.before_scope -> JUMP_TO_SCOPE_NODE + attr (return_value) is_exported + let @func.function_returns = return_value +} + +(function_definition + name: (identifier) @name + body: (_) @body +) @func { + attr (@name.def) node_definition = @name + attr (@name.def) definiens_node = @func + edge @func.after_scope -> @name.def + edge @name.def -> @func.call + + ; Prevent functions defined inside of method bodies from being treated like methods + let @body.class_self_scope = #null + let @body.class_member_attr_scope = #null +} + +; method definition +(class_definition + body: (block + (function_definition + parameters: + (parameters . (identifier) @first_param) + body: (block) @method_body + ) + ) +) +{ + edge @first_param.def -> @first_param.class_self_scope + edge @first_param.class_member_attr_scope -> @first_param.output + edge @first_param.output -> @method_body.after_scope + attr (@first_param.output) push_node = @first_param +} + +[ + (parameters (_) @param) @params + (lambda_parameters (_) @param) @params +] +{ + node @param.param_index + node @param.param_name + + attr (@param.param_index) push_symbol = (named-child-index @param) + edge @param.param_index -> @params.before_scope + edge @params.after_scope -> @param.input + edge @param.param_name -> @params.before_scope +} + + +(parameter/identifier) @param @name +{ + attr (@name.def) node_definition = @name + attr (@param.param_name) push_node = @param + edge @name.def -> @param.param_name + edge @name.def -> @param.param_index + edge @param.input -> @name.def +} + +[ + (parameter/default_parameter + name: (_) @name + value: (_) @value) @param + (parameter/typed_default_parameter + name: (_) @name + value: (_) @value) @param +] { + attr (@name.def) node_definition = @name + attr (@param.param_name) push_node = @name + edge @name.def -> @param.param_name + edge @name.def -> @param.param_index + edge @param.input -> @name.def + edge @name.def -> @value.output +} + +[ + (parameter/typed_parameter + . (_) @name) @param + (parameter/list_splat_pattern + (_) @name) @param + (parameter/dictionary_splat_pattern + (_) @name) @param +] { + attr (@name.def) node_definition = @name + attr (@param.param_name) push_node = @name + edge @name.def -> @param.param_name + edge @name.def -> @param.param_index + edge @param.input -> @name.def +} + +(class_definition) @class { + node @class.call_drop + node @class.member_attrs + node @class.members + node @class.super_scope +} + +(class_definition + name: (identifier) @name) { + attr (@name.def) node_definition = @name +} + +(class_definition + name: (identifier) @name) @class +{ + node call + node self_dot + node self_scope + node members_dot + + attr (@name.def) definiens_node = @class + attr (@name.def) syntax_type = "class" + edge @class.after_scope -> @name.def + edge @name.def -> call + edge @name.def -> members_dot + edge members_dot -> @class.members + edge call -> @class.call_drop + edge @class.call_drop -> self_scope + edge self_scope -> @class.super_scope + edge self_scope -> self_dot + edge self_dot -> @class.members + edge @class.members -> @class.member_attrs + attr (call) pop_scoped_symbol = "()" + attr (@class.call_drop) type = "drop_scopes" + attr (members_dot) pop_symbol = "." + attr (self_dot) pop_symbol = "." + attr (@class.member_attrs) push_symbol = "." + attr (self_scope) is_exported +} + +(class_definition + body: (_) @body) @class +{ + let @body.class_member_attr_scope = @class.member_attrs + node @body.class_parent_scope + edge @body.class_parent_scope -> @class.class_parent_scope + edge @body.class_parent_scope -> @class.local_scope + let @body.class_self_scope = @class.call_drop + let @body.class_super_scope = @class.super_scope +} + +(class_definition + body: (block + (_) @last_stmt .)) @class +{ + edge @class.members -> @last_stmt.after_scope +} + +(class_definition + superclasses: (argument_list + (_) @superclass)) @class +{ + edge @class.super_scope -> @superclass.output +} + +(decorated_definition + definition: (_) @def) @stmt +{ + edge @def.before_scope -> @stmt.before_scope + edge @stmt.after_scope -> @def.after_scope +} + +(match_statement) {} + +(case_clause + (case_pattern) @pattern + consequence: (_) @consequence) +{ + edge @consequence.before_scope -> @pattern.new_bindings +} + +(case_clause + consequence: (_) @consequence) @clause +{ + edge @consequence.before_scope -> @clause.before_scope + edge @clause.after_scope -> @consequence.after_scope +} + +;; +;; ####### +;; # # # ##### ##### ###### #### #### # #### # # #### +;; # # # # # # # # # # # # # ## # # +;; ##### ## # # # # ##### #### #### # # # # # # #### +;; # ## ##### ##### # # # # # # # # # # +;; # # # # # # # # # # # # # # # ## # # +;; ####### # # # # # ###### #### #### # #### # # #### +;; +;; Expressions + +(lambda + body: (_) @body +)@lam { + ;; TODO Unify .before_scope, .local_scope, and .input to simplify + ;; uniform treatment of lambdas and function definitions. + node @body.before_scope + let @body.local_scope = @body.before_scope + edge @body.input -> @body.before_scope + + edge @lam.output -> @lam.call +} + +(conditional_expression) {} + +(named_expression) {} + +(as_pattern) {} + +(await) {} + +(binary_operator + (_) @left + (_) @right) @binop +{ + edge @binop.new_bindings -> @left.new_bindings + edge @binop.new_bindings -> @right.new_bindings +} + +(primary_expression/identifier) @name { + attr (@name.ref) node_reference = @name +} + +(primary_expression/identifier) @name +{ + edge @name.output -> @name.local_scope + edge @name.output -> @name.class_parent_scope + edge @name.local_scope -> @name.input + attr (@name.input) pop_node = @name + attr (@name.output) node_reference = @name + + edge @name.new_bindings -> @name.def +} + +(string) {} + +(concatenated_string) {} + +(integer) {} + +(float) {} + +(true) {} + +(false) {} + +(none) {} + +(unary_operator) {} + +(attribute + object: (_) @object + attribute: (identifier) @name) @expr +{ + node input_dot + node output_dot + + edge @expr.output -> @name.output + edge @name.output -> output_dot + edge output_dot -> @object.output + edge @object.input -> input_dot + edge input_dot -> @name.input + edge @name.input -> @expr.input + attr (output_dot) push_symbol = "." + attr (input_dot) pop_symbol = "." + attr (@name.input) pop_node = @name + attr (@name.output) push_node = @name +} + +(primary_expression/attribute + attribute: (identifier) @name) +{ + attr (@name.output) is_reference +} + +(subscript) {} + +(call + function: (_) @fn + arguments: (argument_list) @args) @call +{ + node output_args + + edge @call.output -> output_args + edge output_args -> @fn.output + attr (output_args) push_scoped_symbol = "()", scope = @args.args +} + +(call + function: (attribute + object: (_) @receiver) + arguments: (argument_list + (expression) @arg) @args) +{ + node receiver_arg_index + attr (receiver_arg_index) pop_symbol = "0" + edge @args.args -> receiver_arg_index + edge receiver_arg_index -> @receiver.output + + ;; FIXME the arguments will also exist with their unshifted indices because of the general + ;; rule below! + node arg_index + attr (arg_index) pop_symbol = (plus 1 (named-child-index @arg)) + edge arg_index -> @arg.output +} + +(call + arguments: (argument_list + (keyword_argument + name: (identifier) @name + value: (_) @val)) @args) +{ + node arg_name + edge @args.args -> arg_name + attr (arg_name) pop_node = @name + edge arg_name -> @val.output +} + +(argument_list) @args { + node @args.args + attr (@args.args) is_exported +} + +(argument_list + (expression) @arg) @args +{ + node arg_index + edge @args.args -> arg_index + attr (arg_index) pop_symbol = (named-child-index @arg) + edge arg_index -> @arg.output +} + +( + (call + function: (identifier) @_fn_name) @call + (#eq? @_fn_name "super") +) +{ + edge @call.output -> @call.class_super_scope +} + +(list) @list +{ + node called + + edge @list.output -> called + edge called -> @list.global_dot + attr (called) push_symbol = "list" +} + +(list (_) @el) @list +{ + edge @list.new_bindings -> @el.new_bindings +} + +(list_comprehension) {} + +(dictionary (pair) @pair) @dict +{ + edge @dict.new_bindings -> @pair.new_bindings +} + +(pair + value: (_) @value) @pair +{ + edge @pair.new_bindings -> @value.new_bindings +} + +(dictionary_comprehension) {} + +(set (_) @el) @set +{ + edge @set.new_bindings -> @el.new_bindings +} + +(set_comprehension) {} + +[ + (tuple (_) @element) + (expression_list (_) @element) +] @tuple +{ + node el_index + + edge @tuple.output -> el_index + attr (el_index) pop_symbol = (named-child-index @element) + edge el_index -> @element.output + + edge @tuple.new_bindings -> @element.new_bindings +} + +(parenthesized_expression) {} + +(generator_expression) {} + +(ellipsis) {} + +(list_splat (_) @splatted) @splat +{ + edge @splat.new_bindings -> @splatted.new_bindings +} + +;; +;; ###### +;; # # ## ##### ##### ###### ##### # # #### +;; # # # # # # # # # ## # # +;; ###### # # # # ##### # # # # # #### +;; # ###### # # # ##### # # # # +;; # # # # # # # # # ## # # +;; # # # # # ###### # # # # #### +;; +;; Patterns + + +[ + (pattern/identifier) @name @pattern + ; identifiers in patterns + (as_pattern (identifier) @name .) @pattern + (keyword_pattern (identifier) @name) @pattern + (splat_pattern (identifier) @name) @pattern + ; every context where _simple_pattern is used, because matching + ; pattern/dotted_name does not work + (case_pattern (dotted_name . (_) @name .) @pattern) + (keyword_pattern (dotted_name . (_) @name .) @pattern) + (union_pattern (dotted_name . (_) @name .) @pattern) +] { + attr (@name.def) node_definition = @name + edge @pattern.output -> @pattern.local_scope + edge @pattern.output -> @pattern.class_parent_scope + edge @pattern.local_scope -> @pattern.input + attr (@pattern.local_scope -> @pattern.input) precedence = 1 + attr (@pattern.input) node_definition = @name + attr (@pattern.output) push_node = @name + + edge @pattern.new_bindings -> @name.def +} + +(pattern/subscript) {} + +(pattern/attribute + attribute: (identifier) @name) +{ + attr (@name.input) is_definition +} + + +(list_splat_pattern (_) @pattern) @list { + edge @list.new_bindings -> @pattern.new_bindings +} + +[ + (pattern_list (_) @pattern) + (tuple_pattern (_) @pattern) + (list_pattern (_) @pattern) +] @list +{ + node pattern_index + + let statement_scope = @list.local_scope + node @pattern.local_scope + edge statement_scope -> @pattern.local_scope + attr (statement_scope -> @pattern.local_scope) precedence = (plus 1 (named-child-index @pattern)) + + edge pattern_index -> @list.input + edge @pattern.input -> pattern_index + attr (pattern_index) push_symbol = (named-child-index @pattern) +} + +(class_pattern (case_pattern) @pattern) @class { + edge @class.new_bindings -> @pattern.new_bindings +} + +(splat_pattern (_) @pattern) @splat { + edge @splat.new_bindings -> @pattern.new_bindings +} + +(union_pattern (_) @pattern) @union { + edge @union.new_bindings -> @pattern.new_bindings +} + +(dict_pattern (_) @pattern) @dict { + edge @dict.new_bindings -> @pattern.new_bindings +} + +(complex_pattern) {} + +(as_pattern + (expression) @value + alias: (as_pattern_target (identifier) @name)) @as_pattern +{ + attr (@name.local_scope -> @name.input) precedence = 1 + attr (@name.input) is_definition + + edge @as_pattern.new_bindings -> @value.new_bindings + edge @as_pattern.new_bindings -> @name.new_bindings +} + +(keyword_pattern) {} + +(case_pattern (_) @pattern) @case +{ + edge @case.new_bindings -> @pattern.new_bindings +} + +[ + (assignment + left: (_) @pattern + right: (_) @value) + (with_item + value: + (as_pattern + (_) @value + alias: (as_pattern_target (_) @pattern))) +] +{ + edge @pattern.input -> @value.output +} + +;; +;; ##### ####### +;; # # # # # # ##### ## # # # # # ##### ###### #### +;; # # # ## # # # # # # # # # # # # # +;; ##### # # # # # # # ## # # # # ##### #### +;; # # # # # # ###### ## # # ##### # # +;; # # # # ## # # # # # # # # # # # +;; ##### # # # # # # # # # # # ###### #### +;; +;; Syntax Types + +;; +;; BEGIN BIG GNARLY DISJUNCTION +;; +;; The following pair of rules is intended to capture the following behavior: +;; +;; If a function definition is used to define a method, by being inside a class +;; definition, then we make its syntax type `method`. Otherwise, we make it's +;; syntax type `function`. Unfortunately, because of the limitations on negation +;; and binding in tree sitter queries, we cannot negate `class_definition` or +;; similar things directly. Instead, we have to manually push the negation down +;; to form the finite disjunction it corresponds to. +;; + +[ + (class_definition (block (decorated_definition (function_definition name: (_)@name)))) + (class_definition (block (function_definition name: (_)@name))) +] +{ + attr (@name.def) syntax_type = "method" +} + +[ + (module (decorated_definition (function_definition name: (_)@name))) + (module (function_definition name: (_)@name)) + + (if_statement (block (decorated_definition (function_definition name: (_)@name)))) + (if_statement (block (function_definition name: (_)@name))) + + (elif_clause (block (decorated_definition (function_definition name: (_)@name)))) + (elif_clause (block (function_definition name: (_)@name))) + + (else_clause (block (decorated_definition (function_definition name: (_)@name)))) + (else_clause (block (function_definition name: (_)@name))) + + (case_clause (block (decorated_definition (function_definition name: (_)@name)))) + (case_clause (block (function_definition name: (_)@name))) + + (for_statement (block (decorated_definition (function_definition name: (_)@name)))) + (for_statement (block (function_definition name: (_)@name))) + + (while_statement (block (decorated_definition (function_definition name: (_)@name)))) + (while_statement (block (function_definition name: (_)@name))) + + (try_statement (block (decorated_definition (function_definition name: (_)@name)))) + (try_statement (block (function_definition name: (_)@name))) + + (except_clause (block (decorated_definition (function_definition name: (_)@name)))) + (except_clause (block (function_definition name: (_)@name))) + + (finally_clause (block (decorated_definition (function_definition name: (_)@name)))) + (finally_clause (block (function_definition name: (_)@name))) + + (with_statement (block (decorated_definition (function_definition name: (_)@name)))) + (with_statement (block (function_definition name: (_)@name))) + + (function_definition (block (decorated_definition (function_definition name: (_)@name)))) + (function_definition (block (function_definition name: (_)@name))) +] +{ + attr (@name.def) syntax_type = "function" +} + +;; +;; END BIG GNARLY DISJUNCTION +;; diff --git a/languages/tree-sitter-stack-graphs-python/test/aliased_imports.py b/languages/tree-sitter-stack-graphs-python/test/aliased_imports.py new file mode 100644 index 000000000..0373a33be --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/aliased_imports.py @@ -0,0 +1,31 @@ +#------ path: foo.py ------# + +# module +class A: + a = 1 + +class B: + class C: + class D: + d = 2 + +#------ path: main.py ---# + +from foo import A as X, B.C.D as Y +import foo as f + +print X.a, Y.d +# ^ defined: 5 +# ^ defined: 10 + +print A, B.C +# ^ defined: +# ^ defined: +# ^ defined: + +print f.B +# ^ defined: 3, 15 +# ^ defined: 7 + +print foo +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/attributes.py b/languages/tree-sitter-stack-graphs-python/test/attributes.py new file mode 100644 index 000000000..6dd3d0e48 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/attributes.py @@ -0,0 +1,14 @@ +a = 1 + +a.b = 2 + +a.c.d = 5 + +print a.b +# ^ defined: 1 +# ^ defined: 3 + +print a.c, a.c.d +# ^ defined: 1 +# ^ defined: +# ^ defined: 5 diff --git a/languages/tree-sitter-stack-graphs-python/test/blocks.py b/languages/tree-sitter-stack-graphs-python/test/blocks.py new file mode 100644 index 000000000..462890baf --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/blocks.py @@ -0,0 +1,21 @@ +def f(): + if a: + b = 1 + else: + c = 2 + + print b, c + # ^ defined: 3 + # ^ defined: 5 + +class G: + if d: + e = 1 + + print e + # ^ defined: 13 + +print b, c, e +# ^ defined: +# ^ defined: +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/chained_functions.py b/languages/tree-sitter-stack-graphs-python/test/chained_functions.py new file mode 100644 index 000000000..ef1dac21d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/chained_functions.py @@ -0,0 +1,23 @@ +class A: + w = 1 + x = 2 + y = 3 + z = 4 + +def get_a(): + return A +def get_b(): + return get_a() +def get_c(): + return get_b() +def get_d(): + return get_c() +def get_e(): + return get_d() +def get_f(): + return get_e() + +g = get_f(A) +print g.x, g.y +# ^ defined: 3 +# ^ defined: 4 diff --git a/languages/tree-sitter-stack-graphs-python/test/chained_methods.py.skip b/languages/tree-sitter-stack-graphs-python/test/chained_methods.py.skip new file mode 100644 index 000000000..9c4f53115 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/chained_methods.py.skip @@ -0,0 +1,27 @@ +class Builder: + def set_a(self, a): + self.a = a + return self + + def set_b(self, b): + self.b = b + return self + + def set_c(self, c): + self.c = c + return self + + def set_d(self, d): + self.d = d + return self + + def set_e(self, e): + self.d = d + return self + +Builder().set_a('a1').set_b('b2').set_c('c3').set_d('d4').set_e('e4') +# ^ defined: 2 +# ^ defined: 6 +# ^ defined: 10 +# ^ defined: 14 +# ^ defined: 18 diff --git a/languages/tree-sitter-stack-graphs-python/test/class_members.py b/languages/tree-sitter-stack-graphs-python/test/class_members.py new file mode 100644 index 000000000..c67ace992 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/class_members.py @@ -0,0 +1,23 @@ +a = 1 + +class B: + c = a + # ^ defined: 1 + + def d(self): + return self.c + + class E: + f = a + # ^ defined: 1 + +print B.c +# ^ defined: 3 +# ^ defined: 1, 4 + +print B.d(1) +# ^ defined: 7 + +print B.a, E.a +# ^ defined: +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/decorators.py b/languages/tree-sitter-stack-graphs-python/test/decorators.py new file mode 100644 index 000000000..b55f44b2f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/decorators.py @@ -0,0 +1,10 @@ +from a import deprecated +from b import ignore_warnings + +class A: + @deprecated + # ^ defined: 1 + @ignore_warnings.all + # ^ defined: 2 + def b(self): + pass diff --git a/languages/tree-sitter-stack-graphs-python/test/exceptions.py b/languages/tree-sitter-stack-graphs-python/test/exceptions.py new file mode 100644 index 000000000..b6b2e57e1 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/exceptions.py @@ -0,0 +1,5 @@ +try: + print() +except Exception as e: + x = e + # ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-python/test/functions.py b/languages/tree-sitter-stack-graphs-python/test/functions.py new file mode 100644 index 000000000..31a1d6782 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/functions.py @@ -0,0 +1,31 @@ +import a.x.y +import b.x.y + +def get_x(value): + return value.x + # ^ defined: 4 + +print get_x(a).y +# ^ defined: 1 + +print get_x(b).y +# ^ defined: 2 + +def get_a(): + return a + +print get_a(b).x +# ^ defined: 1 + +print get_x(foo=1, value=a).y +# ^ defined: 1 + +def foo(w: int, x, y=1, z: int=4, *args, **dict): + local = x +# ^ defined: 23 + print(args, w, z) +# ^ defined: 23 +# ^ defined: 23 +# ^ defined: 23 + return y +# ^ defined: 23 diff --git a/languages/tree-sitter-stack-graphs-python/test/imported_functions.py b/languages/tree-sitter-stack-graphs-python/test/imported_functions.py new file mode 100644 index 000000000..495697740 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imported_functions.py @@ -0,0 +1,17 @@ +#------ path: a.py ------# + +def foo(x): + return x + +#------ path: b.py ------# + +class A: + bar = 1 + +#------ path: main.py ---------# + +from a import * +from b import * + +foo(A).bar +# ^ defined: 9 diff --git a/languages/tree-sitter-stack-graphs-python/test/imports.py b/languages/tree-sitter-stack-graphs-python/test/imports.py new file mode 100644 index 000000000..96d561813 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports.py @@ -0,0 +1,31 @@ +#------ path: one/two.py -----------# + +# module +import a.b.c + +d = 1 + +e = a.b + +#------ path: three/__init__.py ---# + +# module +f = 3 + +#------ path: main.py -------------# + +from one.two import d, e.c +# ^ defined: 3 +# ^ defined: 6 +# ^ defined: 4, 8 + +import three +# ^ defined: 12 + +print(d, e.c) +# ^ defined: 6, 17 +# ^ defined: 4, 17 + +print three.f +# ^ defined: 12, 22 +# ^ defined: 13 diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_submodule.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_submodule.py new file mode 100644 index 000000000..ea51f9450 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_submodule.py @@ -0,0 +1,14 @@ +# --- path: foo/bar.py --- + +BAR = 42 + +# --- path: test.py --- + +from foo import bar + +bar.BAR +# ^ defined: 7, 3 +# ^ defined: 3 + +foo +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_value.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_value.py new file mode 100644 index 000000000..1eb97ffe2 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_value.py @@ -0,0 +1,13 @@ +# --- path: foo.py --- + +FOO = 42 + +# --- path: test.py --- + +from foo import FOO + +FOO +# ^ defined: 7, 3 + +foo +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_value_aliased.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_value_aliased.py new file mode 100644 index 000000000..088ca9544 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_a_value_aliased.py @@ -0,0 +1,13 @@ +# --- path: foo.py --- + +FOO = 42 + +# --- path: test.py --- + +from foo import FOO as QUX + +QUX +# ^ defined: 7, 3 + +FOO +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_wildcard.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_wildcard.py new file mode 100644 index 000000000..3a1de4ffe --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_module_wildcard.py @@ -0,0 +1,13 @@ +# --- path: foo.py --- + +FOO = 42 + +# --- path: test.py --- + +from foo import * + +FOO +# ^ defined: 3 + +foo +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_submodule_a_value.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_submodule_a_value.py new file mode 100644 index 000000000..8d89d8538 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_submodule_a_value.py @@ -0,0 +1,16 @@ +# --- path: foo/bar.py --- + +BAR = 42 + +# --- path: test.py --- + +from foo.bar import BAR + +BAR +# ^ defined: 7, 3 + +foo +# ^ defined: + +bar +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_super_package_a_value.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_super_package_a_value.py new file mode 100644 index 000000000..961cd1622 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_super_package_a_value.py @@ -0,0 +1,10 @@ +# --- path: foo/__init__.py --- + +FOO = 42 + +# --- path: foo/bar/test.py --- + +from .. import FOO + +FOO +# ^ defined: 7, 3 diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_super_package_submodule_a_value.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_super_package_submodule_a_value.py new file mode 100644 index 000000000..75652f7cd --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_super_package_submodule_a_value.py @@ -0,0 +1,10 @@ +# --- path: foo/bar.py --- + +BAR = 42 + +# --- path: foo/baz/test.py --- + +from ..bar import BAR + +BAR +# ^ defined: 7, 3 diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_this_package_a_value.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_this_package_a_value.py new file mode 100644 index 000000000..f8de8042c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_this_package_a_value.py @@ -0,0 +1,10 @@ +# --- path: foo/__init__.py --- + +FOO = 42 + +# --- path: foo/test.py --- + +from . import FOO + +FOO +# ^ defined: 7, 3 diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_from_this_package_submodule_a_value.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_this_package_submodule_a_value.py new file mode 100644 index 000000000..dc64ce387 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_from_this_package_submodule_a_value.py @@ -0,0 +1,10 @@ +# --- path: foo/bar.py --- + +BAR = 42 + +# --- path: foo/test.py --- + +from .bar import BAR + +BAR +# ^ defined: 7, 3 diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_module.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_module.py new file mode 100644 index 000000000..897acdf63 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_module.py @@ -0,0 +1,11 @@ +# --- path: foo.py --- + +FOO = 42 + +# --- path: test.py --- + +import foo + +foo.FOO +# ^ defined: 7, 3 +# ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_module_aliased.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_module_aliased.py new file mode 100644 index 000000000..220a6b72d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_module_aliased.py @@ -0,0 +1,14 @@ +# --- path: foo.py --- + +FOO = 42 + +# --- path: test.py --- + +import foo as qux + +qux.FOO +# ^ defined: 7, 3 +# ^ defined: 3 + +foo +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_submodule.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_submodule.py new file mode 100644 index 000000000..312cd66a1 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_submodule.py @@ -0,0 +1,15 @@ +# --- path: foo/bar.py --- + +BAR = 42 + +# --- path: test.py --- + +import foo.bar + +foo.bar.BAR +# ^ defined: 7 +# ^ defined: 7, 3 +# ^ defined: 3 + +bar +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/import_submodule_aliased.py b/languages/tree-sitter-stack-graphs-python/test/imports/import_submodule_aliased.py new file mode 100644 index 000000000..b6bfdf7b7 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/import_submodule_aliased.py @@ -0,0 +1,17 @@ +# --- path: foo/bar.py --- + +BAR = 42 + +# --- path: test.py --- + +import foo.bar as qux + +qux.BAR +# ^ defined: 7, 3 +# ^ defined: 3 + +foo +# ^ defined: + +bar +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/relative_import_resolves_to_itself.py.skip b/languages/tree-sitter-stack-graphs-python/test/imports/relative_import_resolves_to_itself.py.skip new file mode 100644 index 000000000..2cdb2cf66 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/relative_import_resolves_to_itself.py.skip @@ -0,0 +1,14 @@ +# --- path: foo/__init__.py --- +from . import bar +# ^ defined: 6 + +# --- path: foo/bar/__init__.py --- +BAR = 'b' + +# --- path: main.py --- +from foo import bar +# ^ defined: 6 + +bar.BAR +# ^ defined: 9, 6 +# ^ defined: 6 diff --git a/languages/tree-sitter-stack-graphs-python/test/imports/require_explicit_submodule_import.py.skip b/languages/tree-sitter-stack-graphs-python/test/imports/require_explicit_submodule_import.py.skip new file mode 100644 index 000000000..a9d12848c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/imports/require_explicit_submodule_import.py.skip @@ -0,0 +1,18 @@ +# --- path: foo/__init__.py --- + +FOO = 42 + +# --- path: foo/bar.py --- + +BAR = 42 + +# --- path: test.py --- + +import foo + +foo.FOO +# ^ defined: 11, 3 +# ^ defined: 3 + +foo.bar +# ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/instance_members.py b/languages/tree-sitter-stack-graphs-python/test/instance_members.py new file mode 100644 index 000000000..38a6b6d75 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/instance_members.py @@ -0,0 +1,38 @@ +#---- path: a.py ------- + +class A: + def __init__(self, b, c): + self.b = b + self.c = A(c, 1) + + def get_b(self): + return self.b + # ^ defined: 4, 5 + + def get_c(self): + return self.c + # ^ defined: 6 + + def get_all(self): + return [self.get_b(), self.get_c()] + # ^ defined: 8 + # ^ defined: 12 + +a = A(1, 2) +a.get_all() +# ^ defined: 16 + +a.b +# ^ defined: 4, 5 + +a.c.b +# ^ defined: 4, 5 +# ^ defined: 6 + +#----- path: main.py --------- + +import a + +print a.A, a.a +# ^ defined: 3 +# ^ defined: 21 diff --git a/languages/tree-sitter-stack-graphs-python/test/lambdas.py b/languages/tree-sitter-stack-graphs-python/test/lambdas.py new file mode 100644 index 000000000..9812df4de --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/lambdas.py @@ -0,0 +1,2 @@ +sorted([1, 2, 3], key=lambda x: x) +# ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-python/test/loops.py b/languages/tree-sitter-stack-graphs-python/test/loops.py new file mode 100644 index 000000000..2f0663295 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/loops.py @@ -0,0 +1,19 @@ +class Node3: + value = 3 +class Node2: + value = 2 + next = Node3 +class Node1: + value = 1 + next = Node2 + +def linked_list_search(l, item): + node = l + while node: + if node.value == item: + return node + # ^ defined: 10, 11, 16 + node = node.next + +linked_list_search(Node1, 5).value +# ^ defined: 6 diff --git a/languages/tree-sitter-stack-graphs-python/test/many_definitions.py b/languages/tree-sitter-stack-graphs-python/test/many_definitions.py new file mode 100644 index 000000000..f85f3a8ec --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/many_definitions.py @@ -0,0 +1,76 @@ +#--- path: a.py ---# + +def f0(a): return X.a + X.b +def f1(a): return f0(1) +def f2(a): return f1(2) +def f3(a): return f2(3) +def f4(a): return f3(4) +def f5(a): return f4(5) +def f6(a): return f5(6) +def f7(a): return f6(7) +def f8(a): return f7(8) +def f9(a): return f8(9) + +class C1: + def m0(self, b): return f9(0) + def m1(self, b): return self.m0(1) + def m2(self, b): return self.m1(2) + def m3(self, b): return self.m2(3) + def m4(self, b): return self.m3(4) + def m5(self, b): return self.m4(5) + def m6(self, b): return self.m5(6) + def m7(self, b): return self.m6(7) + def m8(self, b): return self.m7(8) + +def f10(): return C1.m8(0) +def f11(): return f10(1) +def f12(): return X.c(2) +def f13(): return X.c(3) +def f14(): return x(4) +def f15(): return x(5) +def f16(): return x(6) +def f17(): return x(7) +def f18(): return x(8) + +class C2: + def m0(self): return X.d(0) + def m1(self): return X.d(1) + def m2(self): return X.d(2) + def m3(self): return X.d(3) + def m4(self): return X.d(4) + def m5(self): return X.d(5) + def m6(self): return X.d(6) + def m7(self): return X.d(7) + def m8(self): return X.d(8) + +#--- path: main.py ---# + +from a import * + +print f0(), f4(), f8() +# ^ defined: 3 +# ^ defined: 7 +# ^ defined: 11 + +print C1.m0, C1().m0(), C1.m4, C1().m4, C1.m8, C1.m8 +# ^ defined: 14 +# ^ defined: 15 +# ^ defined: 15 +# ^ defined: 19 +# ^ defined: 19 +# ^ defined: 23 +# ^ defined: 23 + +print f10(), f14(), f18() +# ^ defined: 25 +# ^ defined: 29 +# ^ defined: 33 + +print C2.m0, C2().m0(), C2.m4, C2().m4, C2.m8, C2.m8 +# ^ defined: 35 +# ^ defined: 36 +# ^ defined: 36 +# ^ defined: 40 +# ^ defined: 40 +# ^ defined: 44 +# ^ defined: 44 diff --git a/languages/tree-sitter-stack-graphs-python/test/nested_functions.py b/languages/tree-sitter-stack-graphs-python/test/nested_functions.py new file mode 100644 index 000000000..db655e3a3 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/nested_functions.py @@ -0,0 +1,6 @@ +def outer(a): + def inner(b): + pass + + inner(1) + # ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-python/test/not_a_method.py b/languages/tree-sitter-stack-graphs-python/test/not_a_method.py new file mode 100644 index 000000000..f0a207eb0 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/not_a_method.py @@ -0,0 +1,20 @@ +class Foo: + a = 1 + + def first_method(self): + self.a + # ^ defined: 2 + + def second_method(self): + self.a + # ^ defined: 2 + + # First argument here is not self + def not_a_method(not_self): + return not_self.a + # ^ defined: + + +def function(self): + self.a + # ^ defined: diff --git a/languages/tree-sitter-stack-graphs-python/test/pattern_matching.py b/languages/tree-sitter-stack-graphs-python/test/pattern_matching.py new file mode 100644 index 000000000..d6b8980a3 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/pattern_matching.py @@ -0,0 +1,42 @@ +command = 1 +current_room = 2 +character = 3 + +match command.split(): + # ^ defined: 1 + case ["quit"]: + print("Goodbye!") + quit_game() + case ["look"]: + current_room.describe() + # ^ defined: 2 + case ["get", obj]: + character.get(obj, current_room) + # ^ defined: 3 + # ^ defined: 13 + # ^ defined: 2 + case ["go", direction]: + current_room = current_room.neighbor(direction) + # ^ defined: 18 + case { "foo": foo }: + print(foo) + # ^ defined: 21 + case {"bar": bar, "quux": quux}: + print(bar,quux) + # ^ defined: 24 + # ^ defined: 24 + case ["grab", { "key": {"garply": garply}}]: + print(garply) + # ^ defined: 28 + case ["drop", *objs]: + print(objs) + # ^ defined: 31 + case ["get", obj] | ["pick", "up", obj] | ["pick", obj, "up"]: + print(obj) + # ^ defined: 34, 34, 34 + case ["go", ("north" | "south" | "east" | "west") as direction2]: + current_room = current_room.neighbor(direction2) + # ^ defined: 37 + case (foo, "bar"): + print(foo) + # ^ defined: 40 diff --git a/languages/tree-sitter-stack-graphs-python/test/redundant_reexport.py b/languages/tree-sitter-stack-graphs-python/test/redundant_reexport.py new file mode 100644 index 000000000..53afca29e --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/redundant_reexport.py @@ -0,0 +1,15 @@ +#--- path: a/__init__.py ---# + +from . import child + +#--- path: a/child.py ---- + +def f(): + pass + +#--- path: main.py ---# + +import a + +print a.child.f() +# ^ defined: 7 diff --git a/languages/tree-sitter-stack-graphs-python/test/relative_imports.py b/languages/tree-sitter-stack-graphs-python/test/relative_imports.py new file mode 100644 index 000000000..f14571575 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/relative_imports.py @@ -0,0 +1,60 @@ +#------ path: foo/bar/main.py ------# + +from . import a +from .b import B +from ..c import see +from ..c.d import D + +print a.A +# ^ defined: 3, 25 +# ^ defined: 26 + +print B.bee +# ^ defined: 4, 31 +# ^ defined: 32 + +print see() +# ^ defined: 5, 37 + + +print D.d +# ^ defined: 44 + +#------ path: foo/bar/a.py --------# + +# module +A = "a" + +#------ path: foo/bar/b.py --------# + +# module +class B: + bee = 1 + +#------ path: foo/c.py ------------# + +# module +def see(): + pass + +#------ path: foo/c/d.py ---------# + +# module +class D: + d = "d" + +#------ path: foo/e/g.py ---# + +# module +G = 1 + +#------ path: foo/e/__init__.py ---# + +# module +from .g import G +# ^ defined: 49 + +from ..c import see +# ^ defined: 37 + +E = 1 diff --git a/languages/tree-sitter-stack-graphs-python/test/root_path.py b/languages/tree-sitter-stack-graphs-python/test/root_path.py new file mode 100644 index 000000000..23937db7c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/root_path.py @@ -0,0 +1,21 @@ +# ------ path: foo/bar/module.py -----------# +# ------ global: ROOT_PATH=foo/bar -----------# + +foo = 42 + +# ------ path: foo/bar/baz/module.py -----------# +# ------ global: ROOT_PATH=foo/bar -----------# + +bar = "hello" + +# ------ path: foo/bar/main.py -------------# +# ------ global: ROOT_PATH=foo/bar -----------# + +from module import foo +from baz.module import bar + +print(foo) +# ^ defined: 4, 14 + +print(bar) +# ^ defined: 9, 15 diff --git a/languages/tree-sitter-stack-graphs-python/test/self.py b/languages/tree-sitter-stack-graphs-python/test/self.py new file mode 100644 index 000000000..763a52eb6 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/self.py @@ -0,0 +1,11 @@ +class Foo: + a = 1 + + def mathod_1(self): + return self.a + # ^ defined: 2 + + # Self can be named anything + def method_2(actually_self): + return actually_self.a + # ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-python/test/statement_bindings.py b/languages/tree-sitter-stack-graphs-python/test/statement_bindings.py new file mode 100644 index 000000000..6e3a13abb --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/statement_bindings.py @@ -0,0 +1,9 @@ +import a.b +import c.d + +with a as x, c as y: + print x.b, y.d + # ^ defined: 1, 4 + # ^ defined: 1 + # ^ defined: 2, 4 + # ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-python/test/superclasses.py b/languages/tree-sitter-stack-graphs-python/test/superclasses.py new file mode 100644 index 000000000..849a192a6 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/superclasses.py @@ -0,0 +1,19 @@ +class A: + def __init__(self): + self.some_attr = 2 + + def some_method(self): + print self + +class B(A): + def method2(self): + print self.some_attr, self.some_method() + # ^ defined: 3 + # ^ defined: 5, 14 + + def some_method(self): + pass + + def other(self): + super().some_method() + # ^ defined: 5 diff --git a/languages/tree-sitter-stack-graphs-python/test/test.py b/languages/tree-sitter-stack-graphs-python/test/test.py new file mode 100644 index 000000000..e69de29bb diff --git a/languages/tree-sitter-stack-graphs-python/test/tuples.py b/languages/tree-sitter-stack-graphs-python/test/tuples.py new file mode 100644 index 000000000..1ea6ee3ef --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/tuples.py @@ -0,0 +1,38 @@ +class a: + x = 1 +class b: + x = 1 +class c: + x = 1 + +(a1, b1) = (a, b) +a2, b2 = (a, b) +(a3, b3) = (a, b) +a4, b4 = a, b + +print a1.x, b1.x +# ^ defined: 2 +# ^ defined: 4 +print a2.x, b2.x +# ^ defined: 2 +# ^ defined: 4 +print a3.x, b3.x +# ^ defined: 2 +# ^ defined: 4 +print a4.x, b4.x +# ^ defined: 2 +# ^ defined: 4 + +t = (a, b), c +(a5, b5), c5 = t + +print a5.x, b5.x, c5.x +# ^ defined: 2 +# ^ defined: 4 +# ^ defined: 6 + +(a6, (b6, c6)) = (a, (b, c)) + +print a6.x, b6.x +# ^ defined: 2 +# ^ defined: 4 diff --git a/languages/tree-sitter-stack-graphs-python/test/wildcard_import.py b/languages/tree-sitter-stack-graphs-python/test/wildcard_import.py new file mode 100644 index 000000000..f2ac040f2 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/wildcard_import.py @@ -0,0 +1,21 @@ +#------ path: one/two.py ------# + +a = 1 +b = 2 + +#------ path: one/three.py ------# + +b = 3 +c = 4 + +#------ path: main.py ---------# + +from one.two import * +from one.three import * + +print a +# ^ defined: 3 +print b +# ^ defined: 8 +print c +# ^ defined: 9 diff --git a/languages/tree-sitter-stack-graphs-typescript/CHANGELOG.md b/languages/tree-sitter-stack-graphs-typescript/CHANGELOG.md index e5b478f09..5a3bfa3d4 100644 --- a/languages/tree-sitter-stack-graphs-typescript/CHANGELOG.md +++ b/languages/tree-sitter-stack-graphs-typescript/CHANGELOG.md @@ -5,7 +5,43 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## v0.4.0 -- 2024-12-13 + +- The `tree-sitter-stack-graphs` dependency is updated to version 0.10. + +- The `tree-sitter-typescript` dependency is updated to version 0.23.2. + +## v0.3.0 -- 2024-07-09 + +### Added + +- Support for TSX. A new language configuration for TSX is available with `{try_,}language_configuration_tsx`. TSX is enabled in the CLI next to TypeScript. + +### Fixed + +- Imports are more robust to the presence of file extensions in the import name. + +### Changed + +- The functions `{try_,}language_configuration` have been renamed to `{try_,}language_configuration_typescript`. + +### Removed + +- The `FILE_PATH_VAR` constant has been replaced in favor of `tree_sitter_stack_graphs::FILE_PATH_VAR`. + +## v0.2.0 -- 2024-03-06 + +The `tree-sitter-stack-graphs` is updated to `v0.8`. + +### Added + +- An experimental VSCode LSP plugin that supports code navigation based on the stack graph rules. _Purely an experiment, not ready for serious use!_ Requires the `lsp` feature to be enabled. + +### Changed + +- Various improvements to the rules for imports and packages. + +## v0.1.0 -- 2023-01-27 ### Added diff --git a/languages/tree-sitter-stack-graphs-typescript/Cargo.toml b/languages/tree-sitter-stack-graphs-typescript/Cargo.toml index 65511e3f8..ed14d9112 100644 --- a/languages/tree-sitter-stack-graphs-typescript/Cargo.toml +++ b/languages/tree-sitter-stack-graphs-typescript/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "tree-sitter-stack-graphs-typescript" -version = "0.1.0" -description = "Stack graphs definition for TypeScript using tree-sitter-typescript" +version = "0.4.0" +description = "Stack graphs definition for TypeScript & TSX using tree-sitter-typescript" readme = "README.md" -keywords = ["tree-sitter", "stack-graphs", "typescript"] +keywords = ["tree-sitter", "stack-graphs", "typescript", "tsx"] authors = ["Hendrik van Antwerpen "] license = "MIT OR Apache-2.0" edition = "2018" @@ -32,11 +32,15 @@ clap = { version = "4", optional = true } glob = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -stack-graphs = { version = ">=0.11, <=0.12", path = "../../stack-graphs" } -tree-sitter-stack-graphs = { version = "0.7", path = "../../tree-sitter-stack-graphs" } -tree-sitter-typescript = "0.20.2" +stack-graphs = { version = "0.14", path = "../../stack-graphs" } # explicit version is required to be able to publish crate +tree-sitter-stack-graphs = { version = "0.10", path = "../../tree-sitter-stack-graphs" } # explicit version is required to be able to publish crate +tree-sitter-typescript = "=0.23.2" tsconfig = "0.1.0" [dev-dependencies] anyhow = { version = "1.0" } -tree-sitter-stack-graphs = { version = "0.7", path = "../../tree-sitter-stack-graphs", features = ["cli"] } +tree-sitter-stack-graphs = { path = "../../tree-sitter-stack-graphs", features = ["cli"] } + +[build-dependencies] +anyhow = { version = "1.0" } +regex = "1.10.3" diff --git a/languages/tree-sitter-stack-graphs-typescript/README.md b/languages/tree-sitter-stack-graphs-typescript/README.md index 0c1f9d5ee..c83d5849d 100644 --- a/languages/tree-sitter-stack-graphs-typescript/README.md +++ b/languages/tree-sitter-stack-graphs-typescript/README.md @@ -4,72 +4,106 @@ This project defines tree-sitter-stack-graphs rules for TypeScript using the [tr [tree-sitter-typescript]: https://crates.io/crates/tree-sitter-typescript -## Usage +- [API documentation](https://docs.rs/tree-sitter-stack-graphs-typescript/) +- [Release notes](https://github.com/github/stack-graphs/blob/main/languages/tree-sitter-stack-graphs-typescript/CHANGELOG.md) + +## Using the API To use this library, add the following to your `Cargo.toml`: -``` toml +```toml [dependencies] -tree-sitter-stack-graphs-typescript = "0.1" +tree-sitter-stack-graphs-typescript = "0.4" ``` -Check out our [documentation](https://docs.rs/tree-sitter-stack-graphs-typescript/*/) for -more details on how to use this library. +Check out our [documentation](https://docs.rs/tree-sitter-stack-graphs-typescript/*/) for more details on how to use this library. -## Command-line Program +## Using the Command-line Program -The command-line program for `tree-sitter-stack-graphs-typescript` lets you do -stack graph based analysis and lookup from the command line. +The command-line program for `tree-sitter-stack-graphs-typescript` lets you do stack graph based analysis and lookup from the command line. -Install the program using `cargo install` as follows: +The CLI can be run as follows: -``` sh -$ cargo install --features cli tree-sitter-stack-graphs-typescript -$ tree-sitter-stack-graphs-typescript --help -``` +1. _(Installed)_ Install the CLI using Cargo as follows: + + ```sh + cargo install --features cli tree-sitter-stack-graphs-typescript + ``` + + After this, the CLI should be available as `tree-sitter-stack-graphs-typescript`. + +2. _(From source)_ Instead of installing the CLI, it can also be run directly from the crate directory, as a replacement for a `tree-sitter-stack-graphs-typescript` invocation, as follows: + + ```sh + cargo run --features cli -- + ``` + +The basic CLI workflow for the command-line program is to index source code and issue queries against the resulting database: + +1. Index a source folder as follows: + + ```sh + tree-sitter-stack-graphs-typescript index SOURCE_DIR + ``` + + _Indexing will skip any files that have already be indexed. To force a re-index, add the `-f` flag._ + + To check the status if a source folder, run: + + ```sh + tree-sitter-stack-graphs-typescript status SOURCE_DIR + ``` + + To clean the database and start with a clean slate, run: + + ```sh + tree-sitter-stack-graphs-typescript clean + ``` + + _Pass the `--delete` flag to not just empty the database, but also delete it. This is useful to resolve `unsupported database version` errors that may occur after a version update._ + +2. Run a query to find the definition(s) for a reference on a given line and column, run: + + ```sh + tree-sitter-stack-graphs-typescript query definition SOURCE_PATH:LINE:COLUMN + ``` + + Resulting definitions are printed, including a source line if the source file is available. + +Discover all available commands and flags by passing the `-h` flag to the CLI directly, or to any of the subcommands. ## Development -The project is written in Rust, and requires a recent version installed. -Rust can be installed and updated using [rustup][]. +The project is written in Rust, and requires a recent version installed. Rust can be installed and updated using [rustup][]. [rustup]: https://rustup.rs/ - The project is organized as follows: - The stack graph rules are defined in `src/stack-graphs.tsg`. -- Builtins sources and configuration are defined in `src/builtins.ts` and `builtins.cfg` respectively. +- Builtins sources and configuration are defined in `src/builtins.it` and `builtins.cfg` respectively. - Tests are put into the `test` directory. -### Building and Running Tests - -Build the project by running: - -``` sh -$ cargo build -``` +### Running Tests Run the tests as follows: -``` sh -$ cargo test +```sh +cargo test ``` -The project consists of a library and a CLI. -By default, running `cargo` only applies to the library. -To run `cargo` commands on the CLI as well, add `--features cli` or `--all-features`. +The project consists of a library and a CLI. By default, running `cargo` only applies to the library. To run `cargo` commands on the CLI as well, add `--features cli` or `--all-features`. Run the CLI from source as follows: -``` sh -$ cargo run --features cli -- ARGS +```sh +cargo run --features cli -- ARGS ``` Sources are formatted using the standard Rust formatted, which is applied by running: -``` sh -$ cargo fmt +```sh +cargo fmt ``` ### Writing TSG @@ -84,21 +118,21 @@ which contain self-contained TSG rules for specific language features. A VSCode Parse and test a single file by executing the following commands: -``` sh -$ cargo run --features cli -- parse FILES... -$ cargo run --features cli -- test TESTFILES... +```sh +cargo run --features cli -- parse FILES... +cargo run --features cli -- test TESTFILES... ``` Generate a visualization to debug failing tests by passing the `-V` flag: -``` sh -$ cargo run --features cli -- test -V TESTFILES... +```sh +cargo run --features cli -- test -V TESTFILES... ``` To generate the visualization regardless of test outcome, execute: -``` sh -$ cargo run --features cli -- test -V --output-mode=always TESTFILES... +```sh +cargo run --features cli -- test -V --output-mode=always TESTFILES... ``` -Go to https://crates.io/crates/tree-sitter-stack-graphs for links to examples and documentation. +Go to for links to examples and documentation. diff --git a/languages/tree-sitter-stack-graphs-typescript/build.rs b/languages/tree-sitter-stack-graphs-typescript/build.rs new file mode 100644 index 000000000..31e3dd9de --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/build.rs @@ -0,0 +1,92 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2024, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use std::path::Path; + +use anyhow::{bail, Result}; +use regex::Regex; + +const TSG_SOURCE: &str = "src/stack-graphs.tsg"; +const DIALECTS: [&str; 2] = ["typescript", "tsx"]; + +/// preprocess the input file, removing lines that are not for the selected dialect +fn preprocess( + input: impl std::io::Read, + mut output: impl std::io::Write, + dialect: &str, +) -> Result<()> { + // Matches: ; #dialect typescript + let directive_start = Regex::new(r";\s*#dialect\s+(\w+)").unwrap(); + + // Matches: ; #end + let directive_end = Regex::new(r";\s*#end").unwrap(); + + let no_code = Regex::new(r"^[\s;]+").unwrap(); + + let input = std::io::read_to_string(input)?; + + // If the filter is None or Some(true), the lines are written to the output + let mut filter: Option = None; + + for (mut line_no, line) in input.lines().enumerate() { + // Line numbers are one based + line_no += 1; + + if let Some(captures) = directive_start.captures(line) { + if !no_code.is_match(line) { + bail!("Line {line_no}: unexpected code before directive"); + } + + if filter.is_some() { + bail!("Line {line_no}: dialect directive cannot be nested"); + } + + let directive = captures.get(1).unwrap().as_str(); + if !DIALECTS.contains(&directive) { + bail!("Line {line_no}: unknown dialect: {directive}"); + } + + filter = Some(dialect == directive); + } else if directive_end.is_match(line) { + if !no_code.is_match(line) { + bail!("Line {line_no}: unexpected code before directive end"); + } + + if filter.is_none() { + bail!("Line {line_no}: unmatched directive end"); + } + + filter = None; + } else if filter.unwrap_or(true) { + output.write_all(line.as_bytes())?; + } + // a new line is always written so that removed lines are padded to preserve line numbers + output.write(b"\n")?; + } + + if filter.is_some() { + bail!("Unmatched directive end at the end of the file"); + } + + Ok(()) +} + +fn main() { + let out_dir = std::env::var_os("OUT_DIR").expect("OUT_DIR is not set"); + + for dialect in DIALECTS { + let input = std::fs::File::open(TSG_SOURCE).expect("Failed to open stack-graphs.tsg"); + + let out_filename = Path::new(&out_dir).join(format!("stack-graphs-{dialect}.tsg")); + let output = std::fs::File::create(out_filename).expect("Failed to create output file"); + + preprocess(input, output, dialect).expect("Failed to preprocess stack-graphs.tsg"); + } + + println!("cargo:rerun-if-changed={TSG_SOURCE}"); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/languages/tree-sitter-stack-graphs-typescript/rust/bin.rs b/languages/tree-sitter-stack-graphs-typescript/rust/bin.rs index b1ee4f568..ea7713595 100644 --- a/languages/tree-sitter-stack-graphs-typescript/rust/bin.rs +++ b/languages/tree-sitter-stack-graphs-typescript/rust/bin.rs @@ -12,17 +12,23 @@ use tree_sitter_stack_graphs::cli::provided_languages::Subcommands; use tree_sitter_stack_graphs::NoCancellation; fn main() -> anyhow::Result<()> { - let lc = match tree_sitter_stack_graphs_typescript::try_language_configuration(&NoCancellation) - { - Ok(lc) => lc, - Err(err) => { - eprintln!("{}", err.display_pretty()); - return Err(anyhow!("Language configuration error")); - } - }; let cli = Cli::parse(); + let mut lcs = Vec::new(); + for r in [ + tree_sitter_stack_graphs_typescript::try_language_configuration_typescript(&NoCancellation), + tree_sitter_stack_graphs_typescript::try_language_configuration_tsx(&NoCancellation), + ] { + let lc = match r { + Ok(lc) => lc, + Err(err) => { + eprintln!("{}", err.display_pretty()); + return Err(anyhow!("Language configuration error")); + } + }; + lcs.push(lc); + } let default_db_path = default_user_database_path_for_crate(env!("CARGO_PKG_NAME"))?; - cli.subcommand.run(default_db_path, vec![lc]) + cli.subcommand.run(default_db_path, lcs) } #[derive(Parser)] diff --git a/languages/tree-sitter-stack-graphs-typescript/rust/lib.rs b/languages/tree-sitter-stack-graphs-typescript/rust/lib.rs index 45ce6d255..8fd63881d 100644 --- a/languages/tree-sitter-stack-graphs-typescript/rust/lib.rs +++ b/languages/tree-sitter-stack-graphs-typescript/rust/lib.rs @@ -5,7 +5,6 @@ // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. // ------------------------------------------------------------------------------------------------ -use tree_sitter_stack_graphs::loader::FileAnalyzers; use tree_sitter_stack_graphs::loader::LanguageConfiguration; use tree_sitter_stack_graphs::loader::LoadError; use tree_sitter_stack_graphs::CancellationFlag; @@ -20,7 +19,10 @@ pub mod util; /// The stacks graphs tsg path for this language. pub const STACK_GRAPHS_TSG_PATH: &str = "src/stack-graphs.tsg"; /// The stack graphs tsg source for this language -pub const STACK_GRAPHS_TSG_SOURCE: &str = include_str!("../src/stack-graphs.tsg"); +pub const STACK_GRAPHS_TSG_TS_SOURCE: &str = + include_str!(concat!(env!("OUT_DIR"), "/stack-graphs-typescript.tsg")); +pub const STACK_GRAPHS_TSG_TSX_SOURCE: &str = + include_str!(concat!(env!("OUT_DIR"), "/stack-graphs-tsx.tsg")); /// The stack graphs builtins configuration for this language pub const STACK_GRAPHS_BUILTINS_CONFIG: &str = include_str!("../src/builtins.cfg"); @@ -29,33 +31,65 @@ pub const STACK_GRAPHS_BUILTINS_PATH: &str = "src/builtins.ts"; /// The stack graphs builtins source for this language pub const STACK_GRAPHS_BUILTINS_SOURCE: &str = include_str!("../src/builtins.ts"); -/// The name of the file path global variable -pub const FILE_PATH_VAR: &str = "FILE_PATH"; /// The name of the project name global variable pub const PROJECT_NAME_VAR: &str = "PROJECT_NAME"; -pub fn language_configuration(cancellation_flag: &dyn CancellationFlag) -> LanguageConfiguration { - try_language_configuration(cancellation_flag).unwrap_or_else(|err| panic!("{}", err)) +pub fn language_configuration_typescript( + cancellation_flag: &dyn CancellationFlag, +) -> LanguageConfiguration { + try_language_configuration_typescript(cancellation_flag).unwrap_or_else(|err| panic!("{}", err)) } -pub fn try_language_configuration( +pub fn try_language_configuration_typescript( cancellation_flag: &dyn CancellationFlag, ) -> Result { - LanguageConfiguration::from_sources( - tree_sitter_typescript::language_typescript(), + let mut lc = LanguageConfiguration::from_sources( + tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(), Some(String::from("source.ts")), None, vec![String::from("ts")], STACK_GRAPHS_TSG_PATH.into(), - STACK_GRAPHS_TSG_SOURCE, + STACK_GRAPHS_TSG_TS_SOURCE, + Some(( + STACK_GRAPHS_BUILTINS_PATH.into(), + STACK_GRAPHS_BUILTINS_SOURCE, + )), + Some(STACK_GRAPHS_BUILTINS_CONFIG), + cancellation_flag, + )?; + lc.special_files + .add("tsconfig.json".to_string(), TsConfigAnalyzer {}) + .add("package.json".to_string(), NpmPackageAnalyzer {}); + lc.no_similar_paths_in_file = true; + Ok(lc) +} + +pub fn language_configuration_tsx( + cancellation_flag: &dyn CancellationFlag, +) -> LanguageConfiguration { + try_language_configuration_tsx(cancellation_flag).unwrap_or_else(|err| panic!("{}", err)) +} + +pub fn try_language_configuration_tsx( + cancellation_flag: &dyn CancellationFlag, +) -> Result { + let mut lc = LanguageConfiguration::from_sources( + tree_sitter_typescript::LANGUAGE_TSX.into(), + Some(String::from("source.tsx")), + None, + vec![String::from("tsx")], + STACK_GRAPHS_TSG_PATH.into(), + STACK_GRAPHS_TSG_TSX_SOURCE, Some(( STACK_GRAPHS_BUILTINS_PATH.into(), STACK_GRAPHS_BUILTINS_SOURCE, )), Some(STACK_GRAPHS_BUILTINS_CONFIG), - FileAnalyzers::new() - .add("tsconfig.json".to_string(), TsConfigAnalyzer {}) - .add("package.json".to_string(), NpmPackageAnalyzer {}), cancellation_flag, - ) + )?; + lc.special_files + .add("tsconfig.json".to_string(), TsConfigAnalyzer {}) + .add("package.json".to_string(), NpmPackageAnalyzer {}); + lc.no_similar_paths_in_file = true; + Ok(lc) } diff --git a/languages/tree-sitter-stack-graphs-typescript/rust/npm_package.rs b/languages/tree-sitter-stack-graphs-typescript/rust/npm_package.rs index 9b67db598..b7351454a 100644 --- a/languages/tree-sitter-stack-graphs-typescript/rust/npm_package.rs +++ b/languages/tree-sitter-stack-graphs-typescript/rust/npm_package.rs @@ -8,6 +8,7 @@ use serde::Deserialize; use std::collections::HashMap; use std::path::Path; +use std::path::PathBuf; use stack_graphs::arena::Handle; use stack_graphs::graph::File; @@ -15,6 +16,7 @@ use stack_graphs::graph::StackGraph; use tree_sitter_stack_graphs::BuildError; use tree_sitter_stack_graphs::FileAnalyzer; +use crate::tsconfig::NormalizedRelativePath; use crate::util::*; pub struct NpmPackageAnalyzer {} @@ -74,16 +76,28 @@ impl FileAnalyzer for NpmPackageAnalyzer { }; // package definition - let pkg_def = add_module_pops( - graph, - file, - NON_REL_M_NS, - Path::new(&npm_pkg.name), - root, - "npm_package.pkg_def", - ); - let pkg_ref = add_push(graph, file, proj_scope, PKG_M_NS, "npm_package.pkg_ref"); - add_edge(graph, pkg_def, pkg_ref, 0); + if !npm_pkg.name.is_empty() { + let pkg_def = add_module_pops( + graph, + file, + NON_REL_M_NS, + Path::new(&npm_pkg.name), + root, + "npm_package.pkg_def", + ); + let pkg_ref = add_push(graph, file, proj_scope, PKG_M_NS, "npm_package.pkg_ref"); + add_edge(graph, pkg_def, pkg_ref, 0); + + let main = Some(npm_pkg.main) + .filter(|main| !main.is_empty()) + .and_then(|main| NormalizedRelativePath::from_str(&main)) + .map(|p| p.into_path_buf()) + .unwrap_or(PathBuf::from("index")) + .with_extension(""); + let main_ref = + add_module_pushes(graph, file, M_NS, &main, proj_scope, "npm_package.main_ref"); + add_edge(graph, pkg_def, main_ref, 0); + } // dependencies (package references) for (i, (pkg_name, _)) in npm_pkg.dependencies.iter().enumerate() { @@ -113,7 +127,10 @@ impl FileAnalyzer for NpmPackageAnalyzer { #[derive(Default, Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NpmPackage { + #[serde(default)] pub name: String, #[serde(default)] + pub main: String, + #[serde(default)] pub dependencies: HashMap, } diff --git a/languages/tree-sitter-stack-graphs-typescript/rust/test.rs b/languages/tree-sitter-stack-graphs-typescript/rust/test.rs index ca5b8494c..51ef9cdca 100644 --- a/languages/tree-sitter-stack-graphs-typescript/rust/test.rs +++ b/languages/tree-sitter-stack-graphs-typescript/rust/test.rs @@ -11,14 +11,18 @@ use tree_sitter_stack_graphs::ci::Tester; use tree_sitter_stack_graphs::NoCancellation; fn main() -> anyhow::Result<()> { - let lc = match tree_sitter_stack_graphs_typescript::try_language_configuration(&NoCancellation) - { - Ok(lc) => lc, - Err(err) => { - eprintln!("{}", err.display_pretty()); - return Err(anyhow!("Language configuration error")); - } - }; + let lc_factories = [ + tree_sitter_stack_graphs_typescript::try_language_configuration_typescript, + tree_sitter_stack_graphs_typescript::try_language_configuration_tsx, + ]; + + let lcs = lc_factories + .iter() + .map(|lc_factory| lc_factory(&NoCancellation)) + .collect::, _>>() + .inspect_err(|err| eprintln!("{}", err.display_pretty())) + .map_err(|_| anyhow!("Language configuration error"))?; + let test_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test"); - Tester::new(vec![lc], vec![test_path]).run() + Tester::new(lcs, vec![test_path]).run() } diff --git a/languages/tree-sitter-stack-graphs-typescript/rust/tsconfig.rs b/languages/tree-sitter-stack-graphs-typescript/rust/tsconfig.rs index c885c298f..5c9ecae28 100644 --- a/languages/tree-sitter-stack-graphs-typescript/rust/tsconfig.rs +++ b/languages/tree-sitter-stack-graphs-typescript/rust/tsconfig.rs @@ -498,15 +498,15 @@ fn longest_common_prefix(left: &Path, right: &Path) -> Option { Some(prefix) } -struct NormalizedRelativePath(PathBuf); +pub(crate) struct NormalizedRelativePath(PathBuf); impl NormalizedRelativePath { - pub(self) fn from_str(path: &str) -> Option { + pub(crate) fn from_str(path: &str) -> Option { Self::from_path(Path::new(path)) } /// Creates a new normalized, relative path from a path. - pub(self) fn from_path(path: &Path) -> Option { + pub(crate) fn from_path(path: &Path) -> Option { let mut np = PathBuf::new(); let mut normal_components = 0usize; for c in path.components() { @@ -538,18 +538,18 @@ impl NormalizedRelativePath { } /// Returns if the relative path escapes to the parent. - pub(self) fn escapes(&self) -> bool { + pub(crate) fn escapes(&self) -> bool { self.0 .components() .next() .map_or(false, |c| c == Component::ParentDir) } - pub(self) fn as_path(&self) -> &Path { + pub(crate) fn as_path(&self) -> &Path { &self.0 } - pub(self) fn into_path_buf(self) -> PathBuf { + pub(crate) fn into_path_buf(self) -> PathBuf { self.0 } } diff --git a/languages/tree-sitter-stack-graphs-typescript/src/stack-graphs.tsg b/languages/tree-sitter-stack-graphs-typescript/src/stack-graphs.tsg index 0a2a0d0b5..773abde7c 100644 --- a/languages/tree-sitter-stack-graphs-typescript/src/stack-graphs.tsg +++ b/languages/tree-sitter-stack-graphs-typescript/src/stack-graphs.tsg @@ -540,7 +540,8 @@ attribute node_symbol = node => symbol = (source-text node), source_n ; module reference var mod_scope = mod_ref__ns - scan (path-normalize mod_path) { + ; normalize path and remove the extension as we want to match 'foo', 'foo.js', 'foo.ts', etc. + scan (path-normalize (replace mod_path "\.(js|ts|jsx|tsx)$" "")) { "([^/]+)/?" { node mod_ref attr (mod_ref) push_symbol = $1 @@ -626,16 +627,6 @@ attribute node_symbol = node => symbol = (source-text node), source_n (import_statement "type"?@is_type (import_clause (named_imports (import_specifier name:(_)@name))@clause)) ] { if none @is_type { - node @name.expr_def - node @name.expr_def__ns - node @name.expr_ref - node @name.expr_ref__ns - - ; expr reference - attr (@name.expr_ref) node_reference = @name - edge @name.expr_ref -> @name.expr_ref__ns - ; - attr (@name.expr_ref__ns) push_symbol = "%E" edge @name.expr_ref__ns -> @clause.lexical_scope } } @@ -645,6 +636,9 @@ if none @is_type { (import_statement "type"?@is_type (import_clause (named_imports (import_specifier name:(_)@name !alias))@clause)) ] { if none @is_type { + node @name.expr_def + node @name.expr_def__ns + ; expr definition edge @clause.defs -> @name.expr_def__ns ; FIXME defs, lexical_defs? ; @@ -663,7 +657,6 @@ if none @is_type { if none @is_type { node @alias.expr_def node @alias.expr_def__ns - node @alias.expr_ref ; expr definition edge @clause.defs -> @alias.expr_def__ns @@ -687,8 +680,25 @@ if none @is_type { (export_statement "*" source:(_)@name )@export_stmt { - ; export everything from source module - edge @export_stmt.exports -> @name.mod_ref + node expr_pop + node expr_push + attr (expr_pop) pop_symbol = "%E" + attr (expr_push) push_symbol = "%E" + edge expr_pop -> expr_push + + node type_pop + node type_push + attr (type_pop) pop_symbol = "%E" + attr (type_push) push_symbol = "%E" + edge type_pop -> type_push + + ; export all expressions from source module + edge @export_stmt.exports -> expr_pop + edge expr_push -> @name.mod_ref + + ; export all types from source module + edge @export_stmt.exports -> type_pop + edge type_push -> @name.mod_ref } ; export CLAUSE from MODULE @@ -906,18 +916,38 @@ if none @is_default { ; ) (export_statement - (namespace_export (identifier)) - source:(_)@name + (namespace_export (identifier)@name) + source:(_)@from )@export_stmt { ; namespace definitions are specified together with (module) and (internal_module) - ; export definitions - edge @export_stmt.exports -> @name.expr_def__ns - edge @export_stmt.exports -> @name.type_def__ns + ; export expression definition + node expr_ns_pop + attr (expr_ns_pop) pop_symbol = "%E" + ; + edge @export_stmt.exports -> expr_ns_pop + edge expr_ns_pop -> @name.expr_def - ; connect definitions to exports - edge @export_stmt.expr_member__ns -> @name.mod_ref - edge @export_stmt.type_member__ns -> @name.mod_ref + ; export type definition + node type_ns_pop + attr (type_ns_pop) pop_symbol = "%T" + ; + edge @export_stmt.exports -> type_ns_pop + edge type_ns_pop -> @name.type_def + + ; connect expression definition to exports + node expr_ns_push + attr (expr_ns_push) push_symbol = "%E" + ; + edge @name.expr_def_member -> expr_ns_push + edge expr_ns_push -> @from.mod_ref + + ; connect type definition to exports + node type_ns_push + attr (type_ns_push) push_symbol = "%T" + ; + edge @name.type_def_member -> type_ns_push + edge type_ns_push -> @from.mod_ref } ; export as namespace NAME @@ -930,16 +960,36 @@ if none @is_default { (program (export_statement "as" "namespace" . (_)@name -)@export_stmt)@prog { +))@prog { ; namespace definitions are specified together with (module) and (internal_module) - ; make definitions global - edge @prog.globals -> @name.expr_def__ns - edge @prog.globals -> @name.type_def__ns + ; make expression definition global + node expr_ns_pop + attr (expr_ns_pop) pop_symbol = "%E" + ; + edge @prog.globals -> expr_ns_pop + edge expr_ns_pop -> @name.expr_def + + ; make type definition global + node type_ns_pop + attr (type_ns_pop) pop_symbol = "%T" + ; + edge @prog.globals -> type_ns_pop + edge type_ns_pop -> @name.type_def + + ; connect expression definition to exports + node expr_ns_push + attr (expr_ns_push) push_symbol = "%E" + ; + edge @name.expr_def_member -> expr_ns_push + edge expr_ns_push -> @prog.exports - ; connect definitions to exports - edge @export_stmt.expr_member__ns -> @prog.exports - edge @export_stmt.type_member__ns -> @prog.exports + ; connect type definition to exports + node type_ns_push + attr (type_ns_push) push_symbol = "%T" + ; + edge @name.type_def_member -> type_ns_push + edge type_ns_push -> @prog.exports } ;; Imports @@ -1068,14 +1118,38 @@ if none @is_type { (import_statement "type"? - (import_clause (namespace_import (identifier))) + (import_clause (namespace_import (identifier)@name)) source:(_)@from )@import_stmt { ; namespace definitions are specified together with (module) and (internal_module) - ; connect definitions to import - edge @import_stmt.expr_member__ns -> @from.mod_ref - edge @import_stmt.type_member__ns -> @from.mod_ref + ; expose expression definition + node expr_ns_pop + attr (expr_ns_pop) pop_symbol = "%E" + ; + edge @import_stmt.lexical_defs -> expr_ns_pop + edge expr_ns_pop -> @name.expr_def + + ; expose type definition + node type_ns_pop + attr (type_ns_pop) pop_symbol = "%T" + ; + edge @import_stmt.lexical_defs -> type_ns_pop + edge type_ns_pop -> @name.type_def + + ; connect expression definition to import + node expr_ns_push + attr (expr_ns_push) push_symbol = "%E" + ; + edge @name.expr_def_member -> expr_ns_push + edge expr_ns_push -> @from.mod_ref + + ; connect type definition to import + node type_ns_push + attr (type_ns_push) push_symbol = "%T" + ; + edge @name.type_def_member -> type_ns_push + edge type_ns_push -> @from.mod_ref } ;; Import Alias @@ -1259,7 +1333,7 @@ if none @is_type { attr (@name.expr_def__ns) pop_symbol = "%E" edge @name.expr_def__ns -> @name.expr_def ; - attr (@name.expr_def) node_definition = @name + attr (@name.expr_def) node_definition = @name, syntax_type = "function" ; function type edge @name.expr_def -> @name.expr_def__typeof @@ -1387,6 +1461,8 @@ if none @is_async { edge @dec.lexical_scope -> @class_decl.lexical_scope } + + ; definitions [ (abstract_class_declaration name:(_)@name) @@ -1413,10 +1489,17 @@ if none @is_async { edge @class_decl.type_export -> @class_decl.generic_type } [ - (abstract_class_declaration name:(_)) - (class_declaration name:(_)) + (interface_declaration name:(_)@name) +] { + attr (@name.type_def) syntax_type = "interface" +} +[ + (abstract_class_declaration name:(_)@name) + (class_declaration name:(_)@name) ; NOTE not for interface_declaration as body is already a type with an endpoint of needed ]@class_decl { + attr (@name.type_def) syntax_type = "class" + ; mark type scope as endpoint attr (@class_decl.generic_inner_type) is_endpoint } @@ -1432,7 +1515,7 @@ if none @is_async { attr (@name.expr_def__ns) pop_symbol = "%E" edge @name.expr_def__ns -> @name.expr_def ; - attr (@name.expr_def) node_definition = @name + attr (@name.expr_def) node_definition = @name, syntax_type = "class" ; type of expression is .static_type edge @name.expr_def -> @name.expr_def__typeof @@ -1537,6 +1620,7 @@ if none @is_async { [ (abstract_class_declaration body:(_)@body) (class_declaration body:(_)@body) + (interface_declaration body:(_)@body) ]@class_decl { ; propagate lexical scope ; FIXME the static members have access to type variables like this @@ -1550,16 +1634,6 @@ if none @is_async { edge @class_decl.static_type -> @body.static_members attr (@class_decl.static_type -> @body.static_members) precedence = 2 } -[ - (interface_declaration body:(_)@body) -]@class_decl { - ; propagate lexical scope - edge @body.lexical_scope -> @class_decl.generic_inner_lexical_scope - - ; interface type equals body type - edge @class_decl.generic_inner_type -> @body.type -} - ;; Class Body @@ -1573,23 +1647,35 @@ if none @is_async { ; (public_field_definition)] ; ) -(class_body)@class_body { - node @class_body.lexical_scope - node @class_body.type_members - node @class_body.static_members +[ + (class_body) + (interface_body) +]@body { + node @body.lexical_scope + node @body.type_members + node @body.static_members +} + +; decorators +[ + (class_body decorator:(_)@dec) +]@class_body { + ; connect lexical scope + edge @dec.lexical_scope -> @class_body.lexical_scope } -(class_body - (_)@mem -)@class_body { +[ + (class_body (_)@mem) + (interface_body (_)@mem) +]@body { ; propagate lexical scope - edge @mem.lexical_scope -> @class_body.lexical_scope + edge @mem.lexical_scope -> @body.lexical_scope ; body members are member definitions - edge @class_body.type_members -> @mem.type_members + edge @body.type_members -> @mem.type_members ; body static members are static member definitions - edge @class_body.static_members -> @mem.static_members + edge @body.static_members -> @mem.static_members } @@ -2238,147 +2324,104 @@ if none @is_async { [ ; X - (module name:(identifier)@name @mod) - (internal_module name:(identifier)@name @mod) - (export_statement "as" "namespace" . (identifier)@name) @mod - (export_statement (namespace_export (identifier)@name))@mod - (import_statement (import_clause (namespace_import (identifier)@name)))@mod - (ambient_declaration (module name:(string)@name @mod)) + (module name:(identifier)@name) + (internal_module name:(identifier)@name) + (export_statement "as" "namespace" . (identifier)@name) + (export_statement (namespace_export (identifier)@name)) + (import_statement (import_clause (namespace_import (identifier)@name))) + (ambient_declaration (module name:(string) @name)) ; X._ - (module name:(nested_identifier . (identifier)@name @mod)) - (internal_module name:(nested_identifier . (identifier)@name @mod)) - ; X._._ - (module name:(nested_identifier . (nested_identifier . (identifier)@name @mod))) - (internal_module name:(nested_identifier . (nested_identifier . (identifier)@name @mod))) - ; X._._._ - (module name:(nested_identifier . (nested_identifier . (nested_identifier . (identifier)@name @mod)))) - (internal_module name:(nested_identifier . (nested_identifier . (nested_identifier . (identifier)@name @mod)))) - ; X._._._._ - (module name:(nested_identifier . (nested_identifier . (nested_identifier . (nested_identifier . (identifier)@name @mod))))) - (internal_module name:(nested_identifier . (nested_identifier . (nested_identifier . (nested_identifier . (identifier)@name @mod))))) -]@mod_decl { + (nested_identifier object:(identifier)@name) + ; _.X._ + (member_expression object:(identifier)@name) + ; _.X + (nested_identifier property:(_)@name) + ; _._.X + (member_expression property:(_)@name) +] { node @name.expr_def - node @name.expr_def__ns - node @name.expr_def__typeof - node @mod.expr_member - node @mod.expr_member__ns + node expr_def_typeof + node @name.expr_def_member node @name.type_def - node @name.type_def__ns - node @mod.type_member - node @mod.type_member__ns - - ; expose expression definition - edge @mod_decl.lexical_defs -> @name.expr_def__ns + node @name.type_def_member ; expression definition - attr (@name.expr_def__ns) pop_symbol = "%E" - edge @name.expr_def__ns -> @name.expr_def + attr (@name.expr_def) node_definition = @name, syntax_type = "module" + attr (expr_def_typeof) pop_symbol = ":" + attr (@name.expr_def_member) pop_symbol = "." ; - attr (@name.expr_def) node_definition = @name - - ; namespaces mimic objects with members to access exported expressions - edge @name.expr_def -> @name.expr_def__typeof - ; - attr (@name.expr_def__typeof) pop_symbol = ":" - edge @name.expr_def__typeof -> @mod.expr_member - ; - attr (@mod.expr_member) pop_symbol = "." - edge @mod.expr_member -> @mod.expr_member__ns - ; - attr (@mod.expr_member__ns) push_symbol = "%E" - - ; expose type definition - edge @mod_decl.lexical_defs -> @name.type_def__ns + edge @name.expr_def -> expr_def_typeof + edge expr_def_typeof -> @name.expr_def_member ; type definition - attr (@name.type_def__ns) pop_symbol = "%T" - edge @name.type_def__ns -> @name.type_def - ; - attr (@name.type_def) node_definition = @name - - ; namespaces mimic types with member types to access exported expressions - edge @name.type_def -> @mod.type_member + attr (@name.type_def) node_definition = @name, syntax_type = "module" + attr (@name.type_def_member) pop_symbol = "." ; - attr (@mod.type_member) pop_symbol = "." - edge @mod.type_member -> @mod.type_member__ns - ; - attr (@mod.type_member__ns) push_symbol = "%T" + edge @name.type_def -> @name.type_def_member } [ - ; _.X - (module name:(nested_identifier . (_)@basemod . (identifier)@name)@mod) - (internal_module name:(nested_identifier . (_)@basemod . (identifier)@name)@mod) - ; _._.X - (module name:(nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name)@mod)) - (internal_module name:(nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name)@mod)) - ; _._._.X - (module name:(nested_identifier . (nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name)@mod))) - (internal_module name:(nested_identifier . (nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name)@mod))) - ; _._._._.X - (module name:(nested_identifier . (nested_identifier . (nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name)@mod)))) - (internal_module name:(nested_identifier . (nested_identifier . (nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name)@mod)))) -] { - node @name.expr_def - node @name.expr_def__ns - node @name.expr_def__typeof - node @mod.expr_member - node @mod.expr_member__ns - node @name.type_def - node @name.type_def__ns - node @mod.type_member - node @mod.type_member__ns - - ; expose expression definition - edge @basemod.expr_member__ns -> @name.expr_def__ns - - ; expression definition - attr (@name.expr_def__ns) pop_symbol = "%E" - edge @name.expr_def__ns -> @name.expr_def - ; - attr (@name.expr_def) node_definition = @name + (nested_identifier object:(_)@mod) + (member_expression object:[(member_expression) (identifier)]@mod) +]@nested { + node @nested.expr_def + node @nested.type_def - ; namespaces mimic objects with members to access exported expressions - edge @name.expr_def -> @name.expr_def__typeof - ; - attr (@name.expr_def__typeof) pop_symbol = ":" - edge @name.expr_def__typeof -> @mod.expr_member - ; - attr (@mod.expr_member) pop_symbol = "." - edge @mod.expr_member -> @mod.expr_member__ns - ; - attr (@mod.expr_member__ns) push_symbol = "%E" + edge @nested.expr_def -> @mod.expr_def + edge @nested.type_def -> @mod.type_def +} - ; expose type definition - edge @basemod.type_member__ns -> @name.type_def__ns +[ + (nested_identifier object:(_)@mod property:(_)@name) + (member_expression object:[(member_expression) (identifier)]@mod property:(_)@name) +] { + edge @mod.expr_def_member -> @name.expr_def + edge @mod.type_def_member -> @name.type_def +} - ; type definition - attr (@name.type_def__ns) pop_symbol = "%T" - edge @name.type_def__ns -> @name.type_def - ; - attr (@name.type_def) node_definition = @name +[ + (nested_identifier property:(_)@name) + (member_expression property:(_)@name) +]@nested { + node @nested.expr_def_member + node @nested.type_def_member - ; namespaces mimic types with member types to access exported expressions - edge @name.type_def -> @mod.type_member - ; - attr (@mod.type_member) pop_symbol = "." - edge @mod.type_member -> @mod.type_member__ns - ; - attr (@mod.type_member__ns) push_symbol = "%T" + edge @name.expr_def_member -> @nested.expr_def_member + edge @name.type_def_member -> @nested.type_def_member } [ - (module name:(_)@mod body:(_)@body) - (internal_module name:(_)@mod body:(_)@body) + (module name:(_)@mod body:(_)@body) + (internal_module name:(_)@mod body:(_)@body) ]@mod_decl { ; propagate lexical scope edge @body.lexical_scope -> @mod_decl.lexical_scope - ; connect object to exports - edge @mod.expr_member__ns -> @body.exports + ; expose expression definition + node expr_ns_pop + attr (expr_ns_pop) pop_symbol = "%E" + ; + edge @mod_decl.lexical_defs -> expr_ns_pop + edge expr_ns_pop -> @mod.expr_def + + ; expose type definition + node type_ns_pop + attr (type_ns_pop) pop_symbol = "%T" + ; + edge @mod_decl.lexical_defs -> type_ns_pop + edge type_ns_pop -> @mod.type_def + + ; connect expression definition to exports + node expr_ns_push + attr (expr_ns_push) push_symbol = "%E" + edge @mod.expr_def_member -> expr_ns_push + edge expr_ns_push -> @body.exports - ; connect type to exports - edge @mod.type_member__ns -> @body.exports + ; connect type definition to exports + node type_ns_push + attr (type_ns_push) push_symbol = "%T" + edge @mod.type_def_member -> type_ns_push + edge type_ns_push -> @body.exports } ; NOTE internal_module is also an expression in the grammar, not only a statement. @@ -2455,7 +2498,7 @@ if none @is_async { attr (@name.expr_def__ns) pop_symbol = "%E" edge @name.expr_def__ns -> @name.expr_def ; - attr (@name.expr_def) node_definition = @name + attr (@name.expr_def) node_definition = @name, syntax_type = "type" ; type of enum expression is members edge @name.expr_def -> @name.expr_def__typeof @@ -2477,7 +2520,7 @@ if none @is_async { attr (@name.type_def__ns) pop_symbol = "%T" edge @name.type_def__ns -> @name.type_def ; - attr (@name.type_def) node_definition = @name + attr (@name.type_def) node_definition = @name, syntax_type = "type" edge @name.type_def -> @enum_decl.type ; edge @enum_decl.type -> @body.type_members @@ -2567,7 +2610,7 @@ if none @is_async { attr (@name.type_def__ns) pop_symbol = "%T" edge @name.type_def__ns -> @name.type_def ; - attr (@name.type_def) node_definition = @name + attr (@name.type_def) node_definition = @name, syntax_type = "type" edge @name.type_def -> @decl.generic_type ; ; type parameters are handled by generics rules below @@ -2613,7 +2656,7 @@ if none @is_async { (call_expression) (class) (false) - (function) + (function_expression) (generator_function) (import) (member_expression) @@ -2625,6 +2668,7 @@ if none @is_async { (object) (parenthesized_expression) (regex) + (satisfies_expression) (sequence_expression) (spread_element) (string) @@ -2634,7 +2678,16 @@ if none @is_async { (ternary_expression) (this) (true) +; #dialect typescript (type_assertion) +; #end +; #dialect tsx + (jsx_element) + (jsx_self_closing_element) + (jsx_opening_element) + (jsx_closing_element) + (jsx_expression) +; #end (unary_expression) (undefined) (update_expression) @@ -2736,91 +2789,13 @@ if none @is_async { ; x; -[ - (primary_expression/identifier)@name - ; FIXME expansion of _lhs_expression/identifier and _augmented_assignment_lhs - (for_in_statement ["var" "let" "const"]?@is_def left:(identifier)@name) - (assignment_expression left:(identifier)@name) - (augmented_assignment_expression left:(identifier)@name) - (asserts (identifier)@name) - (type_predicate name:(identifier)@name) - ; FIXME type_query has its own restricted expression production - ; we need to do this for every (identifier) inside a type query - ; this cannot be expressed, so we manually unroll three levels here - (type_query (identifier)@name) - (type_query (member_expression object:(identifier)@name)) - (type_query (member_expression object:(member_expression object:(identifier)@name))) - (type_query (member_expression object:(member_expression object:(member_expression object:(identifier)@name)))) - (type_query (subscript_expression object:(identifier)@name)) - (type_query (subscript_expression object:(member_expression object:(identifier)@name))) - (type_query (subscript_expression object:(member_expression object:(member_expression object:(identifier)@name)))) - (type_query (call_expression function:(identifier)@name)) - (type_query (call_expression function:(member_expression object:(identifier)@name))) - (type_query (call_expression function:(member_expression object:(member_expression object:(identifier)@name)))) - (type_query (call_expression function:(member_expression object:(member_expression object:(member_expression object:(identifier)@name))))) - (type_query (call_expression function:(subscript_expression object:(identifier)@name))) - (type_query (call_expression function:(subscript_expression object:(member_expression object:(identifier)@name)))) - (type_query (call_expression function:(subscript_expression object:(member_expression object:(member_expression object:(identifier)@name))))) - ; FIXME decorator has its own restricted expression production - ; we need to do this for every (identifier) inside a decorator - ; this cannot be expressed, so we manually unroll three levels here - (decorator (identifier)@name) - (decorator (member_expression object:(identifier)@name)) - (decorator (member_expression object:(member_expression object:(identifier)@name))) - (decorator (member_expression object:(member_expression object:(member_expression object:(identifier)@name)))) - (decorator (call_expression function:(identifier)@name)) - (decorator (call_expression function:(member_expression object:(identifier)@name))) - (decorator (call_expression function:(member_expression object:(member_expression object:(identifier)@name)))) - (decorator (call_expression function:(member_expression object:(member_expression object:(member_expression object:(identifier)@name))))) -] { -if none @is_def { +(identifier)@name { node @name.cotype node @name.lexical_defs node @name.lexical_scope node @name.type node @name.var_defs -} -} -[ - (primary_expression/identifier)@name - (decorator (identifier)@name) - ; FIXME expansion of _lhs_expression/identifier and _augmented_assignment_lhs - ; we need to do this for every (identifier) inside a type query - ; this cannot be expressed, so we manually unroll three levels here - (for_in_statement ["var" "let" "const"]?@is_def left:(identifier)@name) - (assignment_expression left:(identifier)@name) - (augmented_assignment_expression left:(identifier)@name) - (asserts (identifier)@name) - (type_predicate name:(identifier)@name) - ; FIXME type_query has its own restricted expression production - (type_query (identifier)@name) - (type_query (member_expression object:(identifier)@name)) - (type_query (member_expression object:(member_expression object:(identifier)@name))) - (type_query (member_expression object:(member_expression object:(member_expression object:(identifier)@name)))) - (type_query (subscript_expression object:(identifier)@name)) - (type_query (subscript_expression object:(member_expression object:(identifier)@name))) - (type_query (subscript_expression object:(member_expression object:(member_expression object:(identifier)@name)))) - (type_query (call_expression function:(identifier)@name)) - (type_query (call_expression function:(member_expression object:(identifier)@name))) - (type_query (call_expression function:(member_expression object:(member_expression object:(identifier)@name)))) - (type_query (call_expression function:(member_expression object:(member_expression object:(member_expression object:(identifier)@name))))) - (type_query (call_expression function:(subscript_expression object:(identifier)@name))) - (type_query (call_expression function:(subscript_expression object:(member_expression object:(identifier)@name)))) - (type_query (call_expression function:(subscript_expression object:(member_expression object:(member_expression object:(identifier)@name))))) - ; FIXME decorator has its own restricted expression production - ; we need to do this for every (identifier) inside a decorator - ; this cannot be expressed, so we manually unroll three levels here - (decorator (identifier)@name) - (decorator (member_expression object:(identifier)@name)) - (decorator (member_expression object:(member_expression object:(identifier)@name))) - (decorator (member_expression object:(member_expression object:(member_expression object:(identifier)@name)))) - (decorator (call_expression function:(identifier)@name)) - (decorator (call_expression function:(member_expression object:(identifier)@name))) - (decorator (call_expression function:(member_expression object:(member_expression object:(identifier)@name)))) - (decorator (call_expression function:(member_expression object:(member_expression object:(member_expression object:(identifier)@name))))) -] { -if none @is_def { node @name.expr_ref node @name.expr_ref__ns node @name.expr_ref__typeof @@ -2838,7 +2813,67 @@ if none @is_def { attr (@name.expr_ref__typeof) push_symbol = ":" edge @name.expr_ref__typeof -> @name.expr_ref } -} + +; [ +; (primary_expression/identifier)@name +; ; FIXME expansion of _lhs_expression/identifier and _augmented_assignment_lhs +; ; we need to do this for every (identifier) inside a type query +; ; this cannot be expressed, so we manually unroll three levels here +; (for_in_statement ["var" "let" "const"]?@is_def left:(identifier)@name) +; (assignment_expression left:(identifier)@name) +; (augmented_assignment_expression left:(identifier)@name) +; (asserts (identifier)@name) +; (type_predicate name:(identifier)@name) +; ; FIXME type_query has its own restricted expression production +; ; we need to do this for every (identifier) inside a type query +; ; this cannot be expressed, so we manually unroll three levels here +; (type_query (identifier)@name) +; (type_query (member_expression object:(identifier)@name)) +; (type_query (member_expression object:(member_expression object:(identifier)@name))) +; (type_query (member_expression object:(member_expression object:(member_expression object:(identifier)@name)))) +; (type_query (subscript_expression object:(identifier)@name)) +; (type_query (subscript_expression object:(member_expression object:(identifier)@name))) +; (type_query (subscript_expression object:(member_expression object:(member_expression object:(identifier)@name)))) +; (type_query (call_expression function:(identifier)@name)) +; (type_query (call_expression function:(member_expression object:(identifier)@name))) +; (type_query (call_expression function:(member_expression object:(member_expression object:(identifier)@name)))) +; (type_query (call_expression function:(member_expression object:(member_expression object:(member_expression object:(identifier)@name))))) +; (type_query (call_expression function:(subscript_expression object:(identifier)@name))) +; (type_query (call_expression function:(subscript_expression object:(member_expression object:(identifier)@name)))) +; (type_query (call_expression function:(subscript_expression object:(member_expression object:(member_expression object:(identifier)@name))))) +; ; FIXME decorator has its own restricted expression production +; ; we need to do this for every (identifier) inside a decorator +; ; this cannot be expressed, so we manually unroll three levels here +; (decorator (identifier)@name) +; (decorator (member_expression object:(identifier)@name)) +; (decorator (member_expression object:(member_expression object:(identifier)@name))) +; (decorator (member_expression object:(member_expression object:(member_expression object:(identifier)@name)))) +; (decorator (call_expression function:(identifier)@name)) +; (decorator (call_expression function:(member_expression object:(identifier)@name))) +; (decorator (call_expression function:(member_expression object:(member_expression object:(identifier)@name)))) +; (decorator (call_expression function:(member_expression object:(member_expression object:(member_expression object:(identifier)@name))))) +; ; FIXME nested_identifier has its own restricted expression production +; ; we need to do this for every (identifier) inside a decorator +; ; this cannot be expressed, so we manually unroll three levels here +; (nested_identifier object:(identifier)@name) +; (nested_identifier object:(member_expression object:(identifier)@name)) +; (nested_identifier object:(member_expression object:(member_expression object:(identifier)@name))) +; (nested_identifier object:(member_expression object:(member_expression object:(member_expression object:(identifier)@name)))) +; ; #dialect tsx +; (jsx_opening_element name: (identifier)@name) +; (jsx_closing_element name: (identifier)@name) +; (jsx_self_closing_element name: (identifier)@name) +; ; #end +; ] { +; if none @is_def { +; ; node @name.cotype +; ; node @name.lexical_defs +; ; node @name.lexical_scope +; ; node @name.type +; ; node @name.var_defs + +; } +; } @@ -2937,6 +2972,21 @@ if none @is_def { +;; Satisfies Expressions + +; { foo: 42 } satisfies { foo: number } + +(satisfies_expression (_)@expr . (_)@type)@satisfies { + ; propagate lexical scope + edge @expr.lexical_scope -> @satisfies.lexical_scope + edge @type.lexical_scope -> @satisfies.lexical_scope + + ; type is union of both + edge @satisfies.type -> @expr.type + edge @satisfies.type -> @type.type +} + + ;; Spread Element (spread_element (_)@expr)@spread_elem { @@ -3188,20 +3238,20 @@ if none @is_def { ; function (x) {}; -; (function +; (function_expression ; (formal_parameters (identifier)) ; (statement_block)) ; this captures the parameters -; (function +; (function_expression ; parameters: (_)@params) ; this captures the body -; (function +; (function_expression ; body:(_)@body)@function ; functions with names -; (function +; (function_expression ; name:(_)@name ; parameters:(_)@call_sig)@fun { ; } @@ -3236,7 +3286,7 @@ if none @is_def { ; } [ - (function) + (function_expression) (arrow_function) (generator_function) ]@fun { @@ -3263,9 +3313,9 @@ if none @is_def { } [ - (function parameters:(_)@call_sig) - (arrow_function parameters:(_)@call_sig) - (generator_function parameters:(_)@call_sig) + (function_expression parameters:(_)@call_sig) + (arrow_function parameters:(_)@call_sig) + (generator_function parameters:(_)@call_sig) ]@fun { ; propagate lexical scope edge @call_sig.lexical_scope -> @fun.lexical_scope @@ -3314,9 +3364,9 @@ if none @is_def { } [ - (function body:(_)@body) - (arrow_function body:(_)@body) - (generator_function body:(_)@body) + (function_expression body:(_)@body) + (arrow_function body:(_)@body) + (generator_function body:(_)@body) ]@fun { ; propagate lexical scope edge @body.lexical_scope -> @fun.lexical_scope @@ -3325,8 +3375,8 @@ if none @is_def { ;;;; specified return type [ - (function return_type:(_)@return_type) - (arrow_function return_type:(_)@return_type) + (function_expression return_type:(_)@return_type) + (arrow_function return_type:(_)@return_type) ]@fun { ; propagate lexical scope edge @return_type.lexical_scope -> @fun.lexical_scope @@ -3338,8 +3388,8 @@ if none @is_def { ;;;; inferred return type [ - (function "async"?@is_async !return_type body:(_)@body) - (arrow_function "async"?@is_async !return_type body:(statement_block)@body) + (function_expression "async"?@is_async !return_type body:(_)@body) + (arrow_function "async"?@is_async !return_type body:(statement_block)@body) ]@fun { if none @is_async { ; callable is type of return statement @@ -3357,8 +3407,8 @@ if none @is_async { ;;;; inferred async return type [ - (function "async" !return_type body:(_)@body) - (arrow_function "async" body:(statement_block)@body) + (function_expression "async" !return_type body:(_)@body) + (arrow_function "async" body:(statement_block)@body) ]@fun_decl { ; function returns body return type edge @fun_decl.callable__return -> @fun_decl.async_type @@ -3477,8 +3527,8 @@ if none @is_async { ; (subscript_expression (identifier) (string)) (member_expression - object: (_)@object - property: (_)@prop + object:(_)@object + property:(_)@prop )@member_expr { node @member_expr.member node @prop.expr_ref @@ -3501,7 +3551,6 @@ if none @is_async { edge @prop.expr_ref__typeof -> @prop.expr_ref } - (subscript_expression object: (_)@object index: (_)@index @@ -3807,15 +3856,13 @@ if none @is_async { ; (sequence_expression (number) (number)) -(sequence_expression - left: (_)@left - right: (_)@right -)@sequence_expr { +(sequence_expression (_)@expr)@sequence_expr { ; propagate lexical scope - edge @left.lexical_scope -> @sequence_expr.lexical_scope - edge @right.lexical_scope -> @sequence_expr.lexical_scope + edge @expr.lexical_scope -> @sequence_expr.lexical_scope +} - ; FIXME @sequence_expr.type is type of last, but cannot express because of nesting +(sequence_expression (_)@last .)@sequence_expr { + edge @sequence_expr.type -> @last.type } @@ -3957,6 +4004,12 @@ if none @is_async { edge @class_expr.this__type_def -> @class_expr.generic_inner_type } +; decorators +(class decorator:(_)@dec)@class_expr { + ; connect lexical scope + edge @dec.lexical_scope -> @class_expr.lexical_scope +} + ; default constructor ; FIXME only if no other constructor is defined (class)@class_expr { @@ -3994,7 +4047,7 @@ if none @is_async { } - +; #dialect typescript ;; Type Assertion ; (type_assertion @@ -4009,7 +4062,7 @@ if none @is_async { ; propagate lexical scope edge @expr.lexical_scope -> @type_assert.lexical_scope } - +; #end ;; As Expression @@ -4072,25 +4125,16 @@ if none @is_async { (this) (member_expression) (subscript_expression) + (undefined) ]@pat { node @pat.defs } -[ ; NOTE these are the ones not also variables - (for_in_statement ["var" "let" "const"] left:(identifier)@name) - (variable_declarator name:(identifier)@name) - (pattern/identifier)@name - (rest_pattern (_)@name) -] { - node @name.cotype - node @name.lexical_scope -} - [ (for_in_statement ["var" "let" "const"] left:(identifier)@name) (variable_declarator name:(identifier)@name) (pattern/identifier)@name - (rest_pattern (_)@name) + (rest_pattern (identifier)@name) ] { node @name.defs node @name.expr_def @@ -4301,6 +4345,7 @@ if none @is_async { [ (abstract_method_signature) (call_signature) + (class_static_block) (construct_signature) (decorator) ; not strictly a member, but appears in same positions (index_signature) @@ -4324,6 +4369,14 @@ if none @is_async { +;; Static Block + +(class_static_block body:(_)@body)@static { + edge @body.lexical_scope -> @static.lexical_scope +} + + + ;; Construct Signature ; (construct_signature @@ -4451,7 +4504,7 @@ if none @is_async { attr (@sig.member) pop_symbol = "." edge @sig.member -> @name.expr_def ; - attr (@name.expr_def) node_definition = @name + attr (@name.expr_def) node_definition = @name, syntax_type = "field" } (property_signature @@ -4478,6 +4531,12 @@ if none @is_async { ; value:(_)@value ; opt ; )@def +; decorators +(public_field_definition decorator:(_)@dec)@def { + ; connect lexical scope + edge @dec.lexical_scope -> @def.lexical_scope +} + (public_field_definition name:(_)@name )@def { @@ -4489,7 +4548,7 @@ if none @is_async { attr (@def.member) pop_symbol = "." edge @def.member -> @name.expr_def ; - attr (@name.expr_def) node_definition = @name + attr (@name.expr_def) node_definition = @name, syntax_type = "field" } (public_field_definition @@ -4573,7 +4632,7 @@ if none @is_acc { attr (@mem.member) pop_symbol = "." edge @mem.member -> @name.expr_def ; - attr (@name.expr_def) node_definition = @name + attr (@name.expr_def) node_definition = @name, syntax_type = "method" ; type of method is callable generic type edge @name.expr_def -> @name.expr_def__typeof @@ -4658,7 +4717,7 @@ if none @is_acc { attr (@mem.member) pop_symbol = "." edge @mem.member -> @name.expr_def ; - attr (@name.expr_def) node_definition = @name + attr (@name.expr_def) node_definition = @name, syntax_type = "field" } [ @@ -4863,6 +4922,7 @@ if none @is_acc { [ (array_type) (asserts) + (asserts_annotation) (conditional_type) (constraint) (constructor_type) @@ -4978,6 +5038,9 @@ if none @is_acc { edge @type.lexical_scope -> @asserts.lexical_scope } +(asserts_annotation (_)@asserts)@asserts_annotation { + edge @asserts.lexical_scope -> @asserts_annotation.lexical_scope +} ;; Optional Type @@ -5501,70 +5564,59 @@ if none @is_acc { [ ; X - (nested_type_identifier module:(identifier)@name @mod) + (nested_type_identifier module:(identifier)@name) ; X._ - (nested_type_identifier module:(nested_identifier . (identifier)@name @mod)) - ; X._._ - (nested_type_identifier module:(nested_identifier . (nested_identifier . (identifier)@name @mod))) - ; X._._._ - (nested_type_identifier module:(nested_identifier . (nested_identifier . (nested_identifier . (identifier)@name @mod)))) - ; X._._._._ - (nested_type_identifier module:(nested_identifier . (nested_identifier . (nested_identifier . (nested_identifier . (identifier)@name @mod))))) -]@nested_type_id { + (nested_identifier object:(identifier)@name) + ; _.X._ + (member_expression object:(identifier)@name) + ; _.X + (nested_identifier property:(_)@name) + ; _._.X + (member_expression property:(_)@name) +] { node @name.type_ref - node @name.type_ref__ns - node @mod.type - - ; type reference to namespace name - edge @mod.type -> @name.type_ref - ; attr (@name.type_ref) node_reference = @name - edge @name.type_ref -> @name.type_ref__ns - ; - attr (@name.type_ref__ns) push_symbol = "%T" - edge @name.type_ref__ns -> @nested_type_id.lexical_scope + + node @name.type_ref_member + attr (@name.type_ref_member) push_symbol = "." + + edge @name.type_ref_member -> @name.type_ref } [ - ; _.X - (nested_type_identifier module:(nested_identifier . (_)@basemod . (identifier)@name .)@mod) - ; _._.X - (nested_type_identifier module:(nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name .)@mod)) - ; _._._.X - (nested_type_identifier module:(nested_identifier . (nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name .)@mod))) - ; _._._._.X - (nested_type_identifier module:(nested_identifier . (nested_identifier . (nested_identifier . (nested_identifier . (_)@basemod . (identifier)@name .)@mod)))) + (nested_identifier object:(_)@mod) + (member_expression object:[(member_expression) (identifier)]@mod) +]@nested { + node @nested.type_ref + edge @mod.type_ref -> @nested.type_ref +} + +[ + (nested_identifier object:(_)@mod property:(_)@name) + (member_expression object:[(member_expression) (identifier)]@mod property:(_)@name) ] { - node @name.type_ref - node @name.type_ref__ns - node @mod.member - node @mod.type + edge @name.type_ref -> @mod.type_ref_member +} - ; type reference to namespace name - edge @mod.type -> @name.type_ref - ; - attr (@name.type_ref) node_reference = @name - edge @name.type_ref -> @mod.member - ; - attr (@mod.member) push_symbol = "." - edge @mod.member -> @basemod.type +[ + (nested_identifier property:(_)@name) + (member_expression property:(_)@name) +]@nested { + node @nested.type_ref_member + edge @nested.type_ref_member -> @name.type_ref_member } -(nested_type_identifier - module:(_)@mod - name:(_)@name -)@nested_type_id { - node @name.nested_type_ref ; FIXME non-standard name because the general (type_identifier) rule is also applied to @name - node @nested_type_id.member +(nested_type_identifier module:(_)@mod)@nested_type_id { + node type_ns + attr (type_ns) push_symbol = "%T" - ; type reference into namespace type - edge @nested_type_id.type -> @name.nested_type_ref - ; - attr (@name.nested_type_ref) node_reference = @name - edge @name.nested_type_ref -> @nested_type_id.member - ; - attr (@nested_type_id.member) push_symbol = "." - edge @nested_type_id.member -> @mod.type + edge @mod.type_ref -> type_ns + edge type_ns -> @nested_type_id.lexical_scope +} + +(nested_type_identifier module:(_)@mod name:(_)@name)@nested_type_id { + edge @nested_type_id.type -> @name.type_ref + edge @name.type_ref -> @mod.type_ref_member } @@ -6092,7 +6144,7 @@ if none @is_acc { [ (arrow_function "async") - (function "async") + (function_expression "async") (function_declaration "async") (generator_function "async") (generator_function_declaration "async") @@ -6122,9 +6174,124 @@ if none @is_acc { attr (@async.async_type__type_app) push_scoped_symbol = "<>", scope = @async.async_type__type_args edge @async.async_type__type_app -> @async.async_type__promise_ref ; - attr (@async.async_type__promise_ref) symbol_reference = "Promise", source_node = @async + attr (@async.async_type__promise_ref) symbol_reference = "Promise", source_node = @async, empty_source_span edge @async.async_type__promise_ref -> @async.async_type__promise_ref__ns ; attr (@async.async_type__promise_ref__ns) push_symbol = "%T" edge @async.async_type__promise_ref__ns -> @async.lexical_scope } + +; +; # ##### # # +; # # # # # +; # # # # +; # ##### # +; # # # # # +; # # # # # # +; ##### ##### # # +; +; ;;;;;;;;;;;;;;;;;;;; +; #dialect tsx +; ;;;;;;;;;;;;;;;;;;;; + +(jsx_element + open_tag:(_)@open_tag + close_tag:(_)@close_tag)@jsx_element { + + edge @open_tag.lexical_scope -> @jsx_element.lexical_scope + edge @close_tag.lexical_scope -> @jsx_element.lexical_scope +} + +(jsx_element + [ + (jsx_text) + (jsx_element) + (jsx_self_closing_element) + (jsx_expression) + ]@child +)@parent { + edge @child.lexical_scope -> @parent.lexical_scope +} + +(jsx_text)@jsx_text { + node @jsx_text.lexical_scope +} + +[ + (jsx_opening_element name: (_)@name)@element + (jsx_closing_element name: (_)@name)@element + (jsx_self_closing_element name: (_)@name)@element +] { + edge @name.lexical_scope -> @element.lexical_scope +} + +(jsx_opening_element + name:(_)@element_name + attribute:(_)@attr +) { + edge @attr.lexical_scope -> @element_name.lexical_scope +} + +(jsx_attribute (_) . (_)?@attr_value)@jsx_attribute { + node @jsx_attribute.lexical_scope + + if some @attr_value { + edge @attr_value.lexical_scope -> @jsx_attribute.lexical_scope + } +} + +(jsx_namespace_name (_) @lhs (_) @rhs)@name { + node @name.lexical_scope + + edge @lhs.lexical_scope -> @name.lexical_scope + edge @rhs.lexical_scope -> @name.lexical_scope +} + +(jsx_self_closing_element + name:(_)@element_name)@element { + edge @element_name.lexical_scope -> @element.lexical_scope +} + +(jsx_self_closing_element + name:(_)@element_name + attribute:(_)@attr +) { + edge @attr.lexical_scope -> @element_name.lexical_scope +} + +(jsx_expression (_)@child)@expr { + edge @child.lexical_scope -> @expr.lexical_scope +} + +(jsx_closing_element + name:(_)@element_name)@element +{ + edge @element_name.lexical_scope -> @element.lexical_scope +} + +[ + (jsx_opening_element + name:(identifier)@element_name) + (jsx_self_closing_element + name:(identifier)@element_name) + (jsx_closing_element + name:(identifier)@element_name) +] { + scan (source-text @element_name) { + ; standard HTML elements + "^(a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdi|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|input|ins|kbd|label|legend|li|link|main|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|search|section|select|small|source|span|strike|strong|style|sub|summary|sup|svg|table|tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|tt|u|ul|var|video|wbr)$" { + ; do nothing! + } + + ; everything else + "^.+$" { + node element_name_pop + attr (element_name_pop) node_reference = @element_name + edge element_name_pop -> @element_name.lexical_scope + } + } +} + +; ;;;;;;;;;;;;;;;;;;;; +; #end +; ;;;;;;;;;;;;;;;;;;;; diff --git a/languages/tree-sitter-stack-graphs-typescript/test/expressions/rest-array-attern.ts b/languages/tree-sitter-stack-graphs-typescript/test/expressions/rest-array-attern.ts new file mode 100644 index 000000000..7bc38c4a0 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/expressions/rest-array-attern.ts @@ -0,0 +1,6 @@ +function foo(...[bar]) { + bar; +// ^ defined: 1 +} + +export {} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-typescript/test/expressions/satisfies-type.ts b/languages/tree-sitter-stack-graphs-typescript/test/expressions/satisfies-type.ts new file mode 100644 index 000000000..48b3a86f5 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/expressions/satisfies-type.ts @@ -0,0 +1,8 @@ +type Foo = { foo: number }; + +let x = { foo: 42 } satisfies Foo; + + x.foo +// ^ defined: 3, 1 + +export {} diff --git a/languages/tree-sitter-stack-graphs-typescript/test/expressions/undefined-pattern.ts b/languages/tree-sitter-stack-graphs-typescript/test/expressions/undefined-pattern.ts new file mode 100644 index 000000000..652adf3c6 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/expressions/undefined-pattern.ts @@ -0,0 +1,5 @@ +interface Foo { + bar(undefined?: any): any; +} + +export {} \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_core.tsx b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_core.tsx new file mode 100644 index 000000000..93285bc93 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_core.tsx @@ -0,0 +1,39 @@ +// The core of JSX tests here verify the behavior of the following node types: +// jsx_element +// jsx_identifier +// jsx_attribute +// jsx_expression +// jsx_opening_element +// jsx_closing_element +// There is no real way to avoid testing all of these at once, +// and so we don't even try to. + +let x = 1; + +// Flow In + +const el = {x}; +// ^ defined: 11 +// ^ defined: 11 + +const el2 = +// ^ defined: 11 +// ^ defined: 11 + +let y = 0; +let z = 2; + +const el = +// ^ defined: 23 + {z = 3} +// ^ defined: 24 +; + +/**/ y; +// ^ defined: 23 + +/**/ z; +// ^ defined: 24 + +/**/ x; +// ^ defined: 11 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_fragment.tsx b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_fragment.tsx new file mode 100644 index 000000000..1713694f7 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_fragment.tsx @@ -0,0 +1,12 @@ +let x = 1; + +// Flow Around + +const el = <>; + +/**/ x; +// ^ defined: 1 + +// Children +({x}); +// ^ defined: 1 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_namespace_name.tsx b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_namespace_name.tsx new file mode 100644 index 000000000..74fe0ff9b --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_namespace_name.tsx @@ -0,0 +1,30 @@ +let x = 1; + +// Flow Around +namespace noo.bar { + export let baz = 1; +} + +const el = ; +// ^ defined: 4 +// ^ defined: 4 +// ^ defined: 5 +// ^ defined: 4 +// ^ defined: 4 +// ^ defined: 5 + +/**/ x; +// ^ defined: 1 + +// Flow In + +let foo = { + bar: { + baz: 1 + } +}; + +const el2 = ; +// ^ defined: 21 +// ^ defined: 22 +// ^ defined: 23 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_self_closing_element.tsx b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_self_closing_element.tsx new file mode 100644 index 000000000..d26746137 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_self_closing_element.tsx @@ -0,0 +1,37 @@ +// The core of JSX tests here verify the behavior of the following node types: +// jsx_element +// jsx_identifier +// jsx_attribute +// jsx_expression +// jsx_opening_element +// jsx_closing_element +// There is no real way to avoid testing all of these at once, +// and so we don't even try to. + +let x = 1; + +// Flow In + +const el = ; +// ^ defined: 11 + +const el2 = +// ^ defined: 11 + +// Flow Out + +let y = 2; + +const el = ; +// ^ defined: 23 + +// Flow Across + +const el = ; +// ^ defined: 23 + +// Flow Around + +/**/ x; +// ^ defined: 11 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_text.tsx b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_text.tsx new file mode 100644 index 000000000..0cc359765 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/jsx/jsx_text.tsx @@ -0,0 +1,8 @@ +let x = 1; + +// Flow Around + +const el = bar; + +/**/ x; +// ^ defined: 1 \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-directly-exported-variable.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-directly-exported-variable.ts index bd6a1126b..91e5da6c7 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-directly-exported-variable.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-directly-exported-variable.ts @@ -1,10 +1,10 @@ -/*--- path: ./ModA.ts ---*/ +/*--- path: ModA.ts ---*/ export let a = { v: 42 }; -/*--- path: ./ModB.ts ---*/ +/*--- path: ModB.ts ---*/ import { a } from "./ModA"; // ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-js.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-js.ts new file mode 100644 index 000000000..92817fbaf --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-js.ts @@ -0,0 +1,6 @@ +/* --- path: src/foo.ts --- */ +export const bar = 42; + +/* --- path: src/index.ts --- */ +import { bar } from "./foo.js"; +// ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-none.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-none.ts new file mode 100644 index 000000000..f221684b4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-none.ts @@ -0,0 +1,6 @@ +/* --- path: src/foo.ts --- */ +export const bar = 42; + +/* --- path: src/index.ts --- */ +import { bar } from "./foo"; +// ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-ts.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-ts.ts new file mode 100644 index 000000000..1e46349c9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-extension-ts.ts @@ -0,0 +1,6 @@ +/* --- path: src/foo.ts --- */ +export const bar = 42; + +/* --- path: src/index.ts --- */ +import { bar } from "./foo.ts"; +// ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-ambient-module-declaration.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-ambient-module-declaration.ts index 75aff4238..abef61e5e 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-ambient-module-declaration.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-ambient-module-declaration.ts @@ -1,8 +1,8 @@ -/* --- path: ./index.ts --- */ +/* --- path: index.ts --- */ import { foo } from "@my/lib"; // ^ defined: 8 -/* --- path: ./mod.ts --- */ +/* --- path: mod.ts --- */ declare module "@my/lib" { export const foo = 42; diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-own-direcotry-in-index.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-own-direcotry-in-index.ts new file mode 100644 index 000000000..b62a2fe45 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-own-direcotry-in-index.ts @@ -0,0 +1,8 @@ +/*--- path: foo/index.ts ---*/ + +import { FOO } from "./bar"; +// ^ defined: 8 + +/*--- path: foo/bar.ts ---*/ + +export const FOO = 42; diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-own-project-subdirectory.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-own-project-subdirectory.ts index 2d399830c..743efa90a 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-own-project-subdirectory.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-own-project-subdirectory.ts @@ -1,11 +1,11 @@ -/*--- path: ./A/ModA.ts ---*/ +/*--- path: A/ModA.ts ---*/ /*--- global: PROJECT_NAME=foo/bar ---*/ export let a = { v: 42 }; -/*--- path: ./ModB.ts ---*/ +/*--- path: ModB.ts ---*/ /*--- global: PROJECT_NAME=foo/bar ---*/ import { a } from "./A/ModA"; diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-subdirectory.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-subdirectory.ts index 19e6c03b2..11292dff4 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-subdirectory.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-subdirectory.ts @@ -1,10 +1,10 @@ -/*--- path: ./A/ModA.ts ---*/ +/*--- path: A/ModA.ts ---*/ export let a = { v: 42 }; -/*--- path: ./ModB.ts ---*/ +/*--- path: ModB.ts ---*/ import { a } from "./A/ModA"; // ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-superdirectory.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-superdirectory.ts index bf7865e08..8f728d91c 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-superdirectory.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-from-superdirectory.ts @@ -1,10 +1,10 @@ -/*--- path: ./ModA.ts ---*/ +/*--- path: ModA.ts ---*/ export let a = { v: 42 }; -/*--- path: ./B/ModB.ts ---*/ +/*--- path: B/ModB.ts ---*/ import { a } from "../ModA"; // ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-indirectly-exported-variable.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-indirectly-exported-variable.ts index 3676bf85c..e2906595e 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-indirectly-exported-variable.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-indirectly-exported-variable.ts @@ -1,4 +1,4 @@ -/*--- path: ./ModA.ts ---*/ +/*--- path: ModA.ts ---*/ let a = { v: 42 @@ -7,7 +7,7 @@ let a = { export { a }; // ^ defined: 3 -/*--- path: ./ModB.ts ---*/ +/*--- path: ModB.ts ---*/ import { a } from "./ModA"; // ^ defined: 7, 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-reexports-from-subdirectory-index.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-reexports-from-subdirectory-index.ts new file mode 100644 index 000000000..e1871de89 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-reexports-from-subdirectory-index.ts @@ -0,0 +1,12 @@ +/* --- path: foo/index.ts --- */ + +export * from './bar' + +/* --- path: foo/bar.ts --- */ + +export let baz = 42 + +/* --- path: qux.ts --- */ + +import { baz } from './foo' +// ^ defined: 7 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-reexports-from-superdirectory-index.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-reexports-from-superdirectory-index.ts new file mode 100644 index 000000000..c37ae3cf7 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-reexports-from-superdirectory-index.ts @@ -0,0 +1,12 @@ +/* --- path: index.ts --- */ + +export * from './bar' + +/* --- path: bar.ts --- */ + +export let baz = 42 + +/* --- path: foo/qux.ts --- */ + +import { baz } from '..' +// ^ defined: 7 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-relative-to-project-root.ts.bug b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-relative-to-project-root.ts.skip similarity index 55% rename from languages/tree-sitter-stack-graphs-typescript/test/modules/import-relative-to-project-root.ts.bug rename to languages/tree-sitter-stack-graphs-typescript/test/modules/import-relative-to-project-root.ts.skip index c3a2589da..eed68b4ee 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-relative-to-project-root.ts.bug +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-relative-to-project-root.ts.skip @@ -1,8 +1,8 @@ -/*--- path: ./ModA.ts ---*/ +/*--- path: ModA.ts ---*/ export const a = 42; -/*--- path: ./B/ModB.ts ---*/ +/*--- path: B/ModB.ts ---*/ import { a } from "./ModA"; // ^ defined: \ No newline at end of file diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-index.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-index.ts index ef0504741..3730b1b73 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-index.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-index.ts @@ -1,10 +1,10 @@ -/*--- path: ./A/index.ts ---*/ +/*--- path: A/index.ts ---*/ export let a = { v: 42 }; -/*--- path: ./B/ModB.ts ---*/ +/*--- path: B/ModB.ts ---*/ import { a } from "../A"; // ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-superdirectory.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-superdirectory.ts index 332d6474d..a67e79311 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-superdirectory.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-superdirectory.ts @@ -1,10 +1,10 @@ -/*--- path: ./A/ModA.ts ---*/ +/*--- path: A/ModA.ts ---*/ export let a = { v: 42 }; -/*--- path: ./B/ModB.ts ---*/ +/*--- path: B/ModB.ts ---*/ import { a } from "../A/ModA"; // ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-two-reexports.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-two-reexports.ts new file mode 100644 index 000000000..981e3d15d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/import-via-two-reexports.ts @@ -0,0 +1,15 @@ +/* --- path: src/foo/index.ts --- */ + +export * from "../bar"; + +/* --- path: src/bar/index.ts --- */ + +export * from "./quz"; + +/* --- path: src/bar/quz.ts --- */ + +export const QUZ = 42; + +/* --- path: src/test.ts --- */ + +import { QUZ } from "./foo"; diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/reexport-as-object.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/reexport-as-object.ts new file mode 100644 index 000000000..54e5494b9 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/reexport-as-object.ts @@ -0,0 +1,15 @@ +/* --- path: foo.ts --- */ + +export const FOO = 42; + +/* --- path: bar.ts --- */ + +export * as quz from "./foo"; + +/* --- path: test.ts --- */ + +import { quz } from "./bar"; + + quz.FOO +// ^ defined: 11, 7 +// ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/rename-imported-directly-exported-variable.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/rename-imported-directly-exported-variable.ts index f3b403494..ecfdc2d2c 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/rename-imported-directly-exported-variable.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/rename-imported-directly-exported-variable.ts @@ -1,10 +1,10 @@ -/*--- path: ./ModA.ts ---*/ +/*--- path: ModA.ts ---*/ export let a = { v: 42 }; -/*--- path: ./ModB.ts ---*/ +/*--- path: ModB.ts ---*/ import { a as b } from "./ModA"; // ^ defined: 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/rename-imported-indirectly-exported-variable.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/rename-imported-indirectly-exported-variable.ts index 48f62099e..e1e99db34 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/modules/rename-imported-indirectly-exported-variable.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/rename-imported-indirectly-exported-variable.ts @@ -1,4 +1,4 @@ -/*--- path: ./ModA.ts ---*/ +/*--- path: ModA.ts ---*/ let a = { v: 42 @@ -7,7 +7,7 @@ let a = { export { a }; // ^ defined: 3 -/*--- path: ./ModB.ts ---*/ +/*--- path: ModB.ts ---*/ import { a as b } from "./ModA"; // ^ defined: 7, 3 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/modules/very-deep-namespace.ts b/languages/tree-sitter-stack-graphs-typescript/test/modules/very-deep-namespace.ts new file mode 100644 index 000000000..71af87eee --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/modules/very-deep-namespace.ts @@ -0,0 +1,6 @@ +namespace a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y { + export const Z = 42; +} + +a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.Z +// ^ defined: 2 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/packages/can-access-package-non-main-explicilty.ts b/languages/tree-sitter-stack-graphs-typescript/test/packages/can-access-package-non-main-explicilty.ts new file mode 100644 index 000000000..dda91f57b --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/packages/can-access-package-non-main-explicilty.ts @@ -0,0 +1,51 @@ +/* --- path: acme_foo/tsconfig.json --- */ +/* --- global: FILE_PATH=tsconfig.json --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +{} + +/* --- path: acme_foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +{ + "name": "@acme/foo", + "version": "1.0", + "main": "./api" +} + +/* --- path: acme_foo/api.ts --- */ +/* --- global: FILE_PATH=api.ts --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +export let x; + +/* --- path: acme_foo/core.ts --- */ +/* --- global: FILE_PATH=core.ts --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +export let x; + +/* --- path: bar/tsconfig.json --- */ +/* --- global: FILE_PATH=tsconfig.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{} + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{ + "name": "bar", + "dependencies": { + "@acme/foo": "1" + } +} + +/* --- path: bar/app.ts --- */ +/* --- global: FILE_PATH=app.ts --- */ +/* --- global: PROJECT_NAME=bar --- */ + +import { x } from "@acme/foo/core" +// ^ defined: 27 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/packages/package-does-not-export-non-main.ts b/languages/tree-sitter-stack-graphs-typescript/test/packages/package-does-not-export-non-main.ts new file mode 100644 index 000000000..d39b0d79f --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/packages/package-does-not-export-non-main.ts @@ -0,0 +1,44 @@ +/* --- path: foo/tsconfig.json --- */ +/* --- global: FILE_PATH=tsconfig.json --- */ +/* --- global: PROJECT_NAME=foo --- */ + +{} + +/* --- path: foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=foo --- */ + +{ + "name": "foo", + "version": "1.0" +} + +/* --- path: foo/impl.ts --- */ +/* --- global: FILE_PATH=impl.ts --- */ +/* --- global: PROJECT_NAME=foo --- */ + +export let x; + +/* --- path: bar/tsconfig.json --- */ +/* --- global: FILE_PATH=tsconfig.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{} + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{ + "name": "bar", + "dependencies": { + "foo": "1" + } +} + +/* --- path: bar/app.ts --- */ +/* --- global: FILE_PATH=app.ts --- */ +/* --- global: PROJECT_NAME=bar --- */ + +import { x } from "foo" +// ^ defined: diff --git a/languages/tree-sitter-stack-graphs-typescript/test/packages/package-exports-explicit-main.ts b/languages/tree-sitter-stack-graphs-typescript/test/packages/package-exports-explicit-main.ts new file mode 100644 index 000000000..f6a4f5e45 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/packages/package-exports-explicit-main.ts @@ -0,0 +1,51 @@ +/* --- path: acme_foo/tsconfig.json --- */ +/* --- global: FILE_PATH=tsconfig.json --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +{} + +/* --- path: acme_foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +{ + "name": "@acme/foo", + "version": "1.0", + "main": "./api" +} + +/* --- path: acme_foo/api.ts --- */ +/* --- global: FILE_PATH=api.ts --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +export let x; + +/* --- path: acme_foo/core.ts --- */ +/* --- global: FILE_PATH=core.ts --- */ +/* --- global: PROJECT_NAME=acme_foo --- */ + +export let x; + +/* --- path: bar/tsconfig.json --- */ +/* --- global: FILE_PATH=tsconfig.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{} + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{ + "name": "bar", + "dependencies": { + "@acme/foo": "1" + } +} + +/* --- path: bar/app.ts --- */ +/* --- global: FILE_PATH=app.ts --- */ +/* --- global: PROJECT_NAME=bar --- */ + +import { x } from "@acme/foo" +// ^ defined: 21 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/package-invisible-without-dependency.ts b/languages/tree-sitter-stack-graphs-typescript/test/packages/package-invisible-without-dependency.ts similarity index 71% rename from languages/tree-sitter-stack-graphs-typescript/test/projects/package-invisible-without-dependency.ts rename to languages/tree-sitter-stack-graphs-typescript/test/packages/package-invisible-without-dependency.ts index ca9852a68..87248abe2 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/package-invisible-without-dependency.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/packages/package-invisible-without-dependency.ts @@ -1,36 +1,40 @@ -/* --- path: ./my_lib/package.json --- */ +/* --- path: my_lib/package.json --- */ /* --- global: FILE_PATH=package.json --- */ /* --- global: PROJECT_NAME=my_lib --- */ + { "name": "@my/lib" } -/* --- path: ./my_lib/tsconfig.json --- */ +/* --- path: my_lib/tsconfig.json --- */ /* --- global: FILE_PATH=tsconfig.json --- */ /* --- global: PROJECT_NAME=my_lib --- */ -{ -} -/* --- path: ./my_lib/src/foo.ts --- */ +{} + +/* --- path: my_lib/src/foo.ts --- */ /* --- global: FILE_PATH=src/foo.ts --- */ /* --- global: PROJECT_NAME=my_lib --- */ + export const bar = 42; -/* --- path: ./my_app/package.json --- */ +/* --- path: my_app/package.json --- */ /* --- global: FILE_PATH=package.json --- */ /* --- global: PROJECT_NAME=my_app --- */ + { "name": "@my/app" } -/* --- path: ./my_app/tsconfig.json --- */ +/* --- path: my_app/tsconfig.json --- */ /* --- global: FILE_PATH=tsconfig.json --- */ /* --- global: PROJECT_NAME=my_app --- */ -{ -} -/* --- path: ./my_app/src/index.ts --- */ +{} + +/* --- path: my_app/src/index.ts --- */ /* --- global: FILE_PATH=src/index.ts --- */ /* --- global: PROJECT_NAME=my_app --- */ + import { bar } from "@my/lib/foo"; // ^ defined: diff --git a/languages/tree-sitter-stack-graphs-typescript/test/packages/package-main-defaults-to-index.ts b/languages/tree-sitter-stack-graphs-typescript/test/packages/package-main-defaults-to-index.ts new file mode 100644 index 000000000..b7983551d --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/packages/package-main-defaults-to-index.ts @@ -0,0 +1,50 @@ +/* --- path: foo/tsconfig.json --- */ +/* --- global: FILE_PATH=tsconfig.json --- */ +/* --- global: PROJECT_NAME=foo --- */ + +{} + +/* --- path: foo/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=foo --- */ + +{ + "name": "foo", + "version": "1.0" +} + +/* --- path: foo/index.ts --- */ +/* --- global: FILE_PATH=index.ts --- */ +/* --- global: PROJECT_NAME=foo --- */ + +export let x; + +/* --- path: foo/impl.ts --- */ +/* --- global: FILE_PATH=impl.ts --- */ +/* --- global: PROJECT_NAME=foo --- */ + +export let x; + +/* --- path: bar/tsconfig.json --- */ +/* --- global: FILE_PATH=tsconfig.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{} + +/* --- path: bar/package.json --- */ +/* --- global: FILE_PATH=package.json --- */ +/* --- global: PROJECT_NAME=bar --- */ + +{ + "name": "bar", + "dependencies": { + "foo": "1" + } +} + +/* --- path: bar/app.ts --- */ +/* --- global: FILE_PATH=app.ts --- */ +/* --- global: PROJECT_NAME=bar --- */ + +import { x } from "foo" +// ^ defined: 20 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/baseurl-to-subdir.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/baseurl-to-subdir.ts index a867d24b1..80f5a00d4 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/baseurl-to-subdir.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/baseurl-to-subdir.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true, @@ -6,9 +6,9 @@ } } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "foo"; // ^ defined: 10 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/composite-project.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/composite-project.ts index 732536dbf..e7fd1b57d 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/composite-project.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/composite-project.ts @@ -1,13 +1,13 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true, } } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 9 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/declarations-are-ignored.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/declarations-are-ignored.ts index 27cf54427..8c3d2a6b5 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/declarations-are-ignored.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/declarations-are-ignored.ts @@ -1,12 +1,12 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 6 -/* --- path: ./types/index.d.ts --- */ +/* --- path: types/index.d.ts --- */ diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/exclude-second-subdir.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/exclude-second-subdir.ts index 083f0293b..326557cc7 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/exclude-second-subdir.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/exclude-second-subdir.ts @@ -1,13 +1,13 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "exclude": ["test/**/*"] } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 7 -/* --- path: ./test/index.ts --- */ +/* --- path: test/index.ts --- */ diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/explicit-root-dir.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/explicit-root-dir.ts index c51108621..37dbade29 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/explicit-root-dir.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/explicit-root-dir.ts @@ -1,13 +1,13 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "rootDir": "." } } -/* --- path: ./core/foo.ts --- */ +/* --- path: core/foo.ts --- */ export const bar = 42; -/* --- path: ./core/index.ts --- */ +/* --- path: core/index.ts --- */ import { bar } from "./foo"; // ^ defined: 9 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/import-from-rootdirs-subdir.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/import-from-rootdirs-subdir.ts index 6c8715cec..5c9d2c896 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/import-from-rootdirs-subdir.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/import-from-rootdirs-subdir.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "rootDirs": [ @@ -8,13 +8,13 @@ } } -/* --- path: ./src/core/index.ts --- */ +/* --- path: src/core/index.ts --- */ import { bar } from "./foo/baz"; // ^ defined: 16 -/* --- path: ./src/util/foo/baz.ts --- */ +/* --- path: src/util/foo/baz.ts --- */ export const bar = 42; -/* --- path: ./src/util/index.ts --- */ +/* --- path: src/util/index.ts --- */ -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/import-from-rootdirs.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/import-from-rootdirs.ts index d7720e510..7603a8f91 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/import-from-rootdirs.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/import-from-rootdirs.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "rootDirs": [ @@ -8,13 +8,13 @@ } } -/* --- path: ./src/core/index.ts --- */ +/* --- path: src/core/index.ts --- */ import { bar } from "./foo"; // ^ defined: 16 -/* --- path: ./src/util/foo.ts --- */ +/* --- path: src/util/foo.ts --- */ export const bar = 42; -/* --- path: ./src/util/index.ts --- */ +/* --- path: src/util/index.ts --- */ -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/import-with-own-project-baseurl.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/import-with-own-project-baseurl.ts index 29c2892d8..1e40e23db 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/import-with-own-project-baseurl.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/import-with-own-project-baseurl.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ /* --- global: PROJECT_NAME=a --- */ { "compilerOptions": { @@ -7,11 +7,11 @@ } } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ /* --- global: PROJECT_NAME=a --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ /* --- global: PROJECT_NAME=a --- */ import { bar } from "foo"; // ^ defined: 12 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/include-one-subdir.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/include-one-subdir.ts index 42f991b95..8aed79105 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/include-one-subdir.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/include-one-subdir.ts @@ -1,13 +1,13 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "include": ["src/**/*"] } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 7 -/* --- path: ./test/index.ts --- */ +/* --- path: test/index.ts --- */ diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/invalid-paths-mappings.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/invalid-paths-mappings.ts index 6e422f928..926f28e97 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/invalid-paths-mappings.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/invalid-paths-mappings.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "paths": { diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/js-sources-are-ignored.ts.skip b/languages/tree-sitter-stack-graphs-typescript/test/projects/js-sources-are-ignored.ts.skip index 3ad81a358..21f8fae85 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/js-sources-are-ignored.ts.skip +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/js-sources-are-ignored.ts.skip @@ -1,12 +1,12 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { } -/* --- path: ./lib/index.js --- */ +/* --- path: lib/index.js --- */ -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 8 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/js-sources-can-be-allowed.ts.skip b/languages/tree-sitter-stack-graphs-typescript/test/projects/js-sources-can-be-allowed.ts.skip index 1b4bd4619..ed304a1b9 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/js-sources-can-be-allowed.ts.skip +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/js-sources-can-be-allowed.ts.skip @@ -1,15 +1,15 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "allowJs": true } } -/* --- path: ./lib/index.js --- */ +/* --- path: lib/index.js --- */ -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 11 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-single-module-remap.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-single-module-remap.ts index c948f40ee..64036fc0d 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-single-module-remap.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-single-module-remap.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true, @@ -9,9 +9,9 @@ } } -/* --- path: ./lib/the_foo.ts --- */ +/* --- path: lib/the_foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "foo"; // ^ defined: 13 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-star-remap.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-star-remap.ts index 2d0c07608..5961eeffa 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-star-remap.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-star-remap.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true, @@ -9,9 +9,9 @@ } } -/* --- path: ./lib/foo.ts --- */ +/* --- path: lib/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "foo"; // ^ defined: 13 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-star-remaps.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-star-remaps.ts index f8146cf96..6d3e26955 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-star-remaps.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/non-relative-star-remaps.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true, @@ -9,9 +9,9 @@ } } -/* --- path: ./ext/foo.ts --- */ +/* --- path: ext/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "foo"; // ^ defined: 13 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/one-file-in-subdir.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/one-file-in-subdir.ts index 5b399e022..9aed45a39 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/one-file-in-subdir.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/one-file-in-subdir.ts @@ -1,13 +1,13 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "files": ["src/index.ts"] } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 7 -/* --- path: ./test/index.ts --- */ +/* --- path: test/index.ts --- */ diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/package-dependency-with-nested-source-root.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/package-dependency-with-nested-source-root.ts index f8b2a69fb..16215e31c 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/package-dependency-with-nested-source-root.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/package-dependency-with-nested-source-root.ts @@ -1,22 +1,22 @@ -/* --- path: ./my_lib/package.json --- */ +/* --- path: my_lib/package.json --- */ /* --- global: FILE_PATH=package.json --- */ /* --- global: PROJECT_NAME=my_lib --- */ { "name": "@my/lib" } -/* --- path: ./my_lib/tsconfig.json --- */ +/* --- path: my_lib/tsconfig.json --- */ /* --- global: FILE_PATH=tsconfig.json --- */ /* --- global: PROJECT_NAME=my_lib --- */ { } -/* --- path: ./my_lib/src/foo.ts --- */ +/* --- path: my_lib/src/foo.ts --- */ /* --- global: FILE_PATH=src/foo.ts --- */ /* --- global: PROJECT_NAME=my_lib --- */ export const bar = 42; -/* --- path: ./my_app/package.json --- */ +/* --- path: my_app/package.json --- */ /* --- global: FILE_PATH=package.json --- */ /* --- global: PROJECT_NAME=my_app --- */ { @@ -26,13 +26,13 @@ export const bar = 42; } } -/* --- path: ./my_app/tsconfig.json --- */ +/* --- path: my_app/tsconfig.json --- */ /* --- global: FILE_PATH=tsconfig.json --- */ /* --- global: PROJECT_NAME=my_app --- */ { } -/* --- path: ./my_app/src/index.ts --- */ +/* --- path: my_app/src/index.ts --- */ /* --- global: FILE_PATH=src/index.ts --- */ /* --- global: PROJECT_NAME=my_app --- */ import { bar } from "@my/lib/foo"; diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-import.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-import.ts index 1c2670335..e1951f3af 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-import.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-import.ts @@ -1,13 +1,13 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true } } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 9 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-single-module-remap.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-single-module-remap.ts index 03ee84488..71b6ce834 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-single-module-remap.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-single-module-remap.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true, @@ -8,9 +8,9 @@ } } -/* --- path: ./lib/the_foo.ts --- */ +/* --- path: lib/the_foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "foo"; // ^ defined: 12 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-star-remap.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-star-remap.ts index a116cfbbd..4a169c524 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-star-remap.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/relative-star-remap.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true, @@ -8,9 +8,9 @@ } } -/* --- path: ./lib/foo.ts --- */ +/* --- path: lib/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "foo"; // ^ defined: 12 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/remap-with-baseurl.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/remap-with-baseurl.ts index 53d47534d..cf2788ba0 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/remap-with-baseurl.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/remap-with-baseurl.ts @@ -1,4 +1,4 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { "compilerOptions": { "composite": true, @@ -9,9 +9,9 @@ } } -/* --- path: ./src/util_impl/foo.ts --- */ +/* --- path: src/util_impl/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "util/foo"; // ^ defined: 13 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/sources-in-multiple-subdirs.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/sources-in-multiple-subdirs.ts index e4248c32f..76d763b33 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/sources-in-multiple-subdirs.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/sources-in-multiple-subdirs.ts @@ -1,12 +1,12 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 6 -/* --- path: ./test/index.ts --- */ +/* --- path: test/index.ts --- */ diff --git a/languages/tree-sitter-stack-graphs-typescript/test/projects/sources-in-one-subdir.ts b/languages/tree-sitter-stack-graphs-typescript/test/projects/sources-in-one-subdir.ts index e97ea71ed..24a9a62ca 100644 --- a/languages/tree-sitter-stack-graphs-typescript/test/projects/sources-in-one-subdir.ts +++ b/languages/tree-sitter-stack-graphs-typescript/test/projects/sources-in-one-subdir.ts @@ -1,10 +1,10 @@ -/* --- path: ./tsconfig.json --- */ +/* --- path: tsconfig.json --- */ { } -/* --- path: ./src/foo.ts --- */ +/* --- path: src/foo.ts --- */ export const bar = 42; -/* --- path: ./src/index.ts --- */ +/* --- path: src/index.ts --- */ import { bar } from "./foo"; // ^ defined: 6 diff --git a/languages/tree-sitter-stack-graphs-typescript/test/statements/class-static-block.ts b/languages/tree-sitter-stack-graphs-typescript/test/statements/class-static-block.ts new file mode 100644 index 000000000..6ccdadea4 --- /dev/null +++ b/languages/tree-sitter-stack-graphs-typescript/test/statements/class-static-block.ts @@ -0,0 +1,12 @@ +let bar = 42; + +class Foo { + static foo: number; + static { + this.foo = bar; + // ^ defined: 4 + // ^ defined: 1 + } +} + +export {} diff --git a/languages/tree-sitter-stack-graphs-typescript/vscode/package-lock.json b/languages/tree-sitter-stack-graphs-typescript/vscode/package-lock.json index b1153da95..4d95b10b3 100644 --- a/languages/tree-sitter-stack-graphs-typescript/vscode/package-lock.json +++ b/languages/tree-sitter-stack-graphs-typescript/vscode/package-lock.json @@ -38,9 +38,9 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": { "balanced-match": "^1.0.0" } @@ -68,9 +68,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, diff --git a/lsp-positions/CHANGELOG.md b/lsp-positions/CHANGELOG.md index 36a0d4ebd..f80bc5c5d 100644 --- a/lsp-positions/CHANGELOG.md +++ b/lsp-positions/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v0.3.4 -- 2024-12-12 + +Upgraded the `tree-sitter` dependency to version 0.24. + +## v0.3.3 -- 2024-03-05 + +The `tree-sitter` dependency version was updated to fix install problems. + ## v0.3.2 -- 2023-06-08 ### Added diff --git a/lsp-positions/Cargo.toml b/lsp-positions/Cargo.toml index 2b2d496b3..a9f7acb20 100644 --- a/lsp-positions/Cargo.toml +++ b/lsp-positions/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lsp-positions" -version = "0.3.2" +version = "0.3.4" description = "LSP-compatible character positions" homepage = "https://github.com/github/stack-graphs/tree/main/lsp-positions" repository = "https://github.com/github/stack-graphs/" @@ -17,12 +17,14 @@ edition = "2018" test = false [features] -default = ["tree-sitter"] bincode = ["dep:bincode"] +tree-sitter = ["dep:tree-sitter"] [dependencies] memchr = "2.4" -tree-sitter = { version=">= 0.19", optional=true } -unicode-segmentation = { version="1.8" } -serde = { version="1", optional=true, features=["derive"] } -bincode = { version="2.0.0-rc.3", optional=true } +tree-sitter = { version = "0.24", optional = true } # keep the same minor version as the tree-sitter + # dependency of tree-sitter-stack-graphs to prevent + # install problems +unicode-segmentation = { version = "1.8" } +serde = { version = "1", features = ["derive"], optional = true } +bincode = { version = "2.0.0-rc.3", optional = true } diff --git a/script/ci-test-init b/script/ci-test-init new file mode 100755 index 000000000..0ecc60788 --- /dev/null +++ b/script/ci-test-init @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -eu + +cargo run --bin tree-sitter-stack-graphs --features cli -- init \ + --language-name InitTest \ + --language-id init_test \ + --language-file-extension it \ + --grammar-crate-name tree-sitter-python \ + --grammar-crate-version 0.23.2 \ + --internal \ + --non-interactive + +cargo check -p tree-sitter-stack-graphs-init_test --all-features + +cargo test -p tree-sitter-stack-graphs-init_test + +cargo run -p tree-sitter-stack-graphs-init_test --features cli -- -V diff --git a/script/ci-test-valgrind b/script/ci-test-valgrind new file mode 100755 index 000000000..3f5e3dae6 --- /dev/null +++ b/script/ci-test-valgrind @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -eu + +# Start by building the test binary, before we try to get its filename +cargo test "$@" --no-run + +# Cargo does not have a clean way to get the test binary, so we do some text processing here to get it +test="$(cargo test "$@" --no-run 2>&1 | grep 'Executable' | head -1 | sed -e 's/[^(]*(\(.*\)).*$/\1/')" +log="${RUNNER_TEMP-.}/valgrind.log" + +# Run the test binary under valgrind +if ! valgrind \ + --leak-check=full \ + --show-leak-kinds=all \ + --error-exitcode=1 \ + --suppressions=valgrind.supp \ + --gen-suppressions=all \ + --log-file="$log" \ + "$test"; then + echo "Valgrind detected errors! See logs: $log" + exit 1 +fi diff --git a/stack-graphs/CHANGELOG.md b/stack-graphs/CHANGELOG.md index ee5b6404f..802275fc2 100644 --- a/stack-graphs/CHANGELOG.md +++ b/stack-graphs/CHANGELOG.md @@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v0.14.1 -- 2024-12-12 + +### Fixed + +- A panic when using `StackGraph::incoming_edge_degree` on some nodes without incoming edges. + +## v0.14.0 -- 2024-07-09 + +### Changed + +- The method `StackGraph::add_from_graph` returns the handles of the newly added files. + +## v0.13.0 -- 2024-03-06 + +### Added + +- New type `StitcherConfig` to specify configuration flags that control stitching. +- Path stiching statistics can now be collected during stitching for debugging purposes. Disabled by default and can be enabled with `StitcherConfig`. +- A method `StackGraph::set_edge_precedence` that allows changing the precendence of an existing edge in a stack graph. +- A method `StackGraph::incoming_edge_degree` that returns the number of edges ending in a given node. +- A method `Database::get_incoming_path_degree` that returns the number of partial paths in the database ending in a given node. +- Cycle detection improved by using incoming path or edge degrees to reduce the amount of data to keep in memory. +- Visualization uses different colors for nodes of different files, which makes understanding multi-file graphs easier. + +### Changed + +- Methods and types that do stitching in their implementation now require a `StitcherConfig` value. These include `Assertion::run`, `ForwardPartialPathStitcher::find_*`, and `Test::run`. +- The SQLite storage data encoding and queries changed to improve performance. + +### Removed + +- Method `StackGraph::remove_edge` has been removed. + +### Fixed + +- A panic when using an arena after `Arena::clear` or `SupplementalArena::clear` was called. +- Missing candidates when looking up root paths in a `Database`. + ## v0.12.0 -- 2023-07-27 ### Added @@ -15,7 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - The `Appendable` trait has been simplified. Its `Ctx` type parameter is gone, in favor of a separate trait `ToAppendable` that is used to find appendables for a handle. The type itself moved from the `cycles` to the `stitching` module. -- The `ForwardPartialPathStitcher` has been generalized so that it can be used to build paths from a database or from graph edges. It now takes a type parameter indicating the type of candidates it uses. Instead of a `Database` instance, it expects a value that implements the `Candidates` and `ToAppendable` traits. The `ForwardPartialPathStitcher::process_next_phase` expects an additional `extend_until` closure that controls whether the extended paths are considered for further extension or not (using `|_,_,_| true` retains old behavior). +- The `ForwardPartialPathStitcher` has been generalized so that it can be used to build paths from a database, graph edges, or a SQL reader. It now takes a type parameter indicating the type of candidates it uses. Instead `StackGraph`, `PartialPaths` and `Database` instances, it expects a value that implements the `ForwardCandidates` trait. The `ForwardPartialPathStitcher::process_next_phase` expects an additional `extend_until` closure that controls whether the extended paths are considered for further extension or not (using `|_,_,_| true` retains old behavior). - The SQLite database implementation is using a new schema which stores binary instead of JSON values, resulting in faster write times and smaller databases. - Renamed method `SQLiteReader::load_graph_for_file_or_directory` to `SQLiteReader::load_graphs_for_file_or_directory`. @@ -30,7 +68,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `ForwardPartialPathStitcher::from_nodes` function has been removed. Callers are responsible for creating the right initial paths, which can be done using `PartialPath::from_node`. - The `PartialPaths::find_minimal_partial_path_set_in_file` method has been removed in favor of `ForwardPartialPathStitcher::find_minimal_partial_path_set_in_file`. -- The `PartialPaths::find_all_complete_paths` method has been removed in favor of `ForwardPartialPathStitcher::find_all_complete_partial_paths` using `GraphEdges(None)` for the `db` argument. +- The `PartialPaths::find_all_complete_paths` method has been removed in favor of `ForwardPartialPathStitcher::find_all_complete_partial_paths` using a `GraphEdgeCandidates` instance for the `candidates` argument. +- The `Database::find_all_complete_paths` method has been removed in favor of `ForwardPartialPathStitcher::find_all_complete_partial_paths` using a `DatabaseCandidates` instance for the `candidates` argument. +- The `SQLiteReader::find_all_complete_partial_paths` method has been removed in favor of `ForwardPartialPathStitcher::find_all_complete_partial_paths` using a `SQLiteReader` instance for the `candidates` argument. - The `OwnedOrDatabasePath` has been removed because it was obsolete with the changes to `Appendable`. ## v0.11.0 -- 2023-06-08 diff --git a/stack-graphs/Cargo.toml b/stack-graphs/Cargo.toml index e283e25f6..e542f4163 100644 --- a/stack-graphs/Cargo.toml +++ b/stack-graphs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stack-graphs" -version = "0.12.0" +version = "0.14.1" description = "Name binding for arbitrary programming languages" homepage = "https://github.com/github/stack-graphs/tree/main/stack-graphs" repository = "https://github.com/github/stack-graphs/" @@ -25,14 +25,14 @@ test = false [dependencies] bincode = { version = "2.0.0-rc.3", optional = true } -bitvec = "1.0" -controlled-option = "0.4" +bitvec = "1.0.1" +controlled-option = "0.4.1" either = "1.6" -enumset = "1.0" +enumset = "1.1" fxhash = "0.2" -itertools = "0.10" +itertools = "0.10.2" libc = "0.2" -lsp-positions = { version = "0.3", path = "../lsp-positions" } +lsp-positions = { version = "0.3", path = "../lsp-positions" } # explicit version is required to be able to publish crate rusqlite = { version = "0.28", optional = true, features = ["bundled", "functions"] } serde = { version = "1.0", optional = true, features = ["derive"] } serde_json = { version = "1.0", optional = true } @@ -42,7 +42,6 @@ thiserror = { version = "1.0" } [dev-dependencies] assert-json-diff = "2" -itertools = "0.10" maplit = "1.0" pretty_assertions = "0.7" serde_json = { version = "1.0" } diff --git a/stack-graphs/README.md b/stack-graphs/README.md index 7805d19ef..69efa5bd8 100644 --- a/stack-graphs/README.md +++ b/stack-graphs/README.md @@ -9,7 +9,7 @@ To use this library, add the following to your `Cargo.toml`: ``` toml [dependencies] -stack-graphs = "0.12" +stack-graphs = "0.14" ``` Check out our [documentation](https://docs.rs/stack-graphs/) for more details on diff --git a/stack-graphs/include/stack-graphs.h b/stack-graphs/include/stack-graphs.h index 50b7062ed..aeac9e636 100644 --- a/stack-graphs/include/stack-graphs.h +++ b/stack-graphs/include/stack-graphs.h @@ -437,6 +437,11 @@ struct sg_partial_path { struct sg_partial_path_edge_list edges; }; +struct sg_stitcher_config { + // Enables similar path detection during stiching. + bool detect_similar_paths; +}; + // An array of all of the partial paths in a partial path database. Partial path handles are // indices into this array. There will never be a valid partial path at index 0; a handle with // the value 0 represents a missing partial path. @@ -733,6 +738,7 @@ enum sg_result sg_partial_path_arena_find_partial_paths_in_file(const struct sg_ struct sg_partial_path_arena *partials, sg_file_handle file, struct sg_partial_path_list *partial_path_list, + const struct sg_stitcher_config *stitcher_config, const size_t *cancellation_flag); // Finds all complete paths reachable from a set of starting nodes, placing the result into the @@ -748,6 +754,7 @@ enum sg_result sg_partial_path_arena_find_all_complete_paths(const struct sg_sta size_t starting_node_count, const sg_node_handle *starting_nodes, struct sg_partial_path_list *path_list, + const struct sg_stitcher_config *stitcher_config, const size_t *cancellation_flag); // Returns a reference to the array of partial path data in this partial path database. The diff --git a/stack-graphs/src/arena.rs b/stack-graphs/src/arena.rs index 6e4747267..134a90d14 100644 --- a/stack-graphs/src/arena.rs +++ b/stack-graphs/src/arena.rs @@ -183,10 +183,9 @@ impl Arena { /// Clear the arena, keeping underlying allocated capacity. After this, all previous handles into /// the arena are invalid. - #[cfg_attr(not(feature = "storage"), allow(dead_code))] #[inline(always)] - pub(crate) fn clear(&mut self) { - self.items.clear(); + pub fn clear(&mut self) { + self.items.truncate(1); } /// Adds a new instance to this arena, returning a stable handle to it. @@ -290,10 +289,9 @@ impl SupplementalArena { /// Clear the supplemantal arena, keeping underlying allocated capacity. After this, /// all previous handles into the arena are invalid. - #[cfg_attr(not(feature = "storage"), allow(dead_code))] #[inline(always)] - pub(crate) fn clear(&mut self) { - self.items.clear(); + pub fn clear(&mut self) { + self.items.truncate(1); } /// Creates a new, empty supplemental arena, preallocating enough space to store supplemental diff --git a/stack-graphs/src/assert.rs b/stack-graphs/src/assert.rs index c00087eae..6e1190673 100644 --- a/stack-graphs/src/assert.rs +++ b/stack-graphs/src/assert.rs @@ -18,7 +18,9 @@ use crate::graph::Symbol; use crate::partial::PartialPath; use crate::partial::PartialPaths; use crate::stitching::Database; +use crate::stitching::DatabaseCandidates; use crate::stitching::ForwardPartialPathStitcher; +use crate::stitching::StitcherConfig; use crate::CancellationError; use crate::CancellationFlag; @@ -148,12 +150,19 @@ impl Assertion { graph: &StackGraph, partials: &mut PartialPaths, db: &mut Database, + stitcher_config: StitcherConfig, cancellation_flag: &dyn CancellationFlag, ) -> Result<(), AssertionError> { match self { - Self::Defined { source, targets } => { - self.run_defined(graph, partials, db, source, targets, cancellation_flag) - } + Self::Defined { source, targets } => self.run_defined( + graph, + partials, + db, + source, + targets, + stitcher_config, + cancellation_flag, + ), Self::Defines { source, symbols } => self.run_defines(graph, source, symbols), Self::Refers { source, symbols } => self.run_refers(graph, source, symbols), } @@ -166,6 +175,7 @@ impl Assertion { db: &mut Database, source: &AssertionSource, expected_targets: &Vec, + stitcher_config: StitcherConfig, cancellation_flag: &dyn CancellationFlag, ) -> Result<(), AssertionError> { let references = source.iter_references(graph).collect::>(); @@ -179,10 +189,9 @@ impl Assertion { for reference in &references { let mut reference_paths = Vec::new(); ForwardPartialPathStitcher::find_all_complete_partial_paths( - graph, - partials, - db, + &mut DatabaseCandidates::new(graph, partials, db), vec![*reference], + stitcher_config, cancellation_flag, |_, _, p| { reference_paths.push(p.clone()); diff --git a/stack-graphs/src/c.rs b/stack-graphs/src/c.rs index 1b4c95a34..9b50349ec 100644 --- a/stack-graphs/src/c.rs +++ b/stack-graphs/src/c.rs @@ -29,8 +29,10 @@ use crate::partial::PartialScopeStack; use crate::partial::PartialScopedSymbol; use crate::partial::PartialSymbolStack; use crate::stitching::Database; +use crate::stitching::DatabaseCandidates; use crate::stitching::ForwardPartialPathStitcher; -use crate::stitching::GraphEdges; +use crate::stitching::GraphEdgeCandidates; +use crate::stitching::StitcherConfig; use crate::CancellationError; use crate::CancellationFlag; @@ -1172,18 +1174,21 @@ pub extern "C" fn sg_partial_path_arena_find_partial_paths_in_file( partials: *mut sg_partial_path_arena, file: sg_file_handle, partial_path_list: *mut sg_partial_path_list, + stitcher_config: *const sg_stitcher_config, cancellation_flag: *const usize, ) -> sg_result { let graph = unsafe { &(*graph).inner }; let partials = unsafe { &mut (*partials).inner }; let file = file.into(); let partial_path_list = unsafe { &mut *partial_path_list }; + let stitcher_config = unsafe { *stitcher_config }; let cancellation_flag: Option<&AtomicUsize> = unsafe { std::mem::transmute(cancellation_flag.as_ref()) }; ForwardPartialPathStitcher::find_minimal_partial_path_set_in_file( graph, partials, file, + stitcher_config.into(), &AtomicUsizeCancellationFlag(cancellation_flag), |_graph, partials, path| { let mut path = path.clone(); @@ -1209,19 +1214,20 @@ pub extern "C" fn sg_partial_path_arena_find_all_complete_paths( starting_node_count: usize, starting_nodes: *const sg_node_handle, path_list: *mut sg_partial_path_list, + stitcher_config: *const sg_stitcher_config, cancellation_flag: *const usize, ) -> sg_result { let graph = unsafe { &(*graph).inner }; let partials = unsafe { &mut (*partials).inner }; let starting_nodes = unsafe { std::slice::from_raw_parts(starting_nodes, starting_node_count) }; + let stitcher_config = unsafe { *stitcher_config }; let path_list = unsafe { &mut *path_list }; let cancellation_flag: Option<&AtomicUsize> = unsafe { std::mem::transmute(cancellation_flag.as_ref()) }; ForwardPartialPathStitcher::find_all_complete_partial_paths( - graph, - partials, - &mut GraphEdges(None), + &mut GraphEdgeCandidates::new(graph, partials, None), starting_nodes.iter().copied().map(sg_node_handle::into), + stitcher_config.into(), &AtomicUsizeCancellationFlag(cancellation_flag), |graph, _partials, path| { if path.is_complete(graph) { @@ -1392,6 +1398,20 @@ pub struct sg_forward_partial_path_stitcher { pub is_complete: bool, } +// Configuration for partial path stitchers. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct sg_stitcher_config { + /// Enables similar path detection during stiching. + pub detect_similar_paths: bool, +} + +impl Into for sg_stitcher_config { + fn into(self) -> StitcherConfig { + StitcherConfig::default().with_detect_similar_paths(self.detect_similar_paths) + } +} + // This is the Rust equivalent of a common C trick, where you have two versions of a struct — a // publicly visible one and a private one containing internal implementation details. In our case, // `sg_forward_partial_path_stitcher` is the public struct, and @@ -1535,9 +1555,10 @@ pub extern "C" fn sg_forward_partial_path_stitcher_process_next_phase( let partials = unsafe { &mut (*partials).inner }; let db = unsafe { &mut (*db).inner }; let stitcher = unsafe { &mut *(stitcher as *mut InternalForwardPartialPathStitcher) }; - stitcher - .stitcher - .process_next_phase(graph, partials, db, |_, _, _| true); + stitcher.stitcher.process_next_phase( + &mut DatabaseCandidates::new(graph, partials, db), + |_, _, _| true, + ); stitcher.update_previous_phase_partial_paths(partials); } diff --git a/stack-graphs/src/cycles.rs b/stack-graphs/src/cycles.rs index 51225a06a..212d14085 100644 --- a/stack-graphs/src/cycles.rs +++ b/stack-graphs/src/cycles.rs @@ -31,6 +31,7 @@ use enumset::EnumSet; use smallvec::SmallVec; +use std::cmp::Ordering; use std::collections::HashMap; use crate::arena::Arena; @@ -43,12 +44,14 @@ use crate::partial::Cyclicity; use crate::partial::PartialPath; use crate::partial::PartialPaths; use crate::paths::PathResolutionError; +use crate::stats::FrequencyDistribution; use crate::stitching::Appendable; use crate::stitching::ToAppendable; /// Helps detect similar paths in the path-finding algorithm. pub struct SimilarPathDetector

{ - paths: HashMap>, + paths: HashMap>, + counts: Option>>, } #[doc(hidden)] @@ -91,32 +94,75 @@ where pub fn new() -> SimilarPathDetector

{ SimilarPathDetector { paths: HashMap::new(), + counts: None, } } - /// Determines whether we should process this path during the path-finding algorithm. If we have seen - /// a path with the same start and end node, and the same pre- and postcondition, then we return false. - /// Otherwise, we return true. - pub fn has_similar_path( + /// Set whether to collect statistics for this similar path detector. + pub fn set_collect_stats(&mut self, collect_stats: bool) { + if !collect_stats { + self.counts = None; + } else if self.counts.is_none() { + self.counts = Some(HashMap::new()); + } + } + + /// Add a path, and determine whether we should process this path during the path-finding algorithm. + /// If we have seen a path with the same start and end node, and the same pre- and postcondition, then + /// we return false. Otherwise, we return true. + pub fn add_path( &mut self, _graph: &StackGraph, arena: &mut P::Arena, path: &P, - eq: Eq, + cmp: Cmp, ) -> bool where - Eq: Fn(&mut P::Arena, &P, &P) -> bool, + Cmp: Fn(&mut P::Arena, &P, &P) -> Option, { let key = path.key(); - let possibly_similar_paths = self.paths.entry(key).or_default(); - for other_path in possibly_similar_paths.iter() { - if eq(arena, path, other_path) { - return true; + // Iterate through the bucket to determine if this paths is better than any already known + // path. Note that the bucket might be modified during the loop if a path is removed which + // is shadowed by the new path! + let possibly_similar_paths = self.paths.entry(key.clone()).or_default(); + let mut possible_similar_counts = self + .counts + .as_mut() + .map(move |cs| cs.entry(key).or_default()); + let mut idx = 0; + let mut count = 0; + while idx < possibly_similar_paths.len() { + let other_path = &mut possibly_similar_paths[idx]; + match cmp(arena, path, other_path) { + Some(Ordering::Less) => { + // the new path is better, remove the old one + possibly_similar_paths.remove(idx); + if let Some(possible_similar_counts) = possible_similar_counts.as_mut() { + count += possible_similar_counts[idx]; + possible_similar_counts.remove(idx); + } + // keep `idx` which now points to the next element + continue; + } + Some(_) => { + // the new path is equal or worse, and ignored + if let Some(possible_similar_counts) = possible_similar_counts { + possible_similar_counts[idx] += 1; + } + return true; + } + None => { + idx += 1; + } } } + // this path is either new or better, keep it possibly_similar_paths.push(path.clone()); + if let Some(possible_similar_counts) = possible_similar_counts { + possible_similar_counts.push(count); + } false } @@ -124,6 +170,42 @@ where pub fn max_bucket_size(&self) -> usize { self.paths.iter().map(|b| b.1.len()).max().unwrap_or(0) } + + // Returns the distribution of similar path counts. + pub fn stats(&self) -> SimilarPathStats { + let mut stats = SimilarPathStats::default(); + if let Some(counts) = &self.counts { + for bucket in counts.values() { + stats.similar_path_bucket_size.record(bucket.len()); + for count in bucket.iter() { + stats.similar_path_count.record(*count); + } + } + } + stats + } +} + +#[derive(Clone, Debug, Default)] +pub struct SimilarPathStats { + // The distribution of the number of similar paths detected + pub similar_path_count: FrequencyDistribution, + // The distribution of the internal bucket sizes in the similar path detector + pub similar_path_bucket_size: FrequencyDistribution, +} + +impl std::ops::AddAssign for SimilarPathStats { + fn add_assign(&mut self, rhs: Self) { + self.similar_path_bucket_size += rhs.similar_path_bucket_size; + self.similar_path_count += rhs.similar_path_count; + } +} + +impl std::ops::AddAssign<&Self> for SimilarPathStats { + fn add_assign(&mut self, rhs: &Self) { + self.similar_path_bucket_size += &rhs.similar_path_bucket_size; + self.similar_path_count += &rhs.similar_path_count; + } } // ---------------------------------------------------------------------------- diff --git a/stack-graphs/src/graph.rs b/stack-graphs/src/graph.rs index ae2b9aa62..d55dfc48a 100644 --- a/stack-graphs/src/graph.rs +++ b/stack-graphs/src/graph.rs @@ -425,7 +425,7 @@ impl Handle { /// Each node (except for the _root node_ and _jump to scope_ node) lives in a file, and has a /// _local ID_ that must be unique within its file. #[repr(C)] -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct NodeID { file: ControlledOption>, local_id: u32, @@ -1303,14 +1303,20 @@ impl StackGraph { let edges = &mut self.outgoing_edges[source]; if let Err(index) = edges.binary_search_by_key(&sink, |o| o.sink) { edges.insert(index, OutgoingEdge { sink, precedence }); + self.incoming_edges[sink] += Degree::One; } } - /// Removes an edge from the stack graph. - pub fn remove_edge(&mut self, source: Handle, sink: Handle) { + /// Sets edge precedence of the given edge. + pub fn set_edge_precedence( + &mut self, + source: Handle, + sink: Handle, + precedence: i32, + ) { let edges = &mut self.outgoing_edges[source]; if let Ok(index) = edges.binary_search_by_key(&sink, |o| o.sink) { - edges.remove(index); + edges[index].precedence = precedence; } } @@ -1325,6 +1331,14 @@ impl StackGraph { None => Either::Left(std::iter::empty()), } } + + /// Returns the number of edges that end at a particular sink node. + pub fn incoming_edge_degree(&self, sink: Handle) -> Degree { + self.incoming_edges + .get(sink) + .cloned() + .unwrap_or(Degree::Zero) + } } //------------------------------------------------------------------------------------------------- @@ -1430,6 +1444,36 @@ impl StackGraph { //------------------------------------------------------------------------------------------------- // Stack graphs +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum Degree { + Zero, + One, + Multiple, +} + +impl Default for Degree { + fn default() -> Self { + Self::Zero + } +} + +impl std::ops::Add for Degree { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::Zero, result) | (result, Self::Zero) => result, + _ => Self::Multiple, + } + } +} + +impl std::ops::AddAssign for Degree { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + /// Contains all of the nodes and edges that make up a stack graph. pub struct StackGraph { interned_strings: InternedStringArena, @@ -1442,9 +1486,10 @@ pub struct StackGraph { pub(crate) nodes: Arena, pub(crate) source_info: SupplementalArena, node_id_handles: NodeIDHandles, - outgoing_edges: SupplementalArena>, + outgoing_edges: SupplementalArena>, + incoming_edges: SupplementalArena, pub(crate) node_debug_info: SupplementalArena, - pub(crate) edge_debug_info: SupplementalArena, DebugInfo); 8]>>, + pub(crate) edge_debug_info: SupplementalArena, DebugInfo); 4]>>, } impl StackGraph { @@ -1455,12 +1500,16 @@ impl StackGraph { /// Copies the given stack graph into this stack graph. Panics if any of the files /// in the other stack graph are already defined in the current one. - pub fn add_from_graph(&mut self, other: &StackGraph) -> Result<(), Handle> { + pub fn add_from_graph( + &mut self, + other: &StackGraph, + ) -> Result>, Handle> { let mut files = HashMap::new(); for other_file in other.iter_files() { let file = self.add_file(other[other_file].name())?; files.insert(other_file, file); } + let files = files; let node_id = |other_node_id: NodeID| { if other_node_id.is_root() { NodeID::root() @@ -1603,7 +1652,7 @@ impl StackGraph { } } } - Ok(()) + Ok(files.into_values().collect()) } } @@ -1625,6 +1674,7 @@ impl Default for StackGraph { source_info: SupplementalArena::new(), node_id_handles: NodeIDHandles::new(), outgoing_edges: SupplementalArena::new(), + incoming_edges: SupplementalArena::new(), node_debug_info: SupplementalArena::new(), edge_debug_info: SupplementalArena::new(), } diff --git a/stack-graphs/src/lib.rs b/stack-graphs/src/lib.rs index 009076af9..a8573c5e5 100644 --- a/stack-graphs/src/lib.rs +++ b/stack-graphs/src/lib.rs @@ -69,6 +69,7 @@ pub mod graph; pub mod partial; pub mod paths; pub mod serde; +pub mod stats; pub mod stitching; #[cfg(feature = "storage")] pub mod storage; diff --git a/stack-graphs/src/stats.rs b/stack-graphs/src/stats.rs new file mode 100644 index 000000000..6b9f6ead8 --- /dev/null +++ b/stack-graphs/src/stats.rs @@ -0,0 +1,106 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use std::collections::HashMap; +use std::hash::Hash; + +use itertools::Itertools; + +/// Frequency distribution maintains the frequency of T values. +#[derive(Clone, Debug, Default)] +pub struct FrequencyDistribution +where + T: Eq + Hash, +{ + values: HashMap, + total: usize, +} + +impl FrequencyDistribution { + pub fn record(&mut self, value: T) { + *self.values.entry(value).or_default() += 1; + self.total += 1; + } + + // The number of recorded values. + pub fn count(&self) -> usize { + return self.total; + } + + // The number of unique recorded values. + pub fn unique(&self) -> usize { + return self.values.len(); + } + + pub fn frequencies(&self) -> FrequencyDistribution { + let mut fs = FrequencyDistribution::default(); + for count in self.values.values() { + fs.record(*count); + } + fs + } +} + +impl FrequencyDistribution { + pub fn quantiles(&self, q: usize) -> Vec<&T> { + if q == 0 || self.total == 0 { + return vec![]; + } + + let mut it = self.values.iter().sorted_by_key(|e| e.0); + let mut total_count = 0; + let mut last_value; + let mut result = Vec::new(); + + if let Some((value, count)) = it.next() { + total_count += count; + last_value = value; + } else { + return vec![]; + } + result.push(last_value); + + for k in 1..=q { + let limit = ((self.total as f64 * k as f64) / q as f64).round() as usize; + while total_count < limit { + if let Some((value, count)) = it.next() { + total_count += count; + last_value = value; + } else { + break; + } + } + result.push(last_value); + } + + result + } +} + +impl std::ops::AddAssign for FrequencyDistribution +where + T: Eq + Hash, +{ + fn add_assign(&mut self, rhs: Self) { + for (value, count) in rhs.values { + *self.values.entry(value).or_default() += count; + } + self.total += rhs.total; + } +} + +impl std::ops::AddAssign<&Self> for FrequencyDistribution +where + T: Eq + Hash + Clone, +{ + fn add_assign(&mut self, rhs: &Self) { + for (value, count) in &rhs.values { + *self.values.entry(value.clone()).or_default() += count; + } + self.total += rhs.total; + } +} diff --git a/stack-graphs/src/stitching.rs b/stack-graphs/src/stitching.rs index cf1ecbdd7..99aaa8df1 100644 --- a/stack-graphs/src/stitching.rs +++ b/stack-graphs/src/stitching.rs @@ -35,11 +35,15 @@ //! [`Database`]: struct.Database.html //! [`PathStitcher`]: struct.PathStitcher.html +use std::cmp::Ordering; use std::collections::HashMap; use std::collections::VecDeque; #[cfg(feature = "copious-debugging")] use std::fmt::Display; +use itertools::izip; +use itertools::Itertools; + use crate::arena::Arena; use crate::arena::Handle; use crate::arena::HandleSet; @@ -50,6 +54,8 @@ use crate::arena::SupplementalArena; use crate::cycles::Appendables; use crate::cycles::AppendingCycleDetector; use crate::cycles::SimilarPathDetector; +use crate::cycles::SimilarPathStats; +use crate::graph::Degree; use crate::graph::Edge; use crate::graph::File; use crate::graph::Node; @@ -59,7 +65,9 @@ use crate::partial::Cyclicity; use crate::partial::PartialPath; use crate::partial::PartialPaths; use crate::partial::PartialSymbolStack; +use crate::paths::Extend; use crate::paths::PathResolutionError; +use crate::stats::FrequencyDistribution; use crate::CancellationError; use crate::CancellationFlag; @@ -172,46 +180,91 @@ where //------------------------------------------------------------------------------------------------- // Candidates -/// A trait to support finding candidates for partial path extension. Requires an accompanying -/// [`ToAppendable`] implementation to convert the candidate handles into [`Appendable`]s. -pub trait Candidates { - fn find_candidates( +/// A trait to support finding candidates for partial path extension. The candidates are represented +/// by handles `H`, which are mapped to appendables `A` using the database `Db`. Loading errors are +/// reported as values of the `Err` type. +pub trait ForwardCandidates +where + A: Appendable, + Db: ToAppendable, +{ + /// Load possible forward candidates for the given partial path into this candidates instance. + /// Must be called before [`get_forward_candidates`] to allow lazy-loading implementations. + fn load_forward_candidates( &mut self, - graph: &StackGraph, - partials: &mut PartialPaths, - path: &PartialPath, - result: &mut R, - ) where + _path: &PartialPath, + _cancellation_flag: &dyn CancellationFlag, + ) -> Result<(), Err> { + Ok(()) + } + + /// Get forward candidates for extending the given partial path and add them to the provided + /// result instance. If this instance loads data lazily, this only considers previously loaded + /// data. + fn get_forward_candidates(&mut self, path: &PartialPath, result: &mut R) + where R: std::iter::Extend; + + /// Get the number of available candidates that share the given path's end node. + fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree; + + /// Get the graph, partial path arena, and database backing this candidates instance. + fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &Db); } //------------------------------------------------------------------------------------------------- // FileEdges /// Acts as a database of the edges in the graph. -pub struct GraphEdges(pub Option>); +pub struct GraphEdgeCandidates<'a> { + graph: &'a StackGraph, + partials: &'a mut PartialPaths, + file: Option>, + edges: GraphEdges, +} -impl ToAppendable for GraphEdges { - fn get_appendable<'a>(&'a self, value: &'a Edge) -> &'a Edge { - value +impl<'a> GraphEdgeCandidates<'a> { + pub fn new( + graph: &'a StackGraph, + partials: &'a mut PartialPaths, + file: Option>, + ) -> Self { + Self { + graph, + partials, + file, + edges: GraphEdges, + } } } -impl Candidates for GraphEdges { - fn find_candidates( - &mut self, - graph: &StackGraph, - _partials: &mut PartialPaths, - path: &PartialPath, - result: &mut R, - ) where +impl ForwardCandidates for GraphEdgeCandidates<'_> { + fn get_forward_candidates(&mut self, path: &PartialPath, result: &mut R) + where R: std::iter::Extend, { - result.extend( - graph - .outgoing_edges(path.end_node) - .filter(|e| self.0.map_or(true, |file| graph[e.sink].is_in_file(file))), - ); + result.extend(self.graph.outgoing_edges(path.end_node).filter(|e| { + self.file + .map_or(true, |file| self.graph[e.sink].is_in_file(file)) + })); + } + + fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree { + self.graph.incoming_edge_degree(path.end_node) + } + + fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &GraphEdges) { + (self.graph, self.partials, &self.edges) + } +} + +/// A dummy type to act as the "database" for graph edges. Its [`ToAppendable`] implementation +/// is the identity on edges. +pub struct GraphEdges; + +impl ToAppendable for GraphEdges { + fn get_appendable<'a>(&'a self, edge: &'a Edge) -> &'a Edge { + edge } } @@ -233,7 +286,13 @@ pub struct Database { symbol_stack_keys: ListArena>, symbol_stack_key_cache: HashMap, paths_by_start_node: SupplementalArena>>, - root_paths_by_precondition: SupplementalArena>>, + root_paths_by_precondition_prefix: + SupplementalArena>>, + root_paths_by_precondition_with_variable: + SupplementalArena>>, + root_paths_by_precondition_without_variable: + SupplementalArena>>, + incoming_paths: SupplementalArena, } impl Database { @@ -245,7 +304,10 @@ impl Database { symbol_stack_keys: List::new_arena(), symbol_stack_key_cache: HashMap::new(), paths_by_start_node: SupplementalArena::new(), - root_paths_by_precondition: SupplementalArena::new(), + root_paths_by_precondition_prefix: SupplementalArena::new(), + root_paths_by_precondition_with_variable: SupplementalArena::new(), + root_paths_by_precondition_without_variable: SupplementalArena::new(), + incoming_paths: SupplementalArena::new(), } } @@ -258,7 +320,10 @@ impl Database { self.symbol_stack_keys.clear(); self.symbol_stack_key_cache.clear(); self.paths_by_start_node.clear(); - self.root_paths_by_precondition.clear(); + self.root_paths_by_precondition_prefix.clear(); + self.root_paths_by_precondition_with_variable.clear(); + self.root_paths_by_precondition_without_variable.clear(); + self.incoming_paths.clear(); } /// Adds a partial path to this database. We do not deduplicate partial paths in any way; it's @@ -270,6 +335,7 @@ impl Database { path: PartialPath, ) -> Handle { let start_node = path.start_node; + let end_node = path.end_node; copious_debugging!( " Add {} path to database {}", if graph[start_node].is_root() { @@ -286,23 +352,57 @@ impl Database { if graph[start_node].is_root() { // The join node is root, so there's no need to use half-open symbol stacks here, as we // do for [`PartialPath::concatenate`][]. - let key = SymbolStackKey::from_partial_symbol_stack( + let mut key = SymbolStackKey::from_partial_symbol_stack( partials, self, symbol_stack_precondition, ); if !key.is_empty() { - let key_handle = key.back_handle(); - self.root_paths_by_precondition[key_handle].push(handle); + match symbol_stack_precondition.has_variable() { + true => self.root_paths_by_precondition_with_variable[key.back_handle()] + .push(handle), + false => self.root_paths_by_precondition_without_variable[key.back_handle()] + .push(handle), + } + } + while key.pop_back(self).is_some() && !key.is_empty() { + self.root_paths_by_precondition_prefix[key.back_handle()].push(handle); } } else { // Otherwise index it by its source node. self.paths_by_start_node[start_node].push(handle); } + self.incoming_paths[end_node] += Degree::One; handle } + /// Find all partial paths in this database that start at the given path's end node. + /// If the end node is the root node, returns paths with a symbol stack precondition + /// that are compatible with the path's symbol stack post condition. + pub fn find_candidate_partial_paths( + &mut self, + graph: &StackGraph, + partials: &mut PartialPaths, + path: &PartialPath, + result: &mut R, + ) where + R: std::iter::Extend>, + { + if graph[path.end_node].is_root() { + // The join node is root, so there's no need to use half-open symbol stacks here, as we + // do for [`PartialPath::concatenate`][]. + self.find_candidate_partial_paths_from_root( + graph, + partials, + Some(path.symbol_stack_postcondition), + result, + ); + } else { + self.find_candidate_partial_paths_from_node(graph, partials, path.end_node, result); + } + } + /// Find all partial paths in this database that start at the root node, and have a symbol /// stack precondition that is compatible with a given symbol stack. #[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))] @@ -310,7 +410,7 @@ impl Database { &mut self, graph: &StackGraph, partials: &mut PartialPaths, - symbol_stack: Option, + symbol_stack: Option, result: &mut R, ) where R: std::iter::Extend>, @@ -318,31 +418,76 @@ impl Database { // If the path currently ends at the root node, then we need to look up partial paths whose // symbol stack precondition is compatible with the path. match symbol_stack { - Some(mut symbol_stack) => loop { + Some(symbol_stack) => { + let mut key = + SymbolStackKey::from_partial_symbol_stack(partials, self, symbol_stack); copious_debugging!( " Search for symbol stack <{}>", - symbol_stack.display(graph, self) + key.display(graph, self) ); - let key_handle = symbol_stack.back_handle(); - if let Some(paths) = self.root_paths_by_precondition.get(key_handle) { + // paths that have exactly this symbol stack + if let Some(paths) = self + .root_paths_by_precondition_without_variable + .get(key.back_handle()) + { #[cfg(feature = "copious-debugging")] { for path in paths { copious_debugging!( - " Found path {}", + " Found path with exact stack {}", self[*path].display(graph, partials) ); } } result.extend(paths.iter().copied()); } - if symbol_stack.pop_back(self).is_none() { - break; + // paths that have an extension of this symbol stack + if symbol_stack.has_variable() { + if let Some(paths) = self + .root_paths_by_precondition_prefix + .get(key.back_handle()) + { + #[cfg(feature = "copious-debugging")] + { + for path in paths { + copious_debugging!( + " Found path with smaller stack {}", + self[*path].display(graph, partials) + ); + } + } + result.extend(paths.iter().copied()); + } } - }, + loop { + // paths that have a prefix of this symbol stack + if let Some(paths) = self + .root_paths_by_precondition_with_variable + .get(key.back_handle()) + { + #[cfg(feature = "copious-debugging")] + { + for path in paths { + copious_debugging!( + " Found path with smaller stack {}", + self[*path].display(graph, partials) + ); + } + } + result.extend(paths.iter().copied()); + } + if key.pop_back(self).is_none() { + break; + } + } + } None => { copious_debugging!(" Search for all root paths"); - for (_, paths) in self.root_paths_by_precondition.iter() { + for (_, paths) in self + .root_paths_by_precondition_with_variable + .iter() + .chain(self.root_paths_by_precondition_without_variable.iter()) + { #[cfg(feature = "copious-debugging")] { for path in paths { @@ -388,6 +533,11 @@ impl Database { } } + /// Returns the number of paths in this database that share the given end node. + pub fn get_incoming_path_degree(&self, end_node: Handle) -> Degree { + self.incoming_paths[end_node] + } + /// Determines which nodes in the stack graph are “local”, taking into account the partial /// paths in this database. /// @@ -500,28 +650,43 @@ impl ToAppendable, PartialPath> for Database { } } -impl Candidates> for Database { - fn find_candidates( - &mut self, - graph: &StackGraph, - partials: &mut PartialPaths, - path: &PartialPath, - result: &mut R, - ) where +pub struct DatabaseCandidates<'a> { + graph: &'a StackGraph, + partials: &'a mut PartialPaths, + database: &'a mut Database, +} + +impl<'a> DatabaseCandidates<'a> { + pub fn new( + graph: &'a StackGraph, + partials: &'a mut PartialPaths, + database: &'a mut Database, + ) -> Self { + Self { + graph, + partials, + database, + } + } +} + +impl ForwardCandidates, PartialPath, Database, CancellationError> + for DatabaseCandidates<'_> +{ + fn get_forward_candidates(&mut self, path: &PartialPath, result: &mut R) + where R: std::iter::Extend>, { - if graph[path.end_node].is_root() { - // The join node is root, so there's no need to use half-open symbol stacks here, as we - // do for [`PartialPath::concatenate`][]. - let key = SymbolStackKey::from_partial_symbol_stack( - partials, - self, - path.symbol_stack_postcondition, - ); - self.find_candidate_partial_paths_from_root(graph, partials, Some(key), result); - } else { - self.find_candidate_partial_paths_from_node(graph, partials, path.end_node, result); - } + self.database + .find_candidate_partial_paths(self.graph, self.partials, path, result); + } + + fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree { + self.database.get_incoming_path_degree(path.end_node) + } + + fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &Database) { + (self.graph, self.partials, self.database) } } @@ -660,16 +825,24 @@ impl<'a> Display for DisplaySymbolStackKey<'a> { /// [`find_all_complete_partial_paths`]: #method.find_all_complete_partial_paths pub struct ForwardPartialPathStitcher { candidates: Vec, - queue: VecDeque<(PartialPath, AppendingCycleDetector)>, + extensions: Vec<(PartialPath, AppendingCycleDetector)>, + queue: VecDeque<(PartialPath, AppendingCycleDetector, bool)>, // tracks the number of initial paths in the queue because we do not want call // extend_until on those - initial_paths: usize, + initial_paths_in_queue: usize, // next_iteration is a tuple of queues instead of an queue of tuples so that the path queue // can be cheaply exposed through the C API as a continuous memory block - next_iteration: (VecDeque, VecDeque>), + next_iteration: ( + VecDeque, + VecDeque>, + VecDeque, + ), appended_paths: Appendables, similar_path_detector: Option>, + check_only_join_nodes: bool, max_work_per_phase: usize, + initial_paths: usize, + stats: Option, #[cfg(feature = "copious-debugging")] phase_number: usize, } @@ -687,26 +860,87 @@ impl ForwardPartialPathStitcher { I: IntoIterator, { let mut appended_paths = Appendables::new(); - let next_iteration: (VecDeque<_>, VecDeque<_>) = initial_partial_paths + let next_iteration: (VecDeque<_>, VecDeque<_>, VecDeque<_>) = initial_partial_paths .into_iter() .map(|p| { let c = AppendingCycleDetector::from(&mut appended_paths, p.clone().into()); - (p, c) + (p, c, false) }) - .unzip(); + .multiunzip(); + let initial_paths = next_iteration.0.len(); Self { candidates: Vec::new(), + extensions: Vec::new(), queue: VecDeque::new(), - initial_paths: next_iteration.0.len(), + initial_paths_in_queue: initial_paths, next_iteration, appended_paths, - similar_path_detector: None, + // By default, all paths are checked for similarity + similar_path_detector: Some(SimilarPathDetector::new()), + // By default, all nodes are checked for cycles and (if enabled) similarity + check_only_join_nodes: false, // By default, there's no artificial bound on the amount of work done per phase max_work_per_phase: usize::MAX, + initial_paths, + stats: None, #[cfg(feature = "copious-debugging")] phase_number: 1, } } + + /// Sets whether similar path detection should be enabled during path stitching. Paths are similar + /// if start and end node, and pre- and postconditions are the same. The presence of similar paths + /// can lead to exponential blow up during path stitching. Similar path detection is enabled by + /// default. + pub fn set_similar_path_detection(&mut self, detect_similar_paths: bool) { + if !detect_similar_paths { + self.similar_path_detector = None; + } else if self.similar_path_detector.is_none() { + let mut similar_path_detector = SimilarPathDetector::new(); + similar_path_detector.set_collect_stats(self.stats.is_some()); + self.similar_path_detector = Some(similar_path_detector); + } + } + + /// Sets whether all nodes are checked for cycles and (if enabled) similar paths, or only nodes with multiple + /// incoming candidates. Checking only join nodes is **unsafe** unless the database of candidates is stable + /// between all stitching phases. If paths are added to the database from one phase to another, for example if + /// paths are dynamically loaded from storage, setting this to true is incorrect and might lead to non-termination! + pub fn set_check_only_join_nodes(&mut self, check_only_join_nodes: bool) { + self.check_only_join_nodes = check_only_join_nodes; + } + + /// Sets the maximum amount of work that can be performed during each phase of the algorithm. + /// By bounding our work this way, you can ensure that it's not possible for our CPU-bound + /// algorithm to starve any worker threads or processes that you might be using. If you don't + /// call this method, then we allow ourselves to process all of the extensions of all of the + /// paths found in the previous phase, with no additional bound. + pub fn set_max_work_per_phase(&mut self, max_work_per_phase: usize) { + self.max_work_per_phase = max_work_per_phase; + } + + /// Sets whether to collect statistics during stitching. + pub fn set_collect_stats(&mut self, collect_stats: bool) { + if !collect_stats { + self.stats = None; + } else if self.stats.is_none() { + let mut stats = Stats::default(); + stats.initial_paths.record(self.initial_paths); + self.stats = Some(stats); + } + if let Some(similar_path_detector) = &mut self.similar_path_detector { + similar_path_detector.set_collect_stats(collect_stats); + } + } + + pub fn into_stats(mut self) -> Stats { + if let (Some(stats), Some(similar_path_detector)) = + (&mut self.stats, self.similar_path_detector) + { + stats.similar_paths_stats = similar_path_detector.stats(); + } + self.stats.unwrap_or_default() + } } impl ForwardPartialPathStitcher { @@ -730,79 +964,67 @@ impl ForwardPartialPathStitcher { self.next_iteration.0.as_mut_slices().0 } - /// Sets whether similar path detection should be enabled during path stitching. Paths are similar - /// if start and end node, and pre- and postconditions are the same. The presence of similar paths - /// can lead to exponential blow up during path stitching. Similar path detection is disabled by - /// default because of the associated preformance cost. - pub fn set_similar_path_detection(&mut self, detect_similar_paths: bool) { - if detect_similar_paths { - self.similar_path_detector = Some(SimilarPathDetector::new()); - } else { - self.similar_path_detector = None; - } - } - - /// Sets the maximum amount of work that can be performed during each phase of the algorithm. - /// By bounding our work this way, you can ensure that it's not possible for our CPU-bound - /// algorithm to starve any worker threads or processes that you might be using. If you don't - /// call this method, then we allow ourselves to process all of the extensions of all of the - /// paths found in the previous phase, with no additional bound. - pub fn set_max_work_per_phase(&mut self, max_work_per_phase: usize) { - self.max_work_per_phase = max_work_per_phase; - } - /// Attempts to extend one partial path as part of the algorithm. When calling this function, /// you are responsible for ensuring that `db` already contains all of the possible appendables /// that we might want to extend `partial_path` with. - fn extend( + fn extend( &mut self, - graph: &StackGraph, - partials: &mut PartialPaths, - db: &mut Db, + candidates: &mut C, partial_path: &PartialPath, cycle_detector: AppendingCycleDetector, + has_split: bool, ) -> usize where A: Appendable, - Db: Candidates + ToAppendable, + Db: ToAppendable, + C: ForwardCandidates, { + let check_cycle = !self.check_only_join_nodes + || partial_path.start_node == partial_path.end_node + || candidates.get_joining_candidate_degree(partial_path) == Degree::Multiple; + + let (graph, partials, db) = candidates.get_graph_partials_and_db(); copious_debugging!(" Extend {}", partial_path.display(graph, partials)); - // check is path is cyclic, in which case we do not extend it - let has_precondition_variables = partial_path.symbol_stack_precondition.has_variable() - || partial_path.scope_stack_precondition.has_variable(); - let cycles = cycle_detector - .is_cyclic(graph, partials, db, &mut self.appended_paths) - .expect("cyclic test failed when stitching partial paths"); - let cyclic = match has_precondition_variables { - // If the precondition has no variables, we allow cycles that strengthen the - // precondition, because we know they cannot strengthen the precondition of - // the overall path. - false => !cycles - .into_iter() - .all(|c| c == Cyclicity::StrengthensPrecondition), - // If the precondition has variables, do not allow any cycles, not even those - // that strengthen the precondition. This is more strict than necessary. Better - // might be to disallow precondition strengthening cycles only if they would - // strengthen the overall path precondition. - true => !cycles.is_empty(), - }; - if cyclic { - copious_debugging!(" is discontinued: cyclic"); - return 0; + if check_cycle { + // Check is path is cyclic, in which case we do not extend it. We only do this if the start and end nodes are the same, + // or the current end node has multiple incoming edges. If neither of these hold, the path cannot end in a cycle. + let has_precondition_variables = partial_path.symbol_stack_precondition.has_variable() + || partial_path.scope_stack_precondition.has_variable(); + let cycles = cycle_detector + .is_cyclic(graph, partials, db, &mut self.appended_paths) + .expect("cyclic test failed when stitching partial paths"); + let cyclic = match has_precondition_variables { + // If the precondition has no variables, we allow cycles that strengthen the + // precondition, because we know they cannot strengthen the precondition of + // the overall path. + false => !cycles + .into_iter() + .all(|c| c == Cyclicity::StrengthensPrecondition), + // If the precondition has variables, do not allow any cycles, not even those + // that strengthen the precondition. This is more strict than necessary. Better + // might be to disallow precondition strengthening cycles only if they would + // strengthen the overall path precondition. + true => !cycles.is_empty(), + }; + if cyclic { + copious_debugging!(" is discontinued: cyclic"); + return 0; + } } // find candidates to append self.candidates.clear(); - db.find_candidates(graph, partials, partial_path, &mut self.candidates); + candidates.get_forward_candidates(partial_path, &mut self.candidates); + let (graph, partials, db) = candidates.get_graph_partials_and_db(); // try to extend path with candidates - let extension_count = self.candidates.len(); - self.next_iteration.0.reserve(extension_count); - self.next_iteration.1.reserve(extension_count); - for extension in &self.candidates { - let extension_path = db.get_appendable(extension); - copious_debugging!(" with {}", extension_path.display(graph, partials)); + let candidate_count = self.candidates.len(); + self.extensions.clear(); + self.extensions.reserve(candidate_count); + for candidate in &self.candidates { + let appendable = db.get_appendable(candidate); + copious_debugging!(" with {}", appendable.display(graph, partials)); let mut new_partial_path = partial_path.clone(); let mut new_cycle_detector = cycle_detector.clone(); @@ -810,29 +1032,79 @@ impl ForwardPartialPathStitcher { // partial path, just skip the extension — it's not a fatal error. #[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))] { - if let Err(err) = extension_path.append_to(graph, partials, &mut new_partial_path) { + if let Err(err) = appendable.append_to(graph, partials, &mut new_partial_path) { copious_debugging!(" is invalid: {:?}", err); continue; } - copious_debugging!(" is {}", new_partial_path.display(graph, partials)); - new_cycle_detector.append(&mut self.appended_paths, extension.clone()); + } + new_cycle_detector.append(&mut self.appended_paths, candidate.clone()); + copious_debugging!(" is {}", new_partial_path.display(graph, partials)); + self.extensions.push((new_partial_path, new_cycle_detector)); + } + + let extension_count = self.extensions.len(); + let new_has_split = has_split || self.extensions.len() > 1; + self.next_iteration.0.reserve(extension_count); + self.next_iteration.1.reserve(extension_count); + self.next_iteration.2.reserve(extension_count); + for (new_partial_path, new_cycle_detector) in self.extensions.drain(..) { + let check_similar_path = new_has_split + && (!self.check_only_join_nodes + || candidates.get_joining_candidate_degree(&new_partial_path) + == Degree::Multiple); + let (graph, partials, _) = candidates.get_graph_partials_and_db(); + if check_similar_path { if let Some(similar_path_detector) = &mut self.similar_path_detector { - if similar_path_detector.has_similar_path( + if similar_path_detector.add_path( graph, partials, &new_partial_path, - |ps, left, right| left.equals(ps, right), + |ps, left, right| { + if !left.equals(ps, right) { + None + } else { + if left.shadows(ps, right) { + Some(Ordering::Less) + } else if right.shadows(ps, left) { + Some(Ordering::Greater) + } else { + Some(Ordering::Equal) + } + } + }, ) { + copious_debugging!( + " extension {}", + new_partial_path.display(graph, partials) + ); copious_debugging!(" is rejected: too many similar"); continue; } } } - self.next_iteration.0.push_back(new_partial_path); - self.next_iteration.1.push_back(new_cycle_detector); + + self.next_iteration.0.push(new_partial_path); + self.next_iteration.1.push(new_cycle_detector); + self.next_iteration.2.push(new_has_split); } - extension_count + if let Some(stats) = &mut self.stats { + let (graph, _, _) = candidates.get_graph_partials_and_db(); + let end_node = &graph[partial_path.end_node]; + if end_node.is_root() { + stats.candidates_per_root_path.record(candidate_count); + stats.extensions_per_root_path.record(extension_count); + stats.root_visits += 1; + } else { + stats.candidates_per_node_path.record(candidate_count); + stats.extensions_per_node_path.record(extension_count); + stats.node_visits.record(end_node.id()); + } + if extension_count == 0 { + stats.terminal_path_lengh.record(partial_path.edges.len()); + } + } + candidate_count } /// Returns whether the algorithm has completed. @@ -852,32 +1124,31 @@ impl ForwardPartialPathStitcher { /// or not. It is not called on the initial paths. /// /// [`previous_phase_partial_paths`]: #method.previous_phase_partial_paths - pub fn process_next_phase( - &mut self, - graph: &StackGraph, - partials: &mut PartialPaths, - db: &mut Db, - extend_while: E, - ) where + pub fn process_next_phase(&mut self, candidates: &mut C, extend_while: E) + where A: Appendable, - Db: Candidates + ToAppendable, + Db: ToAppendable, + C: ForwardCandidates, E: Fn(&StackGraph, &mut PartialPaths, &PartialPath) -> bool, { copious_debugging!("==> Start phase {}", self.phase_number); - self.queue.extend( - self.next_iteration - .0 - .drain(..) - .zip(self.next_iteration.1.drain(..)), - ); + self.queue.extend(izip!( + self.next_iteration.0.drain(..), + self.next_iteration.1.drain(..), + self.next_iteration.2.drain(..), + )); + if let Some(stats) = &mut self.stats { + stats.queued_paths_per_phase.record(self.queue.len()); + } let mut work_performed = 0; - while let Some((partial_path, cycle_detector)) = self.queue.pop_front() { + while let Some((partial_path, cycle_detector, has_split)) = self.queue.pop_front() { + let (graph, partials, _) = candidates.get_graph_partials_and_db(); copious_debugging!( "--> Candidate partial path {}", partial_path.display(graph, partials) ); - if self.initial_paths > 0 { - self.initial_paths -= 1; + if self.initial_paths_in_queue > 0 { + self.initial_paths_in_queue -= 1; } else if !extend_while(graph, partials, &partial_path) { copious_debugging!( " Do not extend {}", @@ -885,11 +1156,14 @@ impl ForwardPartialPathStitcher { ); continue; } - work_performed += self.extend(graph, partials, db, &partial_path, cycle_detector); + work_performed += self.extend(candidates, &partial_path, cycle_detector, has_split); if work_performed >= self.max_work_per_phase { break; } } + if let Some(stats) = &mut self.stats { + stats.processed_paths_per_phase.record(work_performed); + } #[cfg(feature = "copious-debugging")] { @@ -926,15 +1200,16 @@ impl ForwardPartialPathStitcher { graph: &StackGraph, partials: &mut PartialPaths, file: Handle, + config: StitcherConfig, cancellation_flag: &dyn CancellationFlag, mut visit: F, - ) -> Result<(), CancellationError> + ) -> Result where F: FnMut(&StackGraph, &mut PartialPaths, &PartialPath), { fn as_complete_as_necessary(graph: &StackGraph, path: &PartialPath) -> bool { path.starts_at_endpoint(graph) - && (path.ends_at_endpoint(graph) || graph[path.end_node].is_jump_to()) + && (path.ends_at_endpoint(graph) || path.ends_in_jump(graph)) } let initial_paths = graph @@ -945,21 +1220,28 @@ impl ForwardPartialPathStitcher { .collect::>(); let mut stitcher = ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths); + config.apply(&mut stitcher); + stitcher.set_check_only_join_nodes(true); + + let mut accepted_path_length = FrequencyDistribution::default(); while !stitcher.is_complete() { cancellation_flag.check("finding complete partial paths")?; stitcher.process_next_phase( - graph, - partials, - &mut GraphEdges(Some(file)), + &mut GraphEdgeCandidates::new(graph, partials, Some(file)), |g, _ps, p| !as_complete_as_necessary(g, p), ); for path in stitcher.previous_phase_partial_paths() { if as_complete_as_necessary(graph, path) { + accepted_path_length.record(path.edges.len()); visit(graph, partials, path); } } } - Ok(()) + + Ok(Stats { + accepted_path_length, + ..stitcher.into_stats() + }) } } @@ -975,20 +1257,22 @@ impl ForwardPartialPathStitcher { /// [`process_next_phase`][] manually. /// /// [`process_next_phase`]: #method.process_next_phase - pub fn find_all_complete_partial_paths( - graph: &StackGraph, - partials: &mut PartialPaths, - db: &mut Db, + pub fn find_all_complete_partial_paths( + candidates: &mut C, starting_nodes: I, + config: StitcherConfig, cancellation_flag: &dyn CancellationFlag, mut visit: F, - ) -> Result<(), CancellationError> + ) -> Result where I: IntoIterator>, A: Appendable, - Db: Candidates + ToAppendable, + Db: ToAppendable, + C: ForwardCandidates, F: FnMut(&StackGraph, &mut PartialPaths, &PartialPath), + Err: std::convert::From, { + let (graph, partials, _) = candidates.get_graph_partials_and_db(); let initial_paths = starting_nodes .into_iter() .filter(|n| graph[*n].is_reference()) @@ -1000,15 +1284,134 @@ impl ForwardPartialPathStitcher { .collect::>(); let mut stitcher = ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths); + config.apply(&mut stitcher); + stitcher.set_check_only_join_nodes(true); + + let mut accepted_path_length = FrequencyDistribution::default(); while !stitcher.is_complete() { cancellation_flag.check("finding complete partial paths")?; - stitcher.process_next_phase(graph, partials, db, |_, _, _| true); + for path in stitcher.previous_phase_partial_paths() { + candidates.load_forward_candidates(path, cancellation_flag)?; + } + stitcher.process_next_phase(candidates, |_, _, _| true); + let (graph, partials, _) = candidates.get_graph_partials_and_db(); for path in stitcher.previous_phase_partial_paths() { if path.is_complete(graph) { + accepted_path_length.record(path.edges.len()); visit(graph, partials, path); } } } - Ok(()) + + Ok(Stats { + accepted_path_length, + ..stitcher.into_stats() + }) + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stats { + /// The distribution of the number of initial paths + pub initial_paths: FrequencyDistribution, + /// The distribution of the number of queued paths per stitching phase + pub queued_paths_per_phase: FrequencyDistribution, + /// The distribution of the number of processed paths per stitching phase + pub processed_paths_per_phase: FrequencyDistribution, + /// The distribution of the length of accepted paths + pub accepted_path_length: FrequencyDistribution, + /// The distribution of the maximal length of paths (when they cannot be extended more) + pub terminal_path_lengh: FrequencyDistribution, + /// The distribution of the number of candidates for paths ending in a regular node + pub candidates_per_node_path: FrequencyDistribution, + /// The distribution of the number of candidates for paths ending in the root node + pub candidates_per_root_path: FrequencyDistribution, + /// The distribution of the number of extensions (accepted candidates) for paths ending in a regular node + pub extensions_per_node_path: FrequencyDistribution, + /// The distribution of the number of extensions (accepted candidates) for paths ending in the root node + pub extensions_per_root_path: FrequencyDistribution, + /// The number of times the root node is visited + pub root_visits: usize, + /// The distribution of the number of times a regular node is visited + pub node_visits: FrequencyDistribution, + /// The distribution of the number of similar paths between node pairs. + pub similar_paths_stats: SimilarPathStats, +} + +impl std::ops::AddAssign for Stats { + fn add_assign(&mut self, rhs: Self) { + self.initial_paths += rhs.initial_paths; + self.queued_paths_per_phase += rhs.queued_paths_per_phase; + self.processed_paths_per_phase += rhs.processed_paths_per_phase; + self.accepted_path_length += rhs.accepted_path_length; + self.terminal_path_lengh += rhs.terminal_path_lengh; + self.candidates_per_node_path += rhs.candidates_per_node_path; + self.candidates_per_root_path += rhs.candidates_per_root_path; + self.extensions_per_node_path += rhs.extensions_per_node_path; + self.extensions_per_root_path += rhs.extensions_per_root_path; + self.root_visits += rhs.root_visits; + self.node_visits += rhs.node_visits; + self.similar_paths_stats += rhs.similar_paths_stats; + } +} + +impl std::ops::AddAssign<&Self> for Stats { + fn add_assign(&mut self, rhs: &Self) { + self.initial_paths += &rhs.initial_paths; + self.processed_paths_per_phase += &rhs.processed_paths_per_phase; + self.accepted_path_length += &rhs.accepted_path_length; + self.terminal_path_lengh += &rhs.terminal_path_lengh; + self.candidates_per_node_path += &rhs.candidates_per_node_path; + self.candidates_per_root_path += &rhs.candidates_per_root_path; + self.extensions_per_node_path += &rhs.extensions_per_node_path; + self.extensions_per_root_path += &rhs.extensions_per_root_path; + self.root_visits += rhs.root_visits; + self.node_visits += &rhs.node_visits; + self.similar_paths_stats += &rhs.similar_paths_stats; + } +} + +/// Configuration for partial path stitchers. +#[derive(Clone, Copy, Debug)] +pub struct StitcherConfig { + /// Enables similar path detection during path stitching. + detect_similar_paths: bool, + /// Collect statistics about path stitching. + collect_stats: bool, +} + +impl StitcherConfig { + pub fn detect_similar_paths(&self) -> bool { + self.detect_similar_paths + } + + pub fn with_detect_similar_paths(mut self, detect_similar_paths: bool) -> Self { + self.detect_similar_paths = detect_similar_paths; + self + } + + pub fn collect_stats(&self) -> bool { + self.collect_stats + } + + pub fn with_collect_stats(mut self, collect_stats: bool) -> Self { + self.collect_stats = collect_stats; + self + } +} + +impl StitcherConfig { + fn apply(&self, stitcher: &mut ForwardPartialPathStitcher) { + stitcher.set_similar_path_detection(self.detect_similar_paths); + stitcher.set_collect_stats(self.collect_stats); + } +} + +impl Default for StitcherConfig { + fn default() -> Self { + Self { + detect_similar_paths: true, + collect_stats: false, + } } } diff --git a/stack-graphs/src/storage.rs b/stack-graphs/src/storage.rs index 808697a4a..c3b3e8ebf 100644 --- a/stack-graphs/src/storage.rs +++ b/stack-graphs/src/storage.rs @@ -7,6 +7,7 @@ use bincode::error::DecodeError; use bincode::error::EncodeError; +use itertools::Itertools; use rusqlite::functions::FunctionFlags; use rusqlite::types::ValueRef; use rusqlite::Connection; @@ -19,6 +20,7 @@ use std::path::PathBuf; use thiserror::Error; use crate::arena::Handle; +use crate::graph::Degree; use crate::graph::File; use crate::graph::Node; use crate::graph::StackGraph; @@ -28,11 +30,11 @@ use crate::partial::PartialSymbolStack; use crate::serde; use crate::serde::FileFilter; use crate::stitching::Database; -use crate::stitching::ForwardPartialPathStitcher; +use crate::stitching::ForwardCandidates; use crate::CancellationError; use crate::CancellationFlag; -const VERSION: usize = 5; +const VERSION: usize = 6; const SCHEMA: &str = r#" CREATE TABLE metadata ( @@ -417,6 +419,7 @@ impl SQLiteWriter { graph: StackGraph::new(), partials: PartialPaths::new(), db: Database::new(), + stats: Stats::default(), } } } @@ -430,6 +433,7 @@ pub struct SQLiteReader { graph: StackGraph, partials: PartialPaths, db: Database, + stats: Stats, } impl SQLiteReader { @@ -452,6 +456,7 @@ impl SQLiteReader { graph: StackGraph::new(), partials: PartialPaths::new(), db: Database::new(), + stats: Stats::default(), }) } @@ -465,6 +470,8 @@ impl SQLiteReader { self.loaded_root_paths.clear(); self.partials.clear(); self.db.clear(); + + self.stats.clear(); } /// Clear path data that has been loaded into this reader instance. @@ -475,6 +482,8 @@ impl SQLiteReader { self.loaded_root_paths.clear(); self.partials.clear(); self.db.clear(); + + self.stats.clear_paths(); } /// Get the file's status in the database. If a tag is provided, it must match or the file @@ -516,7 +525,13 @@ impl SQLiteReader { /// Ensure the graph for the given file is loaded. pub fn load_graph_for_file(&mut self, file: &str) -> Result> { - Self::load_graph_for_file_inner(file, &mut self.graph, &mut self.loaded_graphs, &self.conn) + Self::load_graph_for_file_inner( + file, + &mut self.graph, + &mut self.loaded_graphs, + &self.conn, + &mut self.stats, + ) } fn load_graph_for_file_inner( @@ -524,13 +539,16 @@ impl SQLiteReader { graph: &mut StackGraph, loaded_graphs: &mut HashSet, conn: &Connection, + stats: &mut Stats, ) -> Result> { copious_debugging!("--> Load graph for {}", file); if !loaded_graphs.insert(file.to_string()) { copious_debugging!(" * Already loaded"); + stats.file_cached += 1; return Ok(graph.get_file(file).expect("loaded file to exist")); } copious_debugging!(" * Load from database"); + stats.file_loads += 1; let mut stmt = conn.prepare_cached("SELECT value FROM graphs WHERE file = ?")?; let value = stmt.query_row([file], |row| row.get::<_, Vec>(0))?; let (file_graph, _): (serde::StackGraph, usize) = @@ -552,6 +570,7 @@ impl SQLiteReader { &mut self.graph, &mut self.loaded_graphs, &self.conn, + &mut self.stats, )?; } Ok(()) @@ -566,8 +585,10 @@ impl SQLiteReader { copious_debugging!(" * Load extensions from node {}", node.display(&self.graph)); if !self.loaded_node_paths.insert(node) { copious_debugging!(" > Already loaded"); + self.stats.node_path_cached += 1; return Ok(()); } + self.stats.node_path_loads += 1; let id = self.graph[node].id(); let file = id.file().expect("file node required"); let file = self.graph[file].name(); @@ -589,6 +610,7 @@ impl SQLiteReader { &mut self.graph, &mut self.loaded_graphs, &self.conn, + &mut self.stats, )?; let (path, _): (serde::PartialPath, usize) = bincode::decode_from_slice(&value, BINCODE_CONFIG)?; @@ -615,21 +637,23 @@ impl SQLiteReader { " * Load extensions from root with symbol stack {}", symbol_stack.display(&self.graph, &mut self.partials) ); - let symbol_stack_prefixes = - symbol_stack.storage_key_prefixes(&self.graph, &mut self.partials); - for symbol_stack in symbol_stack_prefixes { + let mut stmt = self.conn.prepare_cached( + "SELECT file,value from root_paths WHERE symbol_stack LIKE ? ESCAPE ?", + )?; + let (symbol_stack_patterns, escape) = + symbol_stack.storage_key_patterns(&self.graph, &mut self.partials); + for symbol_stack in symbol_stack_patterns { copious_debugging!( " * Load extensions from root with prefix symbol stack {}", symbol_stack ); if !self.loaded_root_paths.insert(symbol_stack.clone()) { copious_debugging!(" > Already loaded"); + self.stats.root_path_cached += 1; continue; } - let mut stmt = self - .conn - .prepare_cached("SELECT file,value from root_paths WHERE symbol_stack = ?")?; - let paths = stmt.query_map([symbol_stack], |row| { + self.stats.root_path_loads += 1; + let paths = stmt.query_map([symbol_stack, escape.clone()], |row| { let file = row.get::<_, String>(0)?; let value = row.get::<_, Vec>(1)?; Ok((file, value)) @@ -644,6 +668,7 @@ impl SQLiteReader { &mut self.graph, &mut self.loaded_graphs, &self.conn, + &mut self.stats, )?; let (path, _): (serde::PartialPath, usize) = bincode::decode_from_slice(&value, BINCODE_CONFIG)?; @@ -681,86 +706,114 @@ impl SQLiteReader { } /// Get the stack graph, partial paths arena, and path database for the currently loaded data. - pub fn get(&mut self) -> (&StackGraph, &mut PartialPaths, &mut Database) { - (&self.graph, &mut self.partials, &mut self.db) + pub fn get(&mut self) -> (&mut StackGraph, &mut PartialPaths, &mut Database) { + (&mut self.graph, &mut self.partials, &mut self.db) } - /// Find all paths using the given path stitcher. Data is lazily loaded if necessary. - pub fn find_all_complete_partial_paths( - &mut self, - starting_nodes: I, - cancellation_flag: &dyn CancellationFlag, - mut visit: F, - ) -> Result<()> - where - I: IntoIterator>, - F: FnMut(&StackGraph, &mut PartialPaths, &PartialPath), - { - let initial_paths = starting_nodes - .into_iter() - .map(|n| { - let mut p = PartialPath::from_node(&self.graph, &mut self.partials, n); - p.eliminate_precondition_stack_variables(&mut self.partials); - p - }) - .collect::>(); - let mut stitcher = ForwardPartialPathStitcher::from_partial_paths( - &self.graph, - &mut self.partials, - initial_paths, - ); - stitcher.set_max_work_per_phase(128); - while !stitcher.is_complete() { - cancellation_flag.check("find_all_complete_partial_paths")?; - for path in stitcher.previous_phase_partial_paths() { - self.load_partial_path_extensions(path, cancellation_flag)?; - } - stitcher.process_next_phase( - &self.graph, - &mut self.partials, - &mut self.db, - |_, _, _| true, - ); - for path in stitcher.previous_phase_partial_paths() { - if path.is_complete(&self.graph) { - visit(&self.graph, &mut self.partials, path); - } - } - } - Ok(()) + /// Return stats about this database reader. + pub fn stats(&self) -> Stats { + self.stats.clone() } } +// Methods for computing keys and patterns for a symbol stack. The format of a storage key is: +// +// has-var GS ( symbol (US symbol)* )? +// +// where has-var is "V" if the symbol stack has a variable, "X" otherwise. impl PartialSymbolStack { /// Returns a string representation of this symbol stack for indexing in the database. - fn storage_key(mut self, graph: &StackGraph, partials: &mut PartialPaths) -> String { + fn storage_key(self, graph: &StackGraph, partials: &mut PartialPaths) -> String { let mut key = String::new(); - while let Some(symbol) = self.pop_front(partials) { - if !key.is_empty() { - key += "\u{241F}"; - } - key += &graph[symbol.symbol]; + match self.has_variable() { + true => key += "V\u{241E}", + false => key += "X\u{241E}", } + key += &self + .iter(partials) + .map(|s| &graph[s.symbol]) + .join("\u{241F}"); key } /// Returns string representations for all prefixes of this symbol stack for querying the /// index in the database. - fn storage_key_prefixes( + fn storage_key_patterns( mut self, graph: &StackGraph, partials: &mut PartialPaths, - ) -> Vec { - let mut key_prefixes = vec![String::new()]; + ) -> (Vec, String) { + let mut key_patterns = Vec::new(); + let mut symbols = String::new(); while let Some(symbol) = self.pop_front(partials) { - let mut key = key_prefixes.last().unwrap().to_string(); - if !key.is_empty() { - key += "\u{241F}"; + if !symbols.is_empty() { + symbols += "\u{241F}"; } - key += &graph[symbol.symbol]; - key_prefixes.push(key); + let symbol = graph[symbol.symbol] + .replace("%", "\\%") + .replace("_", "\\_") + .to_string(); + symbols += &symbol; + // patterns for paths matching a prefix of this stack + key_patterns.push("V\u{241E}".to_string() + &symbols); + } + // pattern for paths matching exactly this stack + key_patterns.push("X\u{241E}".to_string() + &symbols); + if self.has_variable() { + // patterns for paths for which this stack is a prefix + key_patterns.push("_\u{241E}".to_string() + &symbols + "\u{241F}%"); + } + (key_patterns, "\\".to_string()) + } +} + +impl ForwardCandidates, PartialPath, Database, StorageError> for SQLiteReader { + fn load_forward_candidates( + &mut self, + path: &PartialPath, + cancellation_flag: &dyn CancellationFlag, + ) -> std::result::Result<(), StorageError> { + self.load_partial_path_extensions(path, cancellation_flag) + } + + fn get_forward_candidates(&mut self, path: &PartialPath, result: &mut R) + where + R: std::iter::Extend>, + { + self.db + .find_candidate_partial_paths(&self.graph, &mut self.partials, path, result); + } + + fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree { + self.db.get_incoming_path_degree(path.end_node) + } + + fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &Database) { + (&self.graph, &mut self.partials, &self.db) + } +} + +#[derive(Clone, Debug, Default)] +pub struct Stats { + pub file_loads: usize, + pub file_cached: usize, + pub root_path_loads: usize, + pub root_path_cached: usize, + pub node_path_loads: usize, + pub node_path_cached: usize, +} + +impl Stats { + fn clear(&mut self) { + *self = Stats::default(); + } + + fn clear_paths(&mut self) { + *self = Stats { + file_loads: self.file_loads, + file_cached: self.file_cached, + ..Stats::default() } - key_prefixes } } diff --git a/stack-graphs/src/visualization/visualization.css b/stack-graphs/src/visualization/visualization.css index 360594182..c3dfc091b 100644 --- a/stack-graphs/src/visualization/visualization.css +++ b/stack-graphs/src/visualization/visualization.css @@ -9,15 +9,33 @@ /* Paul Tol's Colorblind Friendly Color Scheme (vibrant) * Source: https://personal.sron.nl/~pault/ * + * In default order: + * + * orange #ee7733 * blue #0077bb * cyan #33bbee - * teal #009988 - * orange #ee7733 - * red #cc3311 * magenta #ee3377 + * red #cc3311 + * teal #009988 * grey #bbbbbb */ +/* Paul Tol's Colorblind Friendly Color Scheme (light) + * Source: https://personal.sron.nl/~pault/ + * + * In default order: + * + * light blue #77aadd + * orange #ee8866 + * light yellow #eedd88 + * pink #ffaabb + * light cyan #99ddff + * mint #44bb99 + * pear #bbcc33 + * olive #aaaa00 + * pale grey #dddddd + */ + .sg { width: 100%; height: 100%; @@ -50,30 +68,17 @@ /* --- drop scopes --- */ .sg .node.drop_scopes .background { - fill: #cc3311; r: 6px; } /* --- jump to scope --- */ .sg .node.jump_to_scope .background { - fill: #ee7733; r: 6px; - stroke: black; } /* --- pop symbol --- */ -.sg .node.pop_symbol .background, -.sg .node.pop_scoped_symbol .background { - fill: #009988; -} - -.sg .node.pop_symbol .arrow, -.sg .node.pop_scoped_symbol .arrow { - fill: #005b51; -} - .sg .node.pop_scoped_symbol .pop_scope { fill: #ee7733; r: 6px; @@ -81,21 +86,11 @@ } .sg .node.definition .background { - stroke: black; + stroke-width: 2px; } /* --- push symbol --- */ -.sg .node.push_symbol .background, -.sg .node.push_scoped_symbol .background { - fill: #33bbee; -} - -.sg .node.push_symbol .arrow, -.sg .node.push_scoped_symbol .arrow { - fill: #006e96; -} - .sg .node.push_scoped_symbol .push_scope { fill: #bbbbbb; r: 6px; @@ -112,15 +107,13 @@ } .sg .node.reference .background { - stroke: black; + stroke-width: 2px; } /* --- root --- */ .sg .node.root .background { - fill: #0077bb; r: 6px; - stroke: black; } /* --- scope --- */ @@ -131,14 +124,9 @@ } .sg .node.scope .background { - fill: #bbbbbb; r: 6px; } -.sg .node.scope.exported .background { - stroke: black; -} - .sg .node.scope .focus-point { r: 3px; fill: none; @@ -148,6 +136,10 @@ fill: black; } +.sg .node.scope.exported .background { + stroke-width: 2px; +} + /* --- plain labeled node --- */ .sg .node.scope.plain_labeled_node .border { @@ -156,18 +148,12 @@ } .sg .node.scope.plain_labeled_node .background { - fill: #bbbbbb; rx: 6px; } -.sg .node.scope.plain_labeled_node.exported .background { - stroke: black; -} - /* --- path highlight --- */ .sg .node.path-node .border { - stroke: #ee3377; stroke-width: 4px; stroke-dasharray: 5, 5; } @@ -185,30 +171,21 @@ } .sg .edge path { - stroke: black; stroke-width: 1px; fill: none; } .sg .edge text { font-size: 11pt; - stroke: black; stroke-width: 1px; dominant-baseline: central; } .sg .edge.path-edge path { - stroke: #ee3377; stroke-width: 3px; } -.sg .edge.path-edge text -{ - stroke: #ee3377; - fill: #ee3377; -} - /* ------------------------------------------------------------------------------------------------ * Jumps */ @@ -345,6 +322,40 @@ margin: 0px 1px; } +/* ------------------------------------------------------------------------------------------------ + * Legend + */ + +#sg-legend { + position: absolute; + left: 10px; + top: 10px; + background-color: #bbbbbb; + padding: 6px; + border-radius: 6px; + z-index: 1; +} + +#sg-legend h1 { + font-variant: small-caps; + font-weight: bold; + font-size: inherit; + border-bottom: solid 1px #777777; + margin: 0px; +} + +#sg-legend ul { + list-style: none; + padding: 0px; + margin: 0px; +} + +#sg-legend li { + padding: 3px 6px; + margin: 3px 0px; + border: 1px solid white; +} + /* ------------------------------------------------------------------------------------------------ * Help */ @@ -442,3 +453,189 @@ margin: 0px 6px; cursor: pointer; } + +/* ------------------------------------------------------------------------------------------------ + * Colors + */ + +.sg .node.global .background { + fill: #0077bb; /* blue */ +} +#sg-legend .global { + background-color: #0077bb; /* blue */ +} +.sg .edge.global path, +.sg .edge.global text { + stroke: #0077bb; /* blue */ +} + +.sg .node.file-0 .background { + fill: #77aadd; /* light blue */ +} +#sg-legend .file-0 { + background-color: #77aadd; /* light blue */ +} +.sg .node.file-0 .arrow, +.sg .edge.file-0 text +{ + fill: #0e2236; /* light blue (darkened) */ +} +.sg .node.file-0.reference .background, +.sg .node.file-0.definition .background, +.sg .node.file-0.scope.exported .background, +.sg .edge.file-0 path, +.sg .edge.file-0 text +{ + stroke: #0e2236; /* light blue (darkened) */ +} + +.sg .node.file-1 .background, +.sg .edge.file-1 text +{ + fill: #ee8866; /* orange */ +} +#sg-legend .file-1 { + background-color: #ee8866; /* orange */ +} +.sg .node.file-1 .arrow { + fill: #3d1407; /* orange (darkened) */ +} +.sg .node.file-1.reference .background, +.sg .node.file-1.definition .background, +.sg .node.file-1.scope.exported .background, +.sg .edge.file-1 path, +.sg .edge.file-1 text +{ + stroke: #3d1407; /* orange (darkened) */ +} + +.sg .node.file-2 .background, +.sg .edge.file-2 text +{ + fill: #eedd88; /* light yellow */ +} +#sg-legend .file-2 { + background-color: #eedd88; /* light yellow */ +} +.sg .node.file-2 .arrow { + fill: #413809; /* light yellow (darkened) */ +} +.sg .node.file-2.reference .background, +.sg .node.file-2.definition .background, +.sg .node.file-2.scope.exported .background, +.sg .edge.file-2 path, +.sg .edge.file-2 text +{ + stroke: #413809; /* light yellow (darkened) */ +} + +.sg .node.file-3 .background, +.sg .edge.file-3 text +{ + fill: #ffaabb; /* pink */ +} +#sg-legend .file-3 { + background-color: #ffaabb; /* pink */ +} +.sg .node.file-3 .arrow { + fill: #550011; /* pink (darkened) */ +} +.sg .node.file-3.reference .background, +.sg .node.file-3.definition .background, +.sg .node.file-3.scope.exported .background, +.sg .edge.file-3 path, +.sg .edge.file-3 text +{ + stroke: #550011; /* pink (darkened) */ +} + +.sg .node.file-4 .background, +.sg .edge.file-4 text +{ + fill: #99ddff; /* light cyan */ +} +#sg-legend .file-4 { + background-color: #99ddff; /* light cyan */ +} +.sg .node.file-4 .arrow { + fill: #003652; /* light cyan (darkened) */ +} +.sg .node.file-4.reference .background, +.sg .node.file-4.definition .background, +.sg .node.file-4.scope.exported .background, +.sg .edge.file-4 path, +.sg .edge.file-4 text +{ + stroke: #003652; /* light cyan (darkened) */ +} + +.sg .node.file-5 .background, +.sg .edge.file-5 text +{ + fill: #44bb99; /* mint */ +} +#sg-legend .file-5 { + background-color: #44bb99; /* mint */ +} +.sg .node.file-5 .arrow { + fill: #0e251f; /* mint (darkened) */ +} +.sg .node.file-5.reference .background, +.sg .node.file-5.definition .background, +.sg .node.file-5.scope.exported .background, +.sg .edge.file-5 path, +.sg .edge.file-5 text +{ + stroke: #0e251f; /* mint (darkened) */ +} + +.sg .node.file-6 .background, +.sg .edge.file-6 text +{ + fill: #bbcc33; /* pear */ +} +#sg-legend .file-6 { + background-color: #bbcc33; /* pear */ +} +.sg .node.file-6 .arrow { + fill: #25290a; /* pear (darkened) */ +} +.sg .node.file-6.reference .background, +.sg .node.file-6.definition .background, +.sg .node.file-6.scope.exported .background, +.sg .edge.file-6 path, +.sg .edge.file-6 text +{ + stroke: #25290a; /* pear (darkened) */ +} + +.sg .node.file-7 .background, +.sg .edge.file-7 text +{ + fill: #aaaa00; /* olive */ +} +#sg-legend .file-7 { + background-color: #aaaa00; /* olive */ +} +.sg .node.file-7 .arrow { + fill: #222200; /* olive (darkened) */ +} +.sg .node.file-7.reference .background, +.sg .node.file-7.definition .background, +.sg .node.file-7.scope.exported .background, +.sg .edge.file-7 path, +.sg .edge.file-7 text +{ + stroke: #222200; /* olive (darkened) */ +} + +.sg .node.path-node .border, +.sg .edge.path-edge path, +.sg .edge.path-edge text +{ + stroke: #ee7733; +} + +.sg .edge.path-edge text { + fill: #ee7733; +} diff --git a/stack-graphs/src/visualization/visualization.js b/stack-graphs/src/visualization/visualization.js index a1d9bbaac..6fdab1ef6 100644 --- a/stack-graphs/src/visualization/visualization.js +++ b/stack-graphs/src/visualization/visualization.js @@ -17,11 +17,14 @@ class StackGraph { static arrow_head_w = 16; static arrow_head_h = 8; + static number_of_file_colors = 8; + constructor(container, graph, paths, metadata) { this.metadata = metadata; this.graph = graph; this.paths = paths; + this.cleanup_data(); this.compute_data(); this.current_node = null; @@ -31,13 +34,35 @@ class StackGraph { this.render(); } + cleanup_data() { + let idx = 0; + while (idx < this.graph.edges.length) { + let edge = this.graph.edges[idx]; + if (edge.source.file === edge.sink.file && edge.source.local_id === edge.sink.local_id) { + console.log("ignoring self loop", edge); + this.graph.edges.splice(idx, 1); + } else { + idx += 1; + } + } + } + compute_data() { + this.F = {}; this.ID = {}; this.N = []; + this.compute_file_data(); this.compute_node_data(); this.compute_path_data(); } + compute_file_data() { + for (let i in graph.files) { + const file = graph.files[i]; + this.F[file] = i; + } + } + compute_node_data() { for (let i in graph.nodes) { const node = graph.nodes[i]; @@ -151,7 +176,8 @@ class StackGraph { // render UI this.render_help(); this.render_tooltip(); - this.render_graph() + this.render_legend(); + this.render_graph(); // pan & zoom let zoom = d3.zoom() @@ -225,7 +251,7 @@ class StackGraph { .data(dag.links()) .enter() .append("g") - .attr("class", (d) => d.data.is_jump ? "jump" : "edge") + .attr("class", (d) => `${d.data.is_jump ? "jump" : "edge"} ${this.edge_to_file_class(d.data)}`) .attr("id", (d) => this.edge_to_id_str(d.data)); edges.append("path") .attr("id", (d) => this.edge_to_id_str(d.data) + ":path") @@ -286,14 +312,14 @@ class StackGraph { render_node(node, g) { g.attr('id', this.node_to_id_str(node)); - g.attr('class', `node ${node.type}`); + g.attr('class', `node ${node.type} ${this.node_to_file_class(node)}`); switch (node.type) { case "drop_scopes": - this.render_scope(g); + this.render_symbol_node(g, "[drop]", null, ""); break; case "jump_to_scope": - this.render_scope(g); + this.render_symbol_node(g, "[jump]", null, ""); break; case "pop_symbol": this.render_symbol_node(g, node.symbol, null, "pop"); @@ -322,7 +348,7 @@ class StackGraph { } break; case "root": - this.render_scope(g); + this.render_symbol_node(g, "[root]", null, ""); break; case "scope": if (this.show_all_node_labels()) { @@ -347,6 +373,7 @@ class StackGraph { break; } } + render_symbol_node(g, text, scope, shape) { let content = g.append("g"); content.append('text').text(text); @@ -458,9 +485,9 @@ class StackGraph { } } - /* ------------------------------------------------------------------------------------------------ - * Path Highlighting - */ + // ------------------------------------------------------------------------------------------------ + // Path Highlighting + // paths_mouseover(e, node) { if (this.paths_lock !== null) { @@ -768,6 +795,30 @@ class StackGraph { || (this.current_edge !== null && path.derived.edges.hasOwnProperty(this.edge_to_id_str(this.current_edge))); } + // ------------------------------------------------------------------------------------------------ + // Legend + // + + render_legend() { + const legend = d3.select('body').append('div') + .attr('id', 'sg-legend') + legend.append("h1").text("Files"); + const items = legend.append("ul"); + items.append("li") + .classed("global", true) + .text("[global]"); + for (const file in this.F) { + items.append("li") + .classed('file-' + this.F[file], true) + .text(file); + } + } + + legend_update() { + const legend = d3.select('#sg-legend'); + legend.style('visibility', this.show_file_legend() ? null : 'hidden'); + } + // ------------------------------------------------------------------------------------------------ // Help // @@ -789,6 +840,10 @@ class StackGraph { Pan by dragging the background with the mouse. Zoom using the scroll wheel. `); + this.show_files_legend_toggle = this.new_setting(help_content, "sg-files-legend", "Show files legend (f)", true); + this.show_files_legend_toggle.on("change", (e => { + this.legend_update(); + })); this.show_all_node_labels_toggle = this.new_setting(help_content, "sg-scope-labels", "Show all node labels (l)", false); this.show_all_node_labels_toggle.on("change", (e => { this.render_graph(); @@ -825,6 +880,10 @@ class StackGraph { help_keypress(e) { switch (e.keyCode) { + case 70: // f + this.show_files_legend_toggle.property("checked", !this.show_files_legend_toggle.property("checked")); + this.legend_update(); + break; case 72: // h this.help_toggle.property("checked", !this.help_toggle.property("checked")); break; @@ -847,6 +906,10 @@ class StackGraph { return this.show_all_node_labels_toggle.property("checked"); } + show_file_legend() { + return this.show_files_legend_toggle.property("checked"); + } + new_setting(element, id, html, initial) { const toggle = element.append("div"); const toggle_input = toggle.append('input') @@ -869,10 +932,24 @@ class StackGraph { return this.node_id_to_str(node.id); } + node_to_file_class(node) { + return this.node_id_to_file_class(node.id); + } + edge_to_id_str(edge) { return this.node_id_to_str(edge.source) + "->" + this.node_id_to_str(edge.sink); } + edge_to_file_class(edge) { + if (edge.source.hasOwnProperty('file')) { + return "file-" + (this.F[edge.source.file] % StackGraph.number_of_file_colors); + } else if (edge.sink.hasOwnProperty('file')) { + return "file-" + (this.F[edge.sink.file] % StackGraph.number_of_file_colors); + } else { + return "global"; + } + } + node_id_to_str(id) { if (id.hasOwnProperty('file')) { return id.file + "#" + id.local_id; @@ -881,6 +958,14 @@ class StackGraph { } } + node_id_to_file_class(id) { + if (id.hasOwnProperty('file')) { + return "file-" + (this.F[id.file] % StackGraph.number_of_file_colors); + } else { + return "global"; + } + } + id_selector(id) { const sel = "#" + id.replaceAll(/[^a-zA-Z0-9]/g, '\\$&'); return sel; diff --git a/stack-graphs/tests/it/arena.rs b/stack-graphs/tests/it/arena.rs index 5af25d66e..6963dae29 100644 --- a/stack-graphs/tests/it/arena.rs +++ b/stack-graphs/tests/it/arena.rs @@ -229,3 +229,28 @@ fn can_compare_deques() { deque10.ensure_backwards(&mut arena); assert_eq!(deque1.cmp(&mut arena, deque10), Ordering::Less); } + +#[test] +fn can_use_arena_after_clear() { + let mut a = Arena::new(); + let h = a.add(12 as u8); + assert_eq!(12, *a.get(h)); + + a.clear(); + let h = a.add(7); + assert_eq!(7, *a.get(h)); +} + +#[test] +fn can_use_supplemental_arena_after_clear() { + let mut a = Arena::new(); + let h = a.add(()); + + let mut x = SupplementalArena::new(); + x[h] = 12; + assert_eq!(Some(12), x.get(h).cloned()); + + x.clear(); + x[h] = 7; + assert_eq!(Some(7), x.get(h).cloned()); +} diff --git a/stack-graphs/tests/it/c/can_find_local_nodes.rs b/stack-graphs/tests/it/c/can_find_local_nodes.rs index 0a5d3a402..9fbf31859 100644 --- a/stack-graphs/tests/it/c/can_find_local_nodes.rs +++ b/stack-graphs/tests/it/c/can_find_local_nodes.rs @@ -22,6 +22,7 @@ use stack_graphs::c::sg_partial_path_list_free; use stack_graphs::c::sg_partial_path_list_new; use stack_graphs::c::sg_partial_path_list_paths; use stack_graphs::c::sg_stack_graph_nodes; +use stack_graphs::c::sg_stitcher_config; use stack_graphs::graph::Node; use crate::c::test_graph::TestGraph; @@ -33,11 +34,15 @@ fn check_local_nodes(graph: &TestGraph, file: &str, expected_local_nodes: &[&str let partials = sg_partial_path_arena_new(); let path_list = sg_partial_path_list_new(); + let stitcher_config = sg_stitcher_config { + detect_similar_paths: false, + }; sg_partial_path_arena_find_partial_paths_in_file( graph.graph, partials, file.as_u32(), path_list, + &stitcher_config, std::ptr::null(), ); diff --git a/stack-graphs/tests/it/c/can_find_partial_paths_in_file.rs b/stack-graphs/tests/it/c/can_find_partial_paths_in_file.rs index b39bc524a..ae93f84be 100644 --- a/stack-graphs/tests/it/c/can_find_partial_paths_in_file.rs +++ b/stack-graphs/tests/it/c/can_find_partial_paths_in_file.rs @@ -22,6 +22,7 @@ use stack_graphs::c::sg_partial_path_list_new; use stack_graphs::c::sg_partial_path_list_paths; use stack_graphs::c::sg_partial_scope_stack; use stack_graphs::c::sg_partial_symbol_stack; +use stack_graphs::c::sg_stitcher_config; use stack_graphs::c::SG_LIST_EMPTY_HANDLE; use stack_graphs::c::SG_NULL_HANDLE; use stack_graphs::partial::PartialPath; @@ -77,11 +78,15 @@ fn check_partial_paths_in_file(graph: &TestGraph, file: &str, expected_paths: &[ let partials = sg_partial_path_arena_new(); let path_list = sg_partial_path_list_new(); + let stitcher_config = sg_stitcher_config { + detect_similar_paths: false, + }; sg_partial_path_arena_find_partial_paths_in_file( graph.graph, partials, file.as_u32(), path_list, + &stitcher_config, std::ptr::null(), ); diff --git a/stack-graphs/tests/it/c/can_find_qualified_definitions_with_phased_partial_path_stitching.rs b/stack-graphs/tests/it/c/can_find_qualified_definitions_with_phased_partial_path_stitching.rs index dcc276c86..613a54556 100644 --- a/stack-graphs/tests/it/c/can_find_qualified_definitions_with_phased_partial_path_stitching.rs +++ b/stack-graphs/tests/it/c/can_find_qualified_definitions_with_phased_partial_path_stitching.rs @@ -28,6 +28,7 @@ use stack_graphs::c::sg_partial_path_list_free; use stack_graphs::c::sg_partial_path_list_new; use stack_graphs::c::sg_partial_path_list_paths; use stack_graphs::c::sg_stack_graph; +use stack_graphs::c::sg_stitcher_config; use stack_graphs::copious_debugging; use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPath; @@ -55,11 +56,15 @@ impl StorageLayer { let rust_graph = unsafe { &(*graph).inner }; let path_list = sg_partial_path_list_new(); for file in rust_graph.iter_files() { + let stitcher_config = sg_stitcher_config { + detect_similar_paths: false, + }; sg_partial_path_arena_find_partial_paths_in_file( graph, partials, file.as_u32(), path_list, + &stitcher_config, std::ptr::null(), ); } diff --git a/stack-graphs/tests/it/c/can_jump_to_definition.rs b/stack-graphs/tests/it/c/can_jump_to_definition.rs index 800ce06e8..aeea163f6 100644 --- a/stack-graphs/tests/it/c/can_jump_to_definition.rs +++ b/stack-graphs/tests/it/c/can_jump_to_definition.rs @@ -15,6 +15,7 @@ use stack_graphs::c::sg_partial_path_list_count; use stack_graphs::c::sg_partial_path_list_free; use stack_graphs::c::sg_partial_path_list_new; use stack_graphs::c::sg_partial_path_list_paths; +use stack_graphs::c::sg_stitcher_config; use stack_graphs::partial::PartialPath; use crate::c::test_graph::TestGraph; @@ -28,12 +29,16 @@ fn check_jump_to_definition(graph: &TestGraph, expected_paths: &[&str]) { .iter_nodes() .filter(|handle| rust_graph[*handle].is_reference()) .collect::>(); + let stitcher_config = sg_stitcher_config { + detect_similar_paths: false, + }; sg_partial_path_arena_find_all_complete_paths( graph.graph, paths, references.len(), references.as_ptr() as *const _, path_list, + &stitcher_config, std::ptr::null(), ); diff --git a/stack-graphs/tests/it/c/can_jump_to_definition_with_phased_partial_path_stitching.rs b/stack-graphs/tests/it/c/can_jump_to_definition_with_phased_partial_path_stitching.rs index 615ffedd5..ce20ae378 100644 --- a/stack-graphs/tests/it/c/can_jump_to_definition_with_phased_partial_path_stitching.rs +++ b/stack-graphs/tests/it/c/can_jump_to_definition_with_phased_partial_path_stitching.rs @@ -27,6 +27,7 @@ use stack_graphs::c::sg_partial_path_list_free; use stack_graphs::c::sg_partial_path_list_new; use stack_graphs::c::sg_partial_path_list_paths; use stack_graphs::c::sg_stack_graph; +use stack_graphs::c::sg_stitcher_config; use stack_graphs::copious_debugging; use stack_graphs::partial::PartialPath; use stack_graphs::partial::PartialScopeStackBindings; @@ -49,11 +50,15 @@ impl StorageLayer { let rust_graph = unsafe { &(*graph).inner }; let path_list = sg_partial_path_list_new(); for file in rust_graph.iter_files() { + let stitcher_config = sg_stitcher_config { + detect_similar_paths: false, + }; sg_partial_path_arena_find_partial_paths_in_file( graph, partials, file.as_u32(), path_list, + &stitcher_config, std::ptr::null(), ); } diff --git a/stack-graphs/tests/it/can_find_local_nodes.rs b/stack-graphs/tests/it/can_find_local_nodes.rs index 105e5a795..4f471d88f 100644 --- a/stack-graphs/tests/it/can_find_local_nodes.rs +++ b/stack-graphs/tests/it/can_find_local_nodes.rs @@ -12,6 +12,7 @@ use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; use stack_graphs::stitching::Database; use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::NoCancellation; use crate::test_graphs; @@ -24,6 +25,7 @@ fn check_local_nodes(graph: &StackGraph, file: &str, expected_local_nodes: &[&st graph, &mut partials, file, + StitcherConfig::default(), &NoCancellation, |graph, partials, path| { database.add_partial_path(graph, partials, path.clone()); diff --git a/stack-graphs/tests/it/can_find_node_partial_paths_in_database.rs b/stack-graphs/tests/it/can_find_node_partial_paths_in_database.rs index 3ccea9548..4e825c47e 100644 --- a/stack-graphs/tests/it/can_find_node_partial_paths_in_database.rs +++ b/stack-graphs/tests/it/can_find_node_partial_paths_in_database.rs @@ -15,6 +15,7 @@ use stack_graphs::partial::PartialPath; use stack_graphs::partial::PartialPaths; use stack_graphs::stitching::Database; use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::NoCancellation; use crate::test_graphs; @@ -33,6 +34,7 @@ fn check_node_partial_paths( graph, &mut partials, file, + StitcherConfig::default(), &NoCancellation, |graph, partials, path| { db.add_partial_path(graph, partials, path.clone()); diff --git a/stack-graphs/tests/it/can_find_partial_paths_in_file.rs b/stack-graphs/tests/it/can_find_partial_paths_in_file.rs index abba6951b..adef36ae3 100644 --- a/stack-graphs/tests/it/can_find_partial_paths_in_file.rs +++ b/stack-graphs/tests/it/can_find_partial_paths_in_file.rs @@ -10,7 +10,7 @@ use std::collections::BTreeSet; use pretty_assertions::assert_eq; use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; -use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::{ForwardPartialPathStitcher, StitcherConfig}; use stack_graphs::NoCancellation; use crate::test_graphs; @@ -23,6 +23,7 @@ fn check_partial_paths_in_file(graph: &StackGraph, file: &str, expected_paths: & graph, &mut partials, file, + StitcherConfig::default(), &NoCancellation, |graph, partials, path| { results.insert(path.display(graph, partials).to_string()); diff --git a/stack-graphs/tests/it/can_find_root_partial_paths_in_database.rs b/stack-graphs/tests/it/can_find_root_partial_paths_in_database.rs index 97dca4788..e8beff576 100644 --- a/stack-graphs/tests/it/can_find_root_partial_paths_in_database.rs +++ b/stack-graphs/tests/it/can_find_root_partial_paths_in_database.rs @@ -17,7 +17,7 @@ use stack_graphs::partial::PartialScopedSymbol; use stack_graphs::partial::PartialSymbolStack; use stack_graphs::stitching::Database; use stack_graphs::stitching::ForwardPartialPathStitcher; -use stack_graphs::stitching::SymbolStackKey; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::NoCancellation; use crate::test_graphs; @@ -35,6 +35,7 @@ fn check_root_partial_paths( graph, &mut partials, file, + StitcherConfig::default(), &NoCancellation, |graph, partials, path| { db.add_partial_path(graph, partials, path.clone()); @@ -53,8 +54,12 @@ fn check_root_partial_paths( } let mut results = Vec::>::new(); - let key = SymbolStackKey::from_partial_symbol_stack(&mut partials, &mut db, symbol_stack); - db.find_candidate_partial_paths_from_root(graph, &mut partials, Some(key), &mut results); + db.find_candidate_partial_paths_from_root( + graph, + &mut partials, + Some(symbol_stack), + &mut results, + ); let actual_partial_paths = results .into_iter() diff --git a/stack-graphs/tests/it/can_jump_to_definition.rs b/stack-graphs/tests/it/can_jump_to_definition.rs index 7fde42161..70158f495 100644 --- a/stack-graphs/tests/it/can_jump_to_definition.rs +++ b/stack-graphs/tests/it/can_jump_to_definition.rs @@ -10,7 +10,9 @@ use std::collections::BTreeSet; use pretty_assertions::assert_eq; use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; -use stack_graphs::stitching::{ForwardPartialPathStitcher, GraphEdges}; +use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::GraphEdgeCandidates; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::NoCancellation; use crate::test_graphs; @@ -22,10 +24,9 @@ fn check_jump_to_definition(graph: &StackGraph, expected_paths: &[&str]) { .iter_nodes() .filter(|handle| graph[*handle].is_reference()); ForwardPartialPathStitcher::find_all_complete_partial_paths( - graph, - &mut paths, - &mut GraphEdges(None), + &mut GraphEdgeCandidates::new(graph, &mut paths, None), references, + StitcherConfig::default(), &NoCancellation, |graph, paths, path| { results.insert(path.display(graph, paths).to_string()); diff --git a/stack-graphs/tests/it/can_jump_to_definition_with_forward_partial_path_stitching.rs b/stack-graphs/tests/it/can_jump_to_definition_with_forward_partial_path_stitching.rs index 85984e814..7f5996e3e 100644 --- a/stack-graphs/tests/it/can_jump_to_definition_with_forward_partial_path_stitching.rs +++ b/stack-graphs/tests/it/can_jump_to_definition_with_forward_partial_path_stitching.rs @@ -11,7 +11,9 @@ use pretty_assertions::assert_eq; use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; use stack_graphs::stitching::Database; +use stack_graphs::stitching::DatabaseCandidates; use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::NoCancellation; use crate::test_graphs; @@ -26,6 +28,7 @@ fn check_jump_to_definition(graph: &StackGraph, expected_partial_paths: &[&str]) graph, &mut partials, file, + StitcherConfig::default(), &NoCancellation, |graph, partials, path| { db.add_partial_path(graph, partials, path.clone()); @@ -39,10 +42,9 @@ fn check_jump_to_definition(graph: &StackGraph, expected_partial_paths: &[&str]) .filter(|handle| graph[*handle].is_reference()); let mut complete_partial_paths = Vec::new(); ForwardPartialPathStitcher::find_all_complete_partial_paths( - graph, - &mut partials, - &mut db, + &mut DatabaseCandidates::new(graph, &mut partials, &mut db), references, + StitcherConfig::default(), &NoCancellation, |_, _, p| { complete_partial_paths.push(p.clone()); diff --git a/stack-graphs/tests/it/cycles.rs b/stack-graphs/tests/it/cycles.rs index e9584dd2f..df840eba2 100644 --- a/stack-graphs/tests/it/cycles.rs +++ b/stack-graphs/tests/it/cycles.rs @@ -16,6 +16,7 @@ use stack_graphs::partial::PartialPaths; use stack_graphs::stitching::Database; use stack_graphs::stitching::ForwardPartialPathStitcher; use stack_graphs::stitching::GraphEdges; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::CancelAfterDuration; use std::time::Duration; @@ -171,7 +172,7 @@ fn finding_simple_identity_cycle_is_detected() { { let mut edges = Appendables::new(); let mut cd = AppendingCycleDetector::new(); - let db = &GraphEdges(None); + let db = &GraphEdges; for edge in &[ edge(r, foo_ref, 0), @@ -199,6 +200,7 @@ fn finding_simple_identity_cycle_is_detected() { &graph, &mut partials, file, + StitcherConfig::default(), &cancellation_flag, |_, _, _| path_count += 1, ); @@ -261,7 +263,7 @@ fn finding_composite_identity_cycle_is_detected() { { let mut edges = Appendables::new(); let mut cd = AppendingCycleDetector::new(); - let db = &GraphEdges(None); + let db = &GraphEdges; for edge in &[ edge(r, s, 0), edge(r, s, 0), @@ -292,6 +294,7 @@ fn finding_composite_identity_cycle_is_detected() { &graph, &mut partials, file, + StitcherConfig::default(), &cancellation_flag, |_, _, _| path_count += 1, ); @@ -353,6 +356,7 @@ fn appending_eliminating_cycle_terminates() { &graph, &mut partials, file, + StitcherConfig::default(), &cancellation_flag, |_, _, _| path_count += 1, ); diff --git a/stack-graphs/tests/it/graph.rs b/stack-graphs/tests/it/graph.rs index 353850c6f..620d066c9 100644 --- a/stack-graphs/tests/it/graph.rs +++ b/stack-graphs/tests/it/graph.rs @@ -8,7 +8,7 @@ use std::collections::HashSet; use maplit::hashset; -use stack_graphs::graph::StackGraph; +use stack_graphs::graph::{Degree, StackGraph}; use crate::test_graphs; use crate::test_graphs::CreateStackGraph; @@ -157,14 +157,6 @@ fn can_add_and_remove_edges() { .collect::>(), hashset! { (h2, 0), (h3, 0), (h4, 0) } ); - graph.remove_edge(h1, h3); - assert_eq!( - graph - .outgoing_edges(h1) - .map(|edge| edge.sink) - .collect::>(), - hashset! { h2, h4 } - ); } #[test] @@ -204,3 +196,22 @@ fn can_add_graph_to_empty_graph() { ); } } + +#[test] +fn can_get_incoming_edges() { + let mut graph = StackGraph::new(); + let file = graph.get_or_create_file("test.py"); + let h1 = graph.internal_scope(file, 0); + let h2 = graph.internal_scope(file, 1); + let h3 = graph.internal_scope(file, 2); + assert_eq!(Degree::Zero, graph.incoming_edge_degree(h1)); + assert_eq!(Degree::Zero, graph.incoming_edge_degree(h2)); + assert_eq!(Degree::Zero, graph.incoming_edge_degree(h3)); + graph.add_edge(h1, h2, 0); + graph.add_edge(h3, h2, 0); + assert_eq!(Degree::Zero, graph.incoming_edge_degree(h1)); + assert_eq!(Degree::Multiple, graph.incoming_edge_degree(h2)); + assert_eq!(Degree::Zero, graph.incoming_edge_degree(h3)); + graph.add_edge(h3, h1, 0); + assert_eq!(Degree::One, graph.incoming_edge_degree(h1)); +} diff --git a/stack-graphs/tests/it/main.rs b/stack-graphs/tests/it/main.rs index bea8a6030..e5a0e6edd 100644 --- a/stack-graphs/tests/it/main.rs +++ b/stack-graphs/tests/it/main.rs @@ -24,4 +24,8 @@ mod graph; mod partial; #[cfg(feature = "serde")] mod serde; +mod stats; +mod stitching; +#[cfg(feature = "storage")] +mod storage; mod util; diff --git a/stack-graphs/tests/it/serde.rs b/stack-graphs/tests/it/serde.rs index 04679f353..3ecc1c48f 100644 --- a/stack-graphs/tests/it/serde.rs +++ b/stack-graphs/tests/it/serde.rs @@ -12,7 +12,7 @@ use stack_graphs::graph; use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; use stack_graphs::serde; -use stack_graphs::stitching::{Database, ForwardPartialPathStitcher}; +use stack_graphs::stitching::{Database, ForwardPartialPathStitcher, StitcherConfig}; use stack_graphs::NoCancellation; use crate::test_graphs; @@ -984,6 +984,7 @@ fn can_serialize_partial_paths() { &graph, &mut partials, file, + StitcherConfig::default(), &NoCancellation, |g, ps, p| { db.add_partial_path(g, ps, p.clone()); diff --git a/stack-graphs/tests/it/stats.rs b/stack-graphs/tests/it/stats.rs new file mode 100644 index 000000000..4d83d0c1d --- /dev/null +++ b/stack-graphs/tests/it/stats.rs @@ -0,0 +1,52 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use itertools::Itertools; +use pretty_assertions::assert_eq; + +use stack_graphs::stats::*; + +#[test] +fn empty_distribution() { + let hist: FrequencyDistribution = FrequencyDistribution::default(); + + assert_eq!(0, hist.unique()); + assert_eq!(0, hist.count()); + + let result = hist.quantiles(0).into_iter().cloned().collect_vec(); + let expected: Vec = vec![]; + assert_eq!(expected, result); +} + +#[test] +fn singleton_distribution() { + let mut hist = FrequencyDistribution::default(); + hist.record(42); + + assert_eq!(1, hist.unique()); + assert_eq!(1, hist.count()); + + let result = hist.quantiles(4).into_iter().cloned().collect_vec(); + let expected: Vec = vec![42, 42, 42, 42, 42]; + assert_eq!(expected, result); +} + +#[test] +fn four_value_distribution() { + let mut hist = FrequencyDistribution::default(); + hist.record(3); + hist.record(4); + hist.record(1); + hist.record(2); + + assert_eq!(4, hist.unique()); + assert_eq!(4, hist.count()); + + let result = hist.quantiles(4).into_iter().cloned().collect_vec(); + let expected: Vec = vec![1, 1, 2, 3, 4]; + assert_eq!(expected, result); +} diff --git a/stack-graphs/tests/it/stitching.rs b/stack-graphs/tests/it/stitching.rs new file mode 100644 index 000000000..ddb878606 --- /dev/null +++ b/stack-graphs/tests/it/stitching.rs @@ -0,0 +1,104 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use itertools::Itertools; +use stack_graphs::graph::StackGraph; +use stack_graphs::partial::PartialPaths; +use stack_graphs::stitching::Database; + +use crate::util::create_partial_path_and_edges; +use crate::util::create_pop_symbol_node; +use crate::util::create_push_symbol_node; + +fn test_foo_bar_root_candidate_paths(symbols: &[&str], variable: bool) -> usize { + let mut graph = StackGraph::new(); + let file = graph.add_file("test").unwrap(); + let mut partials = PartialPaths::new(); + + let r = StackGraph::root_node(); + let foo = create_pop_symbol_node(&mut graph, file, "foo", true); + let bar = create_pop_symbol_node(&mut graph, file, "bar", true); + + let path_with_variable = + create_partial_path_and_edges(&mut graph, &mut partials, &[r, foo, bar]).unwrap(); + + let mut path_without_variable = path_with_variable.clone(); + path_without_variable.eliminate_precondition_stack_variables(&mut partials); + + let mut db = Database::new(); + db.add_partial_path(&graph, &mut partials, path_with_variable); + db.add_partial_path(&graph, &mut partials, path_without_variable); + + let r = StackGraph::root_node(); + let refs = symbols + .into_iter() + .map(|r| create_push_symbol_node(&mut graph, file, *r, true)) + .chain(std::iter::once(r)) + .collect_vec(); + let mut path = create_partial_path_and_edges(&mut graph, &mut partials, &refs).unwrap(); + if !variable { + path.eliminate_precondition_stack_variables(&mut partials); + } + + let mut results = Vec::new(); + db.find_candidate_partial_paths_from_root( + &mut graph, + &mut partials, + Some(path.symbol_stack_postcondition), + &mut results, + ); + + results.len() +} + +#[test] +fn find_candidates_for_exact_symbol_stack_with_variable() { + // <"foo","bar",%2> ~ <"foo","bar",%1> | yes, %2 = %1 + // <"foo","bar",%2> ~ <"foo","bar"> | yes, %2 = <> + let results = test_foo_bar_root_candidate_paths(&["bar", "foo"], true); + assert_eq!(2, results); +} + +#[test] +fn find_candidates_for_exact_symbol_stack_without_variable() { + // <"foo","bar"> ~ <"foo","bar",%1> | yes, %1 = <> + // <"foo","bar"> ~ <"foo","bar"> | yes + let results = test_foo_bar_root_candidate_paths(&["bar", "foo"], false); + assert_eq!(2, results); +} + +#[test] +fn find_candidates_for_longer_symbol_stack_with_variable() { + // <"foo","bar","quz",%2> ~ <"foo","bar",%1> | yes, %1 = <"quz",%2> + // <"foo","bar","quz",%2> ~ <"foo","bar"> | no + let results = test_foo_bar_root_candidate_paths(&["quz", "bar", "foo"], true); + assert_eq!(1, results); +} + +#[test] +fn find_candidates_for_longer_symbol_stack_without_variable() { + // <"foo","bar","quz"> ~ <"foo","bar",%1> | yes, %1 = <"quz"> + // <"foo","bar","quz"> ~ <"foo","bar"> | no + let results = test_foo_bar_root_candidate_paths(&["quz", "bar", "foo"], false); + assert_eq!(1, results); +} + +#[test] +fn find_candidates_for_shorter_symbol_stack_with_variable() { + // <"foo",%2> ~ <"foo","bar",%1> | yes, %2 = <"bar",%1> + // <"foo",%2> ~ <"foo","bar"> | yes, %2 = <"bar"> + let results = test_foo_bar_root_candidate_paths(&["foo"], true); + assert_eq!(2, results); +} + +#[test] +fn find_candidates_for_shorter_symbol_stack_without_variable() { + // <"foo"> ~ <"foo","bar",%1> | no + // <"foo"> ~ <"foo","bar"> | no + let results = test_foo_bar_root_candidate_paths(&["foo"], false); + assert_eq!(0, results); +} diff --git a/stack-graphs/tests/it/storage.rs b/stack-graphs/tests/it/storage.rs new file mode 100644 index 000000000..464700414 --- /dev/null +++ b/stack-graphs/tests/it/storage.rs @@ -0,0 +1,127 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use itertools::Itertools; +use stack_graphs::graph::StackGraph; +use stack_graphs::partial::PartialPaths; +use stack_graphs::storage::SQLiteWriter; +use stack_graphs::NoCancellation; + +use crate::util::create_partial_path_and_edges; +use crate::util::create_pop_symbol_node; +use crate::util::create_push_symbol_node; + +fn test_foo_bar_root_candidate_paths(symbols: &[&str], variable: bool) -> usize { + let mut reader = { + let mut writer = SQLiteWriter::open_in_memory().unwrap(); + + let mut graph = StackGraph::new(); + let file = graph.add_file("test1").unwrap(); + let mut partials = PartialPaths::new(); + + let r = StackGraph::root_node(); + let foo = create_pop_symbol_node(&mut graph, file, "foo", true); + let bar = create_pop_symbol_node(&mut graph, file, "bar", true); + + let path_with_variable = + create_partial_path_and_edges(&mut graph, &mut partials, &[r, foo, bar]).unwrap(); + + let mut path_without_variable = path_with_variable.clone(); + path_without_variable.eliminate_precondition_stack_variables(&mut partials); + + writer + .store_result_for_file( + &graph, + file, + "", + &mut partials, + vec![&path_with_variable, &path_without_variable], + ) + .unwrap(); + + writer.into_reader() + }; + + { + let (graph, partials, _) = reader.get(); + let file = graph.add_file("test2").unwrap(); + + let r = StackGraph::root_node(); + let refs = symbols + .into_iter() + .map(|r| create_push_symbol_node(graph, file, *r, true)) + .chain(std::iter::once(r)) + .collect_vec(); + let mut path = create_partial_path_and_edges(graph, partials, &refs).unwrap(); + if !variable { + path.eliminate_precondition_stack_variables(partials); + } + + reader + .load_partial_path_extensions(&path, &NoCancellation) + .unwrap(); + + let (graph, partials, db) = reader.get(); + let mut results = Vec::new(); + db.find_candidate_partial_paths_from_root( + graph, + partials, + Some(path.symbol_stack_postcondition), + &mut results, + ); + + results.len() + } +} + +#[test] +fn find_candidates_for_exact_symbol_stack_with_variable() { + // <"foo","bar",%2> ~ <"foo","bar",%1> | yes, %2 = %1 + // <"foo","bar",%2> ~ <"foo","bar"> | yes, %2 = <> + let results = test_foo_bar_root_candidate_paths(&["bar", "foo"], true); + assert_eq!(2, results); +} + +#[test] +fn find_candidates_for_exact_symbol_stack_without_variable() { + // <"foo","bar"> ~ <"foo","bar",%1> | yes, %1 = <> + // <"foo","bar"> ~ <"foo","bar"> | yes + let results = test_foo_bar_root_candidate_paths(&["bar", "foo"], false); + assert_eq!(2, results); +} + +#[test] +fn find_candidates_for_longer_symbol_stack_with_variable() { + // <"foo","bar","quz",%2> ~ <"foo","bar",%1> | yes, %1 = <"quz",%2> + // <"foo","bar","quz",%2> ~ <"foo","bar"> | no + let results = test_foo_bar_root_candidate_paths(&["quz", "bar", "foo"], true); + assert_eq!(1, results); +} + +#[test] +fn find_candidates_for_longer_symbol_stack_without_variable() { + // <"foo","bar","quz"> ~ <"foo","bar",%1> | yes, %1 = <"quz"> + // <"foo","bar","quz"> ~ <"foo","bar"> | no + let results = test_foo_bar_root_candidate_paths(&["quz", "bar", "foo"], false); + assert_eq!(1, results); +} + +#[test] +fn find_candidates_for_shorter_symbol_stack_with_variable() { + // <"foo",%2> ~ <"foo","bar",%1> | yes, %2 = <"bar",%1> + // <"foo",%2> ~ <"foo","bar"> | yes, %2 = <"bar"> + let results = test_foo_bar_root_candidate_paths(&["foo"], true); + assert_eq!(2, results); +} + +#[test] +fn find_candidates_for_shorter_symbol_stack_without_variable() { + // <"foo"> ~ <"foo","bar",%1> | no + // <"foo"> ~ <"foo","bar"> | no + let results = test_foo_bar_root_candidate_paths(&["foo"], false); + assert_eq!(0, results); +} diff --git a/tree-sitter-stack-graphs/CHANGELOG.md b/tree-sitter-stack-graphs/CHANGELOG.md index d4e1d197f..e1df552e4 100644 --- a/tree-sitter-stack-graphs/CHANGELOG.md +++ b/tree-sitter-stack-graphs/CHANGELOG.md @@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v0.10.0 -- 2024-12-12 + +Upgraded `tree-sitter` dependency to version 0.24. + +## v0.9.0 -- 2024-07-09 + +### Library + +- New crate-level constants `FILE_PATH_VAR` and `ROOT_PATH_VAR` standardize the TSG global variable names to use for the file and root path. +- The file path variable will only use the filename set in the stack graph if no value was explicitly set. + +### CLI + +#### Added + +- Tests run faster for languages with builtins sources by caching the partial paths for the builtins. +- Indexing will set a value for the root path variable that is passed to TSG. The value is based on the directory that was provided on the command line. + +#### Changed + +- Failure to index a file will not abort indexing anymore, but simply mark the file as failed, as we already do for files with parse errors. + +#### Removed + +- The NPM distribution has been removed. + +## v0.8.1 -- 2024-03-06 + +The `stack-graphs` dependency was updated to `v0.13` to fix the build problems of the `v0.8.0` release. + +## v0.8.0 -- 2024-03-05 + +The `tree-sitter` dependency version was updated to fix install problems. + +### Library + +#### Changed + +- A new `Reporter` trait is used to support reporting status from CLI actions such as indexing and testing. The CLI actions have been cleaned up to ensure that they are not writing directly to the console anymore, but only call the reporter for output. The `Reporter` trait replaces the old inaccessible `Logger` trait so that clients can more easily implement their own reporters if necessary. A `ConsoleLogger` is provided for clients who just need console printing. + ## v0.7.1 -- 2023-07-27 Support `stack-graphs` version `0.12`. diff --git a/tree-sitter-stack-graphs/Cargo.toml b/tree-sitter-stack-graphs/Cargo.toml index 7b5abd92b..3c1c01a80 100644 --- a/tree-sitter-stack-graphs/Cargo.toml +++ b/tree-sitter-stack-graphs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tree-sitter-stack-graphs" -version = "0.7.1" +version = "0.10.0" description = "Create stack graphs using tree-sitter parsers" homepage = "https://github.com/github/stack-graphs/tree/main/tree-sitter-stack-graphs" repository = "https://github.com/github/stack-graphs/" @@ -26,8 +26,8 @@ cli = [ "base64", "clap", "colored", - "dirs", "dialoguer", + "dirs", "env_logger", "indoc", "pathdiff", @@ -49,37 +49,38 @@ lsp = [ ] [dependencies] -anyhow = "1.0" +anyhow = "1.0.4" base64 = { version = "0.21", optional = true } capture-it = { version = "0.3", optional = true } -clap = { version = "4", optional = true, features=["derive"] } +clap = { version = "4", optional = true, features = ["derive"] } colored = { version = "2.0", optional = true } -controlled-option = ">=0.4" +controlled-option = "0.4.1" crossbeam-channel = { version = "0.5", optional = true } dialoguer = { version = "0.10", optional = true } -dirs = { version = "5", optional=true } +dirs = { version = "5", optional = true } env_logger = { version = "0.9", optional = true } indoc = { version = "1.0", optional = true } itertools = "0.10" log = "0.4" -lsp-positions = { version="0.3", path="../lsp-positions" } +lsp-positions = { version="0.3.4", path="../lsp-positions", features=["tree-sitter"] } # explicit version is required to be able to publish crate once_cell = "1" pathdiff = { version = "0.2.1", optional = true } regex = "1" rust-ini = "0.18" serde_json = { version="1.0", optional=true } sha1 = { version="0.10", optional=true } -stack-graphs = { version=">=0.11, <=0.12", path="../stack-graphs" } +stack-graphs = { version = "0.14", path="../stack-graphs" } # explicit version is required to be able to publish crate thiserror = "1.0" time = { version = "0.3", optional = true } tokio = { version = "1.26", optional = true, features = ["io-std", "rt", "rt-multi-thread"] } tower-lsp = { version = "0.19", optional = true } -tree-sitter = ">= 0.19" -tree-sitter-config = { version = "0.19", optional = true } -tree-sitter-graph = "0.11" -tree-sitter-loader = "0.20" +tree-sitter = "0.24" # keep the same minor version as the tree-sitter dependency + # of tree-sitter-graph to prevent install problems +tree-sitter-config = { version = "0.24", optional = true } +tree-sitter-graph = "0.12" +tree-sitter-loader = "0.24" walkdir = { version = "2.3", optional = true } [dev-dependencies] pretty_assertions = "0.7" -tree-sitter-python = "0.19.1" +tree-sitter-python = "=0.23.5" diff --git a/tree-sitter-stack-graphs/README.md b/tree-sitter-stack-graphs/README.md index 01ce41971..56f61e131 100644 --- a/tree-sitter-stack-graphs/README.md +++ b/tree-sitter-stack-graphs/README.md @@ -1,7 +1,6 @@ # tree-sitter-stack-graphs -The `tree-sitter-stack-graphs` crate lets you create stack graphs using the -[tree-sitter][] grammar for a language. +The `tree-sitter-stack-graphs` crate lets you create stack graphs using the [tree-sitter][] grammar for a language. [tree-sitter]: https://tree-sitter.github.io/ @@ -9,50 +8,82 @@ The `tree-sitter-stack-graphs` crate lets you create stack graphs using the - [Examples](https://github.com/github/stack-graphs/blob/main/tree-sitter-stack-graphs/examples/) - [Release notes](https://github.com/github/stack-graphs/blob/main/tree-sitter-stack-graphs/CHANGELOG.md) -## Usage +## Using the API To use this library, add the following to your `Cargo.toml`: -``` toml +```toml [dependencies] -tree-sitter-stack-graphs = "0.7" +tree-sitter-stack-graphs = "0.10" ``` -Check out our [documentation](https://docs.rs/tree-sitter-stack-graphs/*/) for -more details on how to use this library. +Check out our [documentation](https://docs.rs/tree-sitter-stack-graphs/*/) for more details on how to use this library. -## Command-line Program +## Using the Command-line Program -The command-line program for `tree-sitter-stack-graphs` lets you do stack -graph based analysis and lookup from the command line. +The command-line program for `tree-sitter-stack-graphs` lets you do stack graph based analysis and lookup from the command line. -Install the program using `cargo install` as follows: +The CLI can be run as follows: -``` sh -$ cargo install --features cli tree-sitter-stack-graphs -$ tree-sitter-stack-graphs --help -``` +1. _(Installed)_ Install the CLI using Cargo as follows: -Alternatively, the program can be invoked via NPM as follows: + ```sh + cargo install --features cli tree-sitter-stack-graphs + ``` -``` sh -$ npx tree-sitter-stack-graphs -``` + After this, the CLI should be available as `tree-sitter-stack-graphs`. + +2. _(From source)_ Instead of installing the CLI, it can also be run directly from the crate directory, as a replacement for a `tree-sitter-stack-graphs` invocation, as follows: + + ```sh + cargo run --features cli -- + ``` + +The basic CLI workflow for the command-line program is to index source code and issue queries against the resulting database: + +1. Index a source folder as follows: + + ```sh + tree-sitter-stack-graphs index SOURCE_DIR + ``` + + _Indexing will skip any files that have already be indexed. To force a re-index, add the `-f` flag._ + + To check the status if a source folder, run: + + ```sh + tree-sitter-stack-graphs status SOURCE_DIR + ``` -## Getting Started + To clean the database and start with a clean slate, run: -Starting a new project to develop stack graph definitions for your favourite language -is as easy as running the `init` command: + ```sh + tree-sitter-stack-graphs clean + ``` -``` sh -$ tree-sitter-stack-graphs init PROJECT_DIR + _Pass the `--delete` flag to not just empty the database, but also delete it. This is useful to resolve `unsupported database version` errors that may occur after a version update._ + +2. Run a query to find the definition(s) for a reference on a given line and column, run: + + ```sh + tree-sitter-stack-graphs query definition SOURCE_PATH:LINE:COLUMN + ``` + + Resulting definitions are printed, including a source line if the source file is available. + +Discover all available commands and flags by passing the `-h` flag to the CLI directly, or to any of the subcommands. + +## Getting Started on a new Language + +Starting a new project to develop stack graph definitions for your favourite language is as easy as running the `init` command: + +```sh +tree-sitter-stack-graphs init PROJECT_DIR ``` -Answer the questions to provide information about the language, the grammar dependency, and -the project and hit `Generate` to generate the new project. Check out `PROJECT_DIR/README.md` -to find out how to start developing. +Answer the questions to provide information about the language, the grammar dependency, and the project and hit `Generate` to generate the new project. Check out `PROJECT_DIR/README.md` to find out how to start developing. -Also check out our [examples][] for more details on how to work with the CLI. +Check out [examples][] of stack graph rules for typical language features. [examples]: https://github.com/github/stack-graphs/blob/main/tree-sitter-stack-graphs/examples/ @@ -65,14 +96,14 @@ Rust can be installed and updated using [rustup][]. Build the project by running: -``` -$ cargo build +```sh +cargo build ``` Run the tests by running: -``` -$ cargo test +```sh +cargo test ``` The project consists of a library and a CLI. @@ -81,22 +112,22 @@ To run `cargo` commands on the CLI as well, add `--features cli` or `--all-featu Run the CLI from source as follows: -``` sh -$ cargo run --features cli -- ARGS +```sh +cargo run --features cli -- ARGS ``` Sources are formatted using the standard Rust formatted, which is applied by running: -``` -$ cargo fmt +```sh +cargo fmt ``` ## License Licensed under either of - - [Apache License, Version 2.0][apache] ([LICENSE-APACHE](LICENSE-APACHE)) - - [MIT license][mit] ([LICENSE-MIT](LICENSE-MIT)) +- [Apache License, Version 2.0][apache] ([LICENSE-APACHE](LICENSE-APACHE)) +- [MIT license][mit] ([LICENSE-MIT](LICENSE-MIT)) at your option. diff --git a/tree-sitter-stack-graphs/examples/README.md b/tree-sitter-stack-graphs/examples/README.md index 901616422..5d5186ad5 100644 --- a/tree-sitter-stack-graphs/examples/README.md +++ b/tree-sitter-stack-graphs/examples/README.md @@ -7,19 +7,19 @@ Each directory contains a `stack-graphs.tsg` file that describes at the top what Running the examples requires the Python grammar to be available. This can be installed (in this directory) by executing: ```bash -$ ./bootstrap +./bootstrap ``` Run the tests for an example by executing: ```bash -$ ./run EXAMPLE_DIR +./run EXAMPLE_DIR ``` or, from within the example's directory: ```bash -$ ../run +../run ``` To render HTML visualizations of the stack graphs for the tests in an example, add the `-V` flag to run. @@ -27,7 +27,7 @@ To render HTML visualizations of the stack graphs for the tests in an example, a Print the parse tree of an example file by executing: ```bash -$ ./parse EXAMPLE_FILE +./parse EXAMPLE_FILE ``` The following examples are available: diff --git a/tree-sitter-stack-graphs/npm/.gitignore b/tree-sitter-stack-graphs/npm/.gitignore deleted file mode 100644 index 02b3615e1..000000000 --- a/tree-sitter-stack-graphs/npm/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/.crates.toml -/.crates2.json -/bin/ -/package-lock.json diff --git a/tree-sitter-stack-graphs/npm/README.md b/tree-sitter-stack-graphs/npm/README.md deleted file mode 100644 index 27dd321be..000000000 --- a/tree-sitter-stack-graphs/npm/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# tree-sitter-stack-graphs - -This package provides a convenient way to install the [tree-sitter-stack-graphs](https://crates.io/crates/tree-sitter-stack-graphs) CLI in an NPM project. - -Add it as a dev dependency to an existing project using: - - npm i -D tree-sitter-stack-graphs - -It is also possible to invoke it directly using: - - npx tree-sitter-stack-graphs - -See the tree-sitter-stack-graphs [documentation](https://crates.io/crates/tree-sitter-stack-graphs) for details on usage. diff --git a/tree-sitter-stack-graphs/npm/cli.js b/tree-sitter-stack-graphs/npm/cli.js deleted file mode 100755 index f4d654e15..000000000 --- a/tree-sitter-stack-graphs/npm/cli.js +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -const spawn = require("child_process").spawn; -const path = require("path"); - -const tssg = process.platform === "win32" - ? "tree-sitter-stack-graphs.exe" - : "tree-sitter-stack-graphs"; - -spawn( - path.join(__dirname, "bin", tssg), process.argv.slice(2), - { - "stdio": "inherit" - }, -).on('close', process.exit); diff --git a/tree-sitter-stack-graphs/npm/install.js b/tree-sitter-stack-graphs/npm/install.js deleted file mode 100644 index 6ac20756b..000000000 --- a/tree-sitter-stack-graphs/npm/install.js +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env node - -const child_process = require("child_process"); -const packageJSON = require("./package.json"); - -const cargo = process.platform === "win32" - ? "cargo.exe" - : "cargo"; - -try { - child_process.execSync(cargo); -} catch (error) { - console.error(error.message); - console.error("Failed to execute Cargo. Cargo needs to be available to install this package!"); - process.exit(1); -} - -child_process.spawn( - cargo, [ - "install", - "--quiet", - "--root", ".", - "--version", "^"+packageJSON.version, - "--features", "cli", - packageJSON.name, - ], - { - "stdio": "inherit" - }, -).on('close', process.exit); diff --git a/tree-sitter-stack-graphs/npm/package.json b/tree-sitter-stack-graphs/npm/package.json deleted file mode 100644 index a49ec5fad..000000000 --- a/tree-sitter-stack-graphs/npm/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "tree-sitter-stack-graphs", - "version": "0.7.0", - "description": "Create stack graphs using tree-sitter parsers", - "homepage": "https://github.com/github/stack-graphs/tree/main/tree-sitter-stack-graphs", - "repository": { - "type": "git", - "url": "https://github.com/github/stack-graphs.git" - }, - "keywords": [ - "tree-sitter", - "stack-graphs" - ], - "license": "MIT OR Apache-2.0", - "author": "GitHub ", - "contributors": [ - "Douglas Creager ", - "Hendrik van Antwerpen " - ], - "bin": { - "tree-sitter-stack-graphs": "./cli.js" - }, - "scripts": { - "install": "node install.js" - } -} diff --git a/tree-sitter-stack-graphs/src/cli.rs b/tree-sitter-stack-graphs/src/cli.rs index 859cbb5c2..719ce8f18 100644 --- a/tree-sitter-stack-graphs/src/cli.rs +++ b/tree-sitter-stack-graphs/src/cli.rs @@ -69,7 +69,7 @@ pub mod parse; pub mod query; pub mod status; pub mod test; -mod util; +pub mod util; pub mod visualize; pub mod path_loading { @@ -78,6 +78,7 @@ pub mod path_loading { use clap::Subcommand; use crate::cli::clean::CleanArgs; + use crate::cli::database::DatabaseArgs; use crate::cli::index::IndexArgs; use crate::cli::init::InitArgs; use crate::cli::load::PathLoaderArgs; @@ -90,8 +91,6 @@ pub mod path_loading { use crate::cli::test::TestArgs; use crate::cli::visualize::VisualizeArgs; - use super::database::DatabaseArgs; - #[derive(Subcommand)] pub enum Subcommands { Clean(Clean), @@ -297,6 +296,7 @@ pub mod provided_languages { use clap::Subcommand; use crate::cli::clean::CleanArgs; + use crate::cli::database::DatabaseArgs; use crate::cli::index::IndexArgs; use crate::cli::init::InitArgs; use crate::cli::load::LanguageConfigurationsLoaderArgs; @@ -310,8 +310,6 @@ pub mod provided_languages { use crate::cli::visualize::VisualizeArgs; use crate::loader::LanguageConfiguration; - use super::database::DatabaseArgs; - #[derive(Subcommand)] pub enum Subcommands { Clean(Clean), diff --git a/tree-sitter-stack-graphs/src/cli/index.rs b/tree-sitter-stack-graphs/src/cli/index.rs index 633866379..6425d8ca7 100644 --- a/tree-sitter-stack-graphs/src/cli/index.rs +++ b/tree-sitter-stack-graphs/src/cli/index.rs @@ -11,7 +11,10 @@ use stack_graphs::arena::Handle; use stack_graphs::graph::File; use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; +use stack_graphs::stats::FrequencyDistribution; use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::Stats as StitchingStats; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::storage::FileStatus; use stack_graphs::storage::SQLiteWriter; use std::collections::HashMap; @@ -21,6 +24,17 @@ use std::time::Duration; use thiserror::Error; use tree_sitter_graph::Variables; +use crate::cli::util::duration_from_seconds_str; +use crate::cli::util::iter_files_and_directories; +use crate::cli::util::print_indexing_stats; +use crate::cli::util::reporter::ConsoleReporter; +use crate::cli::util::reporter::Level; +use crate::cli::util::reporter::Reporter; +use crate::cli::util::sha1; +use crate::cli::util::wait_for_input; +use crate::cli::util::BuildErrorWithSource; +use crate::cli::util::CLIFileReporter; +use crate::cli::util::ExistingPathBufValueParser; use crate::loader::FileLanguageConfigurations; use crate::loader::FileReader; use crate::loader::Loader; @@ -28,16 +42,7 @@ use crate::BuildError; use crate::CancelAfterDuration; use crate::CancellationFlag; use crate::NoCancellation; - -use super::util::duration_from_seconds_str; -use super::util::iter_files_and_directories; -use super::util::sha1; -use super::util::wait_for_input; -use super::util::BuildErrorWithSource; -use super::util::ConsoleLogger; -use super::util::ExistingPathBufValueParser; -use super::util::FileLogger; -use super::util::Logger; +use crate::{FILE_PATH_VAR, ROOT_PATH_VAR}; #[derive(Args)] pub struct IndexArgs { @@ -78,6 +83,9 @@ pub struct IndexArgs { )] pub max_file_time: Option, + #[clap(long)] + pub stats: bool, + /// Wait for user input before starting analysis. Useful for profiling. #[clap(long)] pub wait_at_start: bool, @@ -93,6 +101,7 @@ impl IndexArgs { hide_error_details: false, max_file_time: None, wait_at_start: false, + stats: false, } } @@ -101,10 +110,11 @@ impl IndexArgs { wait_for_input()?; } let mut db = SQLiteWriter::open(&db_path)?; - let logger = ConsoleLogger::new(self.verbose, !self.hide_error_details); - let mut indexer = Indexer::new(&mut db, &mut loader, &logger); + let reporter = self.get_reporter(); + let mut indexer = Indexer::new(&mut db, &mut loader, &reporter); indexer.force = self.force; indexer.max_file_time = self.max_file_time; + indexer.set_collect_stats(self.stats); let source_paths = self .source_paths @@ -112,14 +122,45 @@ impl IndexArgs { .map(|p| p.canonicalize()) .collect::, _>>()?; indexer.index_all(source_paths, self.continue_from, &NoCancellation)?; + + if self.stats { + println!(); + print_indexing_stats(indexer.into_stats()); + } Ok(()) } + + fn get_reporter(&self) -> ConsoleReporter { + return ConsoleReporter { + skipped_level: if self.verbose { + Level::Summary + } else { + Level::None + }, + succeeded_level: if self.verbose { + Level::Summary + } else { + Level::None + }, + failed_level: if self.hide_error_details { + Level::Summary + } else { + Level::Details + }, + canceled_level: if self.hide_error_details { + Level::Summary + } else { + Level::Details + }, + }; + } } pub struct Indexer<'a> { db: &'a mut SQLiteWriter, loader: &'a mut Loader, - logger: &'a dyn Logger, + reporter: &'a dyn Reporter, + stats: Option, /// Index files, even if they already exist in the database. pub force: bool, /// Maximum time per file. @@ -127,13 +168,26 @@ pub struct Indexer<'a> { } impl<'a> Indexer<'a> { - pub fn new(db: &'a mut SQLiteWriter, loader: &'a mut Loader, logger: &'a dyn Logger) -> Self { + pub fn new( + db: &'a mut SQLiteWriter, + loader: &'a mut Loader, + reporter: &'a dyn Reporter, + ) -> Self { Self { db, loader, - logger, + reporter, force: false, max_file_time: None, + stats: None, + } + } + + pub fn set_collect_stats(&mut self, collect_stats: bool) { + if !collect_stats { + self.stats = None; + } else if self.stats.is_none() { + self.stats = Some(IndexingStats::default()); } } @@ -149,6 +203,7 @@ impl<'a> Indexer<'a> { Q: AsRef, { for (source_root, source_path, strict) in iter_files_and_directories(source_paths) { + let mut file_status = CLIFileReporter::new(self.reporter, &source_path); cancellation_flag.check("indexing all files")?; self.index_file( &source_root, @@ -156,7 +211,9 @@ impl<'a> Indexer<'a> { strict, &mut continue_from, cancellation_flag, + &mut file_status, )?; + file_status.assert_reported(); } Ok(()) } @@ -167,13 +224,16 @@ impl<'a> Indexer<'a> { source_path: &Path, cancellation_flag: &dyn CancellationFlag, ) -> Result<()> { + let mut file_status = CLIFileReporter::new(self.reporter, source_path); self.index_file( &source_root, &source_path, true, &mut None::<&Path>, cancellation_flag, + &mut file_status, )?; + file_status.assert_reported(); Ok(()) } @@ -185,22 +245,25 @@ impl<'a> Indexer<'a> { missing_is_error: bool, continue_from: &mut Option

, cancellation_flag: &dyn CancellationFlag, + file_status: &mut CLIFileReporter, ) -> Result<()> where P: AsRef, { - let mut file_status = self.logger.file(source_path); match self.index_file_inner( source_root, source_path, missing_is_error, continue_from, cancellation_flag, - file_status.as_mut(), + file_status, ) { - ok @ Ok(_) => ok, + ok @ Ok(_) => { + file_status.assert_reported(); + ok + } err @ Err(_) => { - file_status.default_failure("error", Some(&format!("Error analyzing file {}. To continue analysis from this file later, add: --continue-from {}", source_path.display(), source_path.display()))); + file_status.failure_if_processing("error", Some(&format!("Error analyzing file {}. To continue analysis from this file later, add: --continue-from {}", source_path.display(), source_path.display()))); err } } @@ -213,7 +276,7 @@ impl<'a> Indexer<'a> { missing_is_error: bool, continue_from: &mut Option

, cancellation_flag: &dyn CancellationFlag, - file_status: &mut dyn FileLogger, + file_status: &mut CLIFileReporter<'_>, ) -> Result<()> where P: AsRef, @@ -241,26 +304,35 @@ impl<'a> Indexer<'a> { } Err(e) => return Err(IndexError::LoadError(e)), }; + let stitcher_config = StitcherConfig::default() + .with_detect_similar_paths(!lcs.no_similar_paths_in_file()) + .with_collect_stats(self.stats.is_some()); let source = file_reader.get(source_path)?; let tag = sha1(source); - if !self.force { - match self - .db - .status_for_file(&source_path.to_string_lossy(), Some(&tag))? - { - FileStatus::Missing => {} - FileStatus::Indexed => { + let success_status = match self + .db + .status_for_file(&source_path.to_string_lossy(), Some(&tag))? + { + FileStatus::Missing => "indexed", + FileStatus::Indexed => { + if self.force { + "reindexed" + } else { file_status.skipped("cached index", None); return Ok(()); } - FileStatus::Error(error) => { + } + FileStatus::Error(error) => { + if self.force { + "reindexed" + } else { file_status.skipped(&format!("cached error ({})", error), None); return Ok(()); } } - } + }; let file_cancellation_flag = CancelAfterDuration::from_option(self.max_file_time); let cancellation_flag = cancellation_flag | file_cancellation_flag.as_ref(); @@ -284,26 +356,32 @@ impl<'a> Indexer<'a> { if let Err(err) = result { match err.inner { BuildError::Cancelled(_) => { - file_status.warning("parsing timed out", None); + file_status.warning("timed out", None); self.db - .store_error_for_file(source_path, &tag, "parsing timed out")?; - return Ok(()); - } - BuildError::ParseErrors { .. } => { - file_status.failure("parsing failed", Some(&err.display_pretty())); - self.db.store_error_for_file( - source_path, - &tag, - &format!("parsing failed: {}", err.inner), - )?; + .store_error_for_file(source_path, &tag, "timed out")?; return Ok(()); } _ => { - file_status.failure("failed to build stack graph", Some(&err.display_pretty())); - return Err(IndexError::StackGraph); + file_status.failure("failed", Some(&err.display_pretty())); + self.db.store_error_for_file(source_path, &tag, "failed")?; + return Ok(()); } } }; + if let Some(stats) = &mut self.stats { + stats.total_graph_nodes.record(graph.iter_nodes().count()); + let mut total_edges = 0; + for n in graph.iter_nodes() { + let edge_count = graph.outgoing_edges(n).count(); + if graph[n].is_root() { + stats.root_out_degree = edge_count; + } else { + stats.node_out_degrees.record(edge_count); + total_edges += edge_count; + } + } + stats.total_graph_edges.record(total_edges); + } let mut partials = PartialPaths::new(); let mut paths = Vec::new(); @@ -311,12 +389,17 @@ impl<'a> Indexer<'a> { &graph, &mut partials, file, + stitcher_config, &(&cancellation_flag as &dyn CancellationFlag), |_g, _ps, p| { paths.push(p.clone()); }, ) { - Ok(_) => {} + Ok(stitching_stats) => { + if let Some(stats) = &mut self.stats { + stats.stitching_stats += stitching_stats; + } + } Err(_) => { file_status.warning("path computation timed out", None); self.db.store_error_for_file( @@ -331,7 +414,7 @@ impl<'a> Indexer<'a> { self.db .store_result_for_file(&graph, file, &tag, &mut partials, &paths)?; - file_status.success("success", None); + file_status.success(success_status, None); Ok(()) } @@ -347,7 +430,16 @@ impl<'a> Indexer<'a> { ) -> std::result::Result<(), BuildErrorWithSource<'b>> { let relative_source_path = source_path.strip_prefix(source_root).unwrap(); if let Some(lc) = lcs.primary { - let globals = Variables::new(); + let mut globals = Variables::new(); + + globals + .add(FILE_PATH_VAR.into(), source_path.to_str().unwrap().into()) + .expect("failed to add file path variable"); + + globals + .add(ROOT_PATH_VAR.into(), source_root.to_str().unwrap().into()) + .expect("failed to add root path variable"); + lc.sgl .build_stack_graph_into(graph, file, source, &globals, cancellation_flag) .map_err(|inner| BuildErrorWithSource { @@ -394,6 +486,10 @@ impl<'a> Indexer<'a> { *continue_from = None; false } + + pub fn into_stats(self) -> IndexingStats { + self.stats.unwrap_or_default() + } } #[derive(Debug, Error)] @@ -404,7 +500,7 @@ pub enum IndexError { LoadError(#[source] crate::loader::LoadError<'static>), #[error("failed to read file")] ReadError(#[from] std::io::Error), - #[error("failed to build stank graph")] + #[error("failed to build stack graph")] StackGraph, #[error(transparent)] StorageError(#[from] stack_graphs::storage::StorageError), @@ -417,3 +513,17 @@ impl From for IndexError { } type Result = std::result::Result; + +#[derive(Clone, Debug, Default)] +pub struct IndexingStats { + // The distribution of the total number of nodes per file graph. + pub total_graph_nodes: FrequencyDistribution, + // The distribution of the total number of edges per file graph. + pub total_graph_edges: FrequencyDistribution, + // The distribution of the out-degrees of non-root nodes. + pub node_out_degrees: FrequencyDistribution, + // The root node's out-degree. + pub root_out_degree: usize, + // The stitching statistics. + pub stitching_stats: StitchingStats, +} diff --git a/tree-sitter-stack-graphs/src/cli/init.rs b/tree-sitter-stack-graphs/src/cli/init.rs index e87785477..e513e5acd 100644 --- a/tree-sitter-stack-graphs/src/cli/init.rs +++ b/tree-sitter-stack-graphs/src/cli/init.rs @@ -455,33 +455,79 @@ impl ProjectSettings<'_> { fn generate_readme(&self, project_path: &Path) -> anyhow::Result<()> { let mut file = File::create(project_path.join("README.md"))?; writedoc! {file, r####" - # tree-sitter-stack-graphs definition for {} + # tree-sitter-stack-graphs definition for {language_name} - This project defines tree-sitter-stack-graphs rules for {} using the [{}][] grammar. + This project defines tree-sitter-stack-graphs rules for {language_name} using the [{grammar_crate_name}][] grammar. - [{}]: https://crates.io/crates/{} + [{grammar_crate_name}]: https://crates.io/crates/{grammar_crate_name} - ## Usage + - [API documentation](https://docs.rs/{crate_name}/) + - [Release notes](https://github.com/github/stack-graphs/blob/main/languages/{crate_name}/CHANGELOG.md) + + ## Using the API To use this library, add the following to your `Cargo.toml`: - ``` toml + ```toml [dependencies] - {} = "{}" + {crate_name} = "{crate_version}" ``` - Check out our [documentation](https://docs.rs/{}/*/) for more details on how to use this library. + Check out our [documentation](https://docs.rs/{crate_name}/*/) for more details on how to use this library. - ## Command-line Program + ## Using the Command-line Program - The command-line program for `{}` lets you do stack graph based analysis and lookup from the command line. + The command-line program for `{crate_name}` lets you do stack graph based analysis and lookup from the command line. - Install the program using `cargo install` as follows: + The CLI can be run as follows: - ``` sh - $ cargo install --features cli {} - $ {} --help - ``` + 1. _(Installed)_ Install the CLI using Cargo as follows: + + ```sh + cargo install --features cli {crate_name} + ``` + + After this, the CLI should be available as `{crate_name}`. + + 2. _(From source)_ Instead of installing the CLI, it can also be run directly from the crate directory, as a replacement for a `{crate_name}` invocation, as follows: + + ```sh + cargo run --features cli -- + ``` + + The basic CLI workflow for the command-line program is to index source code and issue queries against the resulting database: + + 1. Index a source folder as follows: + + ```sh + {crate_name} index SOURCE_DIR + ``` + + _Indexing will skip any files that have already be indexed. To force a re-index, add the `-f` flag._ + + To check the status if a source folder, run: + + ```sh + {crate_name} status SOURCE_DIR + ``` + + To clean the database and start with a clean slate, run: + + ```sh + {crate_name} clean + ``` + + _Pass the `--delete` flag to not just empty the database, but also delete it. This is useful to resolve `unsupported database version` errors that may occur after a version update._ + + 2. Run a query to find the definition(s) for a reference on a given line and column, run: + + ```sh + {crate_name} query definition SOURCE_PATH:LINE:COLUMN + ``` + + Resulting definitions are printed, including a source line if the source file is available. + + Discover all available commands and flags by passing the `-h` flag to the CLI directly, or to any of the subcommands. ## Development @@ -492,35 +538,29 @@ impl ProjectSettings<'_> { The project is organized as follows: - The stack graph rules are defined in `src/stack-graphs.tsg`. - - Builtins sources and configuration are defined in `src/builtins.{}` and `builtins.cfg` respectively. + - Builtins sources and configuration are defined in `src/builtins.{language_file_extension}` and `builtins.cfg` respectively. - Tests are put into the `test` directory. - ### Building and Running Tests - - Build the project by running: - - ``` sh - $ cargo build - ``` + ### Running Tests Run the tests as follows: - ``` sh - $ cargo test + ```sh + cargo test ``` The project consists of a library and a CLI. By default, running `cargo` only applies to the library. To run `cargo` commands on the CLI as well, add `--features cli` or `--all-features`. Run the CLI from source as follows: - ``` sh - $ cargo run --features cli -- ARGS + ```sh + cargo run --features cli -- ARGS ``` Sources are formatted using the standard Rust formatted, which is applied by running: - ``` sh - $ cargo fmt + ```sh + cargo fmt ``` ### Writing TSG @@ -535,34 +575,30 @@ impl ProjectSettings<'_> { Parse and test a single file by executing the following commands: - ``` sh - $ cargo run --features cli -- parse FILES... - $ cargo run --features cli -- test TESTFILES... + ```sh + cargo run --features cli -- parse FILES... + cargo run --features cli -- test TESTFILES... ``` Generate a visualization to debug failing tests by passing the `-V` flag: - ``` sh - $ cargo run --features cli -- test -V TESTFILES... + ```sh + cargo run --features cli -- test -V TESTFILES... ``` To generate the visualization regardless of test outcome, execute: - ``` sh - $ cargo run --features cli -- test -V --output-mode=always TESTFILES... + ```sh + cargo run --features cli -- test -V --output-mode=always TESTFILES... ``` - Go to https://crates.io/crates/tree-sitter-stack-graphs for links to examples and documentation. + Go to for links to examples and documentation. "####, - self.language_name, - self.language_name, self.grammar_crate_name(), - self.grammar_crate_name(), self.grammar_crate_name(), - self.crate_name(), self.crate_version(), - self.crate_name(), - self.crate_name(), - self.crate_name(), - self.crate_name(), - self.language_file_extension, + language_name=self.language_name, + grammar_crate_name=self.grammar_crate_name(), + crate_name=self.crate_name(), + crate_version=self.crate_version(), + language_file_extension=self.language_file_extension, }?; Ok(()) } @@ -718,7 +754,6 @@ impl ProjectSettings<'_> { let mut file = File::create(project_path.join("rust/lib.rs"))?; self.write_license_header(&mut file, "// ")?; writedoc! {file, r#" - use tree_sitter_stack_graphs::loader::FileAnalyzers; use tree_sitter_stack_graphs::loader::LanguageConfiguration; use tree_sitter_stack_graphs::loader::LoadError; use tree_sitter_stack_graphs::CancellationFlag; @@ -735,9 +770,6 @@ impl ProjectSettings<'_> { /// The stack graphs builtins source for this language. pub const STACK_GRAPHS_BUILTINS_SOURCE: &str = include_str!("../src/builtins.{}"); - /// The name of the file path global variable. - pub const FILE_PATH_VAR: &str = "FILE_PATH"; - pub fn language_configuration(cancellation_flag: &dyn CancellationFlag) -> LanguageConfiguration {{ try_language_configuration(cancellation_flag).unwrap_or_else(|err| panic!("{{}}", err)) }} @@ -746,7 +778,7 @@ impl ProjectSettings<'_> { cancellation_flag: &dyn CancellationFlag, ) -> Result {{ LanguageConfiguration::from_sources( - {}::language(), + {}::LANGUAGE.into(), Some(String::from("source.{}")), None, vec![String::from("{}")], @@ -757,7 +789,6 @@ impl ProjectSettings<'_> { STACK_GRAPHS_BUILTINS_SOURCE, )), Some(STACK_GRAPHS_BUILTINS_CONFIG), - FileAnalyzers::new(), cancellation_flag, ) }} diff --git a/tree-sitter-stack-graphs/src/cli/load.rs b/tree-sitter-stack-graphs/src/cli/load.rs index b32332f9d..1fd22fa2b 100644 --- a/tree-sitter-stack-graphs/src/cli/load.rs +++ b/tree-sitter-stack-graphs/src/cli/load.rs @@ -67,7 +67,7 @@ impl PathLoaderArgs { builtins_paths, )? } else { - let loader_config = TsConfig::load() + let loader_config = TsConfig::load(None) .and_then(|v| v.get()) .map_err(LoadError::TreeSitter)?; Loader::from_tree_sitter_configuration( diff --git a/tree-sitter-stack-graphs/src/cli/lsp.rs b/tree-sitter-stack-graphs/src/cli/lsp.rs index 3b80ff420..57ed770bb 100644 --- a/tree-sitter-stack-graphs/src/cli/lsp.rs +++ b/tree-sitter-stack-graphs/src/cli/lsp.rs @@ -28,21 +28,19 @@ use tower_lsp::LanguageServer; use tower_lsp::LspService; use tower_lsp::Server; +use crate::cli::index::Indexer; +use crate::cli::query::Querier; +use crate::cli::query::QueryError; +use crate::cli::util::duration_from_milliseconds_str; +use crate::cli::util::duration_from_seconds_str; +use crate::cli::util::reporter::Reporter; +use crate::cli::util::SourcePosition; +use crate::cli::util::SourceSpan; use crate::loader::Loader; use crate::AtomicCancellationFlag; use crate::CancelAfterDuration; use crate::CancellationFlag; -use super::index::Indexer; -use super::query::Querier; -use super::query::QueryError; -use super::util::duration_from_milliseconds_str; -use super::util::duration_from_seconds_str; -use super::util::FileLogger; -use super::util::Logger; -use super::util::SourcePosition; -use super::util::SourceSpan; - #[derive(Args, Clone)] pub struct LspArgs { /// Maximum index runtime per workspace folder in seconds. @@ -208,14 +206,14 @@ impl Backend { } }; - let logger = LspLogger { + let reporter = LspReporter { handle: handle.clone(), logger: self.logger.clone(), }; let folder_cancellation_flag = CancelAfterDuration::from_option(self.args.max_folder_index_time); let cancellation_flag = cancellation_flag | folder_cancellation_flag.as_ref(); - let mut indexer = Indexer::new(&mut db, &mut loader, &logger); + let mut indexer = Indexer::new(&mut db, &mut loader, &reporter); indexer.max_file_time = self.args.max_file_index_time; let result = indexer.index_all(vec![path], None::<&Path>, &cancellation_flag); @@ -283,12 +281,12 @@ impl Backend { }; let handle = Handle::current(); - let logger = LspLogger { + let reporter = LspReporter { handle: handle.clone(), logger: self.logger.clone(), }; let result = { - let mut querier = Querier::new(&mut db, &logger); + let mut querier = Querier::new(&mut db, &reporter); let cancellation_flag = CancelAfterDuration::from_option(self.args.max_query_time); querier.definitions(reference, cancellation_flag.as_ref()) }; @@ -530,16 +528,16 @@ struct BackendLogger { } impl BackendLogger { - async fn info(&self, message: M) { - self.client.log_message(MessageType::INFO, message).await + async fn log(&self, level: MessageType, message: M) { + self.client.log_message(level, message).await } - async fn warning(&self, message: M) { - self.client.log_message(MessageType::WARNING, message).await + async fn info(&self, message: M) { + self.log(MessageType::INFO, message).await } async fn error(&self, message: M) { - self.client.log_message(MessageType::ERROR, message).await + self.log(MessageType::ERROR, message).await } } @@ -562,25 +560,6 @@ impl FromStdError for std::result::Result { } } -trait FromAnyhowError { - #[must_use] - fn from_error(self) -> Result; -} - -impl FromAnyhowError for std::result::Result { - #[must_use] - fn from_error(self) -> Result { - match self { - Ok(value) => Ok(value), - Err(err) => Err(Error { - code: ErrorCode::ServerError(-1), - message: err.to_string(), - data: None, - }), - } - } -} - #[derive(Debug)] pub enum Job { IndexPath(PathBuf), @@ -596,90 +575,43 @@ impl Job { } } -struct LspLogger { - handle: Handle, - logger: BackendLogger, -} -struct LspFileLogger<'a> { - path: &'a Path, +struct LspReporter { handle: Handle, logger: BackendLogger, } -impl Logger for LspLogger { - fn file<'a>(&self, path: &'a Path) -> Box { - Box::new(LspFileLogger { - path, - handle: self.handle.clone(), - logger: self.logger.clone(), - }) - } -} - -impl FileLogger for LspFileLogger<'_> { - fn default_failure(&mut self, status: &str, _details: Option<&dyn std::fmt::Display>) { +impl LspReporter { + fn report(&self, level: MessageType, path: &Path, status: &str) { let logger = self.logger.clone(); - let path = self.path.to_owned(); + let path = path.to_owned(); let status = status.to_owned(); self.handle.spawn(async move { logger - .error(format!("{}: {}", path.display(), status)) + .log(level, format!("{}: {}", path.display(), status)) .await; }); } +} - fn failure(&mut self, status: &str, _details: Option<&dyn std::fmt::Display>) { - let logger = self.logger.clone(); - let path = self.path.to_owned(); - let status = status.to_owned(); - self.handle.spawn(async move { - logger - .error(format!("{}: {}", path.display(), status)) - .await; - }); +impl Reporter for LspReporter { + fn skipped(&self, path: &Path, summary: &str, _details: Option<&dyn std::fmt::Display>) { + self.report(MessageType::INFO, path, summary) } - fn skipped(&mut self, status: &str, _details: Option<&dyn std::fmt::Display>) { - let logger = self.logger.clone(); - let path = self.path.to_owned(); - let status = status.to_owned(); - self.handle.spawn(async move { - logger - .info(format!("{}: skipped: {}", path.display(), status)) - .await; - }); + fn started(&self, path: &Path) { + self.report(MessageType::INFO, path, "started") } - fn warning(&mut self, status: &str, _details: Option<&dyn std::fmt::Display>) { - let logger = self.logger.clone(); - let path = self.path.to_owned(); - let status = status.to_owned(); - self.handle.spawn(async move { - logger - .warning(format!("{}: {}", path.display(), status)) - .await; - }); + fn succeeded(&self, path: &Path, summary: &str, _details: Option<&dyn std::fmt::Display>) { + self.report(MessageType::INFO, path, summary) } - fn success(&mut self, status: &str, _details: Option<&dyn std::fmt::Display>) { - let logger = self.logger.clone(); - let path = self.path.to_owned(); - let status = status.to_owned(); - self.handle.spawn(async move { - logger - .info(format!("{}: success: {}", path.display(), status)) - .await; - }); + fn failed(&self, path: &Path, summary: &str, _details: Option<&dyn std::fmt::Display>) { + self.report(MessageType::ERROR, path, summary) } - fn processing(&mut self) { - let logger = self.logger.clone(); - let path = self.path.to_owned(); - self.handle.spawn(async move { - logger - .info(format!("{}: processing...", path.display())) - .await; - }); + fn cancelled(&self, path: &Path, summary: &str, _details: Option<&dyn std::fmt::Display>) { + self.report(MessageType::WARNING, path, summary) } } diff --git a/tree-sitter-stack-graphs/src/cli/match.rs b/tree-sitter-stack-graphs/src/cli/match.rs index 108c8450e..889d2374a 100644 --- a/tree-sitter-stack-graphs/src/cli/match.rs +++ b/tree-sitter-stack-graphs/src/cli/match.rs @@ -49,7 +49,7 @@ impl MatchArgs { None => return Err(anyhow!("No stack graph language found")), }; let source = file_reader.get(&self.source_path)?; - let tree = parse(lc.language, &self.source_path, source)?; + let tree = parse(&lc.language, &self.source_path, source)?; if self.stanza.is_empty() { lc.sgl.tsg.try_visit_matches(&tree, source, true, |mat| { print_matches(lc.sgl.tsg_path(), &self.source_path, source, mat) diff --git a/tree-sitter-stack-graphs/src/cli/parse.rs b/tree-sitter-stack-graphs/src/cli/parse.rs index b4cc688f8..64275b915 100644 --- a/tree-sitter-stack-graphs/src/cli/parse.rs +++ b/tree-sitter-stack-graphs/src/cli/parse.rs @@ -13,13 +13,12 @@ use std::path::PathBuf; use tree_sitter::Parser; use tree_sitter_graph::parse_error::ParseError; +use crate::cli::util::ExistingPathBufValueParser; use crate::loader::FileReader; use crate::loader::Loader; use crate::util::DisplayParseErrorsPretty; use crate::BuildError; -use super::util::ExistingPathBufValueParser; - #[derive(Args)] pub struct ParseArgs { /// Source file path to parse. @@ -41,14 +40,14 @@ impl ParseArgs { None => return Err(anyhow!("No stack graph language found")), }; let source = file_reader.get(&self.source_path)?; - let tree = parse(lang, &self.source_path, source)?; + let tree = parse(&lang, &self.source_path, source)?; print_tree(tree); Ok(()) } } pub(super) fn parse( - language: tree_sitter::Language, + language: &tree_sitter::Language, path: &Path, source: &str, ) -> anyhow::Result { diff --git a/tree-sitter-stack-graphs/src/cli/query.rs b/tree-sitter-stack-graphs/src/cli/query.rs index f0256bc67..dd5526570 100644 --- a/tree-sitter-stack-graphs/src/cli/query.rs +++ b/tree-sitter-stack-graphs/src/cli/query.rs @@ -5,34 +5,42 @@ // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. // ------------------------------------------------------------------------------------------------ +use std::path::Path; +use std::path::PathBuf; + use clap::Args; use clap::Parser; use clap::Subcommand; use clap::ValueHint; +use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::Stats as StitchingStats; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::storage::FileStatus; use stack_graphs::storage::SQLiteReader; -use std::path::Path; -use std::path::PathBuf; use thiserror::Error; use tree_sitter_graph::parse_error::Excerpt; +use crate::cli::util::print_database_stats; +use crate::cli::util::print_stitching_stats; +use crate::cli::util::reporter::ConsoleReporter; +use crate::cli::util::reporter::Reporter; +use crate::cli::util::sha1; +use crate::cli::util::wait_for_input; +use crate::cli::util::SourcePosition; +use crate::cli::util::SourceSpan; use crate::loader::FileReader; use crate::CancellationFlag; use crate::NoCancellation; -use super::util::sha1; -use super::util::wait_for_input; -use super::util::ConsoleLogger; -use super::util::Logger; -use super::util::SourcePosition; -use super::util::SourceSpan; - #[derive(Args)] pub struct QueryArgs { /// Wait for user input before starting analysis. Useful for profiling. #[clap(long)] pub wait_at_start: bool, + #[clap(long)] + pub stats: bool, + #[clap(subcommand)] target: Target, } @@ -43,7 +51,14 @@ impl QueryArgs { wait_for_input()?; } let mut db = SQLiteReader::open(&db_path)?; - self.target.run(&mut db) + let stitching_stats = self.target.run(&mut db, self.stats)?; + if self.stats { + println!(); + print_stitching_stats(stitching_stats); + println!(); + print_database_stats(db.stats()); + } + Ok(()) } } @@ -53,12 +68,14 @@ pub enum Target { } impl Target { - pub fn run(self, db: &mut SQLiteReader) -> anyhow::Result<()> { - let logger = ConsoleLogger::new(true, true); - let mut querier = Querier::new(db, &logger); + fn run(self, db: &mut SQLiteReader, collect_stats: bool) -> anyhow::Result { + let reporter = ConsoleReporter::details(); + let mut querier = Querier::new(db, &reporter); + querier.set_collect_stats(collect_stats); match self { - Self::Definition(cmd) => cmd.run(&mut querier), + Self::Definition(cmd) => cmd.run(&mut querier)?, } + Ok(querier.into_stats()) } } @@ -116,7 +133,7 @@ impl Definition { n => println!("{}has {} definitions", " ".repeat(indent), n), } for definition in definitions.into_iter() { - println!( + print!( "{}", Excerpt::from_source( &definition.path, @@ -135,12 +152,25 @@ impl Definition { pub struct Querier<'a> { db: &'a mut SQLiteReader, - logger: &'a dyn Logger, + reporter: &'a dyn Reporter, + stats: Option, } impl<'a> Querier<'a> { - pub fn new(db: &'a mut SQLiteReader, logger: &'a dyn Logger) -> Self { - Self { db, logger } + pub fn new(db: &'a mut SQLiteReader, reporter: &'a dyn Reporter) -> Self { + Self { + db, + reporter, + stats: None, + } + } + + pub fn set_collect_stats(&mut self, collect_stats: bool) { + if !collect_stats { + self.stats = None; + } else if self.stats.is_none() { + self.stats = Some(StitchingStats::default()); + } } pub fn definitions( @@ -149,7 +179,6 @@ impl<'a> Querier<'a> { cancellation_flag: &dyn CancellationFlag, ) -> Result> { let log_path = PathBuf::from(reference.to_string()); - let mut logger = self.logger.file(&log_path); let mut file_reader = FileReader::new(); let tag = file_reader.get(&reference.path).ok().map(sha1); @@ -159,12 +188,13 @@ impl<'a> Querier<'a> { { FileStatus::Indexed => {} _ => { - logger.failure("file not indexed", None); - return Ok(Vec::default()); + self.reporter.started(&log_path); + self.reporter.failed(&log_path, "file not indexed", None); + return Ok(Default::default()); } } - logger.processing(); + self.reporter.started(&log_path); self.db .load_graph_for_file(&reference.path.to_string_lossy())?; @@ -172,8 +202,9 @@ impl<'a> Querier<'a> { let starting_nodes = reference.iter_references(graph).collect::>(); if starting_nodes.is_empty() { - logger.warning("no references at location", None); - return Ok(Vec::default()); + self.reporter + .cancelled(&log_path, "no references at location", None); + return Ok(Default::default()); } let mut result = Vec::new(); @@ -184,22 +215,36 @@ impl<'a> Querier<'a> { }; let mut reference_paths = Vec::new(); - if let Err(err) = self.db.find_all_complete_partial_paths( + let stitcher_config = StitcherConfig::default() + // always detect similar paths, we don't know the language configurations for the data in the database + .with_detect_similar_paths(true) + .with_collect_stats(self.stats.is_some()); + let ref_result = ForwardPartialPathStitcher::find_all_complete_partial_paths( + self.db, std::iter::once(node), + stitcher_config, &cancellation_flag, |_g, _ps, p| { reference_paths.push(p.clone()); }, - ) { - logger.failure("query timed out", None); - return Err(err.into()); + ); + match ref_result { + Ok(ref_stats) => { + if let Some(stats) = &mut self.stats { + *stats += ref_stats + } + } + Err(err) => { + self.reporter.failed(&log_path, "query timed out", None); + return Err(err.into()); + } } let (graph, partials, _) = self.db.get(); let mut actual_paths = Vec::new(); for reference_path in &reference_paths { if let Err(err) = cancellation_flag.check("shadowing") { - logger.failure("query timed out", None); + self.reporter.failed(&log_path, "query timed out", None); return Err(err.into()); } if reference_paths @@ -232,7 +277,8 @@ impl<'a> Querier<'a> { } let count: usize = result.iter().map(|r| r.targets.len()).sum(); - logger.success( + self.reporter.succeeded( + &log_path, &format!( "found {} definitions for {} references", count, @@ -243,6 +289,10 @@ impl<'a> Querier<'a> { Ok(result) } + + pub fn into_stats(self) -> StitchingStats { + self.stats.unwrap_or_default() + } } #[derive(Debug, Error)] @@ -255,6 +305,12 @@ pub enum QueryError { StorageError(#[from] stack_graphs::storage::StorageError), } +impl From for QueryError { + fn from(value: stack_graphs::CancellationError) -> Self { + Self::Cancelled(value.0) + } +} + impl From for QueryError { fn from(value: crate::CancellationError) -> Self { Self::Cancelled(value.0) diff --git a/tree-sitter-stack-graphs/src/cli/status.rs b/tree-sitter-stack-graphs/src/cli/status.rs index d877d29bc..847837b80 100644 --- a/tree-sitter-stack-graphs/src/cli/status.rs +++ b/tree-sitter-stack-graphs/src/cli/status.rs @@ -14,8 +14,8 @@ use stack_graphs::storage::SQLiteReader; use std::path::Path; use std::path::PathBuf; -use super::util::ConsoleFileLogger; -use super::util::FileLogger; +use crate::cli::util::reporter::ConsoleReporter; +use crate::cli::util::reporter::Reporter; #[derive(Args)] #[clap(group( @@ -41,38 +41,48 @@ pub struct StatusArgs { impl StatusArgs { pub fn run(self, db_path: &Path) -> anyhow::Result<()> { + let reporter = self.get_reporter(); let mut db = SQLiteReader::open(&db_path)?; if self.all { let mut files = db.list_all()?; let mut entries = files.try_iter()?; - self.status(&mut entries)?; + self.status(&mut entries, &reporter)?; } else { for source_path in &self.source_paths { let source_path = source_path.canonicalize()?; let mut files = db.list_file_or_directory(&source_path)?; let mut entries = files.try_iter()?; - self.status(&mut entries)?; + self.status(&mut entries, &reporter)?; } } Ok(()) } + fn get_reporter(&self) -> ConsoleReporter { + if self.verbose { + ConsoleReporter::details() + } else { + ConsoleReporter::summary() + } + } + fn status( &self, entries: &mut impl Iterator>, + reporter: &dyn Reporter, ) -> anyhow::Result<()> { for entry in entries { let entry = entry?; - let mut logger = ConsoleFileLogger::new(&Path::new(&entry.path), true, self.verbose); + reporter.started(&entry.path); match &entry.status { FileStatus::Missing => { - logger.skipped("missing", None); + reporter.cancelled(&entry.path, "missing", None); } FileStatus::Indexed => { - logger.success("indexed", None); + reporter.succeeded(&entry.path, "indexed", None); } FileStatus::Error(error) => { - logger.failure("failed", Some(error)); + reporter.failed(&entry.path, "failed", Some(error)); } } } diff --git a/tree-sitter-stack-graphs/src/cli/test.rs b/tree-sitter-stack-graphs/src/cli/test.rs index 195f9a532..e3034cbbf 100644 --- a/tree-sitter-stack-graphs/src/cli/test.rs +++ b/tree-sitter-stack-graphs/src/cli/test.rs @@ -16,17 +16,24 @@ use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; use stack_graphs::serde::Filter; use stack_graphs::stitching::Database; +use stack_graphs::stitching::DatabaseCandidates; use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::StitcherConfig; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; use std::time::Duration; +use tree_sitter::Language; use tree_sitter_graph::Variables; use crate::cli::util::duration_from_seconds_str; use crate::cli::util::iter_files_and_directories; -use crate::cli::util::ConsoleFileLogger; +use crate::cli::util::reporter::ConsoleReporter; +use crate::cli::util::reporter::Level; +use crate::cli::util::CLIFileReporter; use crate::cli::util::ExistingPathBufValueParser; -use crate::cli::util::FileLogger; use crate::cli::util::PathSpec; use crate::loader::ContentProvider; use crate::loader::FileReader; @@ -36,6 +43,7 @@ use crate::test::Test; use crate::test::TestResult; use crate::CancelAfterDuration; use crate::CancellationFlag; +use crate::FILE_PATH_VAR; #[derive(Args)] #[clap(after_help = r#"PATH SPECIFICATIONS: @@ -171,9 +179,19 @@ impl TestArgs { } pub fn run(self, mut loader: Loader) -> anyhow::Result<()> { + let reporter = self.get_reporter(); let mut total_result = TestResult::new(); + let mut cache = HashMap::new(); for (test_root, test_path, _) in iter_files_and_directories(self.test_paths.clone()) { - let test_result = self.run_test(&test_root, &test_path, &mut loader)?; + let mut file_status = CLIFileReporter::new(&reporter, &test_path); + let test_result = self.run_test( + &test_root, + &test_path, + &mut loader, + &mut file_status, + &mut cache, + )?; + file_status.assert_reported(); total_result.absorb(test_result); } if total_result.failure_count() > 0 { @@ -182,30 +200,52 @@ impl TestArgs { Ok(()) } + fn get_reporter(&self) -> ConsoleReporter { + return ConsoleReporter { + skipped_level: if self.show_skipped { + Level::Summary + } else { + Level::None + }, + succeeded_level: if self.quiet { + Level::None + } else { + Level::Summary + }, + failed_level: if self.hide_error_details { + Level::Summary + } else { + Level::Details + }, + canceled_level: Level::Details, // tester doesn't report canceled + }; + } + /// Run test file. Takes care of the output when an error is returned. - fn run_test( + fn run_test<'a>( &self, test_root: &Path, test_path: &Path, - loader: &mut Loader, + loader: &'a mut Loader, + file_status: &mut CLIFileReporter, + cache: &mut HashMap, ) -> anyhow::Result { - let mut file_status = - ConsoleFileLogger::new(test_path, !self.quiet, !self.hide_error_details); - match self.run_test_inner(test_root, test_path, loader, &mut file_status) { + match self.run_test_inner(test_root, test_path, loader, file_status, cache) { ok @ Ok(_) => ok, err @ Err(_) => { - file_status.default_failure("error", None); + file_status.failure_if_processing("error", None); err } } } - fn run_test_inner( + fn run_test_inner<'a>( &self, test_root: &Path, test_path: &Path, - loader: &mut Loader, - file_status: &mut ConsoleFileLogger, + loader: &'a mut Loader, + file_status: &mut CLIFileReporter, + cache: &mut HashMap, ) -> anyhow::Result { let cancellation_flag = CancelAfterDuration::from_option(self.max_test_time); @@ -230,19 +270,30 @@ impl TestArgs { .map_or(false, |e| e == "skip"), _ => false, }) { - if self.show_skipped { - file_status.warning("skipped", None); - } + file_status.skipped("skipped", None); return Ok(TestResult::new()); } file_status.processing(); + let stitcher_config = + StitcherConfig::default().with_detect_similar_paths(!lc.no_similar_paths_in_file); + let mut partials = PartialPaths::new(); + let mut db = Database::new(); + let source = file_reader.get(test_path)?; let default_fragment_path = test_path.strip_prefix(test_root).unwrap(); let mut test = Test::from_source(test_path, source, default_fragment_path)?; if !self.no_builtins { - self.load_builtins_into(&lc, &mut test.graph)?; + self.load_builtins_into( + &lc, + &mut test.graph, + &mut partials, + &mut db, + stitcher_config, + cancellation_flag.as_ref(), + cache, + )?; } let mut globals = Variables::new(); for test_fragment in &test.fragments { @@ -266,7 +317,16 @@ impl TestArgs { &mut Some(test_fragment.source.as_ref()), )? { globals.clear(); + test_fragment.add_globals_to(&mut globals); + + globals + .add( + FILE_PATH_VAR.into(), + test_fragment.path.to_str().unwrap().into(), + ) + .unwrap_or_default(); + lc.sgl.build_stack_graph_into( &mut test.graph, test_fragment.file, @@ -300,22 +360,26 @@ impl TestArgs { Ok(_) => {} } } - let mut partials = PartialPaths::new(); - let mut db = Database::new(); - for file in test.graph.iter_files() { + for fragment in &test.fragments { ForwardPartialPathStitcher::find_minimal_partial_path_set_in_file( &test.graph, &mut partials, - file, + fragment.file, + stitcher_config, &cancellation_flag.as_ref(), |g, ps, p| { db.add_partial_path(g, ps, p.clone()); }, )?; } - let result = test.run(&mut partials, &mut db, cancellation_flag.as_ref())?; - let success = self.handle_result(&result, file_status)?; - if self.output_mode.test(!success) { + let result = test.run( + &mut partials, + &mut db, + stitcher_config, + cancellation_flag.as_ref(), + )?; + let success = result.failure_count() == 0; + let outputs = if self.output_mode.test(!success) { let files = test.fragments.iter().map(|f| f.file).collect::>(); self.save_output( test_root, @@ -325,42 +389,75 @@ impl TestArgs { &mut db, &|_: &StackGraph, h: &Handle| files.contains(h), success, + stitcher_config, cancellation_flag.as_ref(), - )?; - } - Ok(result) - } - - fn load_builtins_into( - &self, - lc: &LanguageConfiguration, - graph: &mut StackGraph, - ) -> anyhow::Result<()> { - if let Err(h) = graph.add_from_graph(&lc.builtins) { - return Err(anyhow!("Duplicate builtin file {}", &graph[h])); - } - Ok(()) - } + )? + } else { + Vec::default() + }; - fn handle_result( - &self, - result: &TestResult, - file_status: &mut ConsoleFileLogger, - ) -> anyhow::Result { - let success = result.failure_count() == 0; if success { - file_status.success("success", None); + let details = outputs.join("\n"); + file_status.success("success", Some(&details)); } else { + let details = result + .failures_iter() + .map(|f| f.to_string()) + .chain(outputs) + .join("\n"); file_status.failure( &format!( "{}/{} assertions failed", result.failure_count(), result.count(), ), - Some(&result.failures_iter().join("\n")), + Some(&details), ); } - Ok(success) + + Ok(result) + } + + fn load_builtins_into<'a>( + &self, + lc: &'a LanguageConfiguration, + graph: &mut StackGraph, + partials: &mut PartialPaths, + db: &mut Database, + stitcher_config: StitcherConfig, + cancellation_flag: &dyn CancellationFlag, + cache: &mut HashMap, + ) -> anyhow::Result<()> { + let files = graph + .add_from_graph(&lc.builtins) + .map_err(|h| anyhow!("Duplicate builtin file {}", &graph[h]))?; + let files = files.into_iter().collect::>(); + match cache.entry(lc.language.clone()) { + Entry::Occupied(o) => { + o.get().load_into(graph, partials, db)?; + } + Entry::Vacant(v) => { + for file in &files { + ForwardPartialPathStitcher::find_minimal_partial_path_set_in_file( + graph, + partials, + *file, + stitcher_config, + &cancellation_flag, + |g, ps, p| { + db.add_partial_path(g, ps, p.clone()); + }, + )?; + } + v.insert(db.to_serializable_filter( + graph, + partials, + &|_: &StackGraph, f: &Handle| files.contains(f), + )); + } + }; + + Ok(()) } fn save_output( @@ -372,8 +469,10 @@ impl TestArgs { db: &mut Database, filter: &dyn Filter, success: bool, + stitcher_config: StitcherConfig, cancellation_flag: &dyn CancellationFlag, - ) -> anyhow::Result<()> { + ) -> anyhow::Result> { + let mut outputs = Vec::with_capacity(3); let save_graph = self .save_graph .as_ref() @@ -390,12 +489,23 @@ impl TestArgs { if let Some(path) = save_graph { self.save_graph(&path, &graph, filter)?; if !success || !self.quiet { - println!("{}: graph at {}", test_path.display(), path.display()); + outputs.push(format!( + "{}: graph at {}", + test_path.display(), + path.display() + )); } } let mut db = if save_paths.is_some() || save_visualization.is_some() { - self.compute_paths(graph, partials, db, filter, cancellation_flag)? + self.compute_paths( + graph, + partials, + db, + filter, + stitcher_config, + cancellation_flag, + )? } else { Database::new() }; @@ -403,21 +513,25 @@ impl TestArgs { if let Some(path) = save_paths { self.save_paths(&path, graph, partials, &mut db, filter)?; if !success || !self.quiet { - println!("{}: paths at {}", test_path.display(), path.display()); + outputs.push(format!( + "{}: paths at {}", + test_path.display(), + path.display() + )); } } if let Some(path) = save_visualization { self.save_visualization(&path, graph, partials, &mut db, filter, &test_path)?; if !success || !self.quiet { - println!( + outputs.push(format!( "{}: visualization at {}", test_path.display(), path.display() - ); + )); } } - Ok(()) + Ok(outputs) } fn save_graph( @@ -440,6 +554,7 @@ impl TestArgs { partials: &mut PartialPaths, db: &mut Database, filter: &dyn Filter, + stitcher_config: StitcherConfig, cancellation_flag: &dyn CancellationFlag, ) -> anyhow::Result { let references = graph @@ -448,10 +563,9 @@ impl TestArgs { .collect::>(); let mut paths = Vec::new(); ForwardPartialPathStitcher::find_all_complete_partial_paths( - graph, - partials, - db, + &mut DatabaseCandidates::new(graph, partials, db), references.clone(), + stitcher_config, &cancellation_flag, |_, _, p| { paths.push(p.clone()); diff --git a/tree-sitter-stack-graphs/src/cli/util.rs b/tree-sitter-stack-graphs/src/cli/util.rs index e222fe434..6b7deab4c 100644 --- a/tree-sitter-stack-graphs/src/cli/util.rs +++ b/tree-sitter-stack-graphs/src/cli/util.rs @@ -12,24 +12,31 @@ use clap::builder::TypedValueParser; use clap::error::ContextKind; use clap::error::ContextValue; use clap::error::ErrorKind; -use colored::Colorize; use lsp_positions::Span; use sha1::Digest; use sha1::Sha1; use stack_graphs::arena::Handle; use stack_graphs::graph::Node; use stack_graphs::graph::StackGraph; +use stack_graphs::stats::FrequencyDistribution; +use stack_graphs::stitching::Stats as StitchingStats; +use stack_graphs::storage::Stats as StorageStats; use std::ffi::OsStr; use std::ffi::OsString; +use std::fmt::Display; +use std::hash::Hash; use std::io::Write; use std::ops::Range; use std::path::Path; use std::path::PathBuf; use std::time::Duration; -#[cfg(debug_assertions)] -use std::time::Instant; use walkdir::WalkDir; +use crate::cli::index::IndexingStats; +use crate::cli::util::reporter::Reporter; + +pub mod reporter; + #[derive(Clone)] pub(crate) struct ExistingPathBufValueParser; @@ -297,13 +304,13 @@ pub struct SourceSpan { } impl SourceSpan { - pub fn first_line(&self) -> usize { + pub(crate) fn first_line(&self) -> usize { self.span.start.line } /// Returns a range for the first line of this span. If multiple lines are spanned, it /// will use usize::MAX for the range's end. - pub fn first_line_column_range(&self) -> Range { + pub(crate) fn first_line_column_range(&self) -> Range { let start = self.span.start.column.grapheme_offset; let end = if self.span.start.line == self.span.end.line { self.span.end.column.grapheme_offset @@ -314,13 +321,13 @@ impl SourceSpan { } } -pub fn duration_from_seconds_str(s: &str) -> Result { +pub(crate) fn duration_from_seconds_str(s: &str) -> Result { let seconds = s.parse::()?; Ok(Duration::new(seconds, 0)) } #[cfg(feature = "lsp")] -pub fn duration_from_milliseconds_str(s: &str) -> Result { +pub(crate) fn duration_from_milliseconds_str(s: &str) -> Result { let milliseconds = s.parse::()?; let seconds = milliseconds / 1000; let nano_seconds = (milliseconds % 1000) as u32 * 1_000_000; @@ -364,182 +371,98 @@ where .flatten() } -pub trait Logger { - fn file<'a>(&self, path: &'a Path) -> Box; -} - -pub trait FileLogger { - fn processing(&mut self) {} - fn failure(&mut self, _status: &str, _details: Option<&dyn std::fmt::Display>) {} - fn skipped(&mut self, _status: &str, _details: Option<&dyn std::fmt::Display>) {} - fn success(&mut self, _status: &str, _details: Option<&dyn std::fmt::Display>) {} - fn warning(&mut self, _status: &str, _details: Option<&dyn std::fmt::Display>) {} - fn default_failure(&mut self, _status: &str, _details: Option<&dyn std::fmt::Display>) {} -} - -pub struct ConsoleLogger { - show_info: bool, - show_details: bool, -} - -impl ConsoleLogger { - pub fn new(show_info: bool, show_details: bool) -> Self { - Self { - show_info, - show_details, - } - } -} - -impl Logger for ConsoleLogger { - fn file<'a>(&self, path: &'a Path) -> Box { - Box::new(ConsoleFileLogger::new( - path, - self.show_info, - self.show_details, - )) - } -} - -pub struct ConsoleFileLogger<'a> { +/// Wraps a reporter and ensures that reporter is called properly without requiring +/// the caller of the wrapper to be overly careful about which methods must be called +/// in which order +pub(super) struct CLIFileReporter<'a> { + reporter: &'a dyn Reporter, path: &'a Path, - show_info: bool, - show_details: bool, path_logged: bool, - #[cfg(debug_assertions)] - processing_started: Option, + status_logged: bool, } -impl<'a> ConsoleFileLogger<'a> { - pub fn new(path: &'a Path, show_info: bool, show_details: bool) -> Self { +impl<'a> CLIFileReporter<'a> { + pub(super) fn new(reporter: &'a dyn Reporter, path: &'a Path) -> Self { Self { + reporter, path, - show_info, - show_details, path_logged: false, - #[cfg(debug_assertions)] - processing_started: None, + status_logged: false, } } - fn print_path(&mut self) { + pub(super) fn processing(&mut self) { if self.path_logged { - return; + panic!("Already started or finished"); } - print!("{}: ", self.path.display()); + self.reporter.started(self.path); self.path_logged = true; } - #[cfg(debug_assertions)] - fn print_processing_time(&mut self) { - if let Some(processing_started) = self.processing_started { - print!(" [{:.2} s]", processing_started.elapsed().as_secs_f64()); + fn ensure_started(&mut self) { + if self.status_logged { + panic!("Status already logged"); + } + if !self.path_logged { + self.reporter.started(self.path); + self.path_logged = true; } } - fn flush(&mut self) { - std::io::stdout().flush().expect("flush should succeed"); - } -} - -impl FileLogger for ConsoleFileLogger<'_> { - fn processing(&mut self) { - #[cfg(debug_assertions)] - { - self.processing_started = Some(Instant::now()); - } - if !self.show_info { - return; - } - self.print_path(); - self.flush(); + pub(super) fn success(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { + self.ensure_started(); + self.reporter.succeeded(self.path, status, details); + self.status_logged = true; } - fn success(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { - if !self.show_info { - return; - } - self.print_path(); - print!("{}", status.green()); - #[cfg(debug_assertions)] - self.print_processing_time(); - println!(); - self.path_logged = false; - self.flush(); - if !self.show_details { - return; + pub(super) fn skipped(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { + if self.path_logged { + panic!("Skipped after starting"); } - if let Some(details) = details { - println!("{}", details); + if self.status_logged { + panic!("Status already logged"); } + self.reporter.skipped(self.path, status, details); + self.status_logged = true; } - fn skipped(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { - if !self.show_info { - return; - } - self.print_path(); - print!("{}", status.dimmed()); - #[cfg(debug_assertions)] - self.print_processing_time(); - println!(); - self.path_logged = false; - self.flush(); - if !self.show_details { - return; - } - if let Some(details) = details { - println!("{}", details); - } + pub(super) fn warning(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { + self.ensure_started(); + self.reporter.cancelled(self.path, status, details); + self.status_logged = true; } - fn warning(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { - self.print_path(); - print!("{}", status.yellow()); - #[cfg(debug_assertions)] - self.print_processing_time(); - println!(); - self.path_logged = false; - self.flush(); - if !self.show_details { - return; - } - if let Some(details) = details { - println!("{}", details); - } + pub(super) fn failure(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { + self.ensure_started(); + self.reporter.failed(self.path, status, details); + self.status_logged = true; } - fn failure(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { - self.print_path(); - print!("{}", status.red()); - #[cfg(debug_assertions)] - self.print_processing_time(); - println!(); - self.path_logged = false; - self.flush(); - if !self.show_details { + pub(super) fn failure_if_processing( + &mut self, + status: &str, + details: Option<&dyn std::fmt::Display>, + ) { + if !self.path_logged || self.status_logged { return; } - if let Some(details) = details { - println!("{}", details); - } + self.failure(status, details); } - fn default_failure(&mut self, status: &str, details: Option<&dyn std::fmt::Display>) { - if !self.path_logged { - return; + pub(super) fn assert_reported(&mut self) { + if self.path_logged && !self.status_logged { + panic!("status not reported"); } - self.failure(status, details); } } -pub fn sha1(value: &str) -> String { +pub(crate) fn sha1(value: &str) -> String { let mut hasher = Sha1::new(); hasher.update(value); base64::prelude::BASE64_STANDARD_NO_PAD.encode(hasher.finalize()) } -pub fn wait_for_input() -> anyhow::Result<()> { +pub(crate) fn wait_for_input() -> anyhow::Result<()> { print!(""); std::io::stdout().flush()?; let mut input = String::new(); @@ -548,7 +471,7 @@ pub fn wait_for_input() -> anyhow::Result<()> { } /// Wraps a build error with the relevant sources -pub struct BuildErrorWithSource<'a> { +pub(crate) struct BuildErrorWithSource<'a> { pub inner: crate::BuildError, pub source_path: PathBuf, pub source_str: &'a str, @@ -578,3 +501,94 @@ impl std::fmt::Display for DisplayBuildErrorPretty<'_> { ) } } + +pub(super) fn print_indexing_stats(stats: IndexingStats) { + print_quartiles_header("graph stats"); + print_quartiles_row("total graph nodes", stats.total_graph_nodes); + print_quartiles_row("total graph edges", stats.total_graph_edges); + print_quartiles_row("node out degrees", stats.node_out_degrees); + print_value_row("root out degree", stats.root_out_degree); + println!(); + print_stitching_stats(stats.stitching_stats); +} + +pub(super) fn print_stitching_stats(stats: StitchingStats) { + print_quartiles_header("stitching stats"); + print_quartiles_row("initial paths", stats.initial_paths); + print_quartiles_row("queued paths per phase", stats.queued_paths_per_phase); + print_quartiles_row("processed paths per phase", stats.processed_paths_per_phase); + print_quartiles_row("accepted path length", stats.accepted_path_length); + print_quartiles_row("terminal path length", stats.terminal_path_lengh); + print_quartiles_row("node path candidates", stats.candidates_per_node_path); + print_quartiles_row("node path extensions", stats.extensions_per_node_path); + print_quartiles_row("root path candidates", stats.candidates_per_root_path); + print_quartiles_row("root path extensions", stats.extensions_per_root_path); + print_quartiles_row("node visits", stats.node_visits.frequencies()); + print_value_row("root visits", stats.root_visits); + print_quartiles_row( + "similar path counts", + stats.similar_paths_stats.similar_path_count, + ); + print_quartiles_row( + "similar path bucket sizes", + stats.similar_paths_stats.similar_path_bucket_size, + ); +} + +pub(super) fn print_database_stats(stats: StorageStats) { + println!( + "| {:^29} | {:^9} | {:^9} |", + "database stats", "loads", "cached", + ); + println!("|-------------------------------|-----------|-----------|"); + println!( + "| {:>29} | {:>9} | {:>9} |", + "files", stats.file_loads, stats.file_cached + ); + println!( + "| {:>29} | {:>9} | {:>9} |", + "node paths", stats.node_path_loads, stats.node_path_cached + ); + println!( + "| {:>29} | {:>9} | {:>9} |", + "rootpaths", stats.root_path_loads, stats.root_path_cached + ); +} + +fn print_quartiles_header(title: &str) { + println!( + "| {:^29} | {:^9} | {:^9} | {:^9} | {:^9} | {:^9} | {:^9} |", + title, "min", "p25", "p50", "p75", "max", "count", + ); + println!( + "|-------------------------------|-----------|-----------|-----------|-----------|-----------|-----------|" + ); +} + +fn print_quartiles_row(title: &str, hist: FrequencyDistribution) { + let qs = hist.quantiles(4); + if qs.is_empty() { + println!( + "| {:>29} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9} |", + title, "-", "-", "-", "-", "-", 0 + ); + } else { + println!( + "| {:>29} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9} |", + title, + qs[0], + qs[1], + qs[2], + qs[3], + qs[4], + hist.count(), + ); + } +} + +fn print_value_row(title: &str, value: X) { + println!( + "| {:>29} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9} | {:>9} |", + title, "-", "-", "-", "-", "-", value + ); +} diff --git a/tree-sitter-stack-graphs/src/cli/util/reporter.rs b/tree-sitter-stack-graphs/src/cli/util/reporter.rs new file mode 100644 index 000000000..0dc249a4a --- /dev/null +++ b/tree-sitter-stack-graphs/src/cli/util/reporter.rs @@ -0,0 +1,178 @@ +// -*- coding: utf-8 -*- +// ------------------------------------------------------------------------------------------------ +// Copyright © 2023, stack-graphs authors. +// Licensed under either of Apache License, Version 2.0, or MIT license, at your option. +// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. +// ------------------------------------------------------------------------------------------------ + +use colored::ColoredString; +use colored::Colorize; +use std::io::Write; +use std::path::Path; + +/// Trait that supports reporting file processing status. +/// +/// For each file, either +/// - [`skipped`] is called once, or +/// - [`started`] and then one of [`succeeded`], [`failed`], or [`canceled`] are called. +/// +/// Guidance for severity of these statuses: +/// - Failed files should be reported as errors. +/// - Canceled files can be reported as warnings. +/// - Succeeded and skipped files can be reported as info. +pub trait Reporter { + /// File was skipped. + fn skipped(&self, path: &Path, summary: &str, details: Option<&dyn std::fmt::Display>); + + /// File processing started. + fn started(&self, path: &Path); + + /// File was processed and succeeded. + fn succeeded(&self, path: &Path, summary: &str, details: Option<&dyn std::fmt::Display>); + + /// File was processed and failed. + fn failed(&self, path: &Path, summary: &str, details: Option<&dyn std::fmt::Display>); + + /// File could not be processed and was canceled. + fn cancelled(&self, path: &Path, summary: &str, details: Option<&dyn std::fmt::Display>); +} + +/// An enum describing the level of detail that should be reported. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum Level { + None, + Summary, + Details, +} + +/// A console reporter that outputs the path when processing starts, and appends +/// the status once finished. +#[derive(Clone, Copy, Debug)] +pub struct ConsoleReporter { + pub skipped_level: Level, + pub succeeded_level: Level, + pub failed_level: Level, + pub canceled_level: Level, +} + +impl ConsoleReporter { + pub fn none() -> Self { + Self { + skipped_level: Level::None, + succeeded_level: Level::None, + failed_level: Level::None, + canceled_level: Level::None, + } + } + + pub fn summary() -> Self { + Self { + skipped_level: Level::Summary, + succeeded_level: Level::Summary, + failed_level: Level::Summary, + canceled_level: Level::Summary, + } + } + + pub fn details() -> Self { + Self { + skipped_level: Level::Details, + succeeded_level: Level::Details, + failed_level: Level::Details, + canceled_level: Level::Details, + } + } + fn all_results_are_reported(&self) -> bool { + *[self.succeeded_level, self.failed_level, self.canceled_level] + .iter() + .min() + .unwrap() + > Level::None + } + + fn print_path(&self, path: &Path) { + print!("{}: ", path.display()); + self.flush(); + } + + fn print_result( + &self, + print_details: bool, + summary: ColoredString, + details: Option<&dyn std::fmt::Display>, + ) { + println!("{}", summary); + if !print_details { + return; + } + if let Some(details) = details { + println!("{}", details); + } + } + + fn flush(&self) { + std::io::stdout().flush().expect("flush should succeed"); + } +} + +impl Reporter for ConsoleReporter { + fn skipped(&self, path: &Path, summary: &str, details: Option<&dyn std::fmt::Display>) { + if self.skipped_level < Level::Summary { + return; + } + self.print_path(path); + self.print_result( + self.skipped_level >= Level::Details, + summary.dimmed(), + details, + ); + } + + fn started(&self, path: &Path) { + if self.all_results_are_reported() { + // we can already output the path + self.print_path(path); + } + } + + fn succeeded(&self, path: &Path, summary: &str, details: Option<&dyn std::fmt::Display>) { + if self.succeeded_level < Level::Summary { + return; + } + if !self.all_results_are_reported() { + // the path wasn't outputed when started + self.print_path(path); + } + self.print_result( + self.succeeded_level >= Level::Details, + summary.green(), + details, + ) + } + + fn failed(&self, path: &Path, summary: &str, details: Option<&dyn std::fmt::Display>) { + if self.failed_level < Level::Summary { + return; + } + if !self.all_results_are_reported() { + // the path wasn't outputed when started + self.print_path(path); + } + self.print_result(self.failed_level >= Level::Details, summary.red(), details) + } + + fn cancelled(&self, path: &Path, summary: &str, details: Option<&dyn std::fmt::Display>) { + if self.canceled_level < Level::Summary { + return; + } + if !self.all_results_are_reported() { + // the path wasn't outputed when started + self.print_path(path); + } + self.print_result( + self.canceled_level >= Level::Details, + summary.yellow(), + details, + ) + } +} diff --git a/tree-sitter-stack-graphs/src/cli/visualize.rs b/tree-sitter-stack-graphs/src/cli/visualize.rs index fd947c1d9..f17f23b88 100644 --- a/tree-sitter-stack-graphs/src/cli/visualize.rs +++ b/tree-sitter-stack-graphs/src/cli/visualize.rs @@ -9,6 +9,8 @@ use clap::Args; use clap::ValueHint; use stack_graphs::serde::NoFilter; use stack_graphs::stitching::Database; +use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::StitcherConfig; use stack_graphs::storage::SQLiteReader; use stack_graphs::NoCancellation; use std::path::Path; @@ -53,9 +55,18 @@ impl VisualizeArgs { .filter(|n| graph[*n].is_reference()) .collect::>(); let mut complete_paths_db = Database::new(); - db.find_all_complete_partial_paths(starting_nodes, cancellation_flag, |g, ps, p| { - complete_paths_db.add_partial_path(g, ps, p.clone()); - })?; + let stitcher_config = StitcherConfig::default() + // always detect similar paths, we don't know the language configurations for the data in the database + .with_detect_similar_paths(true); + ForwardPartialPathStitcher::find_all_complete_partial_paths( + &mut db, + starting_nodes, + stitcher_config, + cancellation_flag, + |g, ps, p| { + complete_paths_db.add_partial_path(g, ps, p.clone()); + }, + )?; let (graph, partials, _) = db.get(); let html = graph.to_html_string("stack-graph", partials, &mut complete_paths_db, &NoFilter)?; diff --git a/tree-sitter-stack-graphs/src/lib.rs b/tree-sitter-stack-graphs/src/lib.rs index e2377f853..6319d1cb9 100644 --- a/tree-sitter-stack-graphs/src/lib.rs +++ b/tree-sitter-stack-graphs/src/lib.rs @@ -160,6 +160,35 @@ //! } //! ``` //! +//! ### Annotating nodes with syntax type information +//! +//! You can annotate any stack graph node with information about its syntax type. To do this, add a `syntax_type` +//! attribute, whose value is a string indicating the syntax type. +//! +//! ``` skip +//! (function_definition name: (identifier) @id) @func { +//! node def +//! ; ... +//! attr (def) syntax_type = "function" +//! } +//! ``` +//! +//! ### Annotating definitions with definiens information +//! +//! You cannot annotate definitions with a definiens, which is the thing the definition covers. For example, for +//! a function definition, the definiens would be the function body. To do this, add a `definiens_node` attribute, +//! whose value is a syntax node that spans the definiens. +//! +//! ``` skip +//! (function_definition name: (identifier) @id body: (_) @body) @func { +//! node def +//! ; ... +//! attr (def) definiens_node = @body +//! } +//! ``` +//! +//! Definiens are optional and setting them to `#null` explicitly is allowed. +//! //! ### Connecting stack graph nodes with edges //! //! To connect two stack graph nodes, use the `edge` statement to add an edge between them: @@ -295,7 +324,7 @@ //! import sys //! print(sys.path) //! "#; -//! let grammar = tree_sitter_python::language(); +//! let grammar = tree_sitter_python::LANGUAGE.into(); //! let tsg_source = STACK_GRAPH_RULES; //! let mut language = StackGraphLanguage::from_str(grammar, tsg_source)?; //! let mut stack_graph = StackGraph::new(); @@ -364,6 +393,7 @@ static SCOPE_TYPE: &'static str = "scope"; // Node attribute names static DEBUG_ATTR_PREFIX: &'static str = "debug_"; +static DEFINIENS_NODE_ATTR: &'static str = "definiens_node"; static EMPTY_SOURCE_SPAN_ATTR: &'static str = "empty_source_span"; static IS_DEFINITION_ATTR: &'static str = "is_definition"; static IS_ENDPOINT_ATTR: &'static str = "is_endpoint"; @@ -372,13 +402,28 @@ static IS_REFERENCE_ATTR: &'static str = "is_reference"; static SCOPE_ATTR: &'static str = "scope"; static SOURCE_NODE_ATTR: &'static str = "source_node"; static SYMBOL_ATTR: &'static str = "symbol"; +static SYNTAX_TYPE_ATTR: &'static str = "syntax_type"; static TYPE_ATTR: &'static str = "type"; // Expected attributes per node type -static POP_SCOPED_SYMBOL_ATTRS: Lazy> = - Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_DEFINITION_ATTR])); -static POP_SYMBOL_ATTRS: Lazy> = - Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_DEFINITION_ATTR])); +static POP_SCOPED_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { + HashSet::from([ + TYPE_ATTR, + SYMBOL_ATTR, + IS_DEFINITION_ATTR, + DEFINIENS_NODE_ATTR, + SYNTAX_TYPE_ATTR, + ]) +}); +static POP_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { + HashSet::from([ + TYPE_ATTR, + SYMBOL_ATTR, + IS_DEFINITION_ATTR, + DEFINIENS_NODE_ATTR, + SYNTAX_TYPE_ATTR, + ]) +}); static PUSH_SCOPED_SYMBOL_ATTRS: Lazy> = Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, SCOPE_ATTR, IS_REFERENCE_ATTR])); static PUSH_SYMBOL_ATTRS: Lazy> = @@ -390,9 +435,16 @@ static SCOPE_ATTRS: Lazy> = static PRECEDENCE_ATTR: &'static str = "precedence"; // Global variables -static ROOT_NODE_VAR: &'static str = "ROOT_NODE"; -static JUMP_TO_SCOPE_NODE_VAR: &'static str = "JUMP_TO_SCOPE_NODE"; -static FILE_PATH_VAR: &'static str = "FILE_PATH"; +/// Name of the variable used to pass the root node. +pub const ROOT_NODE_VAR: &'static str = "ROOT_NODE"; +/// Name of the variable used to pass the jump-to-scope node. +pub const JUMP_TO_SCOPE_NODE_VAR: &'static str = "JUMP_TO_SCOPE_NODE"; +/// Name of the variable used to pass the file path. +/// If a root path is given, it should be a descendant of the root path. +pub const FILE_PATH_VAR: &'static str = "FILE_PATH"; +/// Name of the variable used to pass the root path. +/// If given, should be an ancestor of the file path. +pub const ROOT_PATH_VAR: &'static str = "ROOT_PATH"; /// Holds information about how to construct stack graphs for a particular language. pub struct StackGraphLanguage { @@ -427,7 +479,7 @@ impl StackGraphLanguage { language: tree_sitter::Language, tsg_source: &str, ) -> Result { - let tsg = tree_sitter_graph::ast::File::from_str(language, tsg_source)?; + let tsg = tree_sitter_graph::ast::File::from_str(language.clone(), tsg_source)?; Ok(StackGraphLanguage { language, tsg, @@ -466,8 +518,8 @@ impl StackGraphLanguage { &mut self.functions } - pub fn language(&self) -> tree_sitter::Language { - self.language + pub fn language(&self) -> &tree_sitter::Language { + &self.language } /// Returns the original TSG path, if it was provided at construction or set with @@ -572,7 +624,7 @@ impl<'a> Builder<'a> { ) -> Result<(), BuildError> { let tree = { let mut parser = Parser::new(); - parser.set_language(self.sgl.language)?; + parser.set_language(&self.sgl.language)?; let ts_cancellation_flag = TreeSitterCancellationFlag::from(cancellation_flag); // The parser.set_cancellation_flag` is unsafe, because it does not tie the // lifetime of the parser to the lifetime of the cancellation flag in any way. @@ -590,16 +642,17 @@ impl<'a> Builder<'a> { let tree = parse_errors.into_tree(); let mut globals = Variables::nested(globals); - if globals.get(&ROOT_NODE_VAR.into()).is_none() { - let root_node = self.inject_node(NodeID::root()); - globals - .add(ROOT_NODE_VAR.into(), root_node.into()) - .expect("Failed to set ROOT_NODE"); - } + + let root_node = self.inject_node(NodeID::root()); + globals + .add(ROOT_NODE_VAR.into(), root_node.into()) + .unwrap_or_default(); + let jump_to_scope_node = self.inject_node(NodeID::jump_to()); globals .add(JUMP_TO_SCOPE_NODE_VAR.into(), jump_to_scope_node.into()) .expect("Failed to set JUMP_TO_SCOPE_NODE"); + if globals.get(&FILE_PATH_VAR.into()).is_none() { let file_name = self.stack_graph[self.file].to_string(); globals @@ -885,7 +938,7 @@ impl<'a> Builder<'a> { NodeType::PushSymbol => self.load_push_symbol(node_ref)?, NodeType::Scope => self.load_scope(node_ref)?, }; - self.load_span(node_ref, handle)?; + self.load_source_info(node_ref, handle)?; self.load_node_debug_info(node_ref, handle)?; } @@ -1004,10 +1057,14 @@ impl<'a> Builder<'a> { let id = self.node_id_for_graph_node(node_ref); let is_definition = self.load_flag(node, IS_DEFINITION_ATTR)?; self.verify_attributes(node, POP_SCOPED_SYMBOL_TYPE, &POP_SCOPED_SYMBOL_ATTRS); - Ok(self + let node_handle = self .stack_graph .add_pop_scoped_symbol_node(id, symbol, is_definition) - .unwrap()) + .unwrap(); + if is_definition { + self.load_definiens_info(node_ref, node_handle)?; + } + Ok(node_handle) } fn load_pop_symbol(&mut self, node_ref: GraphNodeRef) -> Result, BuildError> { @@ -1020,10 +1077,14 @@ impl<'a> Builder<'a> { let id = self.node_id_for_graph_node(node_ref); let is_definition = self.load_flag(node, IS_DEFINITION_ATTR)?; self.verify_attributes(node, POP_SYMBOL_TYPE, &POP_SYMBOL_ATTRS); - Ok(self + let node_handle = self .stack_graph .add_pop_symbol_node(id, symbol, is_definition) - .unwrap()) + .unwrap(); + if is_definition { + self.load_definiens_info(node_ref, node_handle)?; + } + Ok(node_handle) } fn load_push_scoped_symbol( @@ -1091,28 +1152,53 @@ impl<'a> Builder<'a> { } } - fn load_span( + fn load_source_info( + &mut self, + node_ref: GraphNodeRef, + node_handle: Handle, + ) -> Result<(), BuildError> { + let node = &self.graph[node_ref]; + + if let Some(source_node) = node.attributes.get(SOURCE_NODE_ATTR) { + let source_node = &self.graph[source_node.as_syntax_node_ref()?]; + let mut source_span = self.span_calculator.for_node(source_node); + if match node.attributes.get(EMPTY_SOURCE_SPAN_ATTR) { + Some(empty_source_span) => empty_source_span.as_boolean()?, + None => false, + } { + source_span.end = source_span.start.clone(); + } + let containing_line = &self.source[source_span.start.containing_line.clone()]; + let containing_line = self.stack_graph.add_string(containing_line); + let source_info = self.stack_graph.source_info_mut(node_handle); + source_info.span = source_span; + source_info.containing_line = ControlledOption::some(containing_line); + } + + if let Some(syntax_type) = node.attributes.get(SYNTAX_TYPE_ATTR) { + let syntax_type = syntax_type.as_str()?; + let syntax_type = self.stack_graph.add_string(syntax_type); + let source_info = self.stack_graph.source_info_mut(node_handle); + source_info.syntax_type = syntax_type.into(); + } + + Ok(()) + } + + fn load_definiens_info( &mut self, node_ref: GraphNodeRef, node_handle: Handle, ) -> Result<(), BuildError> { let node = &self.graph[node_ref]; - let source_node = match node.attributes.get(SOURCE_NODE_ATTR) { - Some(source_node) => &self.graph[source_node.as_syntax_node_ref()?], + let definiens_node = match node.attributes.get(DEFINIENS_NODE_ATTR) { + Some(Value::Null) => return Ok(()), + Some(definiens_node) => &self.graph[definiens_node.as_syntax_node_ref()?], None => return Ok(()), }; - let mut span = self.span_calculator.for_node(source_node); - if match node.attributes.get(EMPTY_SOURCE_SPAN_ATTR) { - Some(empty_source_span) => empty_source_span.as_boolean()?, - None => false, - } { - span.end = span.start.clone(); - } - let containing_line = &self.source[span.start.containing_line.clone()]; - let containing_line = self.stack_graph.add_string(containing_line); + let definiens_span = self.span_calculator.for_node(definiens_node); let source_info = self.stack_graph.source_info_mut(node_handle); - source_info.span = span; - source_info.containing_line = ControlledOption::some(containing_line); + source_info.definiens_span = definiens_span; Ok(()) } diff --git a/tree-sitter-stack-graphs/src/loader.rs b/tree-sitter-stack-graphs/src/loader.rs index 1620334ce..861e4ff95 100644 --- a/tree-sitter-stack-graphs/src/loader.rs +++ b/tree-sitter-stack-graphs/src/loader.rs @@ -29,6 +29,9 @@ use tree_sitter_loader::Loader as TsLoader; use crate::CancellationFlag; use crate::FileAnalyzer; use crate::StackGraphLanguage; +use crate::FILE_PATH_VAR; + +const BUILTINS_FILENAME: &str = ""; pub static DEFAULT_TSG_PATHS: Lazy> = Lazy::new(|| vec![LoadPath::Grammar("queries/stack-graphs".into())]); @@ -44,11 +47,16 @@ pub struct LanguageConfiguration { pub sgl: StackGraphLanguage, pub builtins: StackGraph, pub special_files: FileAnalyzers, + /// Can be set to true if the stack graph rules ensure that there can be no similar + /// paths in a file, in which case it is safe to turn of similar path detection. If + /// incorrectly set to true, performance of path finding suffers from exponential + /// blow up. + pub no_similar_paths_in_file: bool, } impl LanguageConfiguration { /// Build a language configuration from tsg and builtins sources. The tsg path - /// is kept for informational only, see [`StackGraphLanguage::from_source`][]. + /// is kept for informational use only, see [`StackGraphLanguage::from_source`][]. pub fn from_sources<'a>( language: Language, scope: Option, @@ -58,23 +66,27 @@ impl LanguageConfiguration { tsg_source: &'a str, builtins_source: Option<(PathBuf, &'a str)>, builtins_config: Option<&str>, - special_files: FileAnalyzers, cancellation_flag: &dyn CancellationFlag, ) -> Result> { - let sgl = StackGraphLanguage::from_source(language, tsg_path.clone(), tsg_source).map_err( - |err| LoadError::SglParse { - inner: err, - tsg_path, - tsg: Cow::from(tsg_source), - }, - )?; + let sgl = StackGraphLanguage::from_source(language.clone(), tsg_path.clone(), tsg_source) + .map_err(|err| LoadError::SglParse { + inner: err, + tsg_path, + tsg: Cow::from(tsg_source), + })?; let mut builtins = StackGraph::new(); if let Some((builtins_path, builtins_source)) = builtins_source { let mut builtins_globals = Variables::new(); + if let Some(builtins_config) = builtins_config { Loader::load_globals_from_config_str(builtins_config, &mut builtins_globals)?; } - let file = builtins.add_file("").unwrap(); + + builtins_globals + .add(FILE_PATH_VAR.into(), BUILTINS_FILENAME.into()) + .unwrap_or_default(); + + let file = builtins.add_file(BUILTINS_FILENAME).unwrap(); sgl.build_stack_graph_into( &mut builtins, file, @@ -97,7 +109,8 @@ impl LanguageConfiguration { file_types, sgl, builtins, - special_files, + special_files: FileAnalyzers::new(), + no_similar_paths_in_file: false, }) } @@ -143,7 +156,7 @@ impl FileAnalyzers { } } - pub fn add( + pub fn with( mut self, file_name: String, analyzer: impl FileAnalyzer + Send + Sync + 'static, @@ -152,6 +165,15 @@ impl FileAnalyzers { self } + pub fn add( + &mut self, + file_name: String, + analyzer: impl FileAnalyzer + Send + Sync + 'static, + ) -> &mut Self { + self.file_analyzers.insert(file_name, Arc::new(analyzer)); + self + } + pub fn get(&self, file_name: &str) -> Option> { self.file_analyzers.get(file_name).cloned() } @@ -250,7 +272,7 @@ impl Loader { &mut self, path: &Path, content: &mut dyn ContentProvider, - ) -> Result, LoadError<'static>> { + ) -> Result, LoadError<'static>> { match &mut self.0 { LoaderImpl::Paths(loader) => loader.load_tree_sitter_language_for_file(path, content), LoaderImpl::Provided(loader) => { @@ -311,9 +333,16 @@ impl Loader { graph: &mut StackGraph, cancellation_flag: &dyn CancellationFlag, ) -> Result<(), LoadError<'a>> { - let file = graph.add_file(&path.to_string_lossy()).unwrap(); + let file_name = path.to_string_lossy(); + let file = graph.add_file(&file_name).unwrap(); let mut globals = Variables::new(); + Self::load_globals_from_config_str(&config, &mut globals)?; + + globals + .add(FILE_PATH_VAR.into(), BUILTINS_FILENAME.into()) + .unwrap_or_default(); + sgl.build_stack_graph_into(graph, file, &source, &globals, cancellation_flag) .map_err(|err| LoadError::Builtins { inner: err, @@ -358,6 +387,17 @@ impl FileLanguageConfigurations<'_> { pub fn has_some(&self) -> bool { self.primary.is_some() || !self.secondary.is_empty() } + + pub fn no_similar_paths_in_file(&self) -> bool { + let mut no_similar_paths_in_file = true; + if let Some(lc) = &self.primary { + no_similar_paths_in_file &= lc.no_similar_paths_in_file; + } + for (lc, _) in &self.secondary { + no_similar_paths_in_file &= lc.no_similar_paths_in_file; + } + return no_similar_paths_in_file; + } } #[derive(Debug, Error)] @@ -455,10 +495,10 @@ impl LanguageConfigurationsLoader { &mut self, path: &Path, content: &mut dyn ContentProvider, - ) -> Result, LoadError<'static>> { + ) -> Result, LoadError<'static>> { for configuration in self.configurations.iter() { if configuration.matches_file(path, content)? { - return Ok(Some(configuration.language)); + return Ok(Some(&configuration.language)); } } Ok(None) @@ -526,9 +566,9 @@ impl PathLoader { &mut self, path: &Path, content: &mut dyn ContentProvider, - ) -> Result, LoadError<'static>> { + ) -> Result, LoadError<'static>> { if let Some(selected_language) = self.select_language_for_file(path, content)? { - return Ok(Some(selected_language.language)); + return Ok(Some(&selected_language.language)); } Ok(None) } @@ -550,7 +590,7 @@ impl PathLoader { Some(index) => index, None => { let tsg = self.load_tsg_from_paths(&language)?; - let sgl = StackGraphLanguage::new(language.language, tsg); + let sgl = StackGraphLanguage::new(language.language.clone(), tsg); let mut builtins = StackGraph::new(); self.load_builtins_from_paths_into( @@ -561,13 +601,15 @@ impl PathLoader { )?; let lc = LanguageConfiguration { - language: language.language, + language: language.language.clone(), scope: language.scope, content_regex: language.content_regex, file_types: language.file_types, sgl, builtins, special_files: FileAnalyzers::new(), + // always detect similar paths, we don't know the language configuration when loading from the file system + no_similar_paths_in_file: false, }; self.cache.push((language.language, lc)); @@ -653,7 +695,7 @@ impl PathLoader { } if tsg_path.exists() { let tsg_source = std::fs::read_to_string(tsg_path)?; - return Loader::load_tsg(language.language, Cow::from(tsg_source)); + return Loader::load_tsg(language.language.clone(), Cow::from(tsg_source)); } } return Err(LoadError::NoTsgFound); @@ -700,11 +742,11 @@ impl PathLoader { graph: &mut StackGraph, cancellation_flag: &dyn CancellationFlag, ) -> Result<(), LoadError<'static>> { - let source = std::fs::read_to_string(builtins_path.clone())?; + let source = std::fs::read_to_string(builtins_path)?; let mut config_path = builtins_path.to_path_buf(); config_path.set_extension("cfg"); let config = if config_path.exists() { - std::fs::read_to_string(builtins_path.clone())? + std::fs::read_to_string(builtins_path)? } else { "".into() }; @@ -743,10 +785,11 @@ impl SupplementedTsLoader { .map_err(LoadError::TreeSitter)?; let configurations = self .0 - .find_language_configurations_at_path(&path) + .find_language_configurations_at_path(&path, true) .map_err(LoadError::TreeSitter)?; let languages = languages .into_iter() + .map(|(l, _)| l) .zip(configurations.into_iter()) .map(SupplementedLanguage::from) .filter(|language| scope.map_or(true, |scope| language.matches_scope(scope))) diff --git a/tree-sitter-stack-graphs/src/test.rs b/tree-sitter-stack-graphs/src/test.rs index 19abda61d..236e254c5 100644 --- a/tree-sitter-stack-graphs/src/test.rs +++ b/tree-sitter-stack-graphs/src/test.rs @@ -74,6 +74,7 @@ use stack_graphs::graph::SourceInfo; use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; use stack_graphs::stitching::Database; +use stack_graphs::stitching::StitcherConfig; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -623,13 +624,20 @@ impl Test { &mut self, partials: &mut PartialPaths, db: &mut Database, + stitcher_config: StitcherConfig, cancellation_flag: &dyn CancellationFlag, ) -> Result { let mut result = TestResult::new(); for fragment in &self.fragments { for assertion in &fragment.assertions { match assertion - .run(&self.graph, partials, db, &cancellation_flag) + .run( + &self.graph, + partials, + db, + stitcher_config, + &cancellation_flag, + ) .map_or_else(|e| self.from_error(e), |v| Ok(v)) { Ok(_) => result.add_success(), diff --git a/tree-sitter-stack-graphs/tests/it/builder.rs b/tree-sitter-stack-graphs/tests/it/builder.rs index 661e8b973..719e25d71 100644 --- a/tree-sitter-stack-graphs/tests/it/builder.rs +++ b/tree-sitter-stack-graphs/tests/it/builder.rs @@ -9,6 +9,7 @@ use stack_graphs::graph::StackGraph; use tree_sitter_graph::Variables; use tree_sitter_stack_graphs::NoCancellation; use tree_sitter_stack_graphs::StackGraphLanguage; +use tree_sitter_stack_graphs::FILE_PATH_VAR; use crate::edges::check_stack_graph_edges; use crate::nodes::check_stack_graph_nodes; @@ -22,13 +23,19 @@ fn can_support_preexisting_nodes() { "#; let python = "pass"; + let file_name = "test.py"; + let mut graph = StackGraph::new(); - let file = graph.get_or_create_file("test.py"); + let file = graph.get_or_create_file(file_name); let node_id = graph.new_node_id(file); let _preexisting_node = graph.add_scope_node(node_id, true).unwrap(); - let globals = Variables::new(); - let language = StackGraphLanguage::from_str(tree_sitter_python::language(), tsg).unwrap(); + let mut globals = Variables::new(); + globals + .add(FILE_PATH_VAR.into(), file_name.into()) + .expect("failed to add file path variable"); + + let language = StackGraphLanguage::from_str(tree_sitter_python::LANGUAGE.into(), tsg).unwrap(); language .build_stack_graph_into(&mut graph, file, python, &globals, &NoCancellation) .expect("Failed to build graph"); @@ -45,15 +52,21 @@ fn can_support_injected_nodes() { "#; let python = "pass"; + let file_name = "test.py"; + let mut graph = StackGraph::new(); - let file = graph.get_or_create_file("test.py"); + let file = graph.get_or_create_file(file_name); let node_id = graph.new_node_id(file); let _preexisting_node = graph.add_scope_node(node_id, true).unwrap(); - let language = StackGraphLanguage::from_str(tree_sitter_python::language(), tsg).unwrap(); + let language = StackGraphLanguage::from_str(tree_sitter_python::LANGUAGE.into(), tsg).unwrap(); let mut builder = language.builder_into_stack_graph(&mut graph, file, python); let mut globals = Variables::new(); + globals + .add(FILE_PATH_VAR.into(), file_name.into()) + .expect("failed to add file path variable"); + globals .add("EXT_NODE".into(), builder.inject_node(node_id).into()) .expect("Failed to add EXT_NODE variable"); diff --git a/tree-sitter-stack-graphs/tests/it/loader.rs b/tree-sitter-stack-graphs/tests/it/loader.rs index 8fcf0ccac..401af46e5 100644 --- a/tree-sitter-stack-graphs/tests/it/loader.rs +++ b/tree-sitter-stack-graphs/tests/it/loader.rs @@ -9,6 +9,7 @@ use once_cell::sync::Lazy; use pretty_assertions::assert_eq; use stack_graphs::graph::StackGraph; use std::path::PathBuf; +use tree_sitter::Language; use tree_sitter_stack_graphs::loader::FileAnalyzers; use tree_sitter_stack_graphs::loader::LanguageConfiguration; use tree_sitter_stack_graphs::loader::Loader; @@ -25,16 +26,17 @@ static TSG: Lazy = Lazy::new(|| { #[test] fn can_load_from_provided_language_configuration() { - let language = tree_sitter_python::language(); - let sgl = StackGraphLanguage::from_str(language, &TSG).unwrap(); + let language: Language = tree_sitter_python::LANGUAGE.into(); + let sgl = StackGraphLanguage::from_str(language.clone(), &TSG).unwrap(); let lc = LanguageConfiguration { - language: language, + language: language.clone(), scope: Some("source.py".into()), content_regex: None, file_types: vec!["py".into()], sgl, builtins: StackGraph::new(), special_files: FileAnalyzers::new(), + no_similar_paths_in_file: false, }; let mut loader = Loader::from_language_configurations(vec![lc], None).expect("Expected loader to succeed"); @@ -42,10 +44,10 @@ fn can_load_from_provided_language_configuration() { let tsl = loader .load_tree_sitter_language_for_file(&PATH, &mut None) .expect("Expected loading tree-sitter language to succeed"); - assert_eq!(tsl, Some(language)); + assert_eq!(tsl, Some(&language)); let lc = loader .load_for_file(&PATH, &mut None, &NoCancellation) .expect("Expected loading stack graph language to succeed"); - assert_eq!(lc.primary.map(|lc| lc.language), Some(language)); + assert_eq!(lc.primary.map(|lc| &lc.language), Some(&language)); } diff --git a/tree-sitter-stack-graphs/tests/it/main.rs b/tree-sitter-stack-graphs/tests/it/main.rs index c1e37a40e..01cfd9754 100644 --- a/tree-sitter-stack-graphs/tests/it/main.rs +++ b/tree-sitter-stack-graphs/tests/it/main.rs @@ -5,6 +5,8 @@ // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. // ------------------------------------------------------------------------------------------------ +use std::path::Path; + use stack_graphs::arena::Handle; use stack_graphs::graph::File; use stack_graphs::graph::StackGraph; @@ -12,6 +14,7 @@ use tree_sitter_graph::Variables; use tree_sitter_stack_graphs::BuildError; use tree_sitter_stack_graphs::NoCancellation; use tree_sitter_stack_graphs::StackGraphLanguage; +use tree_sitter_stack_graphs::FILE_PATH_VAR; mod builder; mod edges; @@ -23,11 +26,18 @@ pub(self) fn build_stack_graph( python_source: &str, tsg_source: &str, ) -> Result<(StackGraph, Handle), BuildError> { + let file_name = "test.py"; let language = - StackGraphLanguage::from_str(tree_sitter_python::language(), tsg_source).unwrap(); + StackGraphLanguage::from_str(tree_sitter_python::LANGUAGE.into(), tsg_source).unwrap(); let mut graph = StackGraph::new(); - let file = graph.get_or_create_file("test.py"); - let globals = Variables::new(); + let file = graph.get_or_create_file(file_name); + let mut globals = Variables::new(); + let source_path = Path::new(file_name); + + globals + .add(FILE_PATH_VAR.into(), source_path.to_str().unwrap().into()) + .expect("failed to add file path variable"); + language.build_stack_graph_into(&mut graph, file, python_source, &globals, &NoCancellation)?; Ok((graph, file)) } diff --git a/tree-sitter-stack-graphs/tests/it/nodes.rs b/tree-sitter-stack-graphs/tests/it/nodes.rs index 2befc6a95..c36a08744 100644 --- a/tree-sitter-stack-graphs/tests/it/nodes.rs +++ b/tree-sitter-stack-graphs/tests/it/nodes.rs @@ -284,3 +284,76 @@ fn can_calculate_spans() { let trimmed_line = &python[source_info.span.start.trimmed_line.clone()]; assert_eq!(trimmed_line, "a"); } + +#[test] +fn can_set_definiens() { + let tsg = r#" + (function_definition name:(_)@name body:(_)@body) { + node result + attr (result) type = "pop_symbol", symbol = (source-text @name), source_node = @name, is_definition + attr (result) definiens_node = @body + } + "#; + let python = r#" + def foo(): + pass + "#; + + let (graph, file) = build_stack_graph(python, tsg).unwrap(); + let node_handle = graph.nodes_for_file(file).next().unwrap(); + let source_info = graph.source_info(node_handle).unwrap(); + + let actual_span = format!( + "{}:{}-{}:{}", + source_info.definiens_span.start.line, + source_info.definiens_span.start.column.utf8_offset, + source_info.definiens_span.end.line, + source_info.definiens_span.end.column.utf8_offset, + ); + assert_eq!("2:8-2:12", actual_span) +} + +#[test] +fn can_set_null_definiens() { + let tsg = r#" + (function_definition name:(_)@name) { + node result + attr (result) type = "pop_symbol", symbol = (source-text @name), source_node = @name, is_definition + attr (result) definiens_node = #null + } + "#; + let python = r#" + def foo(): + pass + "#; + + let (graph, file) = build_stack_graph(python, tsg).unwrap(); + let node_handle = graph.nodes_for_file(file).next().unwrap(); + let source_info = graph.source_info(node_handle).unwrap(); + assert_eq!(lsp_positions::Span::default(), source_info.definiens_span) +} + +#[test] +fn can_set_syntax_type() { + let tsg = r#" + (function_definition) { + node result + attr (result) syntax_type = "function" + } + "#; + let python = r#" + def foo(): + pass + "#; + + let (graph, file) = build_stack_graph(python, tsg).unwrap(); + let node_handle = graph.nodes_for_file(file).next().unwrap(); + let source_info = graph.source_info(node_handle).unwrap(); + + let syntax_type = source_info + .syntax_type + .into_option() + .map(|s| &graph[s]) + .unwrap_or("MISSING"); + assert_eq!("function", syntax_type) +} diff --git a/tree-sitter-stack-graphs/tests/it/test.rs b/tree-sitter-stack-graphs/tests/it/test.rs index b281f283b..fae14fe76 100644 --- a/tree-sitter-stack-graphs/tests/it/test.rs +++ b/tree-sitter-stack-graphs/tests/it/test.rs @@ -5,6 +5,7 @@ // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. // ------------------------------------------------------------------------------------------------ +use crate::FILE_PATH_VAR; use once_cell::sync::Lazy; use pretty_assertions::assert_eq; use stack_graphs::arena::Handle; @@ -13,6 +14,7 @@ use stack_graphs::graph::StackGraph; use stack_graphs::partial::PartialPaths; use stack_graphs::stitching::Database; use stack_graphs::stitching::ForwardPartialPathStitcher; +use stack_graphs::stitching::StitcherConfig; use std::path::Path; use std::path::PathBuf; use tree_sitter_graph::Variables; @@ -71,7 +73,7 @@ fn build_stack_graph_into( globals: &Variables, ) -> Result<(), BuildError> { let language = - StackGraphLanguage::from_str(tree_sitter_python::language(), tsg_source).unwrap(); + StackGraphLanguage::from_str(tree_sitter_python::LANGUAGE.into(), tsg_source).unwrap(); language.build_stack_graph_into(graph, file, python_source, globals, &NoCancellation)?; Ok(()) } @@ -93,10 +95,20 @@ fn check_test( expected_successes + expected_failures, assertion_count, ); + let mut globals = Variables::new(); for fragments in &test.fragments { globals.clear(); + fragments.add_globals_to(&mut globals); + + globals + .add( + FILE_PATH_VAR.into(), + fragments.path.to_str().unwrap().into(), + ) + .unwrap_or_default(); + build_stack_graph_into( &mut test.graph, fragments.file, @@ -113,6 +125,7 @@ fn check_test( &test.graph, &mut partials, fragment.file, + StitcherConfig::default(), &stack_graphs::NoCancellation, |graph, partials, path| { db.add_partial_path(graph, partials, path.clone()); @@ -122,7 +135,12 @@ fn check_test( } let results = test - .run(&mut partials, &mut db, &NoCancellation) + .run( + &mut partials, + &mut db, + StitcherConfig::default(), + &NoCancellation, + ) .expect("should never be cancelled"); assert_eq!( expected_successes, diff --git a/valgrind.supp b/valgrind.supp new file mode 100644 index 000000000..25c3cfbda --- /dev/null +++ b/valgrind.supp @@ -0,0 +1,8 @@ +{ + rust-1.83-false-positive-1 + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:main +}