Add 'src/tools/rustfmt/' from commit '7872306edf'
git-subtree-dir: src/tools/rustfmt git-subtree-mainline:e659b6de91git-subtree-split:7872306edf
This commit is contained in:
commit
b2d45c0d4b
1265 changed files with 78073 additions and 0 deletions
26
src/tools/rustfmt/.editorconfig
Normal file
26
src/tools/rustfmt/.editorconfig
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rs]
|
||||
indent_size = 4
|
||||
|
||||
[tests/**/*.rs]
|
||||
charset = utf-8
|
||||
end_of_line = unset
|
||||
indent_size = unset
|
||||
indent_style = unset
|
||||
trim_trailing_whitespace = unset
|
||||
insert_final_newline = unset
|
||||
|
||||
[appveyor.yml]
|
||||
end_of_line = unset
|
||||
7
src/tools/rustfmt/.gitattributes
vendored
Normal file
7
src/tools/rustfmt/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
* text=auto eol=lf
|
||||
tests/source/issue-3494/crlf.rs -text
|
||||
tests/source/comment_crlf_newline.rs -text
|
||||
tests/source/configs/enum_discrim_align_threshold/40.rs -text
|
||||
tests/target/issue-3494/crlf.rs -text
|
||||
tests/target/comment_crlf_newline.rs -text
|
||||
tests/target/configs/enum_discrim_align_threshold/40.rs -text
|
||||
85
src/tools/rustfmt/.github/workflows/integration.yml
vendored
Normal file
85
src/tools/rustfmt/.github/workflows/integration.yml
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
name: integration
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
runs-on: ubuntu-latest
|
||||
name: ${{ matrix.integration }}
|
||||
strategy:
|
||||
# https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits
|
||||
# There's a limit of 60 concurrent jobs across all repos in the rust-lang organization.
|
||||
# In order to prevent overusing too much of that 60 limit, we throttle the
|
||||
# number of rustfmt jobs that will run concurrently.
|
||||
max-parallel: 4
|
||||
fail-fast: false
|
||||
matrix:
|
||||
integration: [
|
||||
bitflags,
|
||||
error-chain,
|
||||
log,
|
||||
mdbook,
|
||||
packed_simd,
|
||||
rust-semverver,
|
||||
tempdir,
|
||||
futures-rs,
|
||||
rust-clippy,
|
||||
failure,
|
||||
]
|
||||
include:
|
||||
# Allowed Failures
|
||||
# Actions doesn't yet support explicitly marking matrix legs as allowed failures
|
||||
# https://github.community/t5/GitHub-Actions/continue-on-error-allow-failure-UI-indication/td-p/37033
|
||||
# https://github.community/t5/GitHub-Actions/Why-a-matrix-step-will-be-canceled-if-another-one-failed/td-p/30920
|
||||
# Instead, leverage `continue-on-error`
|
||||
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error
|
||||
#
|
||||
# Failing due to breaking changes in rustfmt 2.0 where empty
|
||||
# match blocks have trailing commas removed
|
||||
# https://github.com/rust-lang/rustfmt/pull/4226
|
||||
- integration: chalk
|
||||
allow-failure: true
|
||||
- integration: crater
|
||||
allow-failure: true
|
||||
- integration: glob
|
||||
allow-failure: true
|
||||
- integration: stdsimd
|
||||
allow-failure: true
|
||||
# Using old rustfmt configuration option
|
||||
- integration: rand
|
||||
allow-failure: true
|
||||
# Keep this as an allowed failure as it's fragile to breaking changes of rustc.
|
||||
- integration: rust-clippy
|
||||
allow-failure: true
|
||||
# Using old rustfmt configuration option
|
||||
- integration: packed_simd
|
||||
allow-failure: true
|
||||
# calebcartwright (2019-12-24)
|
||||
# Keeping this as an allowed failure since it was flagged as such in the TravisCI config, even though
|
||||
# it appears to have been passing for quite some time.
|
||||
# Original comment was: temporal build failure due to breaking changes in the nightly compiler
|
||||
- integration: rust-semverver
|
||||
allow-failure: true
|
||||
# Can be moved back to include section after https://github.com/rust-lang-nursery/failure/pull/298 is merged
|
||||
- integration: failure
|
||||
allow-failure: true
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Run build
|
||||
- name: install rustup
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh
|
||||
sh rustup-init.sh -y --default-toolchain none
|
||||
|
||||
- name: run integration tests
|
||||
env:
|
||||
INTEGRATION: ${{ matrix.integration }}
|
||||
TARGET: x86_64-unknown-linux-gnu
|
||||
run: ./ci/integration.sh
|
||||
continue-on-error: ${{ matrix.allow-failure == true }}
|
||||
42
src/tools/rustfmt/.github/workflows/linux.yml
vendored
Normal file
42
src/tools/rustfmt/.github/workflows/linux.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
name: linux
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: (${{ matrix.target }}, nightly)
|
||||
strategy:
|
||||
# https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits
|
||||
# There's a limit of 60 concurrent jobs across all repos in the rust-lang organization.
|
||||
# In order to prevent overusing too much of that 60 limit, we throttle the
|
||||
# number of rustfmt jobs that will run concurrently.
|
||||
max-parallel: 1
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [
|
||||
x86_64-unknown-linux-gnu,
|
||||
]
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Run build
|
||||
- name: install rustup
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh
|
||||
sh rustup-init.sh -y --default-toolchain none
|
||||
rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: build
|
||||
run: |
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
cargo build
|
||||
|
||||
- name: test
|
||||
run: cargo test
|
||||
39
src/tools/rustfmt/.github/workflows/mac.yml
vendored
Normal file
39
src/tools/rustfmt/.github/workflows/mac.yml
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
name: mac
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
# https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners#supported-runners-and-hardware-resources
|
||||
# macOS Catalina 10.15
|
||||
runs-on: macos-latest
|
||||
name: (${{ matrix.target }}, nightly)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [
|
||||
x86_64-apple-darwin,
|
||||
]
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Run build
|
||||
- name: install rustup
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh
|
||||
sh rustup-init.sh -y --default-toolchain none
|
||||
rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: build
|
||||
run: |
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
cargo build
|
||||
|
||||
- name: test
|
||||
run: cargo test
|
||||
80
src/tools/rustfmt/.github/workflows/upload-assets.yml
vendored
Normal file
80
src/tools/rustfmt/.github/workflows/upload-assets.yml
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
name: upload
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
build-release:
|
||||
name: build-release
|
||||
strategy:
|
||||
matrix:
|
||||
build: [linux-x86_64, macos-x86_64, windows-x86_64-gnu, windows-x86_64-msvc]
|
||||
include:
|
||||
- build: linux-x86_64
|
||||
os: ubuntu-latest
|
||||
rust: nightly
|
||||
- build: macos-x86_64
|
||||
os: macos-latest
|
||||
rust: nightly
|
||||
- build: windows-x86_64-gnu
|
||||
os: windows-latest
|
||||
rust: nightly-x86_64-gnu
|
||||
- build: windows-x86_64-msvc
|
||||
os: windows-latest
|
||||
rust: nightly-x86_64-msvc
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Add mingw64 to path for x86_64-gnu
|
||||
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
|
||||
if: matrix.rust == 'nightly-x86_64-gnu'
|
||||
shell: bash
|
||||
|
||||
- name: Install cargo-make
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: install
|
||||
args: --force cargo-make
|
||||
|
||||
- name: Build release binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: make
|
||||
args: release
|
||||
|
||||
- name: Build archive
|
||||
shell: bash
|
||||
run: |
|
||||
staging="rustfmt_${{ matrix.build }}_${{ github.event.release.tag_name }}"
|
||||
mkdir -p "$staging"
|
||||
|
||||
cp {README.md,Configurations.md,CHANGELOG.md,LICENSE-MIT,LICENSE-APACHE} "$staging/"
|
||||
|
||||
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||
cp target/release/{rustfmt.exe,cargo-fmt.exe,rustfmt-format-diff.exe,git-rustfmt.exe} "$staging/"
|
||||
7z a "$staging.zip" "$staging"
|
||||
echo "ASSET=$staging.zip" >> $GITHUB_ENV
|
||||
else
|
||||
cp target/release/{rustfmt,cargo-fmt,rustfmt-format-diff,git-rustfmt} "$staging/"
|
||||
tar czf "$staging.tar.gz" "$staging"
|
||||
echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ${{ env.ASSET }}
|
||||
asset_name: ${{ env.ASSET }}
|
||||
asset_content_type: application/octet-stream
|
||||
69
src/tools/rustfmt/.github/workflows/windows.yml
vendored
Normal file
69
src/tools/rustfmt/.github/workflows/windows.yml
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
name: windows
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
name: (${{ matrix.target }}, nightly)
|
||||
strategy:
|
||||
# https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits
|
||||
# There's a limit of 60 concurrent jobs across all repos in the rust-lang organization.
|
||||
# In order to prevent overusing too much of that 60 limit, we throttle the
|
||||
# number of rustfmt jobs that will run concurrently.
|
||||
max-parallel: 2
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [
|
||||
i686-pc-windows-gnu,
|
||||
i686-pc-windows-msvc,
|
||||
x86_64-pc-windows-gnu,
|
||||
x86_64-pc-windows-msvc,
|
||||
]
|
||||
|
||||
steps:
|
||||
# The Windows runners have autocrlf enabled by default
|
||||
# which causes failures for some of rustfmt's line-ending sensitive tests
|
||||
- name: disable git eol translation
|
||||
run: git config --global core.autocrlf false
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Run build
|
||||
- name: Install Rustup using win.rustup.rs
|
||||
run: |
|
||||
# Disable the download progress bar which can cause perf issues
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe
|
||||
.\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none
|
||||
del rustup-init.exe
|
||||
rustup target add ${{ matrix.target }}
|
||||
shell: powershell
|
||||
|
||||
- name: Add mingw32 to path for i686-gnu
|
||||
run: |
|
||||
echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH
|
||||
if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly'
|
||||
shell: bash
|
||||
|
||||
- name: Add mingw64 to path for x86_64-gnu
|
||||
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
|
||||
if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'
|
||||
shell: bash
|
||||
|
||||
- name: cargo-make
|
||||
run: cargo install --force cargo-make
|
||||
|
||||
- name: build
|
||||
run: |
|
||||
rustc -Vv
|
||||
cargo -V
|
||||
cargo build
|
||||
shell: cmd
|
||||
|
||||
- name: test
|
||||
run: cargo test
|
||||
shell: cmd
|
||||
24
src/tools/rustfmt/.gitignore
vendored
Normal file
24
src/tools/rustfmt/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
# Created by https://www.gitignore.io/api/rust
|
||||
|
||||
### Rust ###
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||
# Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# End of https://www.gitignore.io/api/rust
|
||||
|
||||
# Used by macOS' file system to track custom attributes of containing folder
|
||||
.DS_Store
|
||||
|
||||
# Editors' specific files
|
||||
.idea/
|
||||
.vscode/
|
||||
*~
|
||||
77
src/tools/rustfmt/.travis.yml
Normal file
77
src/tools/rustfmt/.travis.yml
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
sudo: false
|
||||
language: rust
|
||||
rust: nightly
|
||||
os: linux
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cargo
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- env: DEPLOY=LINUX
|
||||
- env: CFG_RELEASE_CHANNEL=beta
|
||||
- os: osx
|
||||
- env: INTEGRATION=bitflags
|
||||
- env: INTEGRATION=chalk
|
||||
- env: INTEGRATION=crater
|
||||
- env: INTEGRATION=error-chain
|
||||
- env: INTEGRATION=glob
|
||||
- env: INTEGRATION=log
|
||||
- env: INTEGRATION=mdbook
|
||||
- env: INTEGRATION=packed_simd
|
||||
- env: INTEGRATION=rust-semverver
|
||||
- env: INTEGRATION=stdsimd TARGET=x86_64-unknown-linux-gnu
|
||||
- env: INTEGRATION=tempdir
|
||||
- env: INTEGRATION=futures-rs
|
||||
allow_failures:
|
||||
# Using old configuration option
|
||||
- env: INTEGRATION=rand
|
||||
# Doesn't build - keep this in allow_failures as it's fragile to breaking changes of rustc.
|
||||
- env: INTEGRATION=rust-clippy
|
||||
# Doesn't build - seems to be because of an option
|
||||
- env: INTEGRATION=packed_simd
|
||||
# Doesn't build - a temporal build failure due to breaking changes in the nightly compilre
|
||||
- env: INTEGRATION=rust-semverver
|
||||
# can be moved back to include section after https://github.com/rust-lang-nursery/failure/pull/298 is merged
|
||||
- env: INTEGRATION=failure
|
||||
# `cargo test` doesn't finish - disabling for now.
|
||||
# - env: INTEGRATION=cargo
|
||||
|
||||
script:
|
||||
- |
|
||||
if [ -z ${INTEGRATION} ]; then
|
||||
export CFG_RELEASE_CHANNEL=nightly
|
||||
export CFG_RELEASE=nightly
|
||||
cargo build
|
||||
cargo test
|
||||
cargo test -- --ignored
|
||||
else
|
||||
./ci/integration.sh
|
||||
fi
|
||||
|
||||
after_success:
|
||||
- if [ -z ${INTEGRATION} ]; then travis-cargo coveralls --no-sudo; fi
|
||||
|
||||
before_deploy:
|
||||
# TODO: cross build
|
||||
- cargo build --release --target=x86_64-unknown-linux-gnu
|
||||
- tar czf rustfmt-x86_64-unknown-linux-gnu.tar.gz Contributing.md Design.md README.md -C target/x86_64-unknown-linux-gnu/release/rustfmt rustfmt
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "your own encrypted key"
|
||||
file:
|
||||
- rustfmt-x86_64-unknown-linux-gnu.tar.gz
|
||||
on:
|
||||
repo: nrc/rustfmt
|
||||
tags: true
|
||||
condition: "$DEPLOY = LINUX"
|
||||
skip_cleanup: true
|
||||
1168
src/tools/rustfmt/CHANGELOG.md
Normal file
1168
src/tools/rustfmt/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load diff
40
src/tools/rustfmt/CODE_OF_CONDUCT.md
Normal file
40
src/tools/rustfmt/CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# The Rust Code of Conduct
|
||||
|
||||
A version of this document [can be found online](https://www.rust-lang.org/conduct.html).
|
||||
|
||||
## Conduct
|
||||
|
||||
**Contact**: [rust-mods@rust-lang.org](mailto:rust-mods@rust-lang.org)
|
||||
|
||||
* We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic.
|
||||
* On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all.
|
||||
* Please be kind and courteous. There's no need to be mean or rude.
|
||||
* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
|
||||
* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
|
||||
* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the <a href="http://citizencodeofconduct.org/">Citizen Code of Conduct</a>; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups.
|
||||
* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [Rust moderation team][mod_team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back.
|
||||
* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
|
||||
|
||||
## Moderation
|
||||
|
||||
|
||||
These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation, please contact the [Rust moderation team][mod_team].
|
||||
|
||||
1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.)
|
||||
2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.
|
||||
3. Moderators will first respond to such remarks with a warning.
|
||||
4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off.
|
||||
5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
|
||||
6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
|
||||
7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed.
|
||||
8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others.
|
||||
|
||||
In the Rust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.
|
||||
|
||||
And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
|
||||
|
||||
The enforcement policies listed above apply to all official Rust venues; including official IRC channels (#rust, #rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org (users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Rust Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
|
||||
|
||||
*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).*
|
||||
|
||||
[mod_team]: https://www.rust-lang.org/team.html#Moderation-team
|
||||
915
src/tools/rustfmt/Cargo.lock
Normal file
915
src/tools/rustfmt/Cargo.lock
Normal file
|
|
@ -0,0 +1,915 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78ea013094e5ea606b1c05fe35f1dd7ea1eb1ea259908d040b25bd5ec677ee5"
|
||||
dependencies = [
|
||||
"yansi-term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
|
||||
dependencies = [
|
||||
"nodrop",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
|
||||
dependencies = [
|
||||
"backtrace-sys",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace-sys"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5850aeee1552f495dd0250014cf64b82b7c8879a89d83b33bbdace2cc4f63182"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0017894339f586ccb943b01b9555de56770c11cda818e7e3d8bd93f4ed7f46e"
|
||||
dependencies = [
|
||||
"packed_simd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "700b3731fd7d357223d0000f4dbf1808401b694609035c3c411fbc0cd375c426"
|
||||
dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-new"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"failure_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "522daefc3b69036f80c7d2990b28ff9e0471c683bad05ca258e0a01dd22c5a1e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex",
|
||||
"same-file",
|
||||
"thread_local 1.0.1",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "packed_simd"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a85ea9fc0d4ac0deb6fe7911d38786b32fc11119afd9e9d38b84ff691ce64220"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn-mid",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rand_os"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
||||
dependencies = [
|
||||
"cloudabi",
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.4.2",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d"
|
||||
dependencies = [
|
||||
"failure",
|
||||
"rand_os",
|
||||
"redox_syscall",
|
||||
"rust-argon2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"thread_local 0.3.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blake2b_simd",
|
||||
"crossbeam-utils 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-workspace-hack"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb"
|
||||
|
||||
[[package]]
|
||||
name = "rustfmt-config_proc_macro"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustfmt-nightly"
|
||||
version = "1.4.37"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"anyhow",
|
||||
"bytecount",
|
||||
"cargo_metadata",
|
||||
"derive-new",
|
||||
"diff",
|
||||
"dirs",
|
||||
"env_logger",
|
||||
"getopts",
|
||||
"ignore",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"regex",
|
||||
"rustc-workspace-hack",
|
||||
"rustfmt-config_proc_macro",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"structopt",
|
||||
"term",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"unicode_categories",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "structopt"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"lazy_static",
|
||||
"structopt-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "structopt-derive"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6e79c80e0f4efd86ca960218d4e056249be189ff1c42824dcd9a7f51a56f0bd"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn-mid"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e"
|
||||
dependencies = [
|
||||
"wincolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc6b305ec0e323c7b6cfff6098a22516e0063d0bb7c3d88660a890217dca099a"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45ba8d810d9c48fc456b7ad54574e8bfb7c7918a57ad7a6e6a0985d7959e8597"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_categories"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "wincolor"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi-term"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
67
src/tools/rustfmt/Cargo.toml
Normal file
67
src/tools/rustfmt/Cargo.toml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
[package]
|
||||
|
||||
name = "rustfmt-nightly"
|
||||
version = "1.4.37"
|
||||
authors = ["Nicholas Cameron <ncameron@mozilla.com>", "The Rustfmt developers"]
|
||||
description = "Tool to find and fix Rust formatting issues"
|
||||
repository = "https://github.com/rust-lang/rustfmt"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0/MIT"
|
||||
build = "build.rs"
|
||||
categories = ["development-tools"]
|
||||
edition = "2018"
|
||||
|
||||
[[bin]]
|
||||
name = "rustfmt"
|
||||
path = "src/bin/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "cargo-fmt"
|
||||
path = "src/cargo-fmt/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rustfmt-format-diff"
|
||||
path = "src/format-diff/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "git-rustfmt"
|
||||
path = "src/git-rustfmt/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["cargo-fmt", "rustfmt-format-diff"]
|
||||
cargo-fmt = []
|
||||
rustfmt-format-diff = []
|
||||
generic-simd = ["bytecount/generic-simd"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.8"
|
||||
toml = "0.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
unicode-segmentation = "1.0.0"
|
||||
regex = "1.0"
|
||||
term = "0.6"
|
||||
diff = "0.1"
|
||||
log = "0.4"
|
||||
env_logger = "0.6"
|
||||
getopts = "0.2"
|
||||
derive-new = "0.5"
|
||||
cargo_metadata = "0.8"
|
||||
bytecount = "0.6"
|
||||
unicode-width = "0.1.5"
|
||||
unicode_categories = "0.1.1"
|
||||
dirs = "2.0.1"
|
||||
ignore = "0.4.11"
|
||||
annotate-snippets = { version = "0.8", features = ["color"] }
|
||||
structopt = "0.3"
|
||||
rustfmt-config_proc_macro = { version = "0.2", path = "config_proc_macro" }
|
||||
lazy_static = "1.0.0"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
|
||||
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
|
||||
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
|
||||
# for more information.
|
||||
rustc-workspace-hack = "1.0.0"
|
||||
|
||||
# Rustc dependencies are loaded from the sysroot, Cargo doesn't know about them.
|
||||
2773
src/tools/rustfmt/Configurations.md
Normal file
2773
src/tools/rustfmt/Configurations.md
Normal file
File diff suppressed because it is too large
Load diff
251
src/tools/rustfmt/Contributing.md
Normal file
251
src/tools/rustfmt/Contributing.md
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
# Contributing
|
||||
|
||||
There are many ways to contribute to Rustfmt. This document lays out what they
|
||||
are and has information on how to get started. If you have any questions about
|
||||
contributing or need help with anything, please ask in the WG-Rustfmt channel
|
||||
on [Discord](https://discordapp.com/invite/rust-lang). Feel free to also ask questions
|
||||
on issues, or file new issues specifically to get help.
|
||||
|
||||
All contributors are expected to follow our [Code of
|
||||
Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## Test and file issues
|
||||
|
||||
It would be really useful to have people use rustfmt on their projects and file
|
||||
issues where it does something you don't expect.
|
||||
|
||||
|
||||
## Create test cases
|
||||
|
||||
Having a strong test suite for a tool like this is essential. It is very easy
|
||||
to create regressions. Any tests you can add are very much appreciated.
|
||||
|
||||
The tests can be run with `cargo test`. This does a number of things:
|
||||
* runs the unit tests for a number of internal functions;
|
||||
* makes sure that rustfmt run on every file in `./tests/source/` is equal to its
|
||||
associated file in `./tests/target/`;
|
||||
* runs idempotence tests on the files in `./tests/target/`. These files should
|
||||
not be changed by rustfmt;
|
||||
* checks that rustfmt's code is not changed by running on itself. This ensures
|
||||
that the project bootstraps.
|
||||
|
||||
Creating a test is as easy as creating a new file in `./tests/source/` and an
|
||||
equally named one in `./tests/target/`. If it is only required that rustfmt
|
||||
leaves a piece of code unformatted, it may suffice to only create a target file.
|
||||
|
||||
Whenever there's a discrepancy between the expected output when running tests, a
|
||||
colourised diff will be printed so that the offending line(s) can quickly be
|
||||
identified.
|
||||
|
||||
Without explicit settings, the tests will be run using rustfmt's default
|
||||
configuration. It is possible to run a test using non-default settings in several
|
||||
ways. Firstly, you can include configuration parameters in comments at the top
|
||||
of the file. For example: to use 3 spaces per tab, start your test with
|
||||
`// rustfmt-tab_spaces: 3`. Just remember that the comment is part of the input,
|
||||
so include in both the source and target files! It is also possible to
|
||||
explicitly specify the name of the expected output file in the target directory.
|
||||
Use `// rustfmt-target: filename.rs` for this. You can also specify a custom
|
||||
configuration by using the `rustfmt-config` directive. Rustfmt will then use
|
||||
that toml file located in `./tests/config/` for its configuration. Including
|
||||
`// rustfmt-config: small_tabs.toml` will run your test with the configuration
|
||||
file found at `./tests/config/small_tabs.toml`. The final option is used when the
|
||||
test source file contains no configuration parameter comments. In this case, the
|
||||
test harness looks for a configuration file with the same filename as the test
|
||||
file in the `./tests/config/` directory, so a test source file named `test-indent.rs`
|
||||
would need a configuration file named `test-indent.toml` in that directory. As an
|
||||
example, the `issue-1111.rs` test file is configured by the file
|
||||
`./tests/config/issue-1111.toml`.
|
||||
|
||||
## Debugging
|
||||
|
||||
Some `rewrite_*` methods use the `debug!` macro for printing useful information.
|
||||
These messages can be printed by using the environment variable `RUST_LOG=rustfmt=DEBUG`.
|
||||
These traces can be helpful in understanding which part of the code was used
|
||||
and get a better grasp on the execution flow.
|
||||
|
||||
## Hack!
|
||||
|
||||
Here are some [good starting issues](https://github.com/rust-lang/rustfmt/issues?q=is%3Aopen+is%3Aissue+label%3Agood-first-issue).
|
||||
|
||||
If you've found areas which need polish and don't have issues, please submit a
|
||||
PR, don't feel there needs to be an issue.
|
||||
|
||||
|
||||
### Guidelines
|
||||
|
||||
Rustfmt bootstraps, that is part of its test suite is running itself on its
|
||||
source code. So, basically, the only style guideline is that you must pass the
|
||||
tests. That ensures that the Rustfmt source code adheres to our own conventions.
|
||||
|
||||
Talking of tests, if you add a new feature or fix a bug, please also add a test.
|
||||
It's really easy, see above for details. Please run `cargo test` before
|
||||
submitting a PR to ensure your patch passes all tests, it's pretty quick.
|
||||
|
||||
Rustfmt is post-1.0 and within major version releases we strive for backwards
|
||||
compatibility (at least when using the default options). That means any code
|
||||
which changes Rustfmt's output must be guarded by either an option or a version
|
||||
check. The latter is implemented as an option called `option`. See the section on
|
||||
[configuration](#Configuration) below.
|
||||
|
||||
Please try to avoid leaving `TODO`s in the code. There are a few around, but I
|
||||
wish there weren't. You can leave `FIXME`s, preferably with an issue number.
|
||||
|
||||
|
||||
### Version-gate formatting changes
|
||||
|
||||
A change that introduces a different code-formatting should be gated on the
|
||||
`version` configuration. This is to ensure the formatting of the current major
|
||||
release is preserved, while allowing fixes to be implemented for the next
|
||||
release.
|
||||
|
||||
This is done by conditionally guarding the change like so:
|
||||
|
||||
```rust
|
||||
if config.version() == Version::One { // if the current major release is 1.x
|
||||
// current formatting
|
||||
} else {
|
||||
// new formatting
|
||||
}
|
||||
```
|
||||
|
||||
This allows the user to apply the next formatting explicitly via the
|
||||
configuration, while being stable by default.
|
||||
|
||||
When the next major release is done, the code block of the previous formatting
|
||||
can be deleted, e.g., the first block in the example above when going from `1.x`
|
||||
to `2.x`.
|
||||
|
||||
| Note: Only formatting changes with default options need to be gated. |
|
||||
| --- |
|
||||
|
||||
### A quick tour of Rustfmt
|
||||
|
||||
Rustfmt is basically a pretty printer - that is, its mode of operation is to
|
||||
take an AST (abstract syntax tree) and print it in a nice way (including staying
|
||||
under the maximum permitted width for a line). In order to get that AST, we
|
||||
first have to parse the source text, we use the Rust compiler's parser to do
|
||||
that (see [src/lib.rs](src/lib.rs)). We shy away from doing anything too fancy, such as
|
||||
algebraic approaches to pretty printing, instead relying on an heuristic
|
||||
approach, 'manually' crafting a string for each AST node. This results in quite
|
||||
a lot of code, but it is relatively simple.
|
||||
|
||||
The AST is a tree view of source code. It carries all the semantic information
|
||||
about the code, but not all of the syntax. In particular, we lose white space
|
||||
and comments (although doc comments are preserved). Rustfmt uses a view of the
|
||||
AST before macros are expanded, so there are still macro uses in the code. The
|
||||
arguments to macros are not an AST, but raw tokens - this makes them harder to
|
||||
format.
|
||||
|
||||
There are different nodes for every kind of item and expression in Rust. For
|
||||
more details see the source code in the compiler -
|
||||
[ast.rs](https://dxr.mozilla.org/rust/source/src/libsyntax/ast.rs) - and/or the
|
||||
[docs](https://doc.rust-lang.org/nightly/nightly-rustc/syntax/ast/index.html).
|
||||
|
||||
Many nodes in the AST (but not all, annoyingly) have a `Span`. A `Span` is a
|
||||
range in the source code, it can easily be converted to a snippet of source
|
||||
text. When the AST does not contain enough information for us, we rely heavily
|
||||
on `Span`s. For example, we can look between spans to try and find comments, or
|
||||
parse a snippet to see how the user wrote their source code.
|
||||
|
||||
The downside of using the AST is that we miss some information - primarily white
|
||||
space and comments. White space is sometimes significant, although mostly we
|
||||
want to ignore it and make our own. We strive to reproduce all comments, but
|
||||
this is sometimes difficult. The crufty corners of Rustfmt are where we hack
|
||||
around the absence of comments in the AST and try to recreate them as best we
|
||||
can.
|
||||
|
||||
Our primary tool here is to look between spans for text we've missed. For
|
||||
example, in a function call `foo(a, b)`, we have spans for `a` and `b`, in this
|
||||
case, there is only a comma and a single space between the end of `a` and the
|
||||
start of `b`, so there is nothing much to do. But if we look at
|
||||
`foo(a /* a comment */, b)`, then between `a` and `b` we find the comment.
|
||||
|
||||
At a higher level, Rustfmt has machinery so that we account for text between
|
||||
'top level' items. Then we can reproduce that text pretty much verbatim. We only
|
||||
count spans we actually reformat, so if we can't format a span it is not missed
|
||||
completely but is reproduced in the output without being formatted. This is
|
||||
mostly handled in [src/missed_spans.rs](src/missed_spans.rs). See also `FmtVisitor::last_pos` in
|
||||
[src/visitor.rs](src/visitor.rs).
|
||||
|
||||
|
||||
#### Some important elements
|
||||
|
||||
At the highest level, Rustfmt uses a `Visitor` implementation called `FmtVisitor`
|
||||
to walk the AST. This is in [src/visitor.rs](src/visitor.rs). This is really just used to walk
|
||||
items, rather than the bodies of functions. We also cover macros and attributes
|
||||
here. Most methods of the visitor call out to `Rewrite` implementations that
|
||||
then walk their own children.
|
||||
|
||||
The `Rewrite` trait is defined in [src/rewrite.rs](src/rewrite.rs). It is implemented for many
|
||||
things that can be rewritten, mostly AST nodes. It has a single function,
|
||||
`rewrite`, which is called to rewrite `self` into an `Option<String>`. The
|
||||
arguments are `width` which is the horizontal space we write into and `offset`
|
||||
which is how much we are currently indented from the lhs of the page. We also
|
||||
take a context which contains information used for parsing, the current block
|
||||
indent, and a configuration (see below).
|
||||
|
||||
##### Rewrite and Indent
|
||||
|
||||
To understand the indents, consider
|
||||
|
||||
```
|
||||
impl Foo {
|
||||
fn foo(...) {
|
||||
bar(argument_one,
|
||||
baz());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When formatting the `bar` call we will format the arguments in order, after the
|
||||
first one we know we are working on multiple lines (imagine it is longer than
|
||||
written). So, when we come to the second argument, the indent we pass to
|
||||
`rewrite` is 12, which puts us under the first argument. The current block
|
||||
indent (stored in the context) is 8. The former is used for visual indenting
|
||||
(when objects are vertically aligned with some marker), the latter is used for
|
||||
block indenting (when objects are tabbed in from the lhs). The width available
|
||||
for `baz()` will be the maximum width, minus the space used for indenting, minus
|
||||
the space used for the `);`. (Note that actual argument formatting does not
|
||||
quite work like this, but it's close enough).
|
||||
|
||||
The `rewrite` function returns an `Option` - either we successfully rewrite and
|
||||
return the rewritten string for the caller to use, or we fail to rewrite and
|
||||
return `None`. This could be because Rustfmt encounters something it doesn't
|
||||
know how to reformat, but more often it is because Rustfmt can't fit the item
|
||||
into the required width. How to handle this is up to the caller. Often the
|
||||
caller just gives up, ultimately relying on the missed spans system to paste in
|
||||
the un-formatted source. A better solution (although not performed in many
|
||||
places) is for the caller to shuffle around some of its other items to make
|
||||
more width, then call the function again with more space.
|
||||
|
||||
Since it is common for callers to bail out when a callee fails, we often use a
|
||||
`?` operator to make this pattern more succinct.
|
||||
|
||||
One way we might find out that we don't have enough space is when computing how much
|
||||
space we have. Something like `available_space = budget - overhead`. Since
|
||||
widths are unsized integers, this would cause underflow. Therefore we use
|
||||
checked subtraction: `available_space = budget.checked_sub(overhead)?`.
|
||||
`checked_sub` returns an `Option`, and if we would underflow `?` returns
|
||||
`None`, otherwise, we proceed with the computed space.
|
||||
|
||||
##### Rewrite of list-like expressions
|
||||
|
||||
Much of the syntax in Rust is lists: lists of arguments, lists of fields, lists of
|
||||
array elements, etc. We have some generic code to handle lists, including how to
|
||||
space them in horizontal and vertical space, indentation, comments between
|
||||
items, trailing separators, etc. However, since there are so many options, the
|
||||
code is a bit complex. Look in [src/lists.rs](src/lists.rs). `write_list` is the key function,
|
||||
and `ListFormatting` the key structure for configuration. You'll need to make a
|
||||
`ListItems` for input, this is usually done using `itemize_list`.
|
||||
|
||||
##### Configuration
|
||||
|
||||
Rustfmt strives to be highly configurable. Often the first part of a patch is
|
||||
creating a configuration option for the feature you are implementing. All
|
||||
handling of configuration options is done in [src/config/mod.rs](src/config/mod.rs). Look for the
|
||||
`create_config!` macro at the end of the file for all the options. The rest of
|
||||
the file defines a bunch of enums used for options, and the machinery to produce
|
||||
the config struct and parse a config file, etc. Checking an option is done by
|
||||
accessing the correct field on the config struct, e.g., `config.max_width()`. Most
|
||||
functions have a `Config`, or one can be accessed via a visitor or context of
|
||||
some kind.
|
||||
184
src/tools/rustfmt/Design.md
Normal file
184
src/tools/rustfmt/Design.md
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# Some thoughts on the design of rustfmt
|
||||
|
||||
## Use cases
|
||||
|
||||
A formatting tool can be used in different ways and the different use cases can
|
||||
affect the design of the tool. The use cases I'm particularly concerned with are:
|
||||
|
||||
* running on a whole repo before check-in
|
||||
- in particular, to replace the `make tidy` pass on the Rust distro
|
||||
* running on code from another project that you are adding to your own
|
||||
* using for mass changes in code style over a project
|
||||
|
||||
Some valid use cases for a formatting tool which I am explicitly not trying to
|
||||
address (although it would be nice, if possible):
|
||||
|
||||
* running 'as you type' in an IDE
|
||||
* running on arbitrary snippets of code
|
||||
* running on Rust-like code, specifically code which doesn't parse
|
||||
* use as a pretty printer inside the compiler
|
||||
* refactoring
|
||||
* formatting totally unformatted source code
|
||||
|
||||
|
||||
## Scope and vision
|
||||
|
||||
I do not subscribe to the notion that a formatting tool should only change
|
||||
whitespace. I believe that we should semantics preserving, but not necessarily
|
||||
syntax preserving, i.e., we can change the AST of a program.
|
||||
|
||||
I.e., we might change glob imports to list or single imports, re-order imports,
|
||||
move bounds to where clauses, combine multiple impls into a single impl, etc.
|
||||
|
||||
However, we will not change the names of variables or make any changes which
|
||||
*could* change the semantics. To be ever so slightly formal, we might imagine
|
||||
a compilers high level intermediate representation, we should strive to only
|
||||
make changes which do not change the HIR, even if they do change the AST.
|
||||
|
||||
I would like to be able to output refactoring scripts for making deeper changes
|
||||
though. (E.g., renaming variables to satisfy our style guidelines).
|
||||
|
||||
My long term goal is that all style lints can be moved from the compiler to
|
||||
rustfmt and, as well as warning, can either fix problems or emit refactoring
|
||||
scripts to do so.
|
||||
|
||||
### Configurability
|
||||
|
||||
I believe reformatting should be configurable to some extent. We should read in
|
||||
options from a configuration file and reformat accordingly. We should supply at
|
||||
least a config file which matches the Rust style guidelines.
|
||||
|
||||
There should be multiple modes for running the tool. As well as simply replacing
|
||||
each file, we should be able to show the user a list of the changes we would
|
||||
make, or show a list of violations without corrections (the difference being
|
||||
that there are multiple ways to satisfy a given set of style guidelines, and we
|
||||
should distinguish violations from deviations from our own model).
|
||||
|
||||
|
||||
## Implementation philosophy
|
||||
|
||||
Some details of the philosophy behind the implementation.
|
||||
|
||||
|
||||
### Operate on the AST
|
||||
|
||||
A reformatting tool can be based on either the AST or a token stream (in Rust
|
||||
this is actually a stream of token trees, but it's not a fundamental difference).
|
||||
There are pros and cons to the two approaches. I have chosen to use the AST
|
||||
approach. The primary reasons are that it allows us to do more sophisticated
|
||||
manipulations, rather than just change whitespace, and it gives us more context
|
||||
when making those changes.
|
||||
|
||||
The advantage of the tokens approach is that you can operate on non-parsable
|
||||
code. I don't care too much about that, it would be nice, but I think being able
|
||||
to perform sophisticated transformations is more important. In the future, I hope to
|
||||
(optionally) be able to use type information for informing reformatting too. One
|
||||
specific case of unparsable code is macros. Using tokens is certainly easier
|
||||
here, but I believe it is perfectly solvable with the AST approach. At the limit,
|
||||
we can operate on just tokens in the macro case.
|
||||
|
||||
I believe that there is not in fact that much difference between the two
|
||||
approaches. Due to imperfect span information, under the AST approach, we
|
||||
sometimes are reduced to examining tokens or do some re-lexing of our own. Under
|
||||
the tokens approach, you need to implement your own (much simpler) parser. I
|
||||
believe that as the tool gets more sophisticated, you end up doing more at the
|
||||
token-level, or having an increasingly sophisticated parser, until at the limit
|
||||
you have the same tool.
|
||||
|
||||
However, I believe starting from the AST gets you more quickly to a usable and
|
||||
useful tool.
|
||||
|
||||
|
||||
### Heuristic rather than algorithmic
|
||||
|
||||
Many formatting tools use a very general algorithmic or even algebraic tool for
|
||||
pretty printing. This results in very elegant code, but I believe does not give
|
||||
the best results. I prefer a more ad hoc approach where each expression/item is
|
||||
formatted using custom rules. We hopefully don't end up with too much code due
|
||||
to good old fashioned abstraction and code sharing. This will give a bigger code
|
||||
base, but hopefully a better result.
|
||||
|
||||
It also means that there will be some cases we can't format and we have to give
|
||||
up. I think that is OK. Hopefully, they are rare enough that manually fixing them
|
||||
is not painful. Better to have a tool that gives great code in 99% of cases and
|
||||
fails in 1% than a tool which gives 50% great code and 50% ugly code, but never
|
||||
fails.
|
||||
|
||||
|
||||
### Incremental development
|
||||
|
||||
I want rustfmt to be useful as soon as possible and to always be useful. I
|
||||
specifically don't want to have to wait for a feature (or worse, the whole tool)
|
||||
to be perfect before it is useful. The main ways this is achieved is to output
|
||||
the source code where we can't yet reformat, be able to turn off new features
|
||||
until they are ready, and the 'do no harm' principle (see next section).
|
||||
|
||||
|
||||
### First, do no harm
|
||||
|
||||
Until rustfmt is perfect, there will always be a trade-off between doing more and
|
||||
doing existing things well. I want to err on the side of the latter.
|
||||
Specifically, rustfmt should never take OK code and make it look worse. If we
|
||||
can't make it better, we should leave it as is. That might mean being less
|
||||
aggressive than we like or using configurability.
|
||||
|
||||
|
||||
### Use the source code as guidance
|
||||
|
||||
There are often multiple ways to format code and satisfy standards. Where this
|
||||
is the case, we should use the source code as a hint for reformatting.
|
||||
Furthermore, where the code has been formatted in a particular way that satisfies
|
||||
the coding standard, it should not be changed (this is sometimes not possible or
|
||||
not worthwhile due to uniformity being desirable, but it is a useful goal).
|
||||
|
||||
|
||||
### Architecture details
|
||||
|
||||
We use the AST from [syntex_syntax], an export of rustc's libsyntax. We use
|
||||
syntex_syntax's visit module to walk the AST to find starting points for
|
||||
reformatting. Eventually, we should reformat everything and we shouldn't need
|
||||
the visit module. We keep track of the last formatted position in the code, and
|
||||
when we reformat the next piece of code we make sure to output the span for all
|
||||
the code in between (handled by missed_spans.rs).
|
||||
|
||||
[syntex_syntax]: https://crates.io/crates/syntex_syntax
|
||||
|
||||
We read in formatting configuration from a `rustfmt.toml` file if there is one.
|
||||
The options and their defaults are defined in `config.rs`. A `Config` object is
|
||||
passed throughout the formatting code, and each formatting routine looks there
|
||||
for its configuration.
|
||||
|
||||
Our visitor keeps track of the desired current indent due to blocks (
|
||||
`block_indent`). Each `visit_*` method reformats code according to this indent,
|
||||
`config.comment_width()` and `config.max_width()`. Most reformatting that is done
|
||||
in the `visit_*` methods is a bit hacky and is meant to be temporary until it can
|
||||
be done properly.
|
||||
|
||||
There are a bunch of methods called `rewrite_*`. They do the bulk of the
|
||||
reformatting. These take the AST node to be reformatted (this may not literally
|
||||
be an AST node from syntex_syntax: there might be multiple parameters
|
||||
describing a logical node), the current indent, and the current width budget.
|
||||
They return a `String` (or sometimes an `Option<String>`) which formats the
|
||||
code in the box given by the indent and width budget. If the method fails, it
|
||||
returns `None` and the calling method then has to fallback in some way to give
|
||||
the callee more space.
|
||||
|
||||
So, in summary, to format a node, we calculate the width budget and then walk down
|
||||
the tree from the node. At a leaf, we generate an actual string and then unwind,
|
||||
combining these strings as we go back up the tree.
|
||||
|
||||
For example, consider a method definition:
|
||||
|
||||
```
|
||||
fn foo(a: A, b: B) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
We start at indent 4, the rewrite function for the whole function knows it must
|
||||
write `fn foo(` before the arguments and `) {` after them, assuming the max width
|
||||
is 100, it thus asks the rewrite argument list function to rewrite with an indent
|
||||
of 11 and in a width of 86. Assuming that is possible (obviously in this case),
|
||||
it returns a string for the arguments and it can make a string for the function
|
||||
header. If the arguments couldn't be fitted in that space, we might try to
|
||||
fallback to a hanging indent, so we try again with indent 8 and width 89.
|
||||
201
src/tools/rustfmt/LICENSE-APACHE
Normal file
201
src/tools/rustfmt/LICENSE-APACHE
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2016-2021 The Rust Project Developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
25
src/tools/rustfmt/LICENSE-MIT
Normal file
25
src/tools/rustfmt/LICENSE-MIT
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2016-2021 The Rust Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
71
src/tools/rustfmt/Makefile.toml
Normal file
71
src/tools/rustfmt/Makefile.toml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
[env]
|
||||
CFG_RELEASE = { value = "${CARGO_MAKE_RUST_VERSION}", condition = { env_not_set = ["CFG_RELEASE"] } }
|
||||
CFG_RELEASE_CHANNEL = { value = "${CARGO_MAKE_RUST_CHANNEL}", condition = { env_not_set = ["CFG_RELEASE_CHANNEL"] } }
|
||||
|
||||
[tasks.build-bin]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"build",
|
||||
"--bin",
|
||||
"rustfmt",
|
||||
"--bin",
|
||||
"cargo-fmt",
|
||||
]
|
||||
|
||||
[tasks.build-bins]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"build",
|
||||
"--bins",
|
||||
]
|
||||
|
||||
[tasks.install]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"install",
|
||||
"--path",
|
||||
".",
|
||||
"--force",
|
||||
"--locked", # Respect Cargo.lock
|
||||
]
|
||||
|
||||
[tasks.release]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"build",
|
||||
"--release",
|
||||
]
|
||||
|
||||
[tasks.test]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"test",
|
||||
]
|
||||
|
||||
[tasks.test-all]
|
||||
dependencies = ["build-bin"]
|
||||
run_task = { name = ["test", "test-ignored"] }
|
||||
|
||||
[tasks.test-ignored]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"test",
|
||||
"--",
|
||||
"--ignored",
|
||||
]
|
||||
|
||||
[tasks.b]
|
||||
alias = "build"
|
||||
|
||||
[tasks.bb]
|
||||
alias = "build-bin"
|
||||
|
||||
[tasks.bins]
|
||||
alias = "build-bins"
|
||||
|
||||
[tasks.c]
|
||||
alias = "check"
|
||||
|
||||
[tasks.t]
|
||||
alias = "test"
|
||||
|
||||
57
src/tools/rustfmt/Processes.md
Normal file
57
src/tools/rustfmt/Processes.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
This document outlines processes regarding management of rustfmt.
|
||||
|
||||
# Stabilising an Option
|
||||
|
||||
In this Section, we describe how to stabilise an option of the rustfmt's configration.
|
||||
|
||||
## Conditions
|
||||
|
||||
- Is the default value correct ?
|
||||
- The design and implementation of the option are sound and clean.
|
||||
- The option is well tested, both in unit tests and, optimally, in real usage.
|
||||
- There is no open bug about the option that prevents its use.
|
||||
|
||||
## Steps
|
||||
|
||||
Open a pull request that closes the tracking issue. The tracking issue is listed beside the option in `Configurations.md`.
|
||||
|
||||
- Update the `Config` enum marking the option as stable.
|
||||
- Update the the `Configuration.md` file marking the option as stable.
|
||||
- Update `CHANGELOG.md` marking the option as stable.
|
||||
|
||||
## After the stabilisation
|
||||
|
||||
The option should remain backward-compatible with previous parameters of the option. For instance, if the option is an enum `enum Foo { Alice, Bob }` and the variant `Foo::Bob` is removed/renamed, existing use of the `Foo::Bob` variant should map to the new logic. Breaking changes can be applied under the condition they are version-gated.
|
||||
|
||||
# Make a Release
|
||||
|
||||
## 0. Update CHANGELOG.md
|
||||
|
||||
## 1. Update Cargo.toml and Cargo.lock
|
||||
|
||||
For example, 1.0.0 -> 1.0.1:
|
||||
|
||||
```diff
|
||||
-version = "1.0.0"
|
||||
+version = "1.0.1"
|
||||
```
|
||||
|
||||
## 2. Push the commit to the master branch
|
||||
|
||||
E.g., https://github.com/rust-lang/rustfmt/commit/5274b49caa1a7db6ac10c76bf1a3d5710ccef569
|
||||
|
||||
## 3. Create a release tag
|
||||
|
||||
```sh
|
||||
git tag -s v1.2.3 -m "Release 1.2.3"
|
||||
```
|
||||
|
||||
## 4. Publish to crates.io
|
||||
|
||||
`cargo publish`
|
||||
|
||||
## 5. Create a PR to rust-lang/rust to update the rustfmt submodule
|
||||
|
||||
Note that if you are updating `rustc-ap-*` crates, then you need to update **every** submodules in the rust-lang/rust repository that depend on the crates to use the same version of those.
|
||||
|
||||
As of 2019/05, there are two such crates: `rls` and `racer` (`racer` depends on `rustc-ap-syntax` and `rls` depends on `racer`, and `rls` is one of submodules of the rust-lang/rust repository).
|
||||
234
src/tools/rustfmt/README.md
Normal file
234
src/tools/rustfmt/README.md
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
# rustfmt [](https://travis-ci.com/rust-lang/rustfmt) [](https://ci.appveyor.com/project/rust-lang-libs/rustfmt) [](https://crates.io/crates/rustfmt-nightly) [](https://travis-ci.org/davidalber/rustfmt-travis)
|
||||
|
||||
A tool for formatting Rust code according to style guidelines.
|
||||
|
||||
If you'd like to help out (and you should, it's a fun project!), see
|
||||
[Contributing.md](Contributing.md) and our [Code of
|
||||
Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
You can use rustfmt in Travis CI builds. We provide a minimal Travis CI
|
||||
configuration (see [here](#checking-style-on-a-ci-server)) and verify its status
|
||||
using another repository. The status of that repository's build is reported by
|
||||
the "travis example" badge above.
|
||||
|
||||
## Quick start
|
||||
|
||||
You can run `rustfmt` with Rust 1.24 and above.
|
||||
|
||||
### On the Stable toolchain
|
||||
|
||||
To install:
|
||||
|
||||
```sh
|
||||
rustup component add rustfmt
|
||||
```
|
||||
|
||||
To run on a cargo project in the current working directory:
|
||||
|
||||
```sh
|
||||
cargo fmt
|
||||
```
|
||||
|
||||
### On the Nightly toolchain
|
||||
|
||||
For the latest and greatest `rustfmt`, nightly is required.
|
||||
|
||||
To install:
|
||||
|
||||
```sh
|
||||
rustup component add rustfmt --toolchain nightly
|
||||
```
|
||||
|
||||
To run on a cargo project in the current working directory:
|
||||
|
||||
```sh
|
||||
cargo +nightly fmt
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
Rustfmt tries to work on as much Rust code as possible, sometimes, the code
|
||||
doesn't even need to compile! As we approach a 1.0 release we are also looking
|
||||
to limit areas of instability; in particular, post-1.0, the formatting of most
|
||||
code should not change as Rustfmt improves. However, there are some things that
|
||||
Rustfmt can't do or can't do well (and thus where formatting might change
|
||||
significantly, even post-1.0). We would like to reduce the list of limitations
|
||||
over time.
|
||||
|
||||
The following list enumerates areas where Rustfmt does not work or where the
|
||||
stability guarantees do not apply (we don't make a distinction between the two
|
||||
because in the future Rustfmt might work on code where it currently does not):
|
||||
|
||||
* a program where any part of the program does not parse (parsing is an early
|
||||
stage of compilation and in Rust includes macro expansion).
|
||||
* Macro declarations and uses (current status: some macro declarations and uses
|
||||
are formatted).
|
||||
* Comments, including any AST node with a comment 'inside' (Rustfmt does not
|
||||
currently attempt to format comments, it does format code with comments inside, but that formatting may change in the future).
|
||||
* Rust code in code blocks in comments.
|
||||
* Any fragment of a program (i.e., stability guarantees only apply to whole
|
||||
programs, even where fragments of a program can be formatted today).
|
||||
* Code containing non-ascii unicode characters (we believe Rustfmt mostly works
|
||||
here, but do not have the test coverage or experience to be 100% sure).
|
||||
* Bugs in Rustfmt (like any software, Rustfmt has bugs, we do not consider bug
|
||||
fixes to break our stability guarantees).
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
rustup component add rustfmt
|
||||
```
|
||||
|
||||
## Installing from source
|
||||
|
||||
To install from source (nightly required), first checkout to the tag or branch you want to install, then issue
|
||||
|
||||
```sh
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
This will install `rustfmt` in your `~/.cargo/bin`. Make sure to add `~/.cargo/bin` directory to
|
||||
your PATH variable.
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
You can run Rustfmt by just typing `rustfmt filename` if you used `cargo
|
||||
install`. This runs rustfmt on the given file, if the file includes out of line
|
||||
modules, then we reformat those too. So to run on a whole module or crate, you
|
||||
just need to run on the root file (usually mod.rs or lib.rs). Rustfmt can also
|
||||
read data from stdin. Alternatively, you can use `cargo fmt` to format all
|
||||
binary and library targets of your crate.
|
||||
|
||||
You can run `rustfmt --help` for information about available arguments.
|
||||
|
||||
When running with `--check`, Rustfmt will exit with `0` if Rustfmt would not
|
||||
make any formatting changes to the input, and `1` if Rustfmt would make changes.
|
||||
In other modes, Rustfmt will exit with `1` if there was some error during
|
||||
formatting (for example a parsing or internal error) and `0` if formatting
|
||||
completed without error (whether or not changes were made).
|
||||
|
||||
|
||||
|
||||
## Running Rustfmt from your editor
|
||||
|
||||
* [Vim](https://github.com/rust-lang/rust.vim#formatting-with-rustfmt)
|
||||
* [Emacs](https://github.com/rust-lang/rust-mode)
|
||||
* [Sublime Text 3](https://packagecontrol.io/packages/RustFmt)
|
||||
* [Atom](atom.md)
|
||||
* Visual Studio Code using [vscode-rust](https://github.com/editor-rs/vscode-rust), [vsc-rustfmt](https://github.com/Connorcpu/vsc-rustfmt) or [rls_vscode](https://github.com/jonathandturner/rls_vscode) through RLS.
|
||||
* [IntelliJ or CLion](intellij.md)
|
||||
|
||||
|
||||
## Checking style on a CI server
|
||||
|
||||
To keep your code base consistently formatted, it can be helpful to fail the CI build
|
||||
when a pull request contains unformatted code. Using `--check` instructs
|
||||
rustfmt to exit with an error code if the input is not formatted correctly.
|
||||
It will also print any found differences. (Older versions of Rustfmt don't
|
||||
support `--check`, use `--write-mode diff`).
|
||||
|
||||
A minimal Travis setup could look like this (requires Rust 1.24.0 or greater):
|
||||
|
||||
```yaml
|
||||
language: rust
|
||||
before_script:
|
||||
- rustup component add rustfmt
|
||||
script:
|
||||
- cargo build
|
||||
- cargo test
|
||||
- cargo fmt --all -- --check
|
||||
```
|
||||
|
||||
See [this blog post](https://medium.com/@ag_dubs/enforcing-style-in-ci-for-rust-projects-18f6b09ec69d)
|
||||
for more info.
|
||||
|
||||
## How to build and test
|
||||
|
||||
`cargo build` to build.
|
||||
|
||||
`cargo test` to run all tests.
|
||||
|
||||
To run rustfmt after this, use `cargo run --bin rustfmt -- filename`. See the
|
||||
notes above on running rustfmt.
|
||||
|
||||
|
||||
## Configuring Rustfmt
|
||||
|
||||
Rustfmt is designed to be very configurable. You can create a TOML file called
|
||||
`rustfmt.toml` or `.rustfmt.toml`, place it in the project or any other parent
|
||||
directory and it will apply the options in that file. See `rustfmt
|
||||
--help=config` for the options which are available, or if you prefer to see
|
||||
visual style previews, [GitHub page](https://rust-lang.github.io/rustfmt/).
|
||||
|
||||
By default, Rustfmt uses a style which conforms to the [Rust style guide][style
|
||||
guide] that has been formalized through the [style RFC
|
||||
process][fmt rfcs].
|
||||
|
||||
Configuration options are either stable or unstable. Stable options can always
|
||||
be used, while unstable ones are only available on a nightly toolchain, and opt-in.
|
||||
See [GitHub page](https://rust-lang.github.io/rustfmt/) for details.
|
||||
|
||||
### Rust's Editions
|
||||
|
||||
Rustfmt is able to pick up the edition used by reading the `Cargo.toml` file if
|
||||
executed through the Cargo's formatting tool `cargo fmt`. Otherwise, the edition
|
||||
needs to be specified in `rustfmt.toml`, e.g., with `edition = "2018"`.
|
||||
|
||||
## Tips
|
||||
|
||||
* For things you do not want rustfmt to mangle, use `#[rustfmt::skip]`
|
||||
* To prevent rustfmt from formatting a macro or an attribute,
|
||||
use `#[rustfmt::skip::macros(target_macro_name)]` or
|
||||
`#[rustfmt::skip::attributes(target_attribute_name)]`
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
#![rustfmt::skip::attributes(custom_attribute)]
|
||||
|
||||
#[custom_attribute(formatting , here , should , be , Skipped)]
|
||||
#[rustfmt::skip::macros(html)]
|
||||
fn main() {
|
||||
let macro_result1 = html! { <div>
|
||||
Hello</div>
|
||||
}.to_string();
|
||||
```
|
||||
* When you run rustfmt, place a file named `rustfmt.toml` or `.rustfmt.toml` in
|
||||
target file directory or its parents to override the default settings of
|
||||
rustfmt. You can generate a file containing the default configuration with
|
||||
`rustfmt --print-config default rustfmt.toml` and customize as needed.
|
||||
* After successful compilation, a `rustfmt` executable can be found in the
|
||||
target directory.
|
||||
* If you're having issues compiling Rustfmt (or compile errors when trying to
|
||||
install), make sure you have the most recent version of Rust installed.
|
||||
|
||||
* You can change the way rustfmt emits the changes with the --emit flag:
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
cargo fmt -- --emit files
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
| Flag |Description| Nightly Only |
|
||||
|:---:|:---:|:---:|
|
||||
| files | overwrites output to files | No |
|
||||
| stdout | writes output to stdout | No |
|
||||
| coverage | displays how much of the input file was processed | Yes |
|
||||
| checkstyle | emits in a checkstyle format | Yes |
|
||||
| json | emits diffs in a json format | Yes |
|
||||
|
||||
## License
|
||||
|
||||
Rustfmt is distributed under the terms of both the MIT license and the
|
||||
Apache License (Version 2.0).
|
||||
|
||||
See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details.
|
||||
|
||||
[rust]: https://github.com/rust-lang/rust
|
||||
[fmt rfcs]: https://github.com/rust-lang-nursery/fmt-rfcs
|
||||
[style guide]: https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md
|
||||
55
src/tools/rustfmt/appveyor.yml
Normal file
55
src/tools/rustfmt/appveyor.yml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# This is based on https://github.com/japaric/rust-everywhere/blob/master/appveyor.yml
|
||||
# and modified (mainly removal of deployment) to suit rustfmt.
|
||||
|
||||
environment:
|
||||
global:
|
||||
PROJECT_NAME: rustfmt
|
||||
matrix:
|
||||
# Stable channel
|
||||
# - TARGET: i686-pc-windows-gnu
|
||||
# CHANNEL: stable
|
||||
# - TARGET: i686-pc-windows-msvc
|
||||
# CHANNEL: stable
|
||||
# - TARGET: x86_64-pc-windows-gnu
|
||||
# CHANNEL: stable
|
||||
# - TARGET: x86_64-pc-windows-msvc
|
||||
# CHANNEL: stable
|
||||
# Beta channel
|
||||
# - TARGET: i686-pc-windows-gnu
|
||||
# CHANNEL: beta
|
||||
# - TARGET: i686-pc-windows-msvc
|
||||
# CHANNEL: beta
|
||||
# - TARGET: x86_64-pc-windows-gnu
|
||||
# CHANNEL: beta
|
||||
# - TARGET: x86_64-pc-windows-msvc
|
||||
# CHANNEL: beta
|
||||
# Nightly channel
|
||||
- TARGET: i686-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
CHANNEL: nightly
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
CHANNEL: nightly
|
||||
|
||||
# Install Rust and Cargo
|
||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||
install:
|
||||
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- if "%TARGET%" == "i686-pc-windows-gnu" set PATH=%PATH%;C:\msys64\mingw32\bin
|
||||
- if "%TARGET%" == "x86_64-pc-windows-gnu" set PATH=%PATH%;C:\msys64\mingw64\bin
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
|
||||
- rustc -Vv
|
||||
- cargo -V
|
||||
|
||||
# ???
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- set CFG_RELEASE_CHANNEL=nightly
|
||||
- set CFG_RELEASE=nightly
|
||||
- cargo build --verbose
|
||||
- cargo test
|
||||
- cargo test -- --ignored
|
||||
31
src/tools/rustfmt/atom.md
Normal file
31
src/tools/rustfmt/atom.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Running Rustfmt from Atom
|
||||
|
||||
## RLS
|
||||
|
||||
Rustfmt is included with the Rust Language Server, itself provided by [ide-rust](https://atom.io/packages/ide-rust).
|
||||
|
||||
`apm install ide-rust`
|
||||
|
||||
Once installed a file is formatted with `ctrl-shift-c` or `cmd-shift-c`, also available in context menu.
|
||||
|
||||
## atom-beautify
|
||||
|
||||
Another way is to install [Beautify](https://atom.io/packages/atom-beautify), you
|
||||
can do this by running `apm install atom-beautify`.
|
||||
|
||||
There are 2 settings that need to be configured in the atom beautifier configuration.
|
||||
|
||||
- Install rustfmt as per the [readme](README.md).
|
||||
- Open the atom beautifier settings
|
||||
|
||||
Go to Edit->Preferences. Click the packages on the left side and click on setting for atom-beautifier
|
||||
|
||||
- Set rustfmt as the beautifier
|
||||
|
||||
Find the setting labeled *Language Config - Rust - Default Beautifier* and make sure it is set to rustfmt as shown below. You can also set the beautifier to auto format on save here.
|
||||

|
||||
|
||||
- Set the path to your rustfmt location
|
||||
|
||||
Find the setting labeled *Rust - Rustfmt Path*. This setting is towards the bottom and you will need to scroll a bit. Set it to the path for your rustfmt executable.
|
||||

|
||||
17
src/tools/rustfmt/bootstrap.sh
Executable file
17
src/tools/rustfmt/bootstrap.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Make sure you double check the diffs after running this script - with great
|
||||
# power comes great responsibility.
|
||||
# We deliberately avoid reformatting files with rustfmt comment directives.
|
||||
|
||||
cargo build --release
|
||||
|
||||
target/release/rustfmt src/lib.rs
|
||||
target/release/rustfmt src/bin/main.rs
|
||||
target/release/rustfmt src/cargo-fmt/main.rs
|
||||
|
||||
for filename in tests/target/*.rs; do
|
||||
if ! grep -q "rustfmt-" "$filename"; then
|
||||
target/release/rustfmt $filename
|
||||
fi
|
||||
done
|
||||
55
src/tools/rustfmt/build.rs
Normal file
55
src/tools/rustfmt/build.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
// Only check .git/HEAD dirty status if it exists - doing so when
|
||||
// building dependent crates may lead to false positives and rebuilds
|
||||
if Path::new(".git/HEAD").exists() {
|
||||
println!("cargo:rerun-if-changed=.git/HEAD");
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-env-changed=CFG_RELEASE_CHANNEL");
|
||||
|
||||
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
|
||||
File::create(out_dir.join("commit-info.txt"))
|
||||
.unwrap()
|
||||
.write_all(commit_info().as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Try to get hash and date of the last commit on a best effort basis. If anything goes wrong
|
||||
// (git not installed or if this is not a git repository) just return an empty string.
|
||||
fn commit_info() -> String {
|
||||
match (channel(), commit_hash(), commit_date()) {
|
||||
(channel, Some(hash), Some(date)) => format!("{} ({} {})", channel, hash.trim_end(), date),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn channel() -> String {
|
||||
if let Ok(channel) = env::var("CFG_RELEASE_CHANNEL") {
|
||||
channel
|
||||
} else {
|
||||
"nightly".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
fn commit_hash() -> Option<String> {
|
||||
Command::new("git")
|
||||
.args(&["rev-parse", "--short", "HEAD"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|r| String::from_utf8(r.stdout).ok())
|
||||
}
|
||||
|
||||
fn commit_date() -> Option<String> {
|
||||
Command::new("git")
|
||||
.args(&["log", "-1", "--date=short", "--pretty=format:%cd"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|r| String::from_utf8(r.stdout).ok())
|
||||
}
|
||||
107
src/tools/rustfmt/ci/integration.sh
Executable file
107
src/tools/rustfmt/ci/integration.sh
Executable file
|
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
: ${INTEGRATION?"The INTEGRATION environment variable must be set."}
|
||||
|
||||
# FIXME: this means we can get a stale cargo-fmt from a previous run.
|
||||
#
|
||||
# `which rustfmt` fails if rustfmt is not found. Since we don't install
|
||||
# `rustfmt` via `rustup`, this is the case unless we manually install it. Once
|
||||
# that happens, `cargo install --force` will be called, which installs
|
||||
# `rustfmt`, `cargo-fmt`, etc to `~/.cargo/bin`. This directory is cached by
|
||||
# travis (see `.travis.yml`'s "cache" key), such that build-bots that arrive
|
||||
# here after the first installation will find `rustfmt` and won't need to build
|
||||
# it again.
|
||||
#
|
||||
#which cargo-fmt || cargo install --force
|
||||
CFG_RELEASE=nightly CFG_RELEASE_CHANNEL=nightly cargo install --path . --force
|
||||
|
||||
echo "Integration tests for: ${INTEGRATION}"
|
||||
cargo fmt -- --version
|
||||
|
||||
# Checks that:
|
||||
#
|
||||
# * `cargo fmt --all` succeeds without any warnings or errors
|
||||
# * `cargo fmt --all -- --check` after formatting returns success
|
||||
# * `cargo test --all` still passes (formatting did not break the build)
|
||||
function check_fmt_with_all_tests {
|
||||
check_fmt_base "--all"
|
||||
return $?
|
||||
}
|
||||
|
||||
# Checks that:
|
||||
#
|
||||
# * `cargo fmt --all` succeeds without any warnings or errors
|
||||
# * `cargo fmt --all -- --check` after formatting returns success
|
||||
# * `cargo test --lib` still passes (formatting did not break the build)
|
||||
function check_fmt_with_lib_tests {
|
||||
check_fmt_base "--lib"
|
||||
return $?
|
||||
}
|
||||
|
||||
function check_fmt_base {
|
||||
local test_args="$1"
|
||||
local build=$(cargo test $test_args 2>&1)
|
||||
if [[ "$build" =~ "build failed" ]] || [[ "$build" =~ "test result: FAILED." ]]; then
|
||||
return 0
|
||||
fi
|
||||
touch rustfmt.toml
|
||||
cargo fmt --all -v |& tee rustfmt_output
|
||||
if [[ ${PIPESTATUS[0]} != 0 ]]; then
|
||||
cat rustfmt_output
|
||||
return 1
|
||||
fi
|
||||
cat rustfmt_output
|
||||
! cat rustfmt_output | grep -q "internal error"
|
||||
if [[ $? != 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
! cat rustfmt_output | grep -q "warning"
|
||||
if [[ $? != 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
! cat rustfmt_output | grep -q "Warning"
|
||||
if [[ $? != 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
cargo fmt --all -- --check |& tee rustfmt_check_output
|
||||
if [[ ${PIPESTATUS[0]} != 0 ]]; then
|
||||
cat rustfmt_check_output
|
||||
return 1
|
||||
fi
|
||||
cargo test $test_args
|
||||
if [[ $? != 0 ]]; then
|
||||
return $?
|
||||
fi
|
||||
}
|
||||
|
||||
function show_head {
|
||||
local head=$(git rev-parse HEAD)
|
||||
echo "Head commit of ${INTEGRATION}: $head"
|
||||
}
|
||||
|
||||
case ${INTEGRATION} in
|
||||
cargo)
|
||||
git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git
|
||||
cd ${INTEGRATION}
|
||||
show_head
|
||||
export CFG_DISABLE_CROSS_TESTS=1
|
||||
check_fmt_with_all_tests
|
||||
cd -
|
||||
;;
|
||||
crater)
|
||||
git clone --depth=1 https://github.com/rust-lang-nursery/${INTEGRATION}.git
|
||||
cd ${INTEGRATION}
|
||||
show_head
|
||||
check_fmt_with_lib_tests
|
||||
cd -
|
||||
;;
|
||||
*)
|
||||
git clone --depth=1 https://github.com/rust-lang-nursery/${INTEGRATION}.git
|
||||
cd ${INTEGRATION}
|
||||
show_head
|
||||
check_fmt_with_all_tests
|
||||
cd -
|
||||
;;
|
||||
esac
|
||||
1
src/tools/rustfmt/config_proc_macro/.gitignore
vendored
Normal file
1
src/tools/rustfmt/config_proc_macro/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
target/
|
||||
68
src/tools/rustfmt/config_proc_macro/Cargo.lock
Normal file
68
src/tools/rustfmt/config_proc_macro/Cargo.lock
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustfmt-config_proc_macro"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f"
|
||||
"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425"
|
||||
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
24
src/tools/rustfmt/config_proc_macro/Cargo.toml
Normal file
24
src/tools/rustfmt/config_proc_macro/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "rustfmt-config_proc_macro"
|
||||
version = "0.2.0"
|
||||
authors = ["topecongiro <seuchida@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "A collection of procedural macros for rustfmt"
|
||||
license = "Apache-2.0/MIT"
|
||||
categories = ["development-tools::procedural-macro-helpers"]
|
||||
repository = "https://github.com/rust-lang/rustfmt"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full", "visit"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
debug-with-rustfmt = []
|
||||
57
src/tools/rustfmt/config_proc_macro/src/attrs.rs
Normal file
57
src/tools/rustfmt/config_proc_macro/src/attrs.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//! This module provides utilities for handling attributes on variants
|
||||
//! of `config_type` enum. Currently there are two types of attributes
|
||||
//! that could appear on the variants of `config_type` enum: `doc_hint`
|
||||
//! and `value`. Both comes in the form of name-value pair whose value
|
||||
//! is string literal.
|
||||
|
||||
/// Returns the value of the first `doc_hint` attribute in the given slice or
|
||||
/// `None` if `doc_hint` attribute is not available.
|
||||
pub fn find_doc_hint(attrs: &[syn::Attribute]) -> Option<String> {
|
||||
attrs.iter().filter_map(doc_hint).next()
|
||||
}
|
||||
|
||||
/// Returns `true` if the given attribute is a `doc_hint` attribute.
|
||||
pub fn is_doc_hint(attr: &syn::Attribute) -> bool {
|
||||
is_attr_name_value(attr, "doc_hint")
|
||||
}
|
||||
|
||||
/// Returns a string literal value if the given attribute is `doc_hint`
|
||||
/// attribute or `None` otherwise.
|
||||
pub fn doc_hint(attr: &syn::Attribute) -> Option<String> {
|
||||
get_name_value_str_lit(attr, "doc_hint")
|
||||
}
|
||||
|
||||
/// Returns the value of the first `value` attribute in the given slice or
|
||||
/// `None` if `value` attribute is not available.
|
||||
pub fn find_config_value(attrs: &[syn::Attribute]) -> Option<String> {
|
||||
attrs.iter().filter_map(config_value).next()
|
||||
}
|
||||
|
||||
/// Returns a string literal value if the given attribute is `value`
|
||||
/// attribute or `None` otherwise.
|
||||
pub fn config_value(attr: &syn::Attribute) -> Option<String> {
|
||||
get_name_value_str_lit(attr, "value")
|
||||
}
|
||||
|
||||
/// Returns `true` if the given attribute is a `value` attribute.
|
||||
pub fn is_config_value(attr: &syn::Attribute) -> bool {
|
||||
is_attr_name_value(attr, "value")
|
||||
}
|
||||
|
||||
fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool {
|
||||
attr.parse_meta().ok().map_or(false, |meta| match meta {
|
||||
syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) if path.is_ident(name) => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option<String> {
|
||||
attr.parse_meta().ok().and_then(|meta| match meta {
|
||||
syn::Meta::NameValue(syn::MetaNameValue {
|
||||
ref path,
|
||||
lit: syn::Lit::Str(ref lit_str),
|
||||
..
|
||||
}) if path.is_ident(name) => Some(lit_str.value()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
15
src/tools/rustfmt/config_proc_macro/src/config_type.rs
Normal file
15
src/tools/rustfmt/config_proc_macro/src/config_type.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use proc_macro2::TokenStream;
|
||||
|
||||
use crate::item_enum::define_config_type_on_enum;
|
||||
use crate::item_struct::define_config_type_on_struct;
|
||||
|
||||
/// Defines `config_type` on enum or struct.
|
||||
// FIXME: Implement this on struct.
|
||||
pub fn define_config_type(input: &syn::Item) -> TokenStream {
|
||||
match input {
|
||||
syn::Item::Struct(st) => define_config_type_on_struct(st),
|
||||
syn::Item::Enum(en) => define_config_type_on_enum(en),
|
||||
_ => panic!("Expected enum or struct"),
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
208
src/tools/rustfmt/config_proc_macro/src/item_enum.rs
Normal file
208
src/tools/rustfmt/config_proc_macro/src/item_enum.rs
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::attrs::*;
|
||||
use crate::utils::*;
|
||||
|
||||
type Variants = syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>;
|
||||
|
||||
/// Defines and implements `config_type` enum.
|
||||
pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result<TokenStream> {
|
||||
let syn::ItemEnum {
|
||||
vis,
|
||||
enum_token,
|
||||
ident,
|
||||
generics,
|
||||
variants,
|
||||
..
|
||||
} = em;
|
||||
|
||||
let mod_name_str = format!("__define_config_type_on_enum_{}", ident);
|
||||
let mod_name = syn::Ident::new(&mod_name_str, ident.span());
|
||||
let variants = fold_quote(variants.iter().map(process_variant), |meta| quote!(#meta,));
|
||||
|
||||
let impl_doc_hint = impl_doc_hint(&em.ident, &em.variants);
|
||||
let impl_from_str = impl_from_str(&em.ident, &em.variants);
|
||||
let impl_display = impl_display(&em.ident, &em.variants);
|
||||
let impl_serde = impl_serde(&em.ident, &em.variants);
|
||||
let impl_deserialize = impl_deserialize(&em.ident, &em.variants);
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
mod #mod_name {
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub #enum_token #ident #generics { #variants }
|
||||
#impl_display
|
||||
#impl_doc_hint
|
||||
#impl_from_str
|
||||
#impl_serde
|
||||
#impl_deserialize
|
||||
}
|
||||
#vis use #mod_name::#ident;
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove attributes specific to `config_proc_macro` from enum variant fields.
|
||||
fn process_variant(variant: &syn::Variant) -> TokenStream {
|
||||
let metas = variant
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr));
|
||||
let attrs = fold_quote(metas, |meta| quote!(#meta));
|
||||
let syn::Variant { ident, fields, .. } = variant;
|
||||
quote!(#attrs #ident #fields)
|
||||
}
|
||||
|
||||
fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream {
|
||||
let doc_hint = variants
|
||||
.iter()
|
||||
.map(doc_hint_of_variant)
|
||||
.collect::<Vec<_>>()
|
||||
.join("|");
|
||||
let doc_hint = format!("[{}]", doc_hint);
|
||||
quote! {
|
||||
use crate::config::ConfigType;
|
||||
impl ConfigType for #ident {
|
||||
fn doc_hint() -> String {
|
||||
#doc_hint.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_display(ident: &syn::Ident, variants: &Variants) -> TokenStream {
|
||||
let vs = variants
|
||||
.iter()
|
||||
.filter(|v| is_unit(v))
|
||||
.map(|v| (config_value_of_variant(v), &v.ident));
|
||||
let match_patterns = fold_quote(vs, |(s, v)| {
|
||||
quote! {
|
||||
#ident::#v => write!(f, "{}", #s),
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
use std::fmt;
|
||||
impl fmt::Display for #ident {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
#match_patterns
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn impl_from_str(ident: &syn::Ident, variants: &Variants) -> TokenStream {
|
||||
let vs = variants
|
||||
.iter()
|
||||
.filter(|v| is_unit(v))
|
||||
.map(|v| (config_value_of_variant(v), &v.ident));
|
||||
let if_patterns = fold_quote(vs, |(s, v)| {
|
||||
quote! {
|
||||
if #s.eq_ignore_ascii_case(s) {
|
||||
return Ok(#ident::#v);
|
||||
}
|
||||
}
|
||||
});
|
||||
let mut err_msg = String::from("Bad variant, expected one of:");
|
||||
for v in variants.iter().filter(|v| is_unit(v)) {
|
||||
err_msg.push_str(&format!(" `{}`", v.ident));
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl ::std::str::FromStr for #ident {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#if_patterns
|
||||
return Err(#err_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn doc_hint_of_variant(variant: &syn::Variant) -> String {
|
||||
find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string())
|
||||
}
|
||||
|
||||
fn config_value_of_variant(variant: &syn::Variant) -> String {
|
||||
find_config_value(&variant.attrs).unwrap_or(variant.ident.to_string())
|
||||
}
|
||||
|
||||
fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream {
|
||||
let arms = fold_quote(variants.iter(), |v| {
|
||||
let v_ident = &v.ident;
|
||||
let pattern = match v.fields {
|
||||
syn::Fields::Named(..) => quote!(#ident::v_ident{..}),
|
||||
syn::Fields::Unnamed(..) => quote!(#ident::#v_ident(..)),
|
||||
syn::Fields::Unit => quote!(#ident::#v_ident),
|
||||
};
|
||||
let option_value = config_value_of_variant(v);
|
||||
quote! {
|
||||
#pattern => serializer.serialize_str(&#option_value),
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl ::serde::ser::Serialize for #ident {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ::serde::ser::Serializer,
|
||||
{
|
||||
use serde::ser::Error;
|
||||
match self {
|
||||
#arms
|
||||
_ => Err(S::Error::custom(format!("Cannot serialize {:?}", self))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Currently only unit variants are supported.
|
||||
fn impl_deserialize(ident: &syn::Ident, variants: &Variants) -> TokenStream {
|
||||
let supported_vs = variants.iter().filter(|v| is_unit(v));
|
||||
let if_patterns = fold_quote(supported_vs, |v| {
|
||||
let config_value = config_value_of_variant(v);
|
||||
let variant_ident = &v.ident;
|
||||
quote! {
|
||||
if #config_value.eq_ignore_ascii_case(s) {
|
||||
return Ok(#ident::#variant_ident);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let supported_vs = variants.iter().filter(|v| is_unit(v));
|
||||
let allowed = fold_quote(supported_vs.map(config_value_of_variant), |s| quote!(#s,));
|
||||
|
||||
quote! {
|
||||
impl<'de> serde::de::Deserialize<'de> for #ident {
|
||||
fn deserialize<D>(d: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{Error, Visitor};
|
||||
use std::marker::PhantomData;
|
||||
use std::fmt;
|
||||
struct StringOnly<T>(PhantomData<T>);
|
||||
impl<'de, T> Visitor<'de> for StringOnly<T>
|
||||
where T: serde::Deserializer<'de> {
|
||||
type Value = String;
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("string")
|
||||
}
|
||||
fn visit_str<E>(self, value: &str) -> Result<String, E> {
|
||||
Ok(String::from(value))
|
||||
}
|
||||
}
|
||||
let s = &d.deserialize_string(StringOnly::<D>(PhantomData))?;
|
||||
|
||||
#if_patterns
|
||||
|
||||
static ALLOWED: &'static[&str] = &[#allowed];
|
||||
Err(D::Error::unknown_variant(&s, ALLOWED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/tools/rustfmt/config_proc_macro/src/item_struct.rs
Normal file
5
src/tools/rustfmt/config_proc_macro/src/item_struct.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
use proc_macro2::TokenStream;
|
||||
|
||||
pub fn define_config_type_on_struct(_st: &syn::ItemStruct) -> syn::Result<TokenStream> {
|
||||
unimplemented!()
|
||||
}
|
||||
25
src/tools/rustfmt/config_proc_macro/src/lib.rs
Normal file
25
src/tools/rustfmt/config_proc_macro/src/lib.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//! This crate provides a derive macro for `ConfigType`.
|
||||
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
mod attrs;
|
||||
mod config_type;
|
||||
mod item_enum;
|
||||
mod item_struct;
|
||||
mod utils;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn config_type(_args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as syn::Item);
|
||||
let output = config_type::define_config_type(&input);
|
||||
|
||||
#[cfg(feature = "debug-with-rustfmt")]
|
||||
{
|
||||
utils::debug_with_rustfmt(&output);
|
||||
}
|
||||
|
||||
TokenStream::from(output)
|
||||
}
|
||||
52
src/tools/rustfmt/config_proc_macro/src/utils.rs
Normal file
52
src/tools/rustfmt/config_proc_macro/src/utils.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
pub fn fold_quote<F, I, T>(input: impl Iterator<Item = I>, f: F) -> TokenStream
|
||||
where
|
||||
F: Fn(I) -> T,
|
||||
T: ToTokens,
|
||||
{
|
||||
input.fold(quote! {}, |acc, x| {
|
||||
let y = f(x);
|
||||
quote! { #acc #y }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_unit(v: &syn::Variant) -> bool {
|
||||
match v.fields {
|
||||
syn::Fields::Unit => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug-with-rustfmt")]
|
||||
/// Pretty-print the output of proc macro using rustfmt.
|
||||
pub fn debug_with_rustfmt(input: &TokenStream) {
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
let rustfmt_var = env::var_os("RUSTFMT");
|
||||
let rustfmt = match &rustfmt_var {
|
||||
Some(rustfmt) => rustfmt,
|
||||
None => OsStr::new("rustfmt"),
|
||||
};
|
||||
let mut child = Command::new(rustfmt)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to spawn rustfmt in stdio mode");
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to get stdin");
|
||||
stdin
|
||||
.write_all(format!("{}", input).as_bytes())
|
||||
.expect("Failed to write to stdin");
|
||||
}
|
||||
let rustfmt_output = child.wait_with_output().expect("rustfmt has failed");
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
String::from_utf8(rustfmt_output.stdout).expect("rustfmt returned non-UTF8 string")
|
||||
);
|
||||
}
|
||||
20
src/tools/rustfmt/config_proc_macro/tests/smoke.rs
Normal file
20
src/tools/rustfmt/config_proc_macro/tests/smoke.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
pub mod config {
|
||||
pub trait ConfigType: Sized {
|
||||
fn doc_hint() -> String;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_imports)]
|
||||
mod tests {
|
||||
use rustfmt_config_proc_macro::config_type;
|
||||
|
||||
#[config_type]
|
||||
enum Bar {
|
||||
Foo,
|
||||
Bar,
|
||||
#[doc_hint = "foo_bar"]
|
||||
FooBar,
|
||||
FooFoo(i32),
|
||||
}
|
||||
}
|
||||
191
src/tools/rustfmt/docs/index.html
Normal file
191
src/tools/rustfmt/docs/index.html
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
|
||||
<style>
|
||||
@media (max-width: 767px) {
|
||||
.markdown-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
#search {
|
||||
max-width: 85%;
|
||||
}
|
||||
}
|
||||
body {
|
||||
overflow: scroll;
|
||||
}
|
||||
.markdown-body {
|
||||
box-sizing: border-box;
|
||||
min-width: 200px;
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 45px;
|
||||
}
|
||||
#search {
|
||||
border: 1px solid #d1d5da;
|
||||
padding-left: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.searchCondition {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.searchCondition > div {
|
||||
margin-right: 30px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<article class="markdown-body">
|
||||
<div class="searchCondition">
|
||||
<div>
|
||||
<form style="display:flex;">
|
||||
<label for="search" style="margin-right: 3px;" >search:</label>
|
||||
<div style="position: relative;">
|
||||
<input id="search" placeholder="Search all options" v-model="searchCondition">
|
||||
<svg style="position: absolute; left: 8px; top: 7px;" class="octicon octicon-search subnav-search-icon" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M15.7 13.3l-3.81-3.83A5.93 5.93 0 0 0 13 6c0-3.31-2.69-6-6-6S1 2.69 1 6s2.69 6 6 6c1.3 0 2.48-.41 3.47-1.11l3.83 3.81c.19.2.45.3.7.3.25 0 .52-.09.7-.3a.996.996 0 0 0 0-1.41v.01zM7 10.7c-2.59 0-4.7-2.11-4.7-4.7 0-2.59 2.11-4.7 4.7-4.7 2.59 0 4.7 2.11 4.7 4.7 0 2.59-2.11 4.7-4.7 4.7z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<label for="stable">stable: </label>
|
||||
<input type="checkbox" id="stable" v-model="shouldStable">
|
||||
</div>
|
||||
</div>
|
||||
<div v-html="aboutHtml"></div>
|
||||
<div v-html="configurationAboutHtml"></div>
|
||||
<div v-html="outputHtml"></div>
|
||||
</article>
|
||||
</div>
|
||||
<script>
|
||||
const ConfigurationMdUrl = 'https://raw.githubusercontent.com/rust-lang/rustfmt/master/Configurations.md';
|
||||
const UrlHash = window.location.hash.replace(/^#/, '');
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
const configurationDescriptions = [];
|
||||
configurationDescriptions.links = {};
|
||||
return {
|
||||
aboutHtml: '',
|
||||
configurationAboutHtml: '',
|
||||
searchCondition: UrlHash,
|
||||
configurationDescriptions,
|
||||
shouldStable: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
outputHtml() {
|
||||
const ast = this.configurationDescriptions
|
||||
.filter(({ head, text, stable }) => {
|
||||
|
||||
if (
|
||||
text.includes(this.searchCondition) === false &&
|
||||
head.includes(this.searchCondition) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return (this.shouldStable)
|
||||
? stable === true
|
||||
: true;
|
||||
})
|
||||
.reduce((stack, { value }) => {
|
||||
return stack.concat(value);
|
||||
}, []);
|
||||
ast.links = {};
|
||||
return marked.parser(ast);
|
||||
}
|
||||
},
|
||||
created: async function() {
|
||||
const res = await axios.get(ConfigurationMdUrl);
|
||||
const {
|
||||
about,
|
||||
configurationAbout,
|
||||
configurationDescriptions
|
||||
} = parseMarkdownAst(res.data);
|
||||
this.aboutHtml = marked.parser(about);
|
||||
this.configurationAboutHtml = marked.parser(configurationAbout);
|
||||
this.configurationDescriptions = configurationDescriptions;
|
||||
},
|
||||
mounted() {
|
||||
if (UrlHash === '') return;
|
||||
const interval = setInterval(() => {
|
||||
const target = document.querySelector(`#${UrlHash}`);
|
||||
if (target != null) {
|
||||
target.scrollIntoView(true);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
const extractDepthOnes = (ast) => {
|
||||
return ast.reduce((stack, next) => {
|
||||
if (next.depth === 1) {
|
||||
stack.push([]);
|
||||
}
|
||||
const lastIndex = stack.length - 1;
|
||||
stack[lastIndex].push(next);
|
||||
return stack;
|
||||
}, []);
|
||||
}
|
||||
const extractDepthTwos = (ast) => {
|
||||
return ast.map((elem) => {
|
||||
return elem.reduce((stack, next) => {
|
||||
if (next.depth === 2) {
|
||||
stack.push([]);
|
||||
}
|
||||
const lastIndex = stack.length - 1;
|
||||
stack[lastIndex].push(next);
|
||||
return stack;
|
||||
},
|
||||
[[]]);
|
||||
});
|
||||
}
|
||||
const createHeadAndValue = (ast) => {
|
||||
return ast.map((elem) => {
|
||||
return elem.map((val) => {
|
||||
return {
|
||||
head: val[0].text,
|
||||
value: val,
|
||||
stable: val.some((elem) => {
|
||||
return !!elem.text && elem.text.includes("**Stable**: Yes")
|
||||
}),
|
||||
text: val.reduce((result, next) => {
|
||||
return next.text != null
|
||||
? `${result} ${next.text}`
|
||||
: result;
|
||||
}, '')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
const parseMarkdownAst = (rawMarkdown) => {
|
||||
const ast = marked.lexer(rawMarkdown);
|
||||
const depthOnes = extractDepthOnes(ast);
|
||||
const depthTwos = extractDepthTwos(depthOnes);
|
||||
const [
|
||||
abouts, configurations
|
||||
] = createHeadAndValue(depthTwos);
|
||||
const about = abouts[0].value;
|
||||
about.links = {};
|
||||
const [
|
||||
configurationAbout, ...configurationDescriptions
|
||||
] = configurations;
|
||||
configurationAbout.value.links = {};
|
||||
|
||||
return {
|
||||
about,
|
||||
configurationAbout: configurationAbout.value,
|
||||
configurationDescriptions
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
26
src/tools/rustfmt/intellij.md
Normal file
26
src/tools/rustfmt/intellij.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Running Rustfmt from IntelliJ or CLion
|
||||
|
||||
## Installation
|
||||
|
||||
- Install [CLion](https://www.jetbrains.com/clion/), [IntelliJ Ultimate or CE](https://www.jetbrains.com/idea/) through the direct download link or using the [JetBrains Toolbox](https://www.jetbrains.com/toolbox/).
|
||||
CLion provides a built-in debugger interface but its not free like IntelliJ CE - which does not provide the debugger interface. (IntelliJ seems to lack the toolchain for that, see this discussion [intellij-rust/issues/535](https://github.com/intellij-rust/intellij-rust/issues/535))
|
||||
|
||||
- Install the [Rust Plugin](https://intellij-rust.github.io/) by navigating to File -> Settings -> Plugins and press "Install JetBrains Plugin"
|
||||

|
||||
|
||||
- Press "Install" on the rust plugin
|
||||

|
||||
|
||||
- Restart CLion/IntelliJ
|
||||
|
||||
## Configuration
|
||||
|
||||
- Open the settings window (File -> Settings) and search for "reformat"
|
||||

|
||||
- Right-click on "Reformat File with Rustfmt" and assign a keyboard shortcut
|
||||
|
||||

|
||||
- Press "OK"
|
||||

|
||||
|
||||
- Done. You can now use rustfmt in an opened *.rs file with your previously specified shortcut
|
||||
2
src/tools/rustfmt/legacy-rustfmt.toml
Normal file
2
src/tools/rustfmt/legacy-rustfmt.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
indent_style = "Visual"
|
||||
combine_control_expr = false
|
||||
3
src/tools/rustfmt/rust-toolchain
Normal file
3
src/tools/rustfmt/rust-toolchain
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "nightly-2021-05-13"
|
||||
components = ["rustc-dev"]
|
||||
3
src/tools/rustfmt/rustfmt.toml
Normal file
3
src/tools/rustfmt/rustfmt.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
error_on_line_overflow = true
|
||||
error_on_unformatted = true
|
||||
version = "Two"
|
||||
534
src/tools/rustfmt/src/attr.rs
Normal file
534
src/tools/rustfmt/src/attr.rs
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
//! Format attributes and meta items.
|
||||
|
||||
use rustc_ast::ast;
|
||||
use rustc_ast::AstLike;
|
||||
use rustc_span::{symbol::sym, Span, Symbol};
|
||||
|
||||
use self::doc_comment::DocCommentFormatter;
|
||||
use crate::comment::{contains_comment, rewrite_doc_comment, CommentStyle};
|
||||
use crate::config::lists::*;
|
||||
use crate::config::IndentStyle;
|
||||
use crate::expr::rewrite_literal;
|
||||
use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator};
|
||||
use crate::overflow;
|
||||
use crate::rewrite::{Rewrite, RewriteContext};
|
||||
use crate::shape::Shape;
|
||||
use crate::types::{rewrite_path, PathContext};
|
||||
use crate::utils::{count_newlines, mk_sp};
|
||||
|
||||
mod doc_comment;
|
||||
|
||||
pub(crate) fn contains_name(attrs: &[ast::Attribute], name: Symbol) -> bool {
|
||||
attrs.iter().any(|attr| attr.has_name(name))
|
||||
}
|
||||
|
||||
pub(crate) fn first_attr_value_str_by_name(
|
||||
attrs: &[ast::Attribute],
|
||||
name: Symbol,
|
||||
) -> Option<Symbol> {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|attr| attr.has_name(name))
|
||||
.and_then(|attr| attr.value_str())
|
||||
}
|
||||
|
||||
/// Returns attributes on the given statement.
|
||||
pub(crate) fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] {
|
||||
stmt.attrs()
|
||||
}
|
||||
|
||||
pub(crate) fn get_span_without_attrs(stmt: &ast::Stmt) -> Span {
|
||||
match stmt.kind {
|
||||
ast::StmtKind::Local(ref local) => local.span,
|
||||
ast::StmtKind::Item(ref item) => item.span,
|
||||
ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => expr.span,
|
||||
ast::StmtKind::MacCall(ref mac_stmt) => mac_stmt.mac.span(),
|
||||
ast::StmtKind::Empty => stmt.span,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns attributes that are within `outer_span`.
|
||||
pub(crate) fn filter_inline_attrs(
|
||||
attrs: &[ast::Attribute],
|
||||
outer_span: Span,
|
||||
) -> Vec<ast::Attribute> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn is_derive(attr: &ast::Attribute) -> bool {
|
||||
attr.has_name(sym::derive)
|
||||
}
|
||||
|
||||
// The shape of the arguments to a function-like attribute.
|
||||
fn argument_shape(
|
||||
left: usize,
|
||||
right: usize,
|
||||
combine: bool,
|
||||
shape: Shape,
|
||||
context: &RewriteContext<'_>,
|
||||
) -> Option<Shape> {
|
||||
match context.config.indent_style() {
|
||||
IndentStyle::Block => {
|
||||
if combine {
|
||||
shape.offset_left(left)
|
||||
} else {
|
||||
Some(
|
||||
shape
|
||||
.block_indent(context.config.tab_spaces())
|
||||
.with_max_width(context.config),
|
||||
)
|
||||
}
|
||||
}
|
||||
IndentStyle::Visual => shape
|
||||
.visual_indent(0)
|
||||
.shrink_left(left)
|
||||
.and_then(|s| s.sub_width(right)),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_derive(
|
||||
derives: &[ast::Attribute],
|
||||
shape: Shape,
|
||||
context: &RewriteContext<'_>,
|
||||
) -> Option<String> {
|
||||
// Collect all items from all attributes
|
||||
let all_items = derives
|
||||
.iter()
|
||||
.map(|attr| {
|
||||
// Parse the derive items and extract the span for each item; if any
|
||||
// attribute is not parseable, none of the attributes will be
|
||||
// reformatted.
|
||||
let item_spans = attr.meta_item_list().map(|meta_item_list| {
|
||||
meta_item_list
|
||||
.into_iter()
|
||||
.map(|nested_meta_item| nested_meta_item.span())
|
||||
})?;
|
||||
|
||||
let items = itemize_list(
|
||||
context.snippet_provider,
|
||||
item_spans,
|
||||
")",
|
||||
",",
|
||||
|span| span.lo(),
|
||||
|span| span.hi(),
|
||||
|span| Some(context.snippet(*span).to_owned()),
|
||||
attr.span.lo(),
|
||||
attr.span.hi(),
|
||||
false,
|
||||
);
|
||||
|
||||
Some(items)
|
||||
})
|
||||
// Fail if any attribute failed.
|
||||
.collect::<Option<Vec<_>>>()?
|
||||
// Collect the results into a single, flat, Vec.
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Collect formatting parameters.
|
||||
let prefix = attr_prefix(&derives[0]);
|
||||
let argument_shape = argument_shape(
|
||||
"[derive()]".len() + prefix.len(),
|
||||
")]".len(),
|
||||
false,
|
||||
shape,
|
||||
context,
|
||||
)?;
|
||||
let one_line_shape = shape
|
||||
.offset_left("[derive()]".len() + prefix.len())?
|
||||
.sub_width("()]".len())?;
|
||||
let one_line_budget = one_line_shape.width;
|
||||
|
||||
let tactic = definitive_tactic(
|
||||
&all_items,
|
||||
ListTactic::HorizontalVertical,
|
||||
Separator::Comma,
|
||||
argument_shape.width,
|
||||
);
|
||||
let trailing_separator = match context.config.indent_style() {
|
||||
// We always add the trailing comma and remove it if it is not needed.
|
||||
IndentStyle::Block => SeparatorTactic::Always,
|
||||
IndentStyle::Visual => SeparatorTactic::Never,
|
||||
};
|
||||
|
||||
// Format the collection of items.
|
||||
let fmt = ListFormatting::new(argument_shape, context.config)
|
||||
.tactic(tactic)
|
||||
.trailing_separator(trailing_separator)
|
||||
.ends_with_newline(false);
|
||||
let item_str = write_list(&all_items, &fmt)?;
|
||||
|
||||
debug!("item_str: '{}'", item_str);
|
||||
|
||||
// Determine if the result will be nested, i.e. if we're using the block
|
||||
// indent style and either the items are on multiple lines or we've exceeded
|
||||
// our budget to fit on a single line.
|
||||
let nested = context.config.indent_style() == IndentStyle::Block
|
||||
&& (item_str.contains('\n') || item_str.len() > one_line_budget);
|
||||
|
||||
// Format the final result.
|
||||
let mut result = String::with_capacity(128);
|
||||
result.push_str(prefix);
|
||||
result.push_str("[derive(");
|
||||
if nested {
|
||||
let nested_indent = argument_shape.indent.to_string_with_newline(context.config);
|
||||
result.push_str(&nested_indent);
|
||||
result.push_str(&item_str);
|
||||
result.push_str(&shape.indent.to_string_with_newline(context.config));
|
||||
} else if let SeparatorTactic::Always = context.config.trailing_comma() {
|
||||
// Retain the trailing comma.
|
||||
result.push_str(&item_str);
|
||||
} else if item_str.ends_with(",") {
|
||||
// Remove the trailing comma.
|
||||
result.push_str(&item_str[..item_str.len() - 1]);
|
||||
} else {
|
||||
result.push_str(&item_str);
|
||||
}
|
||||
result.push_str(")]");
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Returns the first group of attributes that fills the given predicate.
|
||||
/// We consider two doc comments are in different group if they are separated by normal comments.
|
||||
fn take_while_with_pred<'a, P>(
|
||||
context: &RewriteContext<'_>,
|
||||
attrs: &'a [ast::Attribute],
|
||||
pred: P,
|
||||
) -> &'a [ast::Attribute]
|
||||
where
|
||||
P: Fn(&ast::Attribute) -> bool,
|
||||
{
|
||||
let mut len = 0;
|
||||
let mut iter = attrs.iter().peekable();
|
||||
|
||||
while let Some(attr) = iter.next() {
|
||||
if pred(attr) {
|
||||
len += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if let Some(next_attr) = iter.peek() {
|
||||
// Extract comments between two attributes.
|
||||
let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
|
||||
let snippet = context.snippet(span_between_attr);
|
||||
if count_newlines(snippet) >= 2 || snippet.contains('/') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&attrs[..len]
|
||||
}
|
||||
|
||||
/// Rewrite the any doc comments which come before any other attributes.
|
||||
fn rewrite_initial_doc_comments(
|
||||
context: &RewriteContext<'_>,
|
||||
attrs: &[ast::Attribute],
|
||||
shape: Shape,
|
||||
) -> Option<(usize, Option<String>)> {
|
||||
if attrs.is_empty() {
|
||||
return Some((0, None));
|
||||
}
|
||||
// Rewrite doc comments
|
||||
let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_doc_comment());
|
||||
if !sugared_docs.is_empty() {
|
||||
let snippet = sugared_docs
|
||||
.iter()
|
||||
.map(|a| context.snippet(a.span))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
return Some((
|
||||
sugared_docs.len(),
|
||||
Some(rewrite_doc_comment(
|
||||
&snippet,
|
||||
shape.comment(context.config),
|
||||
context.config,
|
||||
)?),
|
||||
));
|
||||
}
|
||||
|
||||
Some((0, None))
|
||||
}
|
||||
|
||||
impl Rewrite for ast::NestedMetaItem {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
match self {
|
||||
ast::NestedMetaItem::MetaItem(ref meta_item) => meta_item.rewrite(context, shape),
|
||||
ast::NestedMetaItem::Literal(ref l) => rewrite_literal(context, l, shape),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
|
||||
// Look at before and after comment and see if there are any empty lines.
|
||||
let comment_begin = comment.find('/');
|
||||
let len = comment_begin.unwrap_or_else(|| comment.len());
|
||||
let mlb = count_newlines(&comment[..len]) > 1;
|
||||
let mla = if comment_begin.is_none() {
|
||||
mlb
|
||||
} else {
|
||||
comment
|
||||
.chars()
|
||||
.rev()
|
||||
.take_while(|c| c.is_whitespace())
|
||||
.filter(|&c| c == '\n')
|
||||
.count()
|
||||
> 1
|
||||
};
|
||||
(if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
|
||||
}
|
||||
|
||||
impl Rewrite for ast::MetaItem {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
Some(match self.kind {
|
||||
ast::MetaItemKind::Word => {
|
||||
rewrite_path(context, PathContext::Type, None, &self.path, shape)?
|
||||
}
|
||||
ast::MetaItemKind::List(ref list) => {
|
||||
let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?;
|
||||
let has_trailing_comma = crate::expr::span_ends_with_comma(context, self.span);
|
||||
overflow::rewrite_with_parens(
|
||||
context,
|
||||
&path,
|
||||
list.iter(),
|
||||
// 1 = "]"
|
||||
shape.sub_width(1)?,
|
||||
self.span,
|
||||
context.config.attr_fn_like_width(),
|
||||
Some(if has_trailing_comma {
|
||||
SeparatorTactic::Always
|
||||
} else {
|
||||
SeparatorTactic::Never
|
||||
}),
|
||||
)?
|
||||
}
|
||||
ast::MetaItemKind::NameValue(ref literal) => {
|
||||
let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?;
|
||||
// 3 = ` = `
|
||||
let lit_shape = shape.shrink_left(path.len() + 3)?;
|
||||
// `rewrite_literal` returns `None` when `literal` exceeds max
|
||||
// width. Since a literal is basically unformattable unless it
|
||||
// is a string literal (and only if `format_strings` is set),
|
||||
// we might be better off ignoring the fact that the attribute
|
||||
// is longer than the max width and continue on formatting.
|
||||
// See #2479 for example.
|
||||
let value = rewrite_literal(context, literal, lit_shape)
|
||||
.unwrap_or_else(|| context.snippet(literal.span).to_owned());
|
||||
format!("{} = {}", path, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Rewrite for ast::Attribute {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
let snippet = context.snippet(self.span);
|
||||
if self.is_doc_comment() {
|
||||
rewrite_doc_comment(snippet, shape.comment(context.config), context.config)
|
||||
} else {
|
||||
let should_skip = self
|
||||
.ident()
|
||||
.map(|s| context.skip_context.skip_attribute(&s.name.as_str()))
|
||||
.unwrap_or(false);
|
||||
let prefix = attr_prefix(self);
|
||||
|
||||
if should_skip || contains_comment(snippet) {
|
||||
return Some(snippet.to_owned());
|
||||
}
|
||||
|
||||
if let Some(ref meta) = self.meta() {
|
||||
// This attribute is possibly a doc attribute needing normalization to a doc comment
|
||||
if context.config.normalize_doc_attributes() && meta.has_name(sym::doc) {
|
||||
if let Some(ref literal) = meta.value_str() {
|
||||
let comment_style = match self.style {
|
||||
ast::AttrStyle::Inner => CommentStyle::Doc,
|
||||
ast::AttrStyle::Outer => CommentStyle::TripleSlash,
|
||||
};
|
||||
|
||||
let literal_str = literal.as_str();
|
||||
let doc_comment_formatter =
|
||||
DocCommentFormatter::new(&*literal_str, comment_style);
|
||||
let doc_comment = format!("{}", doc_comment_formatter);
|
||||
return rewrite_doc_comment(
|
||||
&doc_comment,
|
||||
shape.comment(context.config),
|
||||
context.config,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 1 = `[`
|
||||
let shape = shape.offset_left(prefix.len() + 1)?;
|
||||
Some(
|
||||
meta.rewrite(context, shape)
|
||||
.map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)),
|
||||
)
|
||||
} else {
|
||||
Some(snippet.to_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rewrite for [ast::Attribute] {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
if self.is_empty() {
|
||||
return Some(String::new());
|
||||
}
|
||||
|
||||
// The current remaining attributes.
|
||||
let mut attrs = self;
|
||||
let mut result = String::new();
|
||||
|
||||
// This is not just a simple map because we need to handle doc comments
|
||||
// (where we take as many doc comment attributes as possible) and possibly
|
||||
// merging derives into a single attribute.
|
||||
loop {
|
||||
if attrs.is_empty() {
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
// Handle doc comments.
|
||||
let (doc_comment_len, doc_comment_str) =
|
||||
rewrite_initial_doc_comments(context, attrs, shape)?;
|
||||
if doc_comment_len > 0 {
|
||||
let doc_comment_str = doc_comment_str.expect("doc comments, but no result");
|
||||
result.push_str(&doc_comment_str);
|
||||
|
||||
let missing_span = attrs
|
||||
.get(doc_comment_len)
|
||||
.map(|next| mk_sp(attrs[doc_comment_len - 1].span.hi(), next.span.lo()));
|
||||
if let Some(missing_span) = missing_span {
|
||||
let snippet = context.snippet(missing_span);
|
||||
let (mla, mlb) = has_newlines_before_after_comment(snippet);
|
||||
let comment = crate::comment::recover_missing_comment_in_span(
|
||||
missing_span,
|
||||
shape.with_max_width(context.config),
|
||||
context,
|
||||
0,
|
||||
)?;
|
||||
let comment = if comment.is_empty() {
|
||||
format!("\n{}", mlb)
|
||||
} else {
|
||||
format!("{}{}\n{}", mla, comment, mlb)
|
||||
};
|
||||
result.push_str(&comment);
|
||||
result.push_str(&shape.indent.to_string(context.config));
|
||||
}
|
||||
|
||||
attrs = &attrs[doc_comment_len..];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle derives if we will merge them.
|
||||
if context.config.merge_derives() && is_derive(&attrs[0]) {
|
||||
let derives = take_while_with_pred(context, attrs, is_derive);
|
||||
let derive_str = format_derive(derives, shape, context)?;
|
||||
result.push_str(&derive_str);
|
||||
|
||||
let missing_span = attrs
|
||||
.get(derives.len())
|
||||
.map(|next| mk_sp(attrs[derives.len() - 1].span.hi(), next.span.lo()));
|
||||
if let Some(missing_span) = missing_span {
|
||||
let comment = crate::comment::recover_missing_comment_in_span(
|
||||
missing_span,
|
||||
shape.with_max_width(context.config),
|
||||
context,
|
||||
0,
|
||||
)?;
|
||||
result.push_str(&comment);
|
||||
if let Some(next) = attrs.get(derives.len()) {
|
||||
if next.is_doc_comment() {
|
||||
let snippet = context.snippet(missing_span);
|
||||
let (_, mlb) = has_newlines_before_after_comment(snippet);
|
||||
result.push_str(&mlb);
|
||||
}
|
||||
}
|
||||
result.push('\n');
|
||||
result.push_str(&shape.indent.to_string(context.config));
|
||||
}
|
||||
|
||||
attrs = &attrs[derives.len()..];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we get here, then we have a regular attribute, just handle one
|
||||
// at a time.
|
||||
|
||||
let formatted_attr = attrs[0].rewrite(context, shape)?;
|
||||
result.push_str(&formatted_attr);
|
||||
|
||||
let missing_span = attrs
|
||||
.get(1)
|
||||
.map(|next| mk_sp(attrs[0].span.hi(), next.span.lo()));
|
||||
if let Some(missing_span) = missing_span {
|
||||
let comment = crate::comment::recover_missing_comment_in_span(
|
||||
missing_span,
|
||||
shape.with_max_width(context.config),
|
||||
context,
|
||||
0,
|
||||
)?;
|
||||
result.push_str(&comment);
|
||||
if let Some(next) = attrs.get(1) {
|
||||
if next.is_doc_comment() {
|
||||
let snippet = context.snippet(missing_span);
|
||||
let (_, mlb) = has_newlines_before_after_comment(snippet);
|
||||
result.push_str(&mlb);
|
||||
}
|
||||
}
|
||||
result.push('\n');
|
||||
result.push_str(&shape.indent.to_string(context.config));
|
||||
}
|
||||
|
||||
attrs = &attrs[1..];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attr_prefix(attr: &ast::Attribute) -> &'static str {
|
||||
match attr.style {
|
||||
ast::AttrStyle::Inner => "#!",
|
||||
ast::AttrStyle::Outer => "#",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait MetaVisitor<'ast> {
|
||||
fn visit_meta_item(&mut self, meta_item: &'ast ast::MetaItem) {
|
||||
match meta_item.kind {
|
||||
ast::MetaItemKind::Word => self.visit_meta_word(meta_item),
|
||||
ast::MetaItemKind::List(ref list) => self.visit_meta_list(meta_item, list),
|
||||
ast::MetaItemKind::NameValue(ref lit) => self.visit_meta_name_value(meta_item, lit),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_meta_list(
|
||||
&mut self,
|
||||
_meta_item: &'ast ast::MetaItem,
|
||||
list: &'ast [ast::NestedMetaItem],
|
||||
) {
|
||||
for nm in list {
|
||||
self.visit_nested_meta_item(nm);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_meta_word(&mut self, _meta_item: &'ast ast::MetaItem) {}
|
||||
|
||||
fn visit_meta_name_value(&mut self, _meta_item: &'ast ast::MetaItem, _lit: &'ast ast::Lit) {}
|
||||
|
||||
fn visit_nested_meta_item(&mut self, nm: &'ast ast::NestedMetaItem) {
|
||||
match nm {
|
||||
ast::NestedMetaItem::MetaItem(ref meta_item) => self.visit_meta_item(meta_item),
|
||||
ast::NestedMetaItem::Literal(ref lit) => self.visit_literal(lit),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_literal(&mut self, _lit: &'ast ast::Lit) {}
|
||||
}
|
||||
83
src/tools/rustfmt/src/attr/doc_comment.rs
Normal file
83
src/tools/rustfmt/src/attr/doc_comment.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use crate::comment::CommentStyle;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
/// Formats a string as a doc comment using the given [`CommentStyle`].
|
||||
#[derive(new)]
|
||||
pub(super) struct DocCommentFormatter<'a> {
|
||||
literal: &'a str,
|
||||
style: CommentStyle<'a>,
|
||||
}
|
||||
|
||||
impl Display for DocCommentFormatter<'_> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let opener = self.style.opener().trim_end();
|
||||
let mut lines = self.literal.lines().peekable();
|
||||
|
||||
// Handle `#[doc = ""]`.
|
||||
if lines.peek().is_none() {
|
||||
return write!(formatter, "{}", opener);
|
||||
}
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
let is_last_line = lines.peek().is_none();
|
||||
if is_last_line {
|
||||
write!(formatter, "{}{}", opener, line)?;
|
||||
} else {
|
||||
writeln!(formatter, "{}{}", opener, line)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn literal_controls_leading_spaces() {
|
||||
test_doc_comment_is_formatted_correctly(
|
||||
" Lorem ipsum",
|
||||
"/// Lorem ipsum",
|
||||
CommentStyle::TripleSlash,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_doc_comment_is_formatted_correctly() {
|
||||
test_doc_comment_is_formatted_correctly(
|
||||
"Lorem ipsum",
|
||||
"///Lorem ipsum",
|
||||
CommentStyle::TripleSlash,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line_doc_comment_is_formatted_correctly() {
|
||||
test_doc_comment_is_formatted_correctly(
|
||||
"Lorem ipsum\nDolor sit amet",
|
||||
"///Lorem ipsum\n///Dolor sit amet",
|
||||
CommentStyle::TripleSlash,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whitespace_within_lines_is_preserved() {
|
||||
test_doc_comment_is_formatted_correctly(
|
||||
" Lorem ipsum \n Dolor sit amet ",
|
||||
"/// Lorem ipsum \n/// Dolor sit amet ",
|
||||
CommentStyle::TripleSlash,
|
||||
);
|
||||
}
|
||||
|
||||
fn test_doc_comment_is_formatted_correctly(
|
||||
literal: &str,
|
||||
expected_comment: &str,
|
||||
style: CommentStyle<'_>,
|
||||
) {
|
||||
assert_eq!(
|
||||
expected_comment,
|
||||
format!("{}", DocCommentFormatter::new(&literal, style))
|
||||
);
|
||||
}
|
||||
}
|
||||
705
src/tools/rustfmt/src/bin/main.rs
Normal file
705
src/tools/rustfmt/src/bin/main.rs
Normal file
|
|
@ -0,0 +1,705 @@
|
|||
use anyhow::{format_err, Result};
|
||||
|
||||
use io::Error as IoError;
|
||||
use thiserror::Error;
|
||||
|
||||
use rustfmt_nightly as rustfmt;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{self, stdout, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use getopts::{Matches, Options};
|
||||
|
||||
use crate::rustfmt::{
|
||||
load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName,
|
||||
FormatReportFormatterBuilder, Input, Session, Verbosity,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let opts = make_opts();
|
||||
|
||||
let exit_code = match execute(&opts) {
|
||||
Ok(code) => code,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e.to_string());
|
||||
1
|
||||
}
|
||||
};
|
||||
// Make sure standard output is flushed before we exit.
|
||||
std::io::stdout().flush().unwrap();
|
||||
|
||||
// Exit with given exit code.
|
||||
//
|
||||
// NOTE: this immediately terminates the process without doing any cleanup,
|
||||
// so make sure to finish all necessary cleanup before this is called.
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
/// Rustfmt operations.
|
||||
enum Operation {
|
||||
/// Format files and their child modules.
|
||||
Format {
|
||||
files: Vec<PathBuf>,
|
||||
minimal_config_path: Option<String>,
|
||||
},
|
||||
/// Print the help message.
|
||||
Help(HelpOp),
|
||||
/// Print version information
|
||||
Version,
|
||||
/// Output default config to a file, or stdout if None
|
||||
ConfigOutputDefault { path: Option<String> },
|
||||
/// Output current config (as if formatting to a file) to stdout
|
||||
ConfigOutputCurrent { path: Option<String> },
|
||||
/// No file specified, read from stdin
|
||||
Stdin { input: String },
|
||||
}
|
||||
|
||||
/// Rustfmt operations errors.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum OperationError {
|
||||
/// An unknown help topic was requested.
|
||||
#[error("Unknown help topic: `{0}`.")]
|
||||
UnknownHelpTopic(String),
|
||||
/// An unknown print-config option was requested.
|
||||
#[error("Unknown print-config option: `{0}`.")]
|
||||
UnknownPrintConfigTopic(String),
|
||||
/// Attempt to generate a minimal config from standard input.
|
||||
#[error("The `--print-config=minimal` option doesn't work with standard input.")]
|
||||
MinimalPathWithStdin,
|
||||
/// An io error during reading or writing.
|
||||
#[error("{0}")]
|
||||
IoError(IoError),
|
||||
/// Attempt to use --check with stdin, which isn't currently
|
||||
/// supported.
|
||||
#[error("The `--check` option is not supported with standard input.")]
|
||||
CheckWithStdin,
|
||||
/// Attempt to use --emit=json with stdin, which isn't currently
|
||||
/// supported.
|
||||
#[error("Using `--emit` other than stdout is not supported with standard input.")]
|
||||
EmitWithStdin,
|
||||
}
|
||||
|
||||
impl From<IoError> for OperationError {
|
||||
fn from(e: IoError) -> OperationError {
|
||||
OperationError::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Arguments to `--help`
|
||||
enum HelpOp {
|
||||
None,
|
||||
Config,
|
||||
FileLines,
|
||||
}
|
||||
|
||||
fn make_opts() -> Options {
|
||||
let mut opts = Options::new();
|
||||
|
||||
opts.optflag(
|
||||
"",
|
||||
"check",
|
||||
"Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits \
|
||||
with 1 and prints a diff if formatting is required.",
|
||||
);
|
||||
let is_nightly = is_nightly();
|
||||
let emit_opts = if is_nightly {
|
||||
"[files|stdout|coverage|checkstyle|json]"
|
||||
} else {
|
||||
"[files|stdout]"
|
||||
};
|
||||
opts.optopt("", "emit", "What data to emit and how", emit_opts);
|
||||
opts.optflag("", "backup", "Backup any modified files.");
|
||||
opts.optopt(
|
||||
"",
|
||||
"config-path",
|
||||
"Recursively searches the given path for the rustfmt.toml config file. If not \
|
||||
found reverts to the input file path",
|
||||
"[Path for the configuration file]",
|
||||
);
|
||||
opts.optopt("", "edition", "Rust edition to use", "[2015|2018]");
|
||||
opts.optopt(
|
||||
"",
|
||||
"color",
|
||||
"Use colored output (if supported)",
|
||||
"[always|never|auto]",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"print-config",
|
||||
"Dumps a default or minimal config to PATH. A minimal config is the \
|
||||
subset of the current config file used for formatting the current program. \
|
||||
`current` writes to stdout current config as if formatting the file at PATH.",
|
||||
"[default|minimal|current] PATH",
|
||||
);
|
||||
opts.optflag(
|
||||
"l",
|
||||
"files-with-diff",
|
||||
"Prints the names of mismatched files that were formatted. Prints the names of \
|
||||
files that would be formated when used with `--check` mode. ",
|
||||
);
|
||||
opts.optmulti(
|
||||
"",
|
||||
"config",
|
||||
"Set options from command line. These settings take priority over .rustfmt.toml",
|
||||
"[key1=val1,key2=val2...]",
|
||||
);
|
||||
|
||||
if is_nightly {
|
||||
opts.optflag(
|
||||
"",
|
||||
"unstable-features",
|
||||
"Enables unstable features. Only available on nightly channel.",
|
||||
);
|
||||
opts.optopt(
|
||||
"",
|
||||
"file-lines",
|
||||
"Format specified line ranges. Run with `--help=file-lines` for \
|
||||
more detail (unstable).",
|
||||
"JSON",
|
||||
);
|
||||
opts.optflag(
|
||||
"",
|
||||
"error-on-unformatted",
|
||||
"Error if unable to get comments or string literals within max_width, \
|
||||
or they are left with trailing whitespaces (unstable).",
|
||||
);
|
||||
opts.optflag(
|
||||
"",
|
||||
"skip-children",
|
||||
"Don't reformat child modules (unstable).",
|
||||
);
|
||||
}
|
||||
|
||||
opts.optflag("v", "verbose", "Print verbose output");
|
||||
opts.optflag("q", "quiet", "Print less output");
|
||||
opts.optflag("V", "version", "Show version information");
|
||||
opts.optflagopt(
|
||||
"h",
|
||||
"help",
|
||||
"Show this message or help about a specific topic: `config` or `file-lines`",
|
||||
"=TOPIC",
|
||||
);
|
||||
|
||||
opts
|
||||
}
|
||||
|
||||
fn is_nightly() -> bool {
|
||||
option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev")
|
||||
}
|
||||
|
||||
// Returned i32 is an exit code
|
||||
fn execute(opts: &Options) -> Result<i32> {
|
||||
let matches = opts.parse(env::args().skip(1))?;
|
||||
let options = GetOptsOptions::from_matches(&matches)?;
|
||||
|
||||
match determine_operation(&matches)? {
|
||||
Operation::Help(HelpOp::None) => {
|
||||
print_usage_to_stdout(opts, "");
|
||||
Ok(0)
|
||||
}
|
||||
Operation::Help(HelpOp::Config) => {
|
||||
Config::print_docs(&mut stdout(), options.unstable_features);
|
||||
Ok(0)
|
||||
}
|
||||
Operation::Help(HelpOp::FileLines) => {
|
||||
print_help_file_lines();
|
||||
Ok(0)
|
||||
}
|
||||
Operation::Version => {
|
||||
print_version();
|
||||
Ok(0)
|
||||
}
|
||||
Operation::ConfigOutputDefault { path } => {
|
||||
let toml = Config::default().all_options().to_toml()?;
|
||||
if let Some(path) = path {
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(toml.as_bytes())?;
|
||||
} else {
|
||||
io::stdout().write_all(toml.as_bytes())?;
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
Operation::ConfigOutputCurrent { path } => {
|
||||
let path = match path {
|
||||
Some(path) => path,
|
||||
None => return Err(format_err!("PATH required for `--print-config current`")),
|
||||
};
|
||||
|
||||
let file = PathBuf::from(path);
|
||||
let file = file.canonicalize().unwrap_or(file);
|
||||
|
||||
let (config, _) = load_config(Some(file.parent().unwrap()), Some(options))?;
|
||||
let toml = config.all_options().to_toml()?;
|
||||
io::stdout().write_all(toml.as_bytes())?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
Operation::Stdin { input } => format_string(input, options),
|
||||
Operation::Format {
|
||||
files,
|
||||
minimal_config_path,
|
||||
} => format(files, minimal_config_path, &options),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_string(input: String, options: GetOptsOptions) -> Result<i32> {
|
||||
// try to read config from local directory
|
||||
let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
|
||||
|
||||
if options.check {
|
||||
return Err(OperationError::CheckWithStdin.into());
|
||||
}
|
||||
if let Some(emit_mode) = options.emit_mode {
|
||||
if emit_mode != EmitMode::Stdout {
|
||||
return Err(OperationError::EmitWithStdin.into());
|
||||
}
|
||||
}
|
||||
// emit mode is always Stdout for Stdin.
|
||||
config.set().emit_mode(EmitMode::Stdout);
|
||||
config.set().verbose(Verbosity::Quiet);
|
||||
|
||||
// parse file_lines
|
||||
config.set().file_lines(options.file_lines);
|
||||
for f in config.file_lines().files() {
|
||||
match *f {
|
||||
FileName::Stdin => {}
|
||||
_ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
|
||||
}
|
||||
}
|
||||
|
||||
let out = &mut stdout();
|
||||
let mut session = Session::new(config, Some(out));
|
||||
format_and_emit_report(&mut session, Input::Text(input));
|
||||
|
||||
let exit_code = if session.has_operational_errors() || session.has_parsing_errors() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
||||
fn format(
|
||||
files: Vec<PathBuf>,
|
||||
minimal_config_path: Option<String>,
|
||||
options: &GetOptsOptions,
|
||||
) -> Result<i32> {
|
||||
options.verify_file_lines(&files);
|
||||
let (config, config_path) = load_config(None, Some(options.clone()))?;
|
||||
|
||||
if config.verbose() == Verbosity::Verbose {
|
||||
if let Some(path) = config_path.as_ref() {
|
||||
println!("Using rustfmt config file {}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
let out = &mut stdout();
|
||||
let mut session = Session::new(config, Some(out));
|
||||
|
||||
for file in files {
|
||||
if !file.exists() {
|
||||
eprintln!("Error: file `{}` does not exist", file.to_str().unwrap());
|
||||
session.add_operational_error();
|
||||
} else if file.is_dir() {
|
||||
eprintln!("Error: `{}` is a directory", file.to_str().unwrap());
|
||||
session.add_operational_error();
|
||||
} else {
|
||||
// Check the file directory if the config-path could not be read or not provided
|
||||
if config_path.is_none() {
|
||||
let (local_config, config_path) =
|
||||
load_config(Some(file.parent().unwrap()), Some(options.clone()))?;
|
||||
if local_config.verbose() == Verbosity::Verbose {
|
||||
if let Some(path) = config_path {
|
||||
println!(
|
||||
"Using rustfmt config file {} for {}",
|
||||
path.display(),
|
||||
file.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
session.override_config(local_config, |sess| {
|
||||
format_and_emit_report(sess, Input::File(file))
|
||||
});
|
||||
} else {
|
||||
format_and_emit_report(&mut session, Input::File(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we were given a path via dump-minimal-config, output any options
|
||||
// that were used during formatting as TOML.
|
||||
if let Some(path) = minimal_config_path {
|
||||
let mut file = File::create(path)?;
|
||||
let toml = session.config.used_options().to_toml()?;
|
||||
file.write_all(toml.as_bytes())?;
|
||||
}
|
||||
|
||||
let exit_code = if session.has_operational_errors()
|
||||
|| session.has_parsing_errors()
|
||||
|| ((session.has_diff() || session.has_check_errors()) && options.check)
|
||||
{
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
||||
fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input) {
|
||||
match session.format(input) {
|
||||
Ok(report) => {
|
||||
if report.has_warnings() {
|
||||
eprintln!(
|
||||
"{}",
|
||||
FormatReportFormatterBuilder::new(&report)
|
||||
.enable_colors(should_print_with_colors(session))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(msg) => {
|
||||
eprintln!("Error writing files: {}", msg);
|
||||
session.add_operational_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn should_print_with_colors<T: Write>(session: &mut Session<'_, T>) -> bool {
|
||||
match term::stderr() {
|
||||
Some(ref t)
|
||||
if session.config.color().use_colored_tty()
|
||||
&& t.supports_color()
|
||||
&& t.supports_attr(term::Attr::Bold) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn print_usage_to_stdout(opts: &Options, reason: &str) {
|
||||
let sep = if reason.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("{}\n\n", reason)
|
||||
};
|
||||
let msg = format!(
|
||||
"{}Format Rust code\n\nusage: {} [options] <file>...",
|
||||
sep,
|
||||
env::args_os().next().unwrap().to_string_lossy()
|
||||
);
|
||||
println!("{}", opts.usage(&msg));
|
||||
}
|
||||
|
||||
fn print_help_file_lines() {
|
||||
println!(
|
||||
"If you want to restrict reformatting to specific sets of lines, you can
|
||||
use the `--file-lines` option. Its argument is a JSON array of objects
|
||||
with `file` and `range` properties, where `file` is a file name, and
|
||||
`range` is an array representing a range of lines like `[7,13]`. Ranges
|
||||
are 1-based and inclusive of both end points. Specifying an empty array
|
||||
will result in no files being formatted. For example,
|
||||
|
||||
```
|
||||
rustfmt --file-lines '[
|
||||
{{\"file\":\"src/lib.rs\",\"range\":[7,13]}},
|
||||
{{\"file\":\"src/lib.rs\",\"range\":[21,29]}},
|
||||
{{\"file\":\"src/foo.rs\",\"range\":[10,11]}},
|
||||
{{\"file\":\"src/foo.rs\",\"range\":[15,15]}}]'
|
||||
```
|
||||
|
||||
would format lines `7-13` and `21-29` of `src/lib.rs`, and lines `10-11`,
|
||||
and `15` of `src/foo.rs`. No other files would be formatted, even if they
|
||||
are included as out of line modules from `src/lib.rs`."
|
||||
);
|
||||
}
|
||||
|
||||
fn print_version() {
|
||||
let version_info = format!(
|
||||
"{}-{}",
|
||||
option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"),
|
||||
include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
|
||||
);
|
||||
|
||||
println!("rustfmt {}", version_info);
|
||||
}
|
||||
|
||||
fn determine_operation(matches: &Matches) -> Result<Operation, OperationError> {
|
||||
if matches.opt_present("h") {
|
||||
let topic = matches.opt_str("h");
|
||||
if topic == None {
|
||||
return Ok(Operation::Help(HelpOp::None));
|
||||
} else if topic == Some("config".to_owned()) {
|
||||
return Ok(Operation::Help(HelpOp::Config));
|
||||
} else if topic == Some("file-lines".to_owned()) {
|
||||
return Ok(Operation::Help(HelpOp::FileLines));
|
||||
} else {
|
||||
return Err(OperationError::UnknownHelpTopic(topic.unwrap()));
|
||||
}
|
||||
}
|
||||
let mut free_matches = matches.free.iter();
|
||||
|
||||
let mut minimal_config_path = None;
|
||||
if let Some(kind) = matches.opt_str("print-config") {
|
||||
let path = free_matches.next().cloned();
|
||||
match kind.as_str() {
|
||||
"default" => return Ok(Operation::ConfigOutputDefault { path }),
|
||||
"current" => return Ok(Operation::ConfigOutputCurrent { path }),
|
||||
"minimal" => {
|
||||
minimal_config_path = path;
|
||||
if minimal_config_path.is_none() {
|
||||
eprintln!("WARNING: PATH required for `--print-config minimal`.");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(OperationError::UnknownPrintConfigTopic(kind));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches.opt_present("version") {
|
||||
return Ok(Operation::Version);
|
||||
}
|
||||
|
||||
let files: Vec<_> = free_matches
|
||||
.map(|s| {
|
||||
let p = PathBuf::from(s);
|
||||
// we will do comparison later, so here tries to canonicalize first
|
||||
// to get the expected behavior.
|
||||
p.canonicalize().unwrap_or(p)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// if no file argument is supplied, read from stdin
|
||||
if files.is_empty() {
|
||||
if minimal_config_path.is_some() {
|
||||
return Err(OperationError::MinimalPathWithStdin);
|
||||
}
|
||||
let mut buffer = String::new();
|
||||
io::stdin().read_to_string(&mut buffer)?;
|
||||
|
||||
return Ok(Operation::Stdin { input: buffer });
|
||||
}
|
||||
|
||||
Ok(Operation::Format {
|
||||
files,
|
||||
minimal_config_path,
|
||||
})
|
||||
}
|
||||
|
||||
const STABLE_EMIT_MODES: [EmitMode; 3] = [EmitMode::Files, EmitMode::Stdout, EmitMode::Diff];
|
||||
|
||||
/// Parsed command line options.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct GetOptsOptions {
|
||||
skip_children: Option<bool>,
|
||||
quiet: bool,
|
||||
verbose: bool,
|
||||
config_path: Option<PathBuf>,
|
||||
inline_config: HashMap<String, String>,
|
||||
emit_mode: Option<EmitMode>,
|
||||
backup: bool,
|
||||
check: bool,
|
||||
edition: Option<Edition>,
|
||||
color: Option<Color>,
|
||||
file_lines: FileLines, // Default is all lines in all files.
|
||||
unstable_features: bool,
|
||||
error_on_unformatted: Option<bool>,
|
||||
print_misformatted_file_names: bool,
|
||||
}
|
||||
|
||||
impl GetOptsOptions {
|
||||
pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions> {
|
||||
let mut options = GetOptsOptions::default();
|
||||
options.verbose = matches.opt_present("verbose");
|
||||
options.quiet = matches.opt_present("quiet");
|
||||
if options.verbose && options.quiet {
|
||||
return Err(format_err!("Can't use both `--verbose` and `--quiet`"));
|
||||
}
|
||||
|
||||
let rust_nightly = is_nightly();
|
||||
|
||||
if rust_nightly {
|
||||
options.unstable_features = matches.opt_present("unstable-features");
|
||||
|
||||
if options.unstable_features {
|
||||
if matches.opt_present("skip-children") {
|
||||
options.skip_children = Some(true);
|
||||
}
|
||||
if matches.opt_present("error-on-unformatted") {
|
||||
options.error_on_unformatted = Some(true);
|
||||
}
|
||||
if let Some(ref file_lines) = matches.opt_str("file-lines") {
|
||||
options.file_lines = file_lines.parse()?;
|
||||
}
|
||||
} else {
|
||||
let mut unstable_options = vec![];
|
||||
if matches.opt_present("skip-children") {
|
||||
unstable_options.push("`--skip-children`");
|
||||
}
|
||||
if matches.opt_present("error-on-unformatted") {
|
||||
unstable_options.push("`--error-on-unformatted`");
|
||||
}
|
||||
if matches.opt_present("file-lines") {
|
||||
unstable_options.push("`--file-lines`");
|
||||
}
|
||||
if !unstable_options.is_empty() {
|
||||
let s = if unstable_options.len() == 1 { "" } else { "s" };
|
||||
return Err(format_err!(
|
||||
"Unstable option{} ({}) used without `--unstable-features`",
|
||||
s,
|
||||
unstable_options.join(", "),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.config_path = matches.opt_str("config-path").map(PathBuf::from);
|
||||
|
||||
options.inline_config = matches
|
||||
.opt_strs("config")
|
||||
.iter()
|
||||
.flat_map(|config| config.split(','))
|
||||
.map(
|
||||
|key_val| match key_val.char_indices().find(|(_, ch)| *ch == '=') {
|
||||
Some((middle, _)) => {
|
||||
let (key, val) = (&key_val[..middle], &key_val[middle + 1..]);
|
||||
if !Config::is_valid_key_val(key, val) {
|
||||
Err(format_err!("invalid key=val pair: `{}`", key_val))
|
||||
} else {
|
||||
Ok((key.to_string(), val.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
None => Err(format_err!(
|
||||
"--config expects comma-separated list of key=val pairs, found `{}`",
|
||||
key_val
|
||||
)),
|
||||
},
|
||||
)
|
||||
.collect::<Result<HashMap<_, _>, _>>()?;
|
||||
|
||||
options.check = matches.opt_present("check");
|
||||
if let Some(ref emit_str) = matches.opt_str("emit") {
|
||||
if options.check {
|
||||
return Err(format_err!("Invalid to use `--emit` and `--check`"));
|
||||
}
|
||||
|
||||
options.emit_mode = Some(emit_mode_from_emit_str(emit_str)?);
|
||||
}
|
||||
|
||||
if let Some(ref edition_str) = matches.opt_str("edition") {
|
||||
options.edition = Some(edition_from_edition_str(edition_str)?);
|
||||
}
|
||||
|
||||
if matches.opt_present("backup") {
|
||||
options.backup = true;
|
||||
}
|
||||
|
||||
if matches.opt_present("files-with-diff") {
|
||||
options.print_misformatted_file_names = true;
|
||||
}
|
||||
|
||||
if !rust_nightly {
|
||||
if let Some(ref emit_mode) = options.emit_mode {
|
||||
if !STABLE_EMIT_MODES.contains(emit_mode) {
|
||||
return Err(format_err!(
|
||||
"Invalid value for `--emit` - using an unstable \
|
||||
value without `--unstable-features`",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref color) = matches.opt_str("color") {
|
||||
match Color::from_str(color) {
|
||||
Ok(color) => options.color = Some(color),
|
||||
_ => return Err(format_err!("Invalid color: {}", color)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(options)
|
||||
}
|
||||
|
||||
fn verify_file_lines(&self, files: &[PathBuf]) {
|
||||
for f in self.file_lines.files() {
|
||||
match *f {
|
||||
FileName::Real(ref f) if files.contains(f) => {}
|
||||
FileName::Real(_) => {
|
||||
eprintln!("Warning: Extra file listed in file_lines option '{}'", f)
|
||||
}
|
||||
FileName::Stdin => eprintln!("Warning: Not a file '{}'", f),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliOptions for GetOptsOptions {
|
||||
fn apply_to(self, config: &mut Config) {
|
||||
if self.verbose {
|
||||
config.set().verbose(Verbosity::Verbose);
|
||||
} else if self.quiet {
|
||||
config.set().verbose(Verbosity::Quiet);
|
||||
} else {
|
||||
config.set().verbose(Verbosity::Normal);
|
||||
}
|
||||
config.set().file_lines(self.file_lines);
|
||||
config.set().unstable_features(self.unstable_features);
|
||||
if let Some(skip_children) = self.skip_children {
|
||||
config.set().skip_children(skip_children);
|
||||
}
|
||||
if let Some(error_on_unformatted) = self.error_on_unformatted {
|
||||
config.set().error_on_unformatted(error_on_unformatted);
|
||||
}
|
||||
if let Some(edition) = self.edition {
|
||||
config.set().edition(edition);
|
||||
}
|
||||
if self.check {
|
||||
config.set().emit_mode(EmitMode::Diff);
|
||||
} else if let Some(emit_mode) = self.emit_mode {
|
||||
config.set().emit_mode(emit_mode);
|
||||
}
|
||||
if self.backup {
|
||||
config.set().make_backup(true);
|
||||
}
|
||||
if let Some(color) = self.color {
|
||||
config.set().color(color);
|
||||
}
|
||||
if self.print_misformatted_file_names {
|
||||
config.set().print_misformatted_file_names(true);
|
||||
}
|
||||
|
||||
for (key, val) in self.inline_config {
|
||||
config.override_value(&key, &val);
|
||||
}
|
||||
}
|
||||
|
||||
fn config_path(&self) -> Option<&Path> {
|
||||
self.config_path.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
fn edition_from_edition_str(edition_str: &str) -> Result<Edition> {
|
||||
match edition_str {
|
||||
"2015" => Ok(Edition::Edition2015),
|
||||
"2018" => Ok(Edition::Edition2018),
|
||||
_ => Err(format_err!("Invalid value for `--edition`")),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode> {
|
||||
match emit_str {
|
||||
"files" => Ok(EmitMode::Files),
|
||||
"stdout" => Ok(EmitMode::Stdout),
|
||||
"coverage" => Ok(EmitMode::Coverage),
|
||||
"checkstyle" => Ok(EmitMode::Checkstyle),
|
||||
"json" => Ok(EmitMode::Json),
|
||||
_ => Err(format_err!("Invalid value for `--emit`")),
|
||||
}
|
||||
}
|
||||
760
src/tools/rustfmt/src/cargo-fmt/main.rs
Normal file
760
src/tools/rustfmt/src/cargo-fmt/main.rs
Normal file
|
|
@ -0,0 +1,760 @@
|
|||
// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/).
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::{self, Write};
|
||||
use std::iter::FromIterator;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(
|
||||
bin_name = "cargo fmt",
|
||||
about = "This utility formats all bin and lib files of \
|
||||
the current crate using rustfmt."
|
||||
)]
|
||||
pub struct Opts {
|
||||
/// No output printed to stdout
|
||||
#[structopt(short = "q", long = "quiet")]
|
||||
quiet: bool,
|
||||
|
||||
/// Use verbose output
|
||||
#[structopt(short = "v", long = "verbose")]
|
||||
verbose: bool,
|
||||
|
||||
/// Print rustfmt version and exit
|
||||
#[structopt(long = "version")]
|
||||
version: bool,
|
||||
|
||||
/// Specify package to format (only usable in workspaces)
|
||||
#[structopt(short = "p", long = "package", value_name = "package")]
|
||||
packages: Vec<String>,
|
||||
|
||||
/// Specify path to Cargo.toml
|
||||
#[structopt(long = "manifest-path", value_name = "manifest-path")]
|
||||
manifest_path: Option<String>,
|
||||
|
||||
/// Specify message-format: short|json|human
|
||||
#[structopt(long = "message-format", value_name = "message-format")]
|
||||
message_format: Option<String>,
|
||||
|
||||
/// Options passed to rustfmt
|
||||
// 'raw = true' to make `--` explicit.
|
||||
#[structopt(name = "rustfmt_options", raw(true))]
|
||||
rustfmt_options: Vec<String>,
|
||||
|
||||
/// Format all packages (only usable in workspaces)
|
||||
#[structopt(long = "all")]
|
||||
format_all: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let exit_status = execute();
|
||||
std::io::stdout().flush().unwrap();
|
||||
std::process::exit(exit_status);
|
||||
}
|
||||
|
||||
const SUCCESS: i32 = 0;
|
||||
const FAILURE: i32 = 1;
|
||||
|
||||
fn execute() -> i32 {
|
||||
// Drop extra `fmt` argument provided by `cargo`.
|
||||
let mut found_fmt = false;
|
||||
let args = env::args().filter(|x| {
|
||||
if found_fmt {
|
||||
true
|
||||
} else {
|
||||
found_fmt = x == "fmt";
|
||||
x != "fmt"
|
||||
}
|
||||
});
|
||||
|
||||
let opts = Opts::from_iter(args);
|
||||
|
||||
let verbosity = match (opts.verbose, opts.quiet) {
|
||||
(false, false) => Verbosity::Normal,
|
||||
(false, true) => Verbosity::Quiet,
|
||||
(true, false) => Verbosity::Verbose,
|
||||
(true, true) => {
|
||||
print_usage_to_stderr("quiet mode and verbose mode are not compatible");
|
||||
return FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
if opts.version {
|
||||
return handle_command_status(get_rustfmt_info(&[String::from("--version")]));
|
||||
}
|
||||
if opts.rustfmt_options.iter().any(|s| {
|
||||
["--print-config", "-h", "--help", "-V", "--version"].contains(&s.as_str())
|
||||
|| s.starts_with("--help=")
|
||||
|| s.starts_with("--print-config=")
|
||||
}) {
|
||||
return handle_command_status(get_rustfmt_info(&opts.rustfmt_options));
|
||||
}
|
||||
|
||||
let strategy = CargoFmtStrategy::from_opts(&opts);
|
||||
let mut rustfmt_args = opts.rustfmt_options;
|
||||
if let Some(message_format) = opts.message_format {
|
||||
if let Err(msg) = convert_message_format_to_rustfmt_args(&message_format, &mut rustfmt_args)
|
||||
{
|
||||
print_usage_to_stderr(&msg);
|
||||
return FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(specified_manifest_path) = opts.manifest_path {
|
||||
if !specified_manifest_path.ends_with("Cargo.toml") {
|
||||
print_usage_to_stderr("the manifest-path must be a path to a Cargo.toml file");
|
||||
return FAILURE;
|
||||
}
|
||||
let manifest_path = PathBuf::from(specified_manifest_path);
|
||||
handle_command_status(format_crate(
|
||||
verbosity,
|
||||
&strategy,
|
||||
rustfmt_args,
|
||||
Some(&manifest_path),
|
||||
))
|
||||
} else {
|
||||
handle_command_status(format_crate(verbosity, &strategy, rustfmt_args, None))
|
||||
}
|
||||
}
|
||||
|
||||
fn rustfmt_command() -> Command {
|
||||
let rustfmt_var = env::var_os("RUSTFMT");
|
||||
let rustfmt = match &rustfmt_var {
|
||||
Some(rustfmt) => rustfmt,
|
||||
None => OsStr::new("rustfmt"),
|
||||
};
|
||||
Command::new(rustfmt)
|
||||
}
|
||||
|
||||
fn convert_message_format_to_rustfmt_args(
|
||||
message_format: &str,
|
||||
rustfmt_args: &mut Vec<String>,
|
||||
) -> Result<(), String> {
|
||||
let mut contains_emit_mode = false;
|
||||
let mut contains_check = false;
|
||||
let mut contains_list_files = false;
|
||||
for arg in rustfmt_args.iter() {
|
||||
if arg.starts_with("--emit") {
|
||||
contains_emit_mode = true;
|
||||
}
|
||||
if arg == "--check" {
|
||||
contains_check = true;
|
||||
}
|
||||
if arg == "-l" || arg == "--files-with-diff" {
|
||||
contains_list_files = true;
|
||||
}
|
||||
}
|
||||
match message_format {
|
||||
"short" => {
|
||||
if !contains_list_files {
|
||||
rustfmt_args.push(String::from("-l"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
"json" => {
|
||||
if contains_emit_mode {
|
||||
return Err(String::from(
|
||||
"cannot include --emit arg when --message-format is set to json",
|
||||
));
|
||||
}
|
||||
if contains_check {
|
||||
return Err(String::from(
|
||||
"cannot include --check arg when --message-format is set to json",
|
||||
));
|
||||
}
|
||||
rustfmt_args.push(String::from("--emit"));
|
||||
rustfmt_args.push(String::from("json"));
|
||||
Ok(())
|
||||
}
|
||||
"human" => Ok(()),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"invalid --message-format value: {}. Allowed values are: short|json|human",
|
||||
message_format
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_usage_to_stderr(reason: &str) {
|
||||
eprintln!("{}", reason);
|
||||
let app = Opts::clap();
|
||||
app.after_help("")
|
||||
.write_help(&mut io::stderr())
|
||||
.expect("failed to write to stderr");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Verbosity {
|
||||
Verbose,
|
||||
Normal,
|
||||
Quiet,
|
||||
}
|
||||
|
||||
fn handle_command_status(status: Result<i32, io::Error>) -> i32 {
|
||||
match status {
|
||||
Err(e) => {
|
||||
print_usage_to_stderr(&e.to_string());
|
||||
FAILURE
|
||||
}
|
||||
Ok(status) => status,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rustfmt_info(args: &[String]) -> Result<i32, io::Error> {
|
||||
let mut command = rustfmt_command()
|
||||
.stdout(std::process::Stdio::inherit())
|
||||
.args(args)
|
||||
.spawn()
|
||||
.map_err(|e| match e.kind() {
|
||||
io::ErrorKind::NotFound => io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Could not run rustfmt, please make sure it is in your PATH.",
|
||||
),
|
||||
_ => e,
|
||||
})?;
|
||||
let result = command.wait()?;
|
||||
if result.success() {
|
||||
Ok(SUCCESS)
|
||||
} else {
|
||||
Ok(result.code().unwrap_or(SUCCESS))
|
||||
}
|
||||
}
|
||||
|
||||
fn format_crate(
|
||||
verbosity: Verbosity,
|
||||
strategy: &CargoFmtStrategy,
|
||||
rustfmt_args: Vec<String>,
|
||||
manifest_path: Option<&Path>,
|
||||
) -> Result<i32, io::Error> {
|
||||
let targets = get_targets(strategy, manifest_path)?;
|
||||
|
||||
// Currently only bin and lib files get formatted.
|
||||
run_rustfmt(&targets, &rustfmt_args, verbosity)
|
||||
}
|
||||
|
||||
/// Target uses a `path` field for equality and hashing.
|
||||
#[derive(Debug)]
|
||||
pub struct Target {
|
||||
/// A path to the main source file of the target.
|
||||
path: PathBuf,
|
||||
/// A kind of target (e.g., lib, bin, example, ...).
|
||||
kind: String,
|
||||
/// Rust edition for this target.
|
||||
edition: String,
|
||||
}
|
||||
|
||||
impl Target {
|
||||
pub fn from_target(target: &cargo_metadata::Target) -> Self {
|
||||
let path = PathBuf::from(&target.src_path);
|
||||
let canonicalized = fs::canonicalize(&path).unwrap_or(path);
|
||||
|
||||
Target {
|
||||
path: canonicalized,
|
||||
kind: target.kind[0].clone(),
|
||||
edition: target.edition.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Target {
|
||||
fn eq(&self, other: &Target) -> bool {
|
||||
self.path == other.path
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Target {
|
||||
fn partial_cmp(&self, other: &Target) -> Option<Ordering> {
|
||||
Some(self.path.cmp(&other.path))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Target {
|
||||
fn cmp(&self, other: &Target) -> Ordering {
|
||||
self.path.cmp(&other.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Target {}
|
||||
|
||||
impl Hash for Target {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.path.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum CargoFmtStrategy {
|
||||
/// Format every packages and dependencies.
|
||||
All,
|
||||
/// Format packages that are specified by the command line argument.
|
||||
Some(Vec<String>),
|
||||
/// Format the root packages only.
|
||||
Root,
|
||||
}
|
||||
|
||||
impl CargoFmtStrategy {
|
||||
pub fn from_opts(opts: &Opts) -> CargoFmtStrategy {
|
||||
match (opts.format_all, opts.packages.is_empty()) {
|
||||
(false, true) => CargoFmtStrategy::Root,
|
||||
(true, _) => CargoFmtStrategy::All,
|
||||
(false, false) => CargoFmtStrategy::Some(opts.packages.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Based on the specified `CargoFmtStrategy`, returns a set of main source files.
|
||||
fn get_targets(
|
||||
strategy: &CargoFmtStrategy,
|
||||
manifest_path: Option<&Path>,
|
||||
) -> Result<BTreeSet<Target>, io::Error> {
|
||||
let mut targets = BTreeSet::new();
|
||||
|
||||
match *strategy {
|
||||
CargoFmtStrategy::Root => get_targets_root_only(manifest_path, &mut targets)?,
|
||||
CargoFmtStrategy::All => {
|
||||
get_targets_recursive(manifest_path, &mut targets, &mut BTreeSet::new())?
|
||||
}
|
||||
CargoFmtStrategy::Some(ref hitlist) => {
|
||||
get_targets_with_hitlist(manifest_path, hitlist, &mut targets)?
|
||||
}
|
||||
}
|
||||
|
||||
if targets.is_empty() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Failed to find targets".to_owned(),
|
||||
))
|
||||
} else {
|
||||
Ok(targets)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_targets_root_only(
|
||||
manifest_path: Option<&Path>,
|
||||
targets: &mut BTreeSet<Target>,
|
||||
) -> Result<(), io::Error> {
|
||||
let metadata = get_cargo_metadata(manifest_path, false)?;
|
||||
let workspace_root_path = PathBuf::from(&metadata.workspace_root).canonicalize()?;
|
||||
let (in_workspace_root, current_dir_manifest) = if let Some(target_manifest) = manifest_path {
|
||||
(
|
||||
workspace_root_path == target_manifest,
|
||||
target_manifest.canonicalize()?,
|
||||
)
|
||||
} else {
|
||||
let current_dir = env::current_dir()?.canonicalize()?;
|
||||
(
|
||||
workspace_root_path == current_dir,
|
||||
current_dir.join("Cargo.toml"),
|
||||
)
|
||||
};
|
||||
|
||||
let package_targets = match metadata.packages.len() {
|
||||
1 => metadata.packages.into_iter().next().unwrap().targets,
|
||||
_ => metadata
|
||||
.packages
|
||||
.into_iter()
|
||||
.filter(|p| {
|
||||
in_workspace_root
|
||||
|| PathBuf::from(&p.manifest_path)
|
||||
.canonicalize()
|
||||
.unwrap_or_default()
|
||||
== current_dir_manifest
|
||||
})
|
||||
.map(|p| p.targets)
|
||||
.flatten()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
for target in package_targets {
|
||||
targets.insert(Target::from_target(&target));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_targets_recursive(
|
||||
manifest_path: Option<&Path>,
|
||||
mut targets: &mut BTreeSet<Target>,
|
||||
visited: &mut BTreeSet<String>,
|
||||
) -> Result<(), io::Error> {
|
||||
let metadata = get_cargo_metadata(manifest_path, false)?;
|
||||
let metadata_with_deps = get_cargo_metadata(manifest_path, true)?;
|
||||
|
||||
for package in metadata.packages {
|
||||
add_targets(&package.targets, &mut targets);
|
||||
|
||||
// Look for local dependencies.
|
||||
for dependency in package.dependencies {
|
||||
if dependency.source.is_some() || visited.contains(&dependency.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dependency_package = metadata_with_deps
|
||||
.packages
|
||||
.iter()
|
||||
.find(|p| p.name == dependency.name && p.source.is_none());
|
||||
let manifest_path = if dependency_package.is_some() {
|
||||
PathBuf::from(&dependency_package.unwrap().manifest_path)
|
||||
} else {
|
||||
let mut package_manifest_path = PathBuf::from(&package.manifest_path);
|
||||
package_manifest_path.pop();
|
||||
package_manifest_path.push(&dependency.name);
|
||||
package_manifest_path.push("Cargo.toml");
|
||||
package_manifest_path
|
||||
};
|
||||
|
||||
if manifest_path.exists() {
|
||||
visited.insert(dependency.name);
|
||||
get_targets_recursive(Some(&manifest_path), &mut targets, visited)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_targets_with_hitlist(
|
||||
manifest_path: Option<&Path>,
|
||||
hitlist: &[String],
|
||||
targets: &mut BTreeSet<Target>,
|
||||
) -> Result<(), io::Error> {
|
||||
let metadata = get_cargo_metadata(manifest_path, false)?;
|
||||
|
||||
let mut workspace_hitlist: BTreeSet<&String> = BTreeSet::from_iter(hitlist);
|
||||
|
||||
for package in metadata.packages {
|
||||
if workspace_hitlist.remove(&package.name) {
|
||||
for target in package.targets {
|
||||
targets.insert(Target::from_target(&target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if workspace_hitlist.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
let package = workspace_hitlist.iter().next().unwrap();
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("package `{}` is not a member of the workspace", package),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut BTreeSet<Target>) {
|
||||
for target in target_paths {
|
||||
targets.insert(Target::from_target(target));
|
||||
}
|
||||
}
|
||||
|
||||
fn run_rustfmt(
|
||||
targets: &BTreeSet<Target>,
|
||||
fmt_args: &[String],
|
||||
verbosity: Verbosity,
|
||||
) -> Result<i32, io::Error> {
|
||||
let by_edition = targets
|
||||
.iter()
|
||||
.inspect(|t| {
|
||||
if verbosity == Verbosity::Verbose {
|
||||
println!("[{} ({})] {:?}", t.kind, t.edition, t.path)
|
||||
}
|
||||
})
|
||||
.fold(BTreeMap::new(), |mut h, t| {
|
||||
h.entry(&t.edition).or_insert_with(Vec::new).push(&t.path);
|
||||
h
|
||||
});
|
||||
|
||||
let mut status = vec![];
|
||||
for (edition, files) in by_edition {
|
||||
let stdout = if verbosity == Verbosity::Quiet {
|
||||
std::process::Stdio::null()
|
||||
} else {
|
||||
std::process::Stdio::inherit()
|
||||
};
|
||||
|
||||
if verbosity == Verbosity::Verbose {
|
||||
print!("rustfmt");
|
||||
print!(" --edition {}", edition);
|
||||
fmt_args.iter().for_each(|f| print!(" {}", f));
|
||||
files.iter().for_each(|f| print!(" {}", f.display()));
|
||||
println!();
|
||||
}
|
||||
|
||||
let mut command = rustfmt_command()
|
||||
.stdout(stdout)
|
||||
.args(files)
|
||||
.args(&["--edition", edition])
|
||||
.args(fmt_args)
|
||||
.spawn()
|
||||
.map_err(|e| match e.kind() {
|
||||
io::ErrorKind::NotFound => io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Could not run rustfmt, please make sure it is in your PATH.",
|
||||
),
|
||||
_ => e,
|
||||
})?;
|
||||
|
||||
status.push(command.wait()?);
|
||||
}
|
||||
|
||||
Ok(status
|
||||
.iter()
|
||||
.filter_map(|s| if s.success() { None } else { s.code() })
|
||||
.next()
|
||||
.unwrap_or(SUCCESS))
|
||||
}
|
||||
|
||||
fn get_cargo_metadata(
|
||||
manifest_path: Option<&Path>,
|
||||
include_deps: bool,
|
||||
) -> Result<cargo_metadata::Metadata, io::Error> {
|
||||
let mut cmd = cargo_metadata::MetadataCommand::new();
|
||||
if !include_deps {
|
||||
cmd.no_deps();
|
||||
}
|
||||
if let Some(manifest_path) = manifest_path {
|
||||
cmd.manifest_path(manifest_path);
|
||||
}
|
||||
cmd.other_options(&[String::from("--offline")]);
|
||||
|
||||
match cmd.exec() {
|
||||
Ok(metadata) => Ok(metadata),
|
||||
Err(_) => {
|
||||
cmd.other_options(vec![]);
|
||||
match cmd.exec() {
|
||||
Ok(metadata) => Ok(metadata),
|
||||
Err(error) => Err(io::Error::new(io::ErrorKind::Other, error.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cargo_fmt_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_options() {
|
||||
let empty: Vec<String> = vec![];
|
||||
let o = Opts::from_iter(&empty);
|
||||
assert_eq!(false, o.quiet);
|
||||
assert_eq!(false, o.verbose);
|
||||
assert_eq!(false, o.version);
|
||||
assert_eq!(empty, o.packages);
|
||||
assert_eq!(empty, o.rustfmt_options);
|
||||
assert_eq!(false, o.format_all);
|
||||
assert_eq!(None, o.manifest_path);
|
||||
assert_eq!(None, o.message_format);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn good_options() {
|
||||
let o = Opts::from_iter(&[
|
||||
"test",
|
||||
"-q",
|
||||
"-p",
|
||||
"p1",
|
||||
"-p",
|
||||
"p2",
|
||||
"--message-format",
|
||||
"short",
|
||||
"--",
|
||||
"--edition",
|
||||
"2018",
|
||||
]);
|
||||
assert_eq!(true, o.quiet);
|
||||
assert_eq!(false, o.verbose);
|
||||
assert_eq!(false, o.version);
|
||||
assert_eq!(vec!["p1", "p2"], o.packages);
|
||||
assert_eq!(vec!["--edition", "2018"], o.rustfmt_options);
|
||||
assert_eq!(false, o.format_all);
|
||||
assert_eq!(Some(String::from("short")), o.message_format);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_option() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "unexpected"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_flag() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "--flag"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mandatory_separator() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "--check"])
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
!Opts::clap()
|
||||
.get_matches_from_safe(&["test", "--", "--check"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_packages_one_by_one() {
|
||||
let o = Opts::from_iter(&[
|
||||
"test",
|
||||
"-p",
|
||||
"package1",
|
||||
"--package",
|
||||
"package2",
|
||||
"-p",
|
||||
"package3",
|
||||
]);
|
||||
assert_eq!(3, o.packages.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_packages_grouped() {
|
||||
let o = Opts::from_iter(&[
|
||||
"test",
|
||||
"--package",
|
||||
"package1",
|
||||
"package2",
|
||||
"-p",
|
||||
"package3",
|
||||
"package4",
|
||||
]);
|
||||
assert_eq!(4, o.packages.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_packages_1() {
|
||||
assert!(Opts::clap().get_matches_from_safe(&["test", "-p"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_packages_2() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "-p", "--", "--check"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_packages_3() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "-p", "--verbose"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_packages_4() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "-p", "--check"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
mod convert_message_format_to_rustfmt_args_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn invalid_message_format() {
|
||||
assert_eq!(
|
||||
convert_message_format_to_rustfmt_args("awesome", &mut vec![]),
|
||||
Err(String::from(
|
||||
"invalid --message-format value: awesome. Allowed values are: short|json|human"
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_message_format_and_check_arg() {
|
||||
let mut args = vec![String::from("--check")];
|
||||
assert_eq!(
|
||||
convert_message_format_to_rustfmt_args("json", &mut args),
|
||||
Err(String::from(
|
||||
"cannot include --check arg when --message-format is set to json"
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_message_format_and_emit_arg() {
|
||||
let mut args = vec![String::from("--emit"), String::from("checkstyle")];
|
||||
assert_eq!(
|
||||
convert_message_format_to_rustfmt_args("json", &mut args),
|
||||
Err(String::from(
|
||||
"cannot include --emit arg when --message-format is set to json"
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_message_format() {
|
||||
let mut args = vec![String::from("--edition"), String::from("2018")];
|
||||
assert!(convert_message_format_to_rustfmt_args("json", &mut args).is_ok());
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
String::from("--edition"),
|
||||
String::from("2018"),
|
||||
String::from("--emit"),
|
||||
String::from("json")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn human_message_format() {
|
||||
let exp_args = vec![String::from("--emit"), String::from("json")];
|
||||
let mut act_args = exp_args.clone();
|
||||
assert!(convert_message_format_to_rustfmt_args("human", &mut act_args).is_ok());
|
||||
assert_eq!(act_args, exp_args);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_message_format() {
|
||||
let mut args = vec![String::from("--check")];
|
||||
assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok());
|
||||
assert_eq!(args, vec![String::from("--check"), String::from("-l")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_message_format_included_short_list_files_flag() {
|
||||
let mut args = vec![String::from("--check"), String::from("-l")];
|
||||
assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok());
|
||||
assert_eq!(args, vec![String::from("--check"), String::from("-l")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_message_format_included_long_list_files_flag() {
|
||||
let mut args = vec![String::from("--check"), String::from("--files-with-diff")];
|
||||
assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok());
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![String::from("--check"), String::from("--files-with-diff")]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
891
src/tools/rustfmt/src/chains.rs
Normal file
891
src/tools/rustfmt/src/chains.rs
Normal file
|
|
@ -0,0 +1,891 @@
|
|||
//! Formatting of chained expressions, i.e., expressions that are chained by
|
||||
//! dots: struct and enum field access, method calls, and try shorthand (`?`).
|
||||
//!
|
||||
//! Instead of walking these subexpressions one-by-one, as is our usual strategy
|
||||
//! for expression formatting, we collect maximal sequences of these expressions
|
||||
//! and handle them simultaneously.
|
||||
//!
|
||||
//! Whenever possible, the entire chain is put on a single line. If that fails,
|
||||
//! we put each subexpression on a separate, much like the (default) function
|
||||
//! argument function argument strategy.
|
||||
//!
|
||||
//! Depends on config options: `chain_indent` is the indent to use for
|
||||
//! blocks in the parent/root/base of the chain (and the rest of the chain's
|
||||
//! alignment).
|
||||
//! E.g., `let foo = { aaaa; bbb; ccc }.bar.baz();`, we would layout for the
|
||||
//! following values of `chain_indent`:
|
||||
//! Block:
|
||||
//!
|
||||
//! ```text
|
||||
//! let foo = {
|
||||
//! aaaa;
|
||||
//! bbb;
|
||||
//! ccc
|
||||
//! }.bar
|
||||
//! .baz();
|
||||
//! ```
|
||||
//!
|
||||
//! Visual:
|
||||
//!
|
||||
//! ```text
|
||||
//! let foo = {
|
||||
//! aaaa;
|
||||
//! bbb;
|
||||
//! ccc
|
||||
//! }
|
||||
//! .bar
|
||||
//! .baz();
|
||||
//! ```
|
||||
//!
|
||||
//! If the first item in the chain is a block expression, we align the dots with
|
||||
//! the braces.
|
||||
//! Block:
|
||||
//!
|
||||
//! ```text
|
||||
//! let a = foo.bar
|
||||
//! .baz()
|
||||
//! .qux
|
||||
//! ```
|
||||
//!
|
||||
//! Visual:
|
||||
//!
|
||||
//! ```text
|
||||
//! let a = foo.bar
|
||||
//! .baz()
|
||||
//! .qux
|
||||
//! ```
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::min;
|
||||
|
||||
use rustc_ast::{ast, ptr};
|
||||
use rustc_span::{symbol, BytePos, Span};
|
||||
|
||||
use crate::comment::{rewrite_comment, CharClasses, FullCodeCharKind, RichChar};
|
||||
use crate::config::{IndentStyle, Version};
|
||||
use crate::expr::rewrite_call;
|
||||
use crate::lists::extract_pre_comment;
|
||||
use crate::macros::convert_try_mac;
|
||||
use crate::rewrite::{Rewrite, RewriteContext};
|
||||
use crate::shape::Shape;
|
||||
use crate::source_map::SpanUtils;
|
||||
use crate::utils::{
|
||||
self, first_line_width, last_line_extendable, last_line_width, mk_sp, rewrite_ident,
|
||||
trimmed_last_line_width, wrap_str,
|
||||
};
|
||||
|
||||
pub(crate) fn rewrite_chain(
|
||||
expr: &ast::Expr,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
let chain = Chain::from_ast(expr, context);
|
||||
debug!("rewrite_chain {:?} {:?}", chain, shape);
|
||||
|
||||
// If this is just an expression with some `?`s, then format it trivially and
|
||||
// return early.
|
||||
if chain.children.is_empty() {
|
||||
return chain.parent.rewrite(context, shape);
|
||||
}
|
||||
|
||||
chain.rewrite(context, shape)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CommentPosition {
|
||||
Back,
|
||||
Top,
|
||||
}
|
||||
|
||||
// An expression plus trailing `?`s to be formatted together.
|
||||
#[derive(Debug)]
|
||||
struct ChainItem {
|
||||
kind: ChainItemKind,
|
||||
tries: usize,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
// FIXME: we can't use a reference here because to convert `try!` to `?` we
|
||||
// synthesise the AST node. However, I think we could use `Cow` and that
|
||||
// would remove a lot of cloning.
|
||||
#[derive(Debug)]
|
||||
enum ChainItemKind {
|
||||
Parent(ast::Expr),
|
||||
MethodCall(
|
||||
ast::PathSegment,
|
||||
Vec<ast::GenericArg>,
|
||||
Vec<ptr::P<ast::Expr>>,
|
||||
),
|
||||
StructField(symbol::Ident),
|
||||
TupleField(symbol::Ident, bool),
|
||||
Await,
|
||||
Comment(String, CommentPosition),
|
||||
}
|
||||
|
||||
impl ChainItemKind {
|
||||
fn is_block_like(&self, context: &RewriteContext<'_>, reps: &str) -> bool {
|
||||
match self {
|
||||
ChainItemKind::Parent(ref expr) => utils::is_block_expr(context, expr, reps),
|
||||
ChainItemKind::MethodCall(..)
|
||||
| ChainItemKind::StructField(..)
|
||||
| ChainItemKind::TupleField(..)
|
||||
| ChainItemKind::Await
|
||||
| ChainItemKind::Comment(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_tup_field_access(expr: &ast::Expr) -> bool {
|
||||
match expr.kind {
|
||||
ast::ExprKind::Field(_, ref field) => {
|
||||
field.name.to_string().chars().all(|c| c.is_digit(10))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_ast(context: &RewriteContext<'_>, expr: &ast::Expr) -> (ChainItemKind, Span) {
|
||||
let (kind, span) = match expr.kind {
|
||||
ast::ExprKind::MethodCall(ref segment, ref expressions, _) => {
|
||||
let types = if let Some(ref generic_args) = segment.args {
|
||||
if let ast::GenericArgs::AngleBracketed(ref data) = **generic_args {
|
||||
data.args
|
||||
.iter()
|
||||
.filter_map(|x| match x {
|
||||
ast::AngleBracketedArg::Arg(ref generic_arg) => {
|
||||
Some(generic_arg.clone())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let span = mk_sp(expressions[0].span.hi(), expr.span.hi());
|
||||
let kind = ChainItemKind::MethodCall(segment.clone(), types, expressions.clone());
|
||||
(kind, span)
|
||||
}
|
||||
ast::ExprKind::Field(ref nested, field) => {
|
||||
let kind = if Self::is_tup_field_access(expr) {
|
||||
ChainItemKind::TupleField(field, Self::is_tup_field_access(nested))
|
||||
} else {
|
||||
ChainItemKind::StructField(field)
|
||||
};
|
||||
let span = mk_sp(nested.span.hi(), field.span.hi());
|
||||
(kind, span)
|
||||
}
|
||||
ast::ExprKind::Await(ref nested) => {
|
||||
let span = mk_sp(nested.span.hi(), expr.span.hi());
|
||||
(ChainItemKind::Await, span)
|
||||
}
|
||||
_ => return (ChainItemKind::Parent(expr.clone()), expr.span),
|
||||
};
|
||||
|
||||
// Remove comments from the span.
|
||||
let lo = context.snippet_provider.span_before(span, ".");
|
||||
(kind, mk_sp(lo, span.hi()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Rewrite for ChainItem {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
let shape = shape.sub_width(self.tries)?;
|
||||
let rewrite = match self.kind {
|
||||
ChainItemKind::Parent(ref expr) => expr.rewrite(context, shape)?,
|
||||
ChainItemKind::MethodCall(ref segment, ref types, ref exprs) => {
|
||||
Self::rewrite_method_call(segment.ident, types, exprs, self.span, context, shape)?
|
||||
}
|
||||
ChainItemKind::StructField(ident) => format!(".{}", rewrite_ident(context, ident)),
|
||||
ChainItemKind::TupleField(ident, nested) => format!(
|
||||
"{}.{}",
|
||||
if nested && context.config.version() == Version::One {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
rewrite_ident(context, ident)
|
||||
),
|
||||
ChainItemKind::Await => ".await".to_owned(),
|
||||
ChainItemKind::Comment(ref comment, _) => {
|
||||
rewrite_comment(comment, false, shape, context.config)?
|
||||
}
|
||||
};
|
||||
Some(format!("{}{}", rewrite, "?".repeat(self.tries)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainItem {
|
||||
fn new(context: &RewriteContext<'_>, expr: &ast::Expr, tries: usize) -> ChainItem {
|
||||
let (kind, span) = ChainItemKind::from_ast(context, expr);
|
||||
ChainItem { kind, tries, span }
|
||||
}
|
||||
|
||||
fn comment(span: Span, comment: String, pos: CommentPosition) -> ChainItem {
|
||||
ChainItem {
|
||||
kind: ChainItemKind::Comment(comment, pos),
|
||||
tries: 0,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_comment(&self) -> bool {
|
||||
match self.kind {
|
||||
ChainItemKind::Comment(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_method_call(
|
||||
method_name: symbol::Ident,
|
||||
types: &[ast::GenericArg],
|
||||
args: &[ptr::P<ast::Expr>],
|
||||
span: Span,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
let type_str = if types.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
let type_list = types
|
||||
.iter()
|
||||
.map(|ty| ty.rewrite(context, shape))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
format!("::<{}>", type_list.join(", "))
|
||||
};
|
||||
let callee_str = format!(".{}{}", rewrite_ident(context, method_name), type_str);
|
||||
rewrite_call(context, &callee_str, &args[1..], span, shape)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Chain {
|
||||
parent: ChainItem,
|
||||
children: Vec<ChainItem>,
|
||||
}
|
||||
|
||||
impl Chain {
|
||||
fn from_ast(expr: &ast::Expr, context: &RewriteContext<'_>) -> Chain {
|
||||
let subexpr_list = Self::make_subexpr_list(expr, context);
|
||||
|
||||
// Un-parse the expression tree into ChainItems
|
||||
let mut rev_children = vec![];
|
||||
let mut sub_tries = 0;
|
||||
for subexpr in &subexpr_list {
|
||||
match subexpr.kind {
|
||||
ast::ExprKind::Try(_) => sub_tries += 1,
|
||||
_ => {
|
||||
rev_children.push(ChainItem::new(context, subexpr, sub_tries));
|
||||
sub_tries = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_tries(s: &str) -> bool {
|
||||
s.chars().all(|c| c == '?')
|
||||
}
|
||||
|
||||
fn is_post_comment(s: &str) -> bool {
|
||||
let comment_start_index = s.chars().position(|c| c == '/');
|
||||
if comment_start_index.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let newline_index = s.chars().position(|c| c == '\n');
|
||||
if newline_index.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
comment_start_index.unwrap() < newline_index.unwrap()
|
||||
}
|
||||
|
||||
fn handle_post_comment(
|
||||
post_comment_span: Span,
|
||||
post_comment_snippet: &str,
|
||||
prev_span_end: &mut BytePos,
|
||||
children: &mut Vec<ChainItem>,
|
||||
) {
|
||||
let white_spaces: &[_] = &[' ', '\t'];
|
||||
if post_comment_snippet
|
||||
.trim_matches(white_spaces)
|
||||
.starts_with('\n')
|
||||
{
|
||||
// No post comment.
|
||||
return;
|
||||
}
|
||||
let trimmed_snippet = trim_tries(post_comment_snippet);
|
||||
if is_post_comment(&trimmed_snippet) {
|
||||
children.push(ChainItem::comment(
|
||||
post_comment_span,
|
||||
trimmed_snippet.trim().to_owned(),
|
||||
CommentPosition::Back,
|
||||
));
|
||||
*prev_span_end = post_comment_span.hi();
|
||||
}
|
||||
}
|
||||
|
||||
let parent = rev_children.pop().unwrap();
|
||||
let mut children = vec![];
|
||||
let mut prev_span_end = parent.span.hi();
|
||||
let mut iter = rev_children.into_iter().rev().peekable();
|
||||
if let Some(first_chain_item) = iter.peek() {
|
||||
let comment_span = mk_sp(prev_span_end, first_chain_item.span.lo());
|
||||
let comment_snippet = context.snippet(comment_span);
|
||||
if !is_tries(comment_snippet.trim()) {
|
||||
handle_post_comment(
|
||||
comment_span,
|
||||
comment_snippet,
|
||||
&mut prev_span_end,
|
||||
&mut children,
|
||||
);
|
||||
}
|
||||
}
|
||||
while let Some(chain_item) = iter.next() {
|
||||
let comment_snippet = context.snippet(chain_item.span);
|
||||
// FIXME: Figure out the way to get a correct span when converting `try!` to `?`.
|
||||
let handle_comment =
|
||||
!(context.config.use_try_shorthand() || is_tries(comment_snippet.trim()));
|
||||
|
||||
// Pre-comment
|
||||
if handle_comment {
|
||||
let pre_comment_span = mk_sp(prev_span_end, chain_item.span.lo());
|
||||
let pre_comment_snippet = trim_tries(context.snippet(pre_comment_span));
|
||||
let (pre_comment, _) = extract_pre_comment(&pre_comment_snippet);
|
||||
match pre_comment {
|
||||
Some(ref comment) if !comment.is_empty() => {
|
||||
children.push(ChainItem::comment(
|
||||
pre_comment_span,
|
||||
comment.to_owned(),
|
||||
CommentPosition::Top,
|
||||
));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
prev_span_end = chain_item.span.hi();
|
||||
children.push(chain_item);
|
||||
|
||||
// Post-comment
|
||||
if !handle_comment || iter.peek().is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let next_lo = iter.peek().unwrap().span.lo();
|
||||
let post_comment_span = mk_sp(prev_span_end, next_lo);
|
||||
let post_comment_snippet = context.snippet(post_comment_span);
|
||||
handle_post_comment(
|
||||
post_comment_span,
|
||||
post_comment_snippet,
|
||||
&mut prev_span_end,
|
||||
&mut children,
|
||||
);
|
||||
}
|
||||
|
||||
Chain { parent, children }
|
||||
}
|
||||
|
||||
// Returns a Vec of the prefixes of the chain.
|
||||
// E.g., for input `a.b.c` we return [`a.b.c`, `a.b`, 'a']
|
||||
fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext<'_>) -> Vec<ast::Expr> {
|
||||
let mut subexpr_list = vec![expr.clone()];
|
||||
|
||||
while let Some(subexpr) = Self::pop_expr_chain(subexpr_list.last().unwrap(), context) {
|
||||
subexpr_list.push(subexpr.clone());
|
||||
}
|
||||
|
||||
subexpr_list
|
||||
}
|
||||
|
||||
// Returns the expression's subexpression, if it exists. When the subexpr
|
||||
// is a try! macro, we'll convert it to shorthand when the option is set.
|
||||
fn pop_expr_chain(expr: &ast::Expr, context: &RewriteContext<'_>) -> Option<ast::Expr> {
|
||||
match expr.kind {
|
||||
ast::ExprKind::MethodCall(_, ref expressions, _) => {
|
||||
Some(Self::convert_try(&expressions[0], context))
|
||||
}
|
||||
ast::ExprKind::Field(ref subexpr, _)
|
||||
| ast::ExprKind::Try(ref subexpr)
|
||||
| ast::ExprKind::Await(ref subexpr) => Some(Self::convert_try(subexpr, context)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_try(expr: &ast::Expr, context: &RewriteContext<'_>) -> ast::Expr {
|
||||
match expr.kind {
|
||||
ast::ExprKind::MacCall(ref mac) if context.config.use_try_shorthand() => {
|
||||
if let Some(subexpr) = convert_try_mac(mac, context) {
|
||||
subexpr
|
||||
} else {
|
||||
expr.clone()
|
||||
}
|
||||
}
|
||||
_ => expr.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rewrite for Chain {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
debug!("rewrite chain {:?} {:?}", self, shape);
|
||||
|
||||
let mut formatter = match context.config.indent_style() {
|
||||
IndentStyle::Block => {
|
||||
Box::new(ChainFormatterBlock::new(self)) as Box<dyn ChainFormatter>
|
||||
}
|
||||
IndentStyle::Visual => {
|
||||
Box::new(ChainFormatterVisual::new(self)) as Box<dyn ChainFormatter>
|
||||
}
|
||||
};
|
||||
|
||||
formatter.format_root(&self.parent, context, shape)?;
|
||||
if let Some(result) = formatter.pure_root() {
|
||||
return wrap_str(result, context.config.max_width(), shape);
|
||||
}
|
||||
|
||||
// Decide how to layout the rest of the chain.
|
||||
let child_shape = formatter.child_shape(context, shape)?;
|
||||
|
||||
formatter.format_children(context, child_shape)?;
|
||||
formatter.format_last_child(context, shape, child_shape)?;
|
||||
|
||||
let result = formatter.join_rewrites(context, child_shape)?;
|
||||
wrap_str(result, context.config.max_width(), shape)
|
||||
}
|
||||
}
|
||||
|
||||
// There are a few types for formatting chains. This is because there is a lot
|
||||
// in common between formatting with block vs visual indent, but they are
|
||||
// different enough that branching on the indent all over the place gets ugly.
|
||||
// Anything that can format a chain is a ChainFormatter.
|
||||
trait ChainFormatter {
|
||||
// Parent is the first item in the chain, e.g., `foo` in `foo.bar.baz()`.
|
||||
// Root is the parent plus any other chain items placed on the first line to
|
||||
// avoid an orphan. E.g.,
|
||||
// ```text
|
||||
// foo.bar
|
||||
// .baz()
|
||||
// ```
|
||||
// If `bar` were not part of the root, then foo would be orphaned and 'float'.
|
||||
fn format_root(
|
||||
&mut self,
|
||||
parent: &ChainItem,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<()>;
|
||||
fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape>;
|
||||
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()>;
|
||||
fn format_last_child(
|
||||
&mut self,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
child_shape: Shape,
|
||||
) -> Option<()>;
|
||||
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<String>;
|
||||
// Returns `Some` if the chain is only a root, None otherwise.
|
||||
fn pure_root(&mut self) -> Option<String>;
|
||||
}
|
||||
|
||||
// Data and behaviour that is shared by both chain formatters. The concrete
|
||||
// formatters can delegate much behaviour to `ChainFormatterShared`.
|
||||
struct ChainFormatterShared<'a> {
|
||||
// The current working set of child items.
|
||||
children: &'a [ChainItem],
|
||||
// The current rewrites of items (includes trailing `?`s, but not any way to
|
||||
// connect the rewrites together).
|
||||
rewrites: Vec<String>,
|
||||
// Whether the chain can fit on one line.
|
||||
fits_single_line: bool,
|
||||
// The number of children in the chain. This is not equal to `self.children.len()`
|
||||
// because `self.children` will change size as we process the chain.
|
||||
child_count: usize,
|
||||
}
|
||||
|
||||
impl<'a> ChainFormatterShared<'a> {
|
||||
fn new(chain: &'a Chain) -> ChainFormatterShared<'a> {
|
||||
ChainFormatterShared {
|
||||
children: &chain.children,
|
||||
rewrites: Vec::with_capacity(chain.children.len() + 1),
|
||||
fits_single_line: false,
|
||||
child_count: chain.children.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pure_root(&mut self) -> Option<String> {
|
||||
if self.children.is_empty() {
|
||||
assert_eq!(self.rewrites.len(), 1);
|
||||
Some(self.rewrites.pop().unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite the last child. The last child of a chain requires special treatment. We need to
|
||||
// know whether 'overflowing' the last child make a better formatting:
|
||||
//
|
||||
// A chain with overflowing the last child:
|
||||
// ```text
|
||||
// parent.child1.child2.last_child(
|
||||
// a,
|
||||
// b,
|
||||
// c,
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// A chain without overflowing the last child (in vertical layout):
|
||||
// ```text
|
||||
// parent
|
||||
// .child1
|
||||
// .child2
|
||||
// .last_child(a, b, c)
|
||||
// ```
|
||||
//
|
||||
// In particular, overflowing is effective when the last child is a method with a multi-lined
|
||||
// block-like argument (e.g., closure):
|
||||
// ```text
|
||||
// parent.child1.child2.last_child(|a, b, c| {
|
||||
// let x = foo(a, b, c);
|
||||
// let y = bar(a, b, c);
|
||||
//
|
||||
// // ...
|
||||
//
|
||||
// result
|
||||
// })
|
||||
// ```
|
||||
fn format_last_child(
|
||||
&mut self,
|
||||
may_extend: bool,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
child_shape: Shape,
|
||||
) -> Option<()> {
|
||||
let last = self.children.last()?;
|
||||
let extendable = may_extend && last_line_extendable(&self.rewrites[0]);
|
||||
let prev_last_line_width = last_line_width(&self.rewrites[0]);
|
||||
|
||||
// Total of all items excluding the last.
|
||||
let almost_total = if extendable {
|
||||
prev_last_line_width
|
||||
} else {
|
||||
self.rewrites
|
||||
.iter()
|
||||
.map(|rw| utils::unicode_str_width(&rw))
|
||||
.sum()
|
||||
} + last.tries;
|
||||
let one_line_budget = if self.child_count == 1 {
|
||||
shape.width
|
||||
} else {
|
||||
min(shape.width, context.config.chain_width())
|
||||
}
|
||||
.saturating_sub(almost_total);
|
||||
|
||||
let all_in_one_line = !self.children.iter().any(ChainItem::is_comment)
|
||||
&& self.rewrites.iter().all(|s| !s.contains('\n'))
|
||||
&& one_line_budget > 0;
|
||||
let last_shape = if all_in_one_line {
|
||||
shape.sub_width(last.tries)?
|
||||
} else if extendable {
|
||||
child_shape.sub_width(last.tries)?
|
||||
} else {
|
||||
child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)?
|
||||
};
|
||||
|
||||
let mut last_subexpr_str = None;
|
||||
if all_in_one_line || extendable {
|
||||
// First we try to 'overflow' the last child and see if it looks better than using
|
||||
// vertical layout.
|
||||
let one_line_shape = if context.use_block_indent() {
|
||||
last_shape.offset_left(almost_total)
|
||||
} else {
|
||||
last_shape
|
||||
.visual_indent(almost_total)
|
||||
.sub_width(almost_total)
|
||||
};
|
||||
|
||||
if let Some(one_line_shape) = one_line_shape {
|
||||
if let Some(rw) = last.rewrite(context, one_line_shape) {
|
||||
// We allow overflowing here only if both of the following conditions match:
|
||||
// 1. The entire chain fits in a single line except the last child.
|
||||
// 2. `last_child_str.lines().count() >= 5`.
|
||||
let line_count = rw.lines().count();
|
||||
let could_fit_single_line = first_line_width(&rw) <= one_line_budget;
|
||||
if could_fit_single_line && line_count >= 5 {
|
||||
last_subexpr_str = Some(rw);
|
||||
self.fits_single_line = all_in_one_line;
|
||||
} else {
|
||||
// We could not know whether overflowing is better than using vertical
|
||||
// layout, just by looking at the overflowed rewrite. Now we rewrite the
|
||||
// last child on its own line, and compare two rewrites to choose which is
|
||||
// better.
|
||||
let last_shape = child_shape
|
||||
.sub_width(shape.rhs_overhead(context.config) + last.tries)?;
|
||||
match last.rewrite(context, last_shape) {
|
||||
Some(ref new_rw) if !could_fit_single_line => {
|
||||
last_subexpr_str = Some(new_rw.clone());
|
||||
}
|
||||
Some(ref new_rw) if new_rw.lines().count() >= line_count => {
|
||||
last_subexpr_str = Some(rw);
|
||||
self.fits_single_line = could_fit_single_line && all_in_one_line;
|
||||
}
|
||||
new_rw @ Some(..) => {
|
||||
last_subexpr_str = new_rw;
|
||||
}
|
||||
_ => {
|
||||
last_subexpr_str = Some(rw);
|
||||
self.fits_single_line = could_fit_single_line && all_in_one_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let last_shape = if context.use_block_indent() {
|
||||
last_shape
|
||||
} else {
|
||||
child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)?
|
||||
};
|
||||
|
||||
last_subexpr_str = last_subexpr_str.or_else(|| last.rewrite(context, last_shape));
|
||||
self.rewrites.push(last_subexpr_str?);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<String> {
|
||||
let connector = if self.fits_single_line {
|
||||
// Yay, we can put everything on one line.
|
||||
Cow::from("")
|
||||
} else {
|
||||
// Use new lines.
|
||||
if context.force_one_line_chain.get() {
|
||||
return None;
|
||||
}
|
||||
child_shape.to_string_with_newline(context.config)
|
||||
};
|
||||
|
||||
let mut rewrite_iter = self.rewrites.iter();
|
||||
let mut result = rewrite_iter.next().unwrap().clone();
|
||||
let children_iter = self.children.iter();
|
||||
let iter = rewrite_iter.zip(children_iter);
|
||||
|
||||
for (rewrite, chain_item) in iter {
|
||||
match chain_item.kind {
|
||||
ChainItemKind::Comment(_, CommentPosition::Back) => result.push(' '),
|
||||
ChainItemKind::Comment(_, CommentPosition::Top) => result.push_str(&connector),
|
||||
_ => result.push_str(&connector),
|
||||
}
|
||||
result.push_str(&rewrite);
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
// Formats a chain using block indent.
|
||||
struct ChainFormatterBlock<'a> {
|
||||
shared: ChainFormatterShared<'a>,
|
||||
root_ends_with_block: bool,
|
||||
}
|
||||
|
||||
impl<'a> ChainFormatterBlock<'a> {
|
||||
fn new(chain: &'a Chain) -> ChainFormatterBlock<'a> {
|
||||
ChainFormatterBlock {
|
||||
shared: ChainFormatterShared::new(chain),
|
||||
root_ends_with_block: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ChainFormatter for ChainFormatterBlock<'a> {
|
||||
fn format_root(
|
||||
&mut self,
|
||||
parent: &ChainItem,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<()> {
|
||||
let mut root_rewrite: String = parent.rewrite(context, shape)?;
|
||||
|
||||
let mut root_ends_with_block = parent.kind.is_block_like(context, &root_rewrite);
|
||||
let tab_width = context.config.tab_spaces().saturating_sub(shape.offset);
|
||||
|
||||
while root_rewrite.len() <= tab_width && !root_rewrite.contains('\n') {
|
||||
let item = &self.shared.children[0];
|
||||
if let ChainItemKind::Comment(..) = item.kind {
|
||||
break;
|
||||
}
|
||||
let shape = shape.offset_left(root_rewrite.len())?;
|
||||
match &item.rewrite(context, shape) {
|
||||
Some(rewrite) => root_rewrite.push_str(rewrite),
|
||||
None => break,
|
||||
}
|
||||
|
||||
root_ends_with_block = last_line_extendable(&root_rewrite);
|
||||
|
||||
self.shared.children = &self.shared.children[1..];
|
||||
if self.shared.children.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.shared.rewrites.push(root_rewrite);
|
||||
self.root_ends_with_block = root_ends_with_block;
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> {
|
||||
Some(
|
||||
if self.root_ends_with_block {
|
||||
shape.block_indent(0)
|
||||
} else {
|
||||
shape.block_indent(context.config.tab_spaces())
|
||||
}
|
||||
.with_max_width(context.config),
|
||||
)
|
||||
}
|
||||
|
||||
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
|
||||
for item in &self.shared.children[..self.shared.children.len() - 1] {
|
||||
let rewrite = item.rewrite(context, child_shape)?;
|
||||
self.shared.rewrites.push(rewrite);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn format_last_child(
|
||||
&mut self,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
child_shape: Shape,
|
||||
) -> Option<()> {
|
||||
self.shared
|
||||
.format_last_child(true, context, shape, child_shape)
|
||||
}
|
||||
|
||||
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<String> {
|
||||
self.shared.join_rewrites(context, child_shape)
|
||||
}
|
||||
|
||||
fn pure_root(&mut self) -> Option<String> {
|
||||
self.shared.pure_root()
|
||||
}
|
||||
}
|
||||
|
||||
// Format a chain using visual indent.
|
||||
struct ChainFormatterVisual<'a> {
|
||||
shared: ChainFormatterShared<'a>,
|
||||
// The extra offset from the chain's shape to the position of the `.`
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<'a> ChainFormatterVisual<'a> {
|
||||
fn new(chain: &'a Chain) -> ChainFormatterVisual<'a> {
|
||||
ChainFormatterVisual {
|
||||
shared: ChainFormatterShared::new(chain),
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ChainFormatter for ChainFormatterVisual<'a> {
|
||||
fn format_root(
|
||||
&mut self,
|
||||
parent: &ChainItem,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<()> {
|
||||
let parent_shape = shape.visual_indent(0);
|
||||
let mut root_rewrite = parent.rewrite(context, parent_shape)?;
|
||||
let multiline = root_rewrite.contains('\n');
|
||||
self.offset = if multiline {
|
||||
last_line_width(&root_rewrite).saturating_sub(shape.used_width())
|
||||
} else {
|
||||
trimmed_last_line_width(&root_rewrite)
|
||||
};
|
||||
|
||||
if !multiline || parent.kind.is_block_like(context, &root_rewrite) {
|
||||
let item = &self.shared.children[0];
|
||||
if let ChainItemKind::Comment(..) = item.kind {
|
||||
self.shared.rewrites.push(root_rewrite);
|
||||
return Some(());
|
||||
}
|
||||
let child_shape = parent_shape
|
||||
.visual_indent(self.offset)
|
||||
.sub_width(self.offset)?;
|
||||
let rewrite = item.rewrite(context, child_shape)?;
|
||||
match wrap_str(rewrite, context.config.max_width(), shape) {
|
||||
Some(rewrite) => root_rewrite.push_str(&rewrite),
|
||||
None => {
|
||||
// We couldn't fit in at the visual indent, try the last
|
||||
// indent.
|
||||
let rewrite = item.rewrite(context, parent_shape)?;
|
||||
root_rewrite.push_str(&rewrite);
|
||||
self.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
self.shared.children = &self.shared.children[1..];
|
||||
}
|
||||
|
||||
self.shared.rewrites.push(root_rewrite);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> {
|
||||
shape
|
||||
.with_max_width(context.config)
|
||||
.offset_left(self.offset)
|
||||
.map(|s| s.visual_indent(0))
|
||||
}
|
||||
|
||||
fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> {
|
||||
for item in &self.shared.children[..self.shared.children.len() - 1] {
|
||||
let rewrite = item.rewrite(context, child_shape)?;
|
||||
self.shared.rewrites.push(rewrite);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn format_last_child(
|
||||
&mut self,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
child_shape: Shape,
|
||||
) -> Option<()> {
|
||||
self.shared
|
||||
.format_last_child(false, context, shape, child_shape)
|
||||
}
|
||||
|
||||
fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<String> {
|
||||
self.shared.join_rewrites(context, child_shape)
|
||||
}
|
||||
|
||||
fn pure_root(&mut self) -> Option<String> {
|
||||
self.shared.pure_root()
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes try operators (`?`s) that appear in the given string. If removing
|
||||
/// them leaves an empty line, remove that line as well unless it is the first
|
||||
/// line (we need the first newline for detecting pre/post comment).
|
||||
fn trim_tries(s: &str) -> String {
|
||||
let mut result = String::with_capacity(s.len());
|
||||
let mut line_buffer = String::with_capacity(s.len());
|
||||
for (kind, rich_char) in CharClasses::new(s.chars()) {
|
||||
match rich_char.get_char() {
|
||||
'\n' => {
|
||||
if result.is_empty() || !line_buffer.trim().is_empty() {
|
||||
result.push_str(&line_buffer);
|
||||
result.push('\n')
|
||||
}
|
||||
line_buffer.clear();
|
||||
}
|
||||
'?' if kind == FullCodeCharKind::Normal => continue,
|
||||
c => line_buffer.push(c),
|
||||
}
|
||||
}
|
||||
if !line_buffer.trim().is_empty() {
|
||||
result.push_str(&line_buffer);
|
||||
}
|
||||
result
|
||||
}
|
||||
429
src/tools/rustfmt/src/closures.rs
Normal file
429
src/tools/rustfmt/src/closures.rs
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
use rustc_ast::{ast, ptr};
|
||||
use rustc_span::Span;
|
||||
|
||||
use crate::attr::get_attrs_from_stmt;
|
||||
use crate::config::lists::*;
|
||||
use crate::config::Version;
|
||||
use crate::expr::{block_contains_comment, is_simple_block, is_unsafe_block, rewrite_cond};
|
||||
use crate::items::{span_hi_for_param, span_lo_for_param};
|
||||
use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator};
|
||||
use crate::overflow::OverflowableItem;
|
||||
use crate::rewrite::{Rewrite, RewriteContext};
|
||||
use crate::shape::Shape;
|
||||
use crate::source_map::SpanUtils;
|
||||
use crate::utils::{last_line_width, left_most_sub_expr, stmt_expr, NodeIdExt};
|
||||
|
||||
// This module is pretty messy because of the rules around closures and blocks:
|
||||
// FIXME - the below is probably no longer true in full.
|
||||
// * if there is a return type, then there must be braces,
|
||||
// * given a closure with braces, whether that is parsed to give an inner block
|
||||
// or not depends on if there is a return type and if there are statements
|
||||
// in that block,
|
||||
// * if the first expression in the body ends with a block (i.e., is a
|
||||
// statement without needing a semi-colon), then adding or removing braces
|
||||
// can change whether it is treated as an expression or statement.
|
||||
|
||||
pub(crate) fn rewrite_closure(
|
||||
capture: ast::CaptureBy,
|
||||
is_async: &ast::Async,
|
||||
movability: ast::Movability,
|
||||
fn_decl: &ast::FnDecl,
|
||||
body: &ast::Expr,
|
||||
span: Span,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
debug!("rewrite_closure {:?}", body);
|
||||
|
||||
let (prefix, extra_offset) = rewrite_closure_fn_decl(
|
||||
capture, is_async, movability, fn_decl, body, span, context, shape,
|
||||
)?;
|
||||
// 1 = space between `|...|` and body.
|
||||
let body_shape = shape.offset_left(extra_offset)?;
|
||||
|
||||
if let ast::ExprKind::Block(ref block, _) = body.kind {
|
||||
// The body of the closure is an empty block.
|
||||
if block.stmts.is_empty() && !block_contains_comment(context, block) {
|
||||
return body
|
||||
.rewrite(context, shape)
|
||||
.map(|s| format!("{} {}", prefix, s));
|
||||
}
|
||||
|
||||
let result = match fn_decl.output {
|
||||
ast::FnRetTy::Default(_) if !context.inside_macro() => {
|
||||
try_rewrite_without_block(body, &prefix, context, shape, body_shape)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
result.or_else(|| {
|
||||
// Either we require a block, or tried without and failed.
|
||||
rewrite_closure_block(block, &prefix, context, body_shape)
|
||||
})
|
||||
} else {
|
||||
rewrite_closure_expr(body, &prefix, context, body_shape).or_else(|| {
|
||||
// The closure originally had a non-block expression, but we can't fit on
|
||||
// one line, so we'll insert a block.
|
||||
rewrite_closure_with_block(body, &prefix, context, body_shape)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn try_rewrite_without_block(
|
||||
expr: &ast::Expr,
|
||||
prefix: &str,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
body_shape: Shape,
|
||||
) -> Option<String> {
|
||||
let expr = get_inner_expr(expr, prefix, context);
|
||||
|
||||
if is_block_closure_forced(context, expr) {
|
||||
rewrite_closure_with_block(expr, prefix, context, shape)
|
||||
} else {
|
||||
rewrite_closure_expr(expr, prefix, context, body_shape)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inner_expr<'a>(
|
||||
expr: &'a ast::Expr,
|
||||
prefix: &str,
|
||||
context: &RewriteContext<'_>,
|
||||
) -> &'a ast::Expr {
|
||||
if let ast::ExprKind::Block(ref block, _) = expr.kind {
|
||||
if !needs_block(block, prefix, context) {
|
||||
// block.stmts.len() == 1 except with `|| {{}}`;
|
||||
// https://github.com/rust-lang/rustfmt/issues/3844
|
||||
if let Some(expr) = block.stmts.first().and_then(stmt_expr) {
|
||||
return get_inner_expr(expr, prefix, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
// Figure out if a block is necessary.
|
||||
fn needs_block(block: &ast::Block, prefix: &str, context: &RewriteContext<'_>) -> bool {
|
||||
let has_attributes = block.stmts.first().map_or(false, |first_stmt| {
|
||||
!get_attrs_from_stmt(first_stmt).is_empty()
|
||||
});
|
||||
|
||||
is_unsafe_block(block)
|
||||
|| block.stmts.len() > 1
|
||||
|| has_attributes
|
||||
|| block_contains_comment(context, block)
|
||||
|| prefix.contains('\n')
|
||||
}
|
||||
|
||||
fn veto_block(e: &ast::Expr) -> bool {
|
||||
match e.kind {
|
||||
ast::ExprKind::Call(..)
|
||||
| ast::ExprKind::Binary(..)
|
||||
| ast::ExprKind::Cast(..)
|
||||
| ast::ExprKind::Type(..)
|
||||
| ast::ExprKind::Assign(..)
|
||||
| ast::ExprKind::AssignOp(..)
|
||||
| ast::ExprKind::Field(..)
|
||||
| ast::ExprKind::Index(..)
|
||||
| ast::ExprKind::Range(..)
|
||||
| ast::ExprKind::Try(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite closure with a single expression wrapping its body with block.
|
||||
// || { #[attr] foo() } -> Block { #[attr] foo() }
|
||||
fn rewrite_closure_with_block(
|
||||
body: &ast::Expr,
|
||||
prefix: &str,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
let left_most = left_most_sub_expr(body);
|
||||
let veto_block = veto_block(body) && !expr_requires_semi_to_be_stmt(left_most);
|
||||
if veto_block {
|
||||
return None;
|
||||
}
|
||||
|
||||
let block = ast::Block {
|
||||
stmts: vec![ast::Stmt {
|
||||
id: ast::NodeId::root(),
|
||||
kind: ast::StmtKind::Expr(ptr::P(body.clone())),
|
||||
span: body.span,
|
||||
}],
|
||||
id: ast::NodeId::root(),
|
||||
rules: ast::BlockCheckMode::Default,
|
||||
tokens: None,
|
||||
span: body
|
||||
.attrs
|
||||
.first()
|
||||
.map(|attr| attr.span.to(body.span))
|
||||
.unwrap_or(body.span),
|
||||
};
|
||||
let block = crate::expr::rewrite_block_with_visitor(
|
||||
context,
|
||||
"",
|
||||
&block,
|
||||
Some(&body.attrs),
|
||||
None,
|
||||
shape,
|
||||
false,
|
||||
)?;
|
||||
Some(format!("{} {}", prefix, block))
|
||||
}
|
||||
|
||||
// Rewrite closure with a single expression without wrapping its body with block.
|
||||
fn rewrite_closure_expr(
|
||||
expr: &ast::Expr,
|
||||
prefix: &str,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
fn allow_multi_line(expr: &ast::Expr) -> bool {
|
||||
match expr.kind {
|
||||
ast::ExprKind::Match(..)
|
||||
| ast::ExprKind::Async(..)
|
||||
| ast::ExprKind::Block(..)
|
||||
| ast::ExprKind::TryBlock(..)
|
||||
| ast::ExprKind::Loop(..)
|
||||
| ast::ExprKind::Struct(..) => true,
|
||||
|
||||
ast::ExprKind::AddrOf(_, _, ref expr)
|
||||
| ast::ExprKind::Box(ref expr)
|
||||
| ast::ExprKind::Try(ref expr)
|
||||
| ast::ExprKind::Unary(_, ref expr)
|
||||
| ast::ExprKind::Cast(ref expr, _) => allow_multi_line(expr),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// When rewriting closure's body without block, we require it to fit in a single line
|
||||
// unless it is a block-like expression or we are inside macro call.
|
||||
let veto_multiline = (!allow_multi_line(expr) && !context.inside_macro())
|
||||
|| context.config.force_multiline_blocks();
|
||||
expr.rewrite(context, shape)
|
||||
.and_then(|rw| {
|
||||
if veto_multiline && rw.contains('\n') {
|
||||
None
|
||||
} else {
|
||||
Some(rw)
|
||||
}
|
||||
})
|
||||
.map(|rw| format!("{} {}", prefix, rw))
|
||||
}
|
||||
|
||||
// Rewrite closure whose body is block.
|
||||
fn rewrite_closure_block(
|
||||
block: &ast::Block,
|
||||
prefix: &str,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
Some(format!("{} {}", prefix, block.rewrite(context, shape)?))
|
||||
}
|
||||
|
||||
// Return type is (prefix, extra_offset)
|
||||
fn rewrite_closure_fn_decl(
|
||||
capture: ast::CaptureBy,
|
||||
asyncness: &ast::Async,
|
||||
movability: ast::Movability,
|
||||
fn_decl: &ast::FnDecl,
|
||||
body: &ast::Expr,
|
||||
span: Span,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<(String, usize)> {
|
||||
let is_async = if asyncness.is_async() { "async " } else { "" };
|
||||
let mover = if capture == ast::CaptureBy::Value {
|
||||
"move "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let immovable = if movability == ast::Movability::Static {
|
||||
"static "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
// 4 = "|| {".len(), which is overconservative when the closure consists of
|
||||
// a single expression.
|
||||
let nested_shape = shape
|
||||
.shrink_left(is_async.len() + mover.len() + immovable.len())?
|
||||
.sub_width(4)?;
|
||||
|
||||
// 1 = |
|
||||
let param_offset = nested_shape.indent + 1;
|
||||
let param_shape = nested_shape.offset_left(1)?.visual_indent(0);
|
||||
let ret_str = fn_decl.output.rewrite(context, param_shape)?;
|
||||
|
||||
let param_items = itemize_list(
|
||||
context.snippet_provider,
|
||||
fn_decl.inputs.iter(),
|
||||
"|",
|
||||
",",
|
||||
|param| span_lo_for_param(param),
|
||||
|param| span_hi_for_param(context, param),
|
||||
|param| param.rewrite(context, param_shape),
|
||||
context.snippet_provider.span_after(span, "|"),
|
||||
body.span.lo(),
|
||||
false,
|
||||
);
|
||||
let item_vec = param_items.collect::<Vec<_>>();
|
||||
// 1 = space between parameters and return type.
|
||||
let horizontal_budget = nested_shape.width.saturating_sub(ret_str.len() + 1);
|
||||
let tactic = definitive_tactic(
|
||||
&item_vec,
|
||||
ListTactic::HorizontalVertical,
|
||||
Separator::Comma,
|
||||
horizontal_budget,
|
||||
);
|
||||
let param_shape = match tactic {
|
||||
DefinitiveListTactic::Horizontal => param_shape.sub_width(ret_str.len() + 1)?,
|
||||
_ => param_shape,
|
||||
};
|
||||
|
||||
let fmt = ListFormatting::new(param_shape, context.config)
|
||||
.tactic(tactic)
|
||||
.preserve_newline(true);
|
||||
let list_str = write_list(&item_vec, &fmt)?;
|
||||
let mut prefix = format!("{}{}{}|{}|", is_async, immovable, mover, list_str);
|
||||
|
||||
if !ret_str.is_empty() {
|
||||
if prefix.contains('\n') {
|
||||
prefix.push('\n');
|
||||
prefix.push_str(¶m_offset.to_string(context.config));
|
||||
} else {
|
||||
prefix.push(' ');
|
||||
}
|
||||
prefix.push_str(&ret_str);
|
||||
}
|
||||
// 1 = space between `|...|` and body.
|
||||
let extra_offset = last_line_width(&prefix) + 1;
|
||||
|
||||
Some((prefix, extra_offset))
|
||||
}
|
||||
|
||||
// Rewriting closure which is placed at the end of the function call's arg.
|
||||
// Returns `None` if the reformatted closure 'looks bad'.
|
||||
pub(crate) fn rewrite_last_closure(
|
||||
context: &RewriteContext<'_>,
|
||||
expr: &ast::Expr,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
if let ast::ExprKind::Closure(capture, ref is_async, movability, ref fn_decl, ref body, _) =
|
||||
expr.kind
|
||||
{
|
||||
let body = match body.kind {
|
||||
ast::ExprKind::Block(ref block, _)
|
||||
if !is_unsafe_block(block)
|
||||
&& !context.inside_macro()
|
||||
&& is_simple_block(context, block, Some(&body.attrs)) =>
|
||||
{
|
||||
stmt_expr(&block.stmts[0]).unwrap_or(body)
|
||||
}
|
||||
_ => body,
|
||||
};
|
||||
let (prefix, extra_offset) = rewrite_closure_fn_decl(
|
||||
capture, is_async, movability, fn_decl, body, expr.span, context, shape,
|
||||
)?;
|
||||
// If the closure goes multi line before its body, do not overflow the closure.
|
||||
if prefix.contains('\n') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let body_shape = shape.offset_left(extra_offset)?;
|
||||
|
||||
// We force to use block for the body of the closure for certain kinds of expressions.
|
||||
if is_block_closure_forced(context, body) {
|
||||
return rewrite_closure_with_block(body, &prefix, context, body_shape).and_then(
|
||||
|body_str| {
|
||||
match fn_decl.output {
|
||||
ast::FnRetTy::Default(..) if body_str.lines().count() <= 7 => {
|
||||
// If the expression can fit in a single line, we need not force block
|
||||
// closure. However, if the closure has a return type, then we must
|
||||
// keep the blocks.
|
||||
match rewrite_closure_expr(body, &prefix, context, shape) {
|
||||
Some(ref single_line_body_str)
|
||||
if !single_line_body_str.contains('\n') =>
|
||||
{
|
||||
Some(single_line_body_str.clone())
|
||||
}
|
||||
_ => Some(body_str),
|
||||
}
|
||||
}
|
||||
_ => Some(body_str),
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// When overflowing the closure which consists of a single control flow expression,
|
||||
// force to use block if its condition uses multi line.
|
||||
let is_multi_lined_cond = rewrite_cond(context, body, body_shape).map_or(false, |cond| {
|
||||
cond.contains('\n') || cond.len() > body_shape.width
|
||||
});
|
||||
if is_multi_lined_cond {
|
||||
return rewrite_closure_with_block(body, &prefix, context, body_shape);
|
||||
}
|
||||
|
||||
// Seems fine, just format the closure in usual manner.
|
||||
return expr.rewrite(context, shape);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns `true` if the given vector of arguments has more than one `ast::ExprKind::Closure`.
|
||||
pub(crate) fn args_have_many_closure(args: &[OverflowableItem<'_>]) -> bool {
|
||||
args.iter()
|
||||
.filter_map(OverflowableItem::to_expr)
|
||||
.filter(|expr| match expr.kind {
|
||||
ast::ExprKind::Closure(..) => true,
|
||||
_ => false,
|
||||
})
|
||||
.count()
|
||||
> 1
|
||||
}
|
||||
|
||||
fn is_block_closure_forced(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool {
|
||||
// If we are inside macro, we do not want to add or remove block from closure body.
|
||||
if context.inside_macro() {
|
||||
false
|
||||
} else {
|
||||
is_block_closure_forced_inner(expr, context.config.version())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_block_closure_forced_inner(expr: &ast::Expr, version: Version) -> bool {
|
||||
match expr.kind {
|
||||
ast::ExprKind::If(..) | ast::ExprKind::While(..) | ast::ExprKind::ForLoop(..) => true,
|
||||
ast::ExprKind::Loop(..) if version == Version::Two => true,
|
||||
ast::ExprKind::AddrOf(_, _, ref expr)
|
||||
| ast::ExprKind::Box(ref expr)
|
||||
| ast::ExprKind::Try(ref expr)
|
||||
| ast::ExprKind::Unary(_, ref expr)
|
||||
| ast::ExprKind::Cast(ref expr, _) => is_block_closure_forced_inner(expr, version),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this expression require a semicolon to be treated
|
||||
/// as a statement? The negation of this: 'can this expression
|
||||
/// be used as a statement without a semicolon' -- is used
|
||||
/// as an early-bail-out in the parser so that, for instance,
|
||||
/// if true {...} else {...}
|
||||
/// |x| 5
|
||||
/// isn't parsed as (if true {...} else {...} | x) | 5
|
||||
// From https://github.com/rust-lang/rust/blob/master/src/libsyntax/parse/classify.rs.
|
||||
fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
|
||||
match e.kind {
|
||||
ast::ExprKind::If(..)
|
||||
| ast::ExprKind::Match(..)
|
||||
| ast::ExprKind::Block(..)
|
||||
| ast::ExprKind::While(..)
|
||||
| ast::ExprKind::Loop(..)
|
||||
| ast::ExprKind::ForLoop(..)
|
||||
| ast::ExprKind::TryBlock(..) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
1920
src/tools/rustfmt/src/comment.rs
Normal file
1920
src/tools/rustfmt/src/comment.rs
Normal file
File diff suppressed because it is too large
Load diff
448
src/tools/rustfmt/src/config/config_type.rs
Normal file
448
src/tools/rustfmt/src/config/config_type.rs
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
use crate::config::file_lines::FileLines;
|
||||
use crate::config::options::{IgnoreList, WidthHeuristics};
|
||||
|
||||
/// Trait for types that can be used in `Config`.
|
||||
pub(crate) trait ConfigType: Sized {
|
||||
/// Returns hint text for use in `Config::print_docs()`. For enum types, this is a
|
||||
/// pipe-separated list of variants; for other types it returns "<type>".
|
||||
fn doc_hint() -> String;
|
||||
}
|
||||
|
||||
impl ConfigType for bool {
|
||||
fn doc_hint() -> String {
|
||||
String::from("<boolean>")
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigType for usize {
|
||||
fn doc_hint() -> String {
|
||||
String::from("<unsigned integer>")
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigType for isize {
|
||||
fn doc_hint() -> String {
|
||||
String::from("<signed integer>")
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigType for String {
|
||||
fn doc_hint() -> String {
|
||||
String::from("<string>")
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigType for FileLines {
|
||||
fn doc_hint() -> String {
|
||||
String::from("<json>")
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigType for WidthHeuristics {
|
||||
fn doc_hint() -> String {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigType for IgnoreList {
|
||||
fn doc_hint() -> String {
|
||||
String::from("[<string>,..]")
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! create_config {
|
||||
($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
|
||||
#[cfg(test)]
|
||||
use std::collections::HashSet;
|
||||
use std::io::Write;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(unreachable_pub)]
|
||||
pub struct Config {
|
||||
// if a license_template_path has been specified, successfully read, parsed and compiled
|
||||
// into a regex, it will be stored here
|
||||
pub license_template: Option<Regex>,
|
||||
// For each config item, we store a bool indicating whether it has
|
||||
// been accessed and the value, and a bool whether the option was
|
||||
// manually initialised, or taken from the default,
|
||||
$($i: (Cell<bool>, bool, $ty, bool)),+
|
||||
}
|
||||
|
||||
// Just like the Config struct but with each property wrapped
|
||||
// as Option<T>. This is used to parse a rustfmt.toml that doesn't
|
||||
// specify all properties of `Config`.
|
||||
// We first parse into `PartialConfig`, then create a default `Config`
|
||||
// and overwrite the properties with corresponding values from `PartialConfig`.
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
#[allow(unreachable_pub)]
|
||||
pub struct PartialConfig {
|
||||
$(pub $i: Option<$ty>),+
|
||||
}
|
||||
|
||||
// Macro hygiene won't allow us to make `set_$i()` methods on Config
|
||||
// for each item, so this struct is used to give the API to set values:
|
||||
// `config.set().option(false)`. It's pretty ugly. Consider replacing
|
||||
// with `config.set_option(false)` if we ever get a stable/usable
|
||||
// `concat_idents!()`.
|
||||
#[allow(unreachable_pub)]
|
||||
pub struct ConfigSetter<'a>(&'a mut Config);
|
||||
|
||||
impl<'a> ConfigSetter<'a> {
|
||||
$(
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn $i(&mut self, value: $ty) {
|
||||
(self.0).$i.2 = value;
|
||||
match stringify!($i) {
|
||||
"max_width"
|
||||
| "use_small_heuristics"
|
||||
| "fn_call_width"
|
||||
| "single_line_if_else_max_width"
|
||||
| "attr_fn_like_width"
|
||||
| "struct_lit_width"
|
||||
| "struct_variant_width"
|
||||
| "array_width"
|
||||
| "chain_width" => self.0.set_heuristics(),
|
||||
"license_template_path" => self.0.set_license_template(),
|
||||
"merge_imports" => self.0.set_merge_imports(),
|
||||
&_ => (),
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
|
||||
// Query each option, returns true if the user set the option, false if
|
||||
// a default was used.
|
||||
#[allow(unreachable_pub)]
|
||||
pub struct ConfigWasSet<'a>(&'a Config);
|
||||
|
||||
impl<'a> ConfigWasSet<'a> {
|
||||
$(
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn $i(&self) -> bool {
|
||||
(self.0).$i.1
|
||||
}
|
||||
)+
|
||||
}
|
||||
|
||||
impl Config {
|
||||
$(
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn $i(&self) -> $ty {
|
||||
self.$i.0.set(true);
|
||||
self.$i.2.clone()
|
||||
}
|
||||
)+
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn set(&mut self) -> ConfigSetter<'_> {
|
||||
ConfigSetter(self)
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn was_set(&self) -> ConfigWasSet<'_> {
|
||||
ConfigWasSet(self)
|
||||
}
|
||||
|
||||
fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
|
||||
$(
|
||||
if let Some(val) = parsed.$i {
|
||||
if self.$i.3 {
|
||||
self.$i.1 = true;
|
||||
self.$i.2 = val;
|
||||
} else {
|
||||
if crate::is_nightly_channel!() {
|
||||
self.$i.1 = true;
|
||||
self.$i.2 = val;
|
||||
} else {
|
||||
eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \
|
||||
available in nightly channel.", stringify!($i), val);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
self.set_heuristics();
|
||||
self.set_license_template();
|
||||
self.set_ignore(dir);
|
||||
self.set_merge_imports();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a hash set initialized with every user-facing config option name.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn hash_set() -> HashSet<String> {
|
||||
let mut hash_set = HashSet::new();
|
||||
$(
|
||||
hash_set.insert(stringify!($i).to_owned());
|
||||
)+
|
||||
hash_set
|
||||
}
|
||||
|
||||
pub(crate) fn is_valid_name(name: &str) -> bool {
|
||||
match name {
|
||||
$(
|
||||
stringify!($i) => true,
|
||||
)+
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn is_valid_key_val(key: &str, val: &str) -> bool {
|
||||
match key {
|
||||
$(
|
||||
stringify!($i) => val.parse::<$ty>().is_ok(),
|
||||
)+
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn used_options(&self) -> PartialConfig {
|
||||
PartialConfig {
|
||||
$(
|
||||
$i: if self.$i.0.get() {
|
||||
Some(self.$i.2.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn all_options(&self) -> PartialConfig {
|
||||
PartialConfig {
|
||||
$(
|
||||
$i: Some(self.$i.2.clone()),
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn override_value(&mut self, key: &str, val: &str)
|
||||
{
|
||||
match key {
|
||||
$(
|
||||
stringify!($i) => {
|
||||
self.$i.1 = true;
|
||||
self.$i.2 = val.parse::<$ty>()
|
||||
.expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
|
||||
stringify!($i),
|
||||
val,
|
||||
stringify!($ty)));
|
||||
}
|
||||
)+
|
||||
_ => panic!("Unknown config key in override: {}", key)
|
||||
}
|
||||
|
||||
match key {
|
||||
"max_width"
|
||||
| "use_small_heuristics"
|
||||
| "fn_call_width"
|
||||
| "single_line_if_else_max_width"
|
||||
| "attr_fn_like_width"
|
||||
| "struct_lit_width"
|
||||
| "struct_variant_width"
|
||||
| "array_width"
|
||||
| "chain_width" => self.set_heuristics(),
|
||||
"license_template_path" => self.set_license_template(),
|
||||
"merge_imports" => self.set_merge_imports(),
|
||||
&_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn is_hidden_option(name: &str) -> bool {
|
||||
const HIDE_OPTIONS: [&str; 5] =
|
||||
["verbose", "verbose_diff", "file_lines", "width_heuristics", "merge_imports"];
|
||||
HIDE_OPTIONS.contains(&name)
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
pub fn print_docs(out: &mut dyn Write, include_unstable: bool) {
|
||||
use std::cmp;
|
||||
let max = 0;
|
||||
$( let max = cmp::max(max, stringify!($i).len()+1); )+
|
||||
let space_str = " ".repeat(max);
|
||||
writeln!(out, "Configuration Options:").unwrap();
|
||||
$(
|
||||
if $stb || include_unstable {
|
||||
let name_raw = stringify!($i);
|
||||
|
||||
if !Config::is_hidden_option(name_raw) {
|
||||
let mut name_out = String::with_capacity(max);
|
||||
for _ in name_raw.len()..max-1 {
|
||||
name_out.push(' ')
|
||||
}
|
||||
name_out.push_str(name_raw);
|
||||
name_out.push(' ');
|
||||
let mut default_str = format!("{}", $def);
|
||||
if default_str.is_empty() {
|
||||
default_str = String::from("\"\"");
|
||||
}
|
||||
writeln!(out,
|
||||
"{}{} Default: {}{}",
|
||||
name_out,
|
||||
<$ty>::doc_hint(),
|
||||
default_str,
|
||||
if !$stb { " (unstable)" } else { "" }).unwrap();
|
||||
$(
|
||||
writeln!(out, "{}{}", space_str, $dstring).unwrap();
|
||||
)+
|
||||
writeln!(out).unwrap();
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
|
||||
fn set_width_heuristics(&mut self, heuristics: WidthHeuristics) {
|
||||
let max_width = self.max_width.2;
|
||||
let get_width_value = |
|
||||
was_set: bool,
|
||||
override_value: usize,
|
||||
heuristic_value: usize,
|
||||
config_key: &str,
|
||||
| -> usize {
|
||||
if !was_set {
|
||||
return heuristic_value;
|
||||
}
|
||||
if override_value > max_width {
|
||||
eprintln!(
|
||||
"`{0}` cannot have a value that exceeds `max_width`. \
|
||||
`{0}` will be set to the same value as `max_width`",
|
||||
config_key,
|
||||
);
|
||||
return max_width;
|
||||
}
|
||||
override_value
|
||||
};
|
||||
|
||||
let fn_call_width = get_width_value(
|
||||
self.was_set().fn_call_width(),
|
||||
self.fn_call_width.2,
|
||||
heuristics.fn_call_width,
|
||||
"fn_call_width",
|
||||
);
|
||||
self.fn_call_width.2 = fn_call_width;
|
||||
|
||||
let attr_fn_like_width = get_width_value(
|
||||
self.was_set().attr_fn_like_width(),
|
||||
self.attr_fn_like_width.2,
|
||||
heuristics.attr_fn_like_width,
|
||||
"attr_fn_like_width",
|
||||
);
|
||||
self.attr_fn_like_width.2 = attr_fn_like_width;
|
||||
|
||||
let struct_lit_width = get_width_value(
|
||||
self.was_set().struct_lit_width(),
|
||||
self.struct_lit_width.2,
|
||||
heuristics.struct_lit_width,
|
||||
"struct_lit_width",
|
||||
);
|
||||
self.struct_lit_width.2 = struct_lit_width;
|
||||
|
||||
let struct_variant_width = get_width_value(
|
||||
self.was_set().struct_variant_width(),
|
||||
self.struct_variant_width.2,
|
||||
heuristics.struct_variant_width,
|
||||
"struct_variant_width",
|
||||
);
|
||||
self.struct_variant_width.2 = struct_variant_width;
|
||||
|
||||
let array_width = get_width_value(
|
||||
self.was_set().array_width(),
|
||||
self.array_width.2,
|
||||
heuristics.array_width,
|
||||
"array_width",
|
||||
);
|
||||
self.array_width.2 = array_width;
|
||||
|
||||
let chain_width = get_width_value(
|
||||
self.was_set().chain_width(),
|
||||
self.chain_width.2,
|
||||
heuristics.chain_width,
|
||||
"chain_width",
|
||||
);
|
||||
self.chain_width.2 = chain_width;
|
||||
|
||||
let single_line_if_else_max_width = get_width_value(
|
||||
self.was_set().single_line_if_else_max_width(),
|
||||
self.single_line_if_else_max_width.2,
|
||||
heuristics.single_line_if_else_max_width,
|
||||
"single_line_if_else_max_width",
|
||||
);
|
||||
self.single_line_if_else_max_width.2 = single_line_if_else_max_width;
|
||||
}
|
||||
|
||||
fn set_heuristics(&mut self) {
|
||||
let max_width = self.max_width.2;
|
||||
match self.use_small_heuristics.2 {
|
||||
Heuristics::Default =>
|
||||
self.set_width_heuristics(WidthHeuristics::scaled(max_width)),
|
||||
Heuristics::Max => self.set_width_heuristics(WidthHeuristics::set(max_width)),
|
||||
Heuristics::Off => self.set_width_heuristics(WidthHeuristics::null()),
|
||||
};
|
||||
}
|
||||
|
||||
fn set_license_template(&mut self) {
|
||||
if self.was_set().license_template_path() {
|
||||
let lt_path = self.license_template_path();
|
||||
if lt_path.len() > 0 {
|
||||
match license::load_and_compile_template(<_path) {
|
||||
Ok(re) => self.license_template = Some(re),
|
||||
Err(msg) => eprintln!("Warning for license template file {:?}: {}",
|
||||
lt_path, msg),
|
||||
}
|
||||
} else {
|
||||
self.license_template = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_ignore(&mut self, dir: &Path) {
|
||||
self.ignore.2.add_prefix(dir);
|
||||
}
|
||||
|
||||
fn set_merge_imports(&mut self) {
|
||||
if self.was_set().merge_imports() {
|
||||
eprintln!(
|
||||
"Warning: the `merge_imports` option is deprecated. \
|
||||
Use `imports_granularity=Crate` instead"
|
||||
);
|
||||
if !self.was_set().imports_granularity() {
|
||||
self.imports_granularity.2 = if self.merge_imports() {
|
||||
ImportGranularity::Crate
|
||||
} else {
|
||||
ImportGranularity::Preserve
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_pub)]
|
||||
/// Returns `true` if the config key was explicitly set and is the default value.
|
||||
pub fn is_default(&self, key: &str) -> bool {
|
||||
$(
|
||||
if let stringify!($i) = key {
|
||||
return self.$i.1 && self.$i.2 == $def;
|
||||
}
|
||||
)+
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Template for the default configuration
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
license_template: None,
|
||||
$(
|
||||
$i: (Cell::new(false), false, $def, $stb),
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
440
src/tools/rustfmt/src/config/file_lines.rs
Normal file
440
src/tools/rustfmt/src/config/file_lines.rs
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
//! This module contains types and functions to support formatting specific line ranges.
|
||||
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::{cmp, fmt, iter, str};
|
||||
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_span::{self, SourceFile};
|
||||
use serde::{ser, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json as json;
|
||||
use thiserror::Error;
|
||||
|
||||
/// A range of lines in a file, inclusive of both ends.
|
||||
pub struct LineRange {
|
||||
pub file: Lrc<SourceFile>,
|
||||
pub lo: usize,
|
||||
pub hi: usize,
|
||||
}
|
||||
|
||||
/// Defines the name of an input - either a file or stdin.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub enum FileName {
|
||||
Real(PathBuf),
|
||||
Stdin,
|
||||
}
|
||||
|
||||
impl From<rustc_span::FileName> for FileName {
|
||||
fn from(name: rustc_span::FileName) -> FileName {
|
||||
match name {
|
||||
rustc_span::FileName::Real(rustc_span::RealFileName::LocalPath(p)) => FileName::Real(p),
|
||||
rustc_span::FileName::Custom(ref f) if f == "stdin" => FileName::Stdin,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FileName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()),
|
||||
FileName::Stdin => write!(f, "stdin"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for FileName {
|
||||
fn deserialize<D>(deserializer: D) -> Result<FileName, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if s == "stdin" {
|
||||
Ok(FileName::Stdin)
|
||||
} else {
|
||||
Ok(FileName::Real(s.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for FileName {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let s = match self {
|
||||
FileName::Stdin => Ok("stdin"),
|
||||
FileName::Real(path) => path
|
||||
.to_str()
|
||||
.ok_or_else(|| ser::Error::custom("path can't be serialized as UTF-8 string")),
|
||||
};
|
||||
|
||||
s.and_then(|s| serializer.serialize_str(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl LineRange {
|
||||
pub fn file_name(&self) -> FileName {
|
||||
self.file.name.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A range that is inclusive of both ends.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize)]
|
||||
pub struct Range {
|
||||
lo: usize,
|
||||
hi: usize,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a LineRange> for Range {
|
||||
fn from(range: &'a LineRange) -> Range {
|
||||
Range::new(range.lo, range.hi)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Range {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}..{}", self.lo, self.hi)
|
||||
}
|
||||
}
|
||||
|
||||
impl Range {
|
||||
pub fn new(lo: usize, hi: usize) -> Range {
|
||||
Range { lo, hi }
|
||||
}
|
||||
|
||||
fn is_empty(self) -> bool {
|
||||
self.lo > self.hi
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn contains(self, other: Range) -> bool {
|
||||
if other.is_empty() {
|
||||
true
|
||||
} else {
|
||||
!self.is_empty() && self.lo <= other.lo && self.hi >= other.hi
|
||||
}
|
||||
}
|
||||
|
||||
fn intersects(self, other: Range) -> bool {
|
||||
if self.is_empty() || other.is_empty() {
|
||||
false
|
||||
} else {
|
||||
(self.lo <= other.hi && other.hi <= self.hi)
|
||||
|| (other.lo <= self.hi && self.hi <= other.hi)
|
||||
}
|
||||
}
|
||||
|
||||
fn adjacent_to(self, other: Range) -> bool {
|
||||
if self.is_empty() || other.is_empty() {
|
||||
false
|
||||
} else {
|
||||
self.hi + 1 == other.lo || other.hi + 1 == self.lo
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new `Range` with lines from `self` and `other` if they were adjacent or
|
||||
/// intersect; returns `None` otherwise.
|
||||
fn merge(self, other: Range) -> Option<Range> {
|
||||
if self.adjacent_to(other) || self.intersects(other) {
|
||||
Some(Range::new(
|
||||
cmp::min(self.lo, other.lo),
|
||||
cmp::max(self.hi, other.hi),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of lines in files.
|
||||
///
|
||||
/// It is represented as a multimap keyed on file names, with values a collection of
|
||||
/// non-overlapping ranges sorted by their start point. An inner `None` is interpreted to mean all
|
||||
/// lines in all files.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct FileLines(Option<HashMap<FileName, Vec<Range>>>);
|
||||
|
||||
impl fmt::Display for FileLines {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
None => write!(f, "None")?,
|
||||
Some(map) => {
|
||||
for (file_name, ranges) in map.iter() {
|
||||
write!(f, "{}: ", file_name)?;
|
||||
write!(f, "{}\n", ranges.iter().format(", "))?;
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping,
|
||||
/// and ordered by their start point.
|
||||
fn normalize_ranges(ranges: &mut HashMap<FileName, Vec<Range>>) {
|
||||
for ranges in ranges.values_mut() {
|
||||
ranges.sort();
|
||||
let mut result = vec![];
|
||||
let mut iter = ranges.iter_mut().peekable();
|
||||
while let Some(next) = iter.next() {
|
||||
let mut next = *next;
|
||||
while let Some(&&mut peek) = iter.peek() {
|
||||
if let Some(merged) = next.merge(peek) {
|
||||
iter.next().unwrap();
|
||||
next = merged;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.push(next)
|
||||
}
|
||||
*ranges = result;
|
||||
}
|
||||
}
|
||||
|
||||
impl FileLines {
|
||||
/// Creates a `FileLines` that contains all lines in all files.
|
||||
pub(crate) fn all() -> FileLines {
|
||||
FileLines(None)
|
||||
}
|
||||
|
||||
/// Returns `true` if this `FileLines` contains all lines in all files.
|
||||
pub(crate) fn is_all(&self) -> bool {
|
||||
self.0.is_none()
|
||||
}
|
||||
|
||||
pub fn from_ranges(mut ranges: HashMap<FileName, Vec<Range>>) -> FileLines {
|
||||
normalize_ranges(&mut ranges);
|
||||
FileLines(Some(ranges))
|
||||
}
|
||||
|
||||
/// Returns an iterator over the files contained in `self`.
|
||||
pub fn files(&self) -> Files<'_> {
|
||||
Files(self.0.as_ref().map(HashMap::keys))
|
||||
}
|
||||
|
||||
/// Returns JSON representation as accepted by the `--file-lines JSON` arg.
|
||||
pub fn to_json_spans(&self) -> Vec<JsonSpan> {
|
||||
match &self.0 {
|
||||
None => vec![],
|
||||
Some(file_ranges) => file_ranges
|
||||
.iter()
|
||||
.flat_map(|(file, ranges)| ranges.iter().map(move |r| (file, r)))
|
||||
.map(|(file, range)| JsonSpan {
|
||||
file: file.to_owned(),
|
||||
range: (range.lo, range.hi),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` includes all lines in all files. Otherwise runs `f` on all ranges
|
||||
/// in the designated file (if any) and returns true if `f` ever does.
|
||||
fn file_range_matches<F>(&self, file_name: &FileName, f: F) -> bool
|
||||
where
|
||||
F: FnMut(&Range) -> bool,
|
||||
{
|
||||
let map = match self.0 {
|
||||
// `None` means "all lines in all files".
|
||||
None => return true,
|
||||
Some(ref map) => map,
|
||||
};
|
||||
|
||||
match canonicalize_path_string(file_name).and_then(|file| map.get(&file)) {
|
||||
Some(ranges) => ranges.iter().any(f),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `range` is fully contained in `self`.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn contains(&self, range: &LineRange) -> bool {
|
||||
self.file_range_matches(&range.file_name(), |r| r.contains(Range::from(range)))
|
||||
}
|
||||
|
||||
/// Returns `true` if any lines in `range` are in `self`.
|
||||
pub(crate) fn intersects(&self, range: &LineRange) -> bool {
|
||||
self.file_range_matches(&range.file_name(), |r| r.intersects(Range::from(range)))
|
||||
}
|
||||
|
||||
/// Returns `true` if `line` from `file_name` is in `self`.
|
||||
pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool {
|
||||
self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line)
|
||||
}
|
||||
|
||||
/// Returns `true` if all the lines between `lo` and `hi` from `file_name` are in `self`.
|
||||
pub(crate) fn contains_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool {
|
||||
self.file_range_matches(file_name, |r| r.contains(Range::new(lo, hi)))
|
||||
}
|
||||
}
|
||||
|
||||
/// `FileLines` files iterator.
|
||||
pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, FileName, Vec<Range>>>);
|
||||
|
||||
impl<'a> iter::Iterator for Files<'a> {
|
||||
type Item = &'a FileName;
|
||||
|
||||
fn next(&mut self) -> Option<&'a FileName> {
|
||||
self.0.as_mut().and_then(Iterator::next)
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize_path_string(file: &FileName) -> Option<FileName> {
|
||||
match *file {
|
||||
FileName::Real(ref path) => path.canonicalize().ok().map(FileName::Real),
|
||||
_ => Some(file.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum FileLinesError {
|
||||
#[error("{0}")]
|
||||
Json(json::Error),
|
||||
#[error("Can't canonicalize {0}")]
|
||||
CannotCanonicalize(FileName),
|
||||
}
|
||||
|
||||
// This impl is needed for `Config::override_value` to work for use in tests.
|
||||
impl str::FromStr for FileLines {
|
||||
type Err = FileLinesError;
|
||||
|
||||
fn from_str(s: &str) -> Result<FileLines, Self::Err> {
|
||||
let v: Vec<JsonSpan> = json::from_str(s).map_err(FileLinesError::Json)?;
|
||||
let mut m = HashMap::new();
|
||||
for js in v {
|
||||
let (s, r) = JsonSpan::into_tuple(js)?;
|
||||
m.entry(s).or_insert_with(|| vec![]).push(r);
|
||||
}
|
||||
Ok(FileLines::from_ranges(m))
|
||||
}
|
||||
}
|
||||
|
||||
// For JSON decoding.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct JsonSpan {
|
||||
file: FileName,
|
||||
range: (usize, usize),
|
||||
}
|
||||
|
||||
impl JsonSpan {
|
||||
fn into_tuple(self) -> Result<(FileName, Range), FileLinesError> {
|
||||
let (lo, hi) = self.range;
|
||||
let canonical = canonicalize_path_string(&self.file)
|
||||
.ok_or_else(|| FileLinesError::CannotCanonicalize(self.file))?;
|
||||
Ok((canonical, Range::new(lo, hi)))
|
||||
}
|
||||
}
|
||||
|
||||
// This impl is needed for inclusion in the `Config` struct. We don't have a toml representation
|
||||
// for `FileLines`, so it will just panic instead.
|
||||
impl<'de> ::serde::de::Deserialize<'de> for FileLines {
|
||||
fn deserialize<D>(_: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: ::serde::de::Deserializer<'de>,
|
||||
{
|
||||
panic!(
|
||||
"FileLines cannot be deserialized from a project rustfmt.toml file: please \
|
||||
specify it via the `--file-lines` option instead"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// We also want to avoid attempting to serialize a FileLines to toml. The
|
||||
// `Config` struct should ensure this impl is never reached.
|
||||
impl ::serde::ser::Serialize for FileLines {
|
||||
fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: ::serde::ser::Serializer,
|
||||
{
|
||||
unreachable!("FileLines cannot be serialized. This is a rustfmt bug.");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Range;
|
||||
|
||||
#[test]
|
||||
fn test_range_intersects() {
|
||||
assert!(Range::new(1, 2).intersects(Range::new(1, 1)));
|
||||
assert!(Range::new(1, 2).intersects(Range::new(2, 2)));
|
||||
assert!(!Range::new(1, 2).intersects(Range::new(0, 0)));
|
||||
assert!(!Range::new(1, 2).intersects(Range::new(3, 10)));
|
||||
assert!(!Range::new(1, 3).intersects(Range::new(5, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_adjacent_to() {
|
||||
assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1)));
|
||||
assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2)));
|
||||
assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0)));
|
||||
assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10)));
|
||||
assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_contains() {
|
||||
assert!(Range::new(1, 2).contains(Range::new(1, 1)));
|
||||
assert!(Range::new(1, 2).contains(Range::new(2, 2)));
|
||||
assert!(!Range::new(1, 2).contains(Range::new(0, 0)));
|
||||
assert!(!Range::new(1, 2).contains(Range::new(3, 10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_merge() {
|
||||
assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5)));
|
||||
assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1)));
|
||||
assert_eq!(
|
||||
Some(Range::new(3, 7)),
|
||||
Range::new(3, 5).merge(Range::new(4, 7))
|
||||
);
|
||||
assert_eq!(
|
||||
Some(Range::new(3, 7)),
|
||||
Range::new(3, 5).merge(Range::new(5, 7))
|
||||
);
|
||||
assert_eq!(
|
||||
Some(Range::new(3, 7)),
|
||||
Range::new(3, 5).merge(Range::new(6, 7))
|
||||
);
|
||||
assert_eq!(
|
||||
Some(Range::new(3, 7)),
|
||||
Range::new(3, 7).merge(Range::new(4, 5))
|
||||
);
|
||||
}
|
||||
|
||||
use super::json::{self, json};
|
||||
use super::{FileLines, FileName};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
#[test]
|
||||
fn file_lines_to_json() {
|
||||
let ranges: HashMap<FileName, Vec<Range>> = [
|
||||
(
|
||||
FileName::Real(PathBuf::from("src/main.rs")),
|
||||
vec![Range::new(1, 3), Range::new(5, 7)],
|
||||
),
|
||||
(
|
||||
FileName::Real(PathBuf::from("src/lib.rs")),
|
||||
vec![Range::new(1, 7)],
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let file_lines = FileLines::from_ranges(ranges);
|
||||
let mut spans = file_lines.to_json_spans();
|
||||
spans.sort();
|
||||
let json = json::to_value(&spans).unwrap();
|
||||
assert_eq!(
|
||||
json,
|
||||
json! {[
|
||||
{"file": "src/lib.rs", "range": [1, 7]},
|
||||
{"file": "src/main.rs", "range": [1, 3]},
|
||||
{"file": "src/main.rs", "range": [5, 7]},
|
||||
]}
|
||||
);
|
||||
}
|
||||
}
|
||||
266
src/tools/rustfmt/src/config/license.rs
Normal file
266
src/tools/rustfmt/src/config/license.rs
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
|
||||
use regex;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum LicenseError {
|
||||
IO(io::Error),
|
||||
Regex(regex::Error),
|
||||
Parse(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for LicenseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
LicenseError::IO(ref err) => err.fmt(f),
|
||||
LicenseError::Regex(ref err) => err.fmt(f),
|
||||
LicenseError::Parse(ref err) => write!(f, "parsing failed, {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for LicenseError {
|
||||
fn from(err: io::Error) -> LicenseError {
|
||||
LicenseError::IO(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<regex::Error> for LicenseError {
|
||||
fn from(err: regex::Error) -> LicenseError {
|
||||
LicenseError::Regex(err)
|
||||
}
|
||||
}
|
||||
|
||||
// the template is parsed using a state machine
|
||||
enum ParsingState {
|
||||
Lit,
|
||||
LitEsc,
|
||||
// the u32 keeps track of brace nesting
|
||||
Re(u32),
|
||||
ReEsc(u32),
|
||||
Abort(String),
|
||||
}
|
||||
|
||||
use self::ParsingState::*;
|
||||
|
||||
pub(crate) struct TemplateParser {
|
||||
parsed: String,
|
||||
buffer: String,
|
||||
state: ParsingState,
|
||||
linum: u32,
|
||||
open_brace_line: u32,
|
||||
}
|
||||
|
||||
impl TemplateParser {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
parsed: "^".to_owned(),
|
||||
buffer: String::new(),
|
||||
state: Lit,
|
||||
linum: 1,
|
||||
// keeps track of last line on which a regex placeholder was started
|
||||
open_brace_line: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a license template into a string which can be turned into a regex.
|
||||
///
|
||||
/// The license template could use regex syntax directly, but that would require a lot of manual
|
||||
/// escaping, which is inconvenient. It is therefore literal by default, with optional regex
|
||||
/// subparts delimited by `{` and `}`. Additionally:
|
||||
///
|
||||
/// - to insert literal `{`, `}` or `\`, escape it with `\`
|
||||
/// - an empty regex placeholder (`{}`) is shorthand for `{.*?}`
|
||||
///
|
||||
/// This function parses this input format and builds a properly escaped *string* representation
|
||||
/// of the equivalent regular expression. It **does not** however guarantee that the returned
|
||||
/// string is a syntactically valid regular expression.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```text
|
||||
/// assert_eq!(
|
||||
/// TemplateParser::parse(
|
||||
/// r"
|
||||
/// // Copyright {\d+} The \} Rust \\ Project \{ Developers. See the {([A-Z]+)}
|
||||
/// // file at the top-level directory of this distribution and at
|
||||
/// // {}.
|
||||
/// //
|
||||
/// // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
/// // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
/// // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
/// // option. This file may not be copied, modified, or distributed
|
||||
/// // except according to those terms.
|
||||
/// "
|
||||
/// ).unwrap(),
|
||||
/// r"^
|
||||
/// // Copyright \d+ The \} Rust \\ Project \{ Developers\. See the ([A-Z]+)
|
||||
/// // file at the top\-level directory of this distribution and at
|
||||
/// // .*?\.
|
||||
/// //
|
||||
/// // Licensed under the Apache License, Version 2\.0 <LICENSE\-APACHE or
|
||||
/// // http://www\.apache\.org/licenses/LICENSE\-2\.0> or the MIT license
|
||||
/// // <LICENSE\-MIT or http://opensource\.org/licenses/MIT>, at your
|
||||
/// // option\. This file may not be copied, modified, or distributed
|
||||
/// // except according to those terms\.
|
||||
/// "
|
||||
/// );
|
||||
/// ```
|
||||
pub(crate) fn parse(template: &str) -> Result<String, LicenseError> {
|
||||
let mut parser = Self::new();
|
||||
for chr in template.chars() {
|
||||
if chr == '\n' {
|
||||
parser.linum += 1;
|
||||
}
|
||||
parser.state = match parser.state {
|
||||
Lit => parser.trans_from_lit(chr),
|
||||
LitEsc => parser.trans_from_litesc(chr),
|
||||
Re(brace_nesting) => parser.trans_from_re(chr, brace_nesting),
|
||||
ReEsc(brace_nesting) => parser.trans_from_reesc(chr, brace_nesting),
|
||||
Abort(msg) => return Err(LicenseError::Parse(msg)),
|
||||
};
|
||||
}
|
||||
// check if we've ended parsing in a valid state
|
||||
match parser.state {
|
||||
Abort(msg) => return Err(LicenseError::Parse(msg)),
|
||||
Re(_) | ReEsc(_) => {
|
||||
return Err(LicenseError::Parse(format!(
|
||||
"escape or balance opening brace on l. {}",
|
||||
parser.open_brace_line
|
||||
)));
|
||||
}
|
||||
LitEsc => {
|
||||
return Err(LicenseError::Parse(format!(
|
||||
"incomplete escape sequence on l. {}",
|
||||
parser.linum
|
||||
)));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
parser.parsed.push_str(®ex::escape(&parser.buffer));
|
||||
|
||||
Ok(parser.parsed)
|
||||
}
|
||||
|
||||
fn trans_from_lit(&mut self, chr: char) -> ParsingState {
|
||||
match chr {
|
||||
'{' => {
|
||||
self.parsed.push_str(®ex::escape(&self.buffer));
|
||||
self.buffer.clear();
|
||||
self.open_brace_line = self.linum;
|
||||
Re(1)
|
||||
}
|
||||
'}' => Abort(format!(
|
||||
"escape or balance closing brace on l. {}",
|
||||
self.linum
|
||||
)),
|
||||
'\\' => LitEsc,
|
||||
_ => {
|
||||
self.buffer.push(chr);
|
||||
Lit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn trans_from_litesc(&mut self, chr: char) -> ParsingState {
|
||||
self.buffer.push(chr);
|
||||
Lit
|
||||
}
|
||||
|
||||
fn trans_from_re(&mut self, chr: char, brace_nesting: u32) -> ParsingState {
|
||||
match chr {
|
||||
'{' => {
|
||||
self.buffer.push(chr);
|
||||
Re(brace_nesting + 1)
|
||||
}
|
||||
'}' => {
|
||||
match brace_nesting {
|
||||
1 => {
|
||||
// default regex for empty placeholder {}
|
||||
if self.buffer.is_empty() {
|
||||
self.parsed.push_str(".*?");
|
||||
} else {
|
||||
self.parsed.push_str(&self.buffer);
|
||||
}
|
||||
self.buffer.clear();
|
||||
Lit
|
||||
}
|
||||
_ => {
|
||||
self.buffer.push(chr);
|
||||
Re(brace_nesting - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
'\\' => {
|
||||
self.buffer.push(chr);
|
||||
ReEsc(brace_nesting)
|
||||
}
|
||||
_ => {
|
||||
self.buffer.push(chr);
|
||||
Re(brace_nesting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn trans_from_reesc(&mut self, chr: char, brace_nesting: u32) -> ParsingState {
|
||||
self.buffer.push(chr);
|
||||
Re(brace_nesting)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_and_compile_template(path: &str) -> Result<Regex, LicenseError> {
|
||||
let mut lt_file = File::open(&path)?;
|
||||
let mut lt_str = String::new();
|
||||
lt_file.read_to_string(&mut lt_str)?;
|
||||
let lt_parsed = TemplateParser::parse(<_str)?;
|
||||
Ok(Regex::new(<_parsed)?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::TemplateParser;
|
||||
|
||||
#[test]
|
||||
fn test_parse_license_template() {
|
||||
assert_eq!(
|
||||
TemplateParser::parse("literal (.*)").unwrap(),
|
||||
r"^literal \(\.\*\)"
|
||||
);
|
||||
assert_eq!(
|
||||
TemplateParser::parse(r"escaping \}").unwrap(),
|
||||
r"^escaping \}"
|
||||
);
|
||||
assert!(TemplateParser::parse("unbalanced } without escape").is_err());
|
||||
assert_eq!(
|
||||
TemplateParser::parse(r"{\d+} place{-?}holder{s?}").unwrap(),
|
||||
r"^\d+ place-?holders?"
|
||||
);
|
||||
assert_eq!(TemplateParser::parse("default {}").unwrap(), "^default .*?");
|
||||
assert_eq!(
|
||||
TemplateParser::parse(r"unbalanced nested braces {\{{3}}").unwrap(),
|
||||
r"^unbalanced nested braces \{{3}"
|
||||
);
|
||||
assert_eq!(
|
||||
&TemplateParser::parse("parsing error }")
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"parsing failed, escape or balance closing brace on l. 1"
|
||||
);
|
||||
assert_eq!(
|
||||
&TemplateParser::parse("parsing error {\nsecond line")
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"parsing failed, escape or balance opening brace on l. 1"
|
||||
);
|
||||
assert_eq!(
|
||||
&TemplateParser::parse(r"parsing error \")
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"parsing failed, incomplete escape sequence on l. 1"
|
||||
);
|
||||
}
|
||||
}
|
||||
92
src/tools/rustfmt/src/config/lists.rs
Normal file
92
src/tools/rustfmt/src/config/lists.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
//! Configuration options related to rewriting a list.
|
||||
|
||||
use rustfmt_config_proc_macro::config_type;
|
||||
|
||||
use crate::config::IndentStyle;
|
||||
|
||||
/// The definitive formatting tactic for lists.
|
||||
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
|
||||
pub enum DefinitiveListTactic {
|
||||
Vertical,
|
||||
Horizontal,
|
||||
Mixed,
|
||||
/// Special case tactic for `format!()`, `write!()` style macros.
|
||||
SpecialMacro(usize),
|
||||
}
|
||||
|
||||
impl DefinitiveListTactic {
|
||||
pub fn ends_with_newline(&self, indent_style: IndentStyle) -> bool {
|
||||
match indent_style {
|
||||
IndentStyle::Block => *self != DefinitiveListTactic::Horizontal,
|
||||
IndentStyle::Visual => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formatting tactic for lists. This will be cast down to a
|
||||
/// `DefinitiveListTactic` depending on the number and length of the items and
|
||||
/// their comments.
|
||||
#[config_type]
|
||||
pub enum ListTactic {
|
||||
/// One item per row.
|
||||
Vertical,
|
||||
/// All items on one row.
|
||||
Horizontal,
|
||||
/// Try Horizontal layout, if that fails then vertical.
|
||||
HorizontalVertical,
|
||||
/// HorizontalVertical with a soft limit of n characters.
|
||||
LimitedHorizontalVertical(usize),
|
||||
/// Pack as many items as possible per row over (possibly) many rows.
|
||||
Mixed,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
pub enum SeparatorTactic {
|
||||
Always,
|
||||
Never,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl SeparatorTactic {
|
||||
pub fn from_bool(b: bool) -> SeparatorTactic {
|
||||
if b {
|
||||
SeparatorTactic::Always
|
||||
} else {
|
||||
SeparatorTactic::Never
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Where to put separator.
|
||||
#[config_type]
|
||||
pub enum SeparatorPlace {
|
||||
Front,
|
||||
Back,
|
||||
}
|
||||
|
||||
impl SeparatorPlace {
|
||||
pub fn is_front(self) -> bool {
|
||||
self == SeparatorPlace::Front
|
||||
}
|
||||
|
||||
pub fn is_back(self) -> bool {
|
||||
self == SeparatorPlace::Back
|
||||
}
|
||||
|
||||
pub fn from_tactic(
|
||||
default: SeparatorPlace,
|
||||
tactic: DefinitiveListTactic,
|
||||
sep: &str,
|
||||
) -> SeparatorPlace {
|
||||
match tactic {
|
||||
DefinitiveListTactic::Vertical => default,
|
||||
_ => {
|
||||
if sep == "," {
|
||||
SeparatorPlace::Back
|
||||
} else {
|
||||
default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
963
src/tools/rustfmt/src/config/mod.rs
Normal file
963
src/tools/rustfmt/src/config/mod.rs
Normal file
|
|
@ -0,0 +1,963 @@
|
|||
use std::cell::Cell;
|
||||
use std::default::Default;
|
||||
use std::fs::File;
|
||||
use std::io::{Error, ErrorKind, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{env, fs};
|
||||
|
||||
use regex::Regex;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::config::config_type::ConfigType;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use crate::config::file_lines::{FileLines, FileName, Range};
|
||||
#[allow(unreachable_pub)]
|
||||
pub use crate::config::lists::*;
|
||||
#[allow(unreachable_pub)]
|
||||
pub use crate::config::options::*;
|
||||
|
||||
#[macro_use]
|
||||
pub(crate) mod config_type;
|
||||
#[macro_use]
|
||||
pub(crate) mod options;
|
||||
|
||||
pub(crate) mod file_lines;
|
||||
pub(crate) mod license;
|
||||
pub(crate) mod lists;
|
||||
|
||||
// This macro defines configuration options used in rustfmt. Each option
|
||||
// is defined as follows:
|
||||
//
|
||||
// `name: value type, default value, is stable, description;`
|
||||
create_config! {
|
||||
// Fundamental stuff
|
||||
max_width: usize, 100, true, "Maximum width of each line";
|
||||
hard_tabs: bool, false, true, "Use tab characters for indentation, spaces for alignment";
|
||||
tab_spaces: usize, 4, true, "Number of spaces per tab";
|
||||
newline_style: NewlineStyle, NewlineStyle::Auto, true, "Unix or Windows line endings";
|
||||
indent_style: IndentStyle, IndentStyle::Block, false, "How do we indent expressions or items";
|
||||
|
||||
// Width Heuristics
|
||||
use_small_heuristics: Heuristics, Heuristics::Default, true, "Whether to use different \
|
||||
formatting for items and expressions if they satisfy a heuristic notion of 'small'";
|
||||
width_heuristics: WidthHeuristics, WidthHeuristics::scaled(100), false,
|
||||
"'small' heuristic values";
|
||||
fn_call_width: usize, 60, true, "Maximum width of the args of a function call before \
|
||||
falling back to vertical formatting.";
|
||||
attr_fn_like_width: usize, 70, true, "Maximum width of the args of a function-like \
|
||||
attributes before falling back to vertical formatting.";
|
||||
struct_lit_width: usize, 18, true, "Maximum width in the body of a struct lit before \
|
||||
falling back to vertical formatting.";
|
||||
struct_variant_width: usize, 35, true, "Maximum width in the body of a struct variant before \
|
||||
falling back to vertical formatting.";
|
||||
array_width: usize, 60, true, "Maximum width of an array literal before falling \
|
||||
back to vertical formatting.";
|
||||
chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line.";
|
||||
single_line_if_else_max_width: usize, 50, true, "Maximum line length for single line if-else \
|
||||
expressions. A value of zero means always break if-else expressions.";
|
||||
|
||||
// Comments. macros, and strings
|
||||
wrap_comments: bool, false, false, "Break comments to fit on the line";
|
||||
format_code_in_doc_comments: bool, false, false, "Format the code snippet in doc comments.";
|
||||
comment_width: usize, 80, false,
|
||||
"Maximum length of comments. No effect unless wrap_comments = true";
|
||||
normalize_comments: bool, false, false, "Convert /* */ comments to // comments where possible";
|
||||
normalize_doc_attributes: bool, false, false, "Normalize doc attributes as doc comments";
|
||||
license_template_path: String, String::default(), false,
|
||||
"Beginning of file must match license template";
|
||||
format_strings: bool, false, false, "Format string literals where necessary";
|
||||
format_macro_matchers: bool, false, false,
|
||||
"Format the metavariable matching patterns in macros";
|
||||
format_macro_bodies: bool, true, false, "Format the bodies of macros";
|
||||
|
||||
// Single line expressions and items
|
||||
empty_item_single_line: bool, true, false,
|
||||
"Put empty-body functions and impls on a single line";
|
||||
struct_lit_single_line: bool, true, false,
|
||||
"Put small struct literals on a single line";
|
||||
fn_single_line: bool, false, false, "Put single-expression functions on a single line";
|
||||
where_single_line: bool, false, false, "Force where-clauses to be on a single line";
|
||||
|
||||
// Imports
|
||||
imports_indent: IndentStyle, IndentStyle::Block, false, "Indent of imports";
|
||||
imports_layout: ListTactic, ListTactic::Mixed, false, "Item layout inside a import block";
|
||||
imports_granularity: ImportGranularity, ImportGranularity::Preserve, false,
|
||||
"Merge or split imports to the provided granularity";
|
||||
group_imports: GroupImportsTactic, GroupImportsTactic::Preserve, false,
|
||||
"Controls the strategy for how imports are grouped together";
|
||||
merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)";
|
||||
|
||||
// Ordering
|
||||
reorder_imports: bool, true, true, "Reorder import and extern crate statements alphabetically";
|
||||
reorder_modules: bool, true, true, "Reorder module statements alphabetically in group";
|
||||
reorder_impl_items: bool, false, false, "Reorder impl items";
|
||||
|
||||
// Spaces around punctuation
|
||||
type_punctuation_density: TypeDensity, TypeDensity::Wide, false,
|
||||
"Determines if '+' or '=' are wrapped in spaces in the punctuation of types";
|
||||
space_before_colon: bool, false, false, "Leave a space before the colon";
|
||||
space_after_colon: bool, true, false, "Leave a space after the colon";
|
||||
spaces_around_ranges: bool, false, false, "Put spaces around the .. and ..= range operators";
|
||||
binop_separator: SeparatorPlace, SeparatorPlace::Front, false,
|
||||
"Where to put a binary operator when a binary expression goes multiline";
|
||||
|
||||
// Misc.
|
||||
remove_nested_parens: bool, true, true, "Remove nested parens";
|
||||
combine_control_expr: bool, true, false, "Combine control expressions with function calls";
|
||||
overflow_delimited_expr: bool, false, false,
|
||||
"Allow trailing bracket/brace delimited expressions to overflow";
|
||||
struct_field_align_threshold: usize, 0, false,
|
||||
"Align struct fields if their diffs fits within threshold";
|
||||
enum_discrim_align_threshold: usize, 0, false,
|
||||
"Align enum variants discrims, if their diffs fit within threshold";
|
||||
match_arm_blocks: bool, true, false, "Wrap the body of arms in blocks when it does not fit on \
|
||||
the same line with the pattern of arms";
|
||||
match_arm_leading_pipes: MatchArmLeadingPipe, MatchArmLeadingPipe::Never, true,
|
||||
"Determines whether leading pipes are emitted on match arms";
|
||||
force_multiline_blocks: bool, false, false,
|
||||
"Force multiline closure bodies and match arms to be wrapped in a block";
|
||||
fn_args_layout: Density, Density::Tall, true,
|
||||
"Control the layout of arguments in a function";
|
||||
brace_style: BraceStyle, BraceStyle::SameLineWhere, false, "Brace style for items";
|
||||
control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, false,
|
||||
"Brace style for control flow constructs";
|
||||
trailing_semicolon: bool, true, false,
|
||||
"Add trailing semicolon after break, continue and return";
|
||||
trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, false,
|
||||
"How to handle trailing commas for lists";
|
||||
match_block_trailing_comma: bool, false, false,
|
||||
"Put a trailing comma after a block based match arm (non-block arms are not affected)";
|
||||
blank_lines_upper_bound: usize, 1, false,
|
||||
"Maximum number of blank lines which can be put between items";
|
||||
blank_lines_lower_bound: usize, 0, false,
|
||||
"Minimum number of blank lines which must be put between items";
|
||||
edition: Edition, Edition::Edition2015, true, "The edition of the parser (RFC 2052)";
|
||||
version: Version, Version::One, false, "Version of formatting rules";
|
||||
inline_attribute_width: usize, 0, false,
|
||||
"Write an item and its attribute on the same line \
|
||||
if their combined width is below a threshold";
|
||||
|
||||
// Options that can change the source code beyond whitespace/blocks (somewhat linty things)
|
||||
merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one";
|
||||
use_try_shorthand: bool, false, true, "Replace uses of the try! macro by the ? shorthand";
|
||||
use_field_init_shorthand: bool, false, true, "Use field initialization shorthand if possible";
|
||||
force_explicit_abi: bool, true, true, "Always print the abi for extern items";
|
||||
condense_wildcard_suffixes: bool, false, false, "Replace strings of _ wildcards by a single .. \
|
||||
in tuple patterns";
|
||||
|
||||
// Control options (changes the operation of rustfmt, rather than the formatting)
|
||||
color: Color, Color::Auto, false,
|
||||
"What Color option to use when none is supplied: Always, Never, Auto";
|
||||
required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false,
|
||||
"Require a specific version of rustfmt";
|
||||
unstable_features: bool, false, false,
|
||||
"Enables unstable features. Only available on nightly channel";
|
||||
disable_all_formatting: bool, false, false, "Don't reformat anything";
|
||||
skip_children: bool, false, false, "Don't reformat out of line modules";
|
||||
hide_parse_errors: bool, false, false, "Hide errors from the parser";
|
||||
error_on_line_overflow: bool, false, false, "Error if unable to get all lines within max_width";
|
||||
error_on_unformatted: bool, false, false,
|
||||
"Error if unable to get comments or string literals within max_width, \
|
||||
or they are left with trailing whitespaces";
|
||||
report_todo: ReportTactic, ReportTactic::Never, false,
|
||||
"Report all, none or unnumbered occurrences of TODO in source file comments";
|
||||
report_fixme: ReportTactic, ReportTactic::Never, false,
|
||||
"Report all, none or unnumbered occurrences of FIXME in source file comments";
|
||||
ignore: IgnoreList, IgnoreList::default(), false,
|
||||
"Skip formatting the specified files and directories";
|
||||
|
||||
// Not user-facing
|
||||
verbose: Verbosity, Verbosity::Normal, false, "How much to information to emit to the user";
|
||||
file_lines: FileLines, FileLines::all(), false,
|
||||
"Lines to format; this is not supported in rustfmt.toml, and can only be specified \
|
||||
via the --file-lines option";
|
||||
emit_mode: EmitMode, EmitMode::Files, false,
|
||||
"What emit Mode to use when none is supplied";
|
||||
make_backup: bool, false, false, "Backup changed files";
|
||||
print_misformatted_file_names: bool, false, true,
|
||||
"Prints the names of mismatched files that were formatted. Prints the names of \
|
||||
files that would be formated when used with `--check` mode. ";
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Could not output config: {0}")]
|
||||
pub struct ToTomlError(toml::ser::Error);
|
||||
|
||||
impl PartialConfig {
|
||||
pub fn to_toml(&self) -> Result<String, ToTomlError> {
|
||||
// Non-user-facing options can't be specified in TOML
|
||||
let mut cloned = self.clone();
|
||||
cloned.file_lines = None;
|
||||
cloned.verbose = None;
|
||||
cloned.width_heuristics = None;
|
||||
cloned.print_misformatted_file_names = None;
|
||||
cloned.merge_imports = None;
|
||||
|
||||
::toml::to_string(&cloned).map_err(ToTomlError)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn version_meets_requirement(&self) -> bool {
|
||||
if self.was_set().required_version() {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let required_version = self.required_version();
|
||||
if version != required_version {
|
||||
println!(
|
||||
"Error: rustfmt version ({}) doesn't match the required version ({})",
|
||||
version, required_version,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Constructs a `Config` from the toml file specified at `file_path`.
|
||||
///
|
||||
/// This method only looks at the provided path, for a method that
|
||||
/// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`.
|
||||
///
|
||||
/// Returns a `Config` if the config could be read and parsed from
|
||||
/// the file, otherwise errors.
|
||||
pub(super) fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
|
||||
let mut file = File::open(&file_path)?;
|
||||
let mut toml = String::new();
|
||||
file.read_to_string(&mut toml)?;
|
||||
Config::from_toml(&toml, file_path.parent().unwrap())
|
||||
.map_err(|err| Error::new(ErrorKind::InvalidData, err))
|
||||
}
|
||||
|
||||
/// Resolves the config for input in `dir`.
|
||||
///
|
||||
/// Searches for `rustfmt.toml` beginning with `dir`, and
|
||||
/// recursively checking parents of `dir` if no config file is found.
|
||||
/// If no config file exists in `dir` or in any parent, a
|
||||
/// default `Config` will be returned (and the returned path will be empty).
|
||||
///
|
||||
/// Returns the `Config` to use, and the path of the project file if there was
|
||||
/// one.
|
||||
pub(super) fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option<PathBuf>), Error> {
|
||||
/// Try to find a project file in the given directory and its parents.
|
||||
/// Returns the path of a the nearest project file if one exists,
|
||||
/// or `None` if no project file was found.
|
||||
fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
|
||||
let mut current = if dir.is_relative() {
|
||||
env::current_dir()?.join(dir)
|
||||
} else {
|
||||
dir.to_path_buf()
|
||||
};
|
||||
|
||||
current = fs::canonicalize(current)?;
|
||||
|
||||
loop {
|
||||
match get_toml_path(¤t) {
|
||||
Ok(Some(path)) => return Ok(Some(path)),
|
||||
Err(e) => return Err(e),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// If the current directory has no parent, we're done searching.
|
||||
if !current.pop() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing was found, check in the home directory.
|
||||
if let Some(home_dir) = dirs::home_dir() {
|
||||
if let Some(path) = get_toml_path(&home_dir)? {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
}
|
||||
|
||||
// If none was found ther either, check in the user's configuration directory.
|
||||
if let Some(mut config_dir) = dirs::config_dir() {
|
||||
config_dir.push("rustfmt");
|
||||
if let Some(path) = get_toml_path(&config_dir)? {
|
||||
return Ok(Some(path));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
match resolve_project_file(dir)? {
|
||||
None => Ok((Config::default(), None)),
|
||||
Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_toml(toml: &str, dir: &Path) -> Result<Config, String> {
|
||||
let parsed: ::toml::Value = toml
|
||||
.parse()
|
||||
.map_err(|e| format!("Could not parse TOML: {}", e))?;
|
||||
let mut err = String::new();
|
||||
let table = parsed
|
||||
.as_table()
|
||||
.ok_or_else(|| String::from("Parsed config was not table"))?;
|
||||
for key in table.keys() {
|
||||
if !Config::is_valid_name(key) {
|
||||
let msg = &format!("Warning: Unknown configuration option `{}`\n", key);
|
||||
err.push_str(msg)
|
||||
}
|
||||
}
|
||||
match parsed.try_into() {
|
||||
Ok(parsed_config) => {
|
||||
if !err.is_empty() {
|
||||
eprint!("{}", err);
|
||||
}
|
||||
Ok(Config::default().fill_from_parsed_config(parsed_config, dir))
|
||||
}
|
||||
Err(e) => {
|
||||
err.push_str("Error: Decoding config file failed:\n");
|
||||
err.push_str(format!("{}\n", e).as_str());
|
||||
err.push_str("Please check your config file.");
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a config by checking the client-supplied options and if appropriate, the
|
||||
/// file system (including searching the file system for overrides).
|
||||
pub fn load_config<O: CliOptions>(
|
||||
file_path: Option<&Path>,
|
||||
options: Option<O>,
|
||||
) -> Result<(Config, Option<PathBuf>), Error> {
|
||||
let over_ride = match options {
|
||||
Some(ref opts) => config_path(opts)?,
|
||||
None => None,
|
||||
};
|
||||
|
||||
let result = if let Some(over_ride) = over_ride {
|
||||
Config::from_toml_path(over_ride.as_ref()).map(|p| (p, Some(over_ride.to_owned())))
|
||||
} else if let Some(file_path) = file_path {
|
||||
Config::from_resolved_toml_path(file_path)
|
||||
} else {
|
||||
Ok((Config::default(), None))
|
||||
};
|
||||
|
||||
result.map(|(mut c, p)| {
|
||||
if let Some(options) = options {
|
||||
options.apply_to(&mut c);
|
||||
}
|
||||
(c, p)
|
||||
})
|
||||
}
|
||||
|
||||
// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir`
|
||||
//
|
||||
// Return the path if a config file exists, empty if no file exists, and Error for IO errors
|
||||
fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
|
||||
const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"];
|
||||
for config_file_name in &CONFIG_FILE_NAMES {
|
||||
let config_file = dir.join(config_file_name);
|
||||
match fs::metadata(&config_file) {
|
||||
// Only return if it's a file to handle the unlikely situation of a directory named
|
||||
// `rustfmt.toml`.
|
||||
Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
|
||||
// Return the error if it's something other than `NotFound`; otherwise we didn't
|
||||
// find the project file yet, and continue searching.
|
||||
Err(e) => {
|
||||
if e.kind() != ErrorKind::NotFound {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn config_path(options: &dyn CliOptions) -> Result<Option<PathBuf>, Error> {
|
||||
let config_path_not_found = |path: &str| -> Result<Option<PathBuf>, Error> {
|
||||
Err(Error::new(
|
||||
ErrorKind::NotFound,
|
||||
format!(
|
||||
"Error: unable to find a config file for the given path: `{}`",
|
||||
path
|
||||
),
|
||||
))
|
||||
};
|
||||
|
||||
// Read the config_path and convert to parent dir if a file is provided.
|
||||
// If a config file cannot be found from the given path, return error.
|
||||
match options.config_path() {
|
||||
Some(path) if !path.exists() => config_path_not_found(path.to_str().unwrap()),
|
||||
Some(path) if path.is_dir() => {
|
||||
let config_file_path = get_toml_path(path)?;
|
||||
if config_file_path.is_some() {
|
||||
Ok(config_file_path)
|
||||
} else {
|
||||
config_path_not_found(path.to_str().unwrap())
|
||||
}
|
||||
}
|
||||
path => Ok(path.map(ToOwned::to_owned)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::str;
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod mock {
|
||||
use super::super::*;
|
||||
|
||||
create_config! {
|
||||
// Options that are used by the generated functions
|
||||
max_width: usize, 100, true, "Maximum width of each line";
|
||||
license_template_path: String, String::default(), false,
|
||||
"Beginning of file must match license template";
|
||||
required_version: String, env!("CARGO_PKG_VERSION").to_owned(), false,
|
||||
"Require a specific version of rustfmt.";
|
||||
ignore: IgnoreList, IgnoreList::default(), false,
|
||||
"Skip formatting the specified files and directories.";
|
||||
verbose: Verbosity, Verbosity::Normal, false,
|
||||
"How much to information to emit to the user";
|
||||
file_lines: FileLines, FileLines::all(), false,
|
||||
"Lines to format; this is not supported in rustfmt.toml, and can only be specified \
|
||||
via the --file-lines option";
|
||||
|
||||
// merge_imports deprecation
|
||||
imports_granularity: ImportGranularity, ImportGranularity::Preserve, false,
|
||||
"Merge imports";
|
||||
merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)";
|
||||
|
||||
// Width Heuristics
|
||||
use_small_heuristics: Heuristics, Heuristics::Default, true,
|
||||
"Whether to use different formatting for items and \
|
||||
expressions if they satisfy a heuristic notion of 'small'.";
|
||||
width_heuristics: WidthHeuristics, WidthHeuristics::scaled(100), false,
|
||||
"'small' heuristic values";
|
||||
|
||||
fn_call_width: usize, 60, true, "Maximum width of the args of a function call before \
|
||||
falling back to vertical formatting.";
|
||||
attr_fn_like_width: usize, 70, true, "Maximum width of the args of a function-like \
|
||||
attributes before falling back to vertical formatting.";
|
||||
struct_lit_width: usize, 18, true, "Maximum width in the body of a struct lit before \
|
||||
falling back to vertical formatting.";
|
||||
struct_variant_width: usize, 35, true, "Maximum width in the body of a struct \
|
||||
variant before falling back to vertical formatting.";
|
||||
array_width: usize, 60, true, "Maximum width of an array literal before falling \
|
||||
back to vertical formatting.";
|
||||
chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line.";
|
||||
single_line_if_else_max_width: usize, 50, true, "Maximum line length for single \
|
||||
line if-else expressions. A value of zero means always break if-else expressions.";
|
||||
|
||||
// Options that are used by the tests
|
||||
stable_option: bool, false, true, "A stable option";
|
||||
unstable_option: bool, false, false, "An unstable option";
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_set() {
|
||||
let mut config = Config::default();
|
||||
config.set().verbose(Verbosity::Quiet);
|
||||
assert_eq!(config.verbose(), Verbosity::Quiet);
|
||||
config.set().verbose(Verbosity::Normal);
|
||||
assert_eq!(config.verbose(), Verbosity::Normal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_used_to_toml() {
|
||||
let config = Config::default();
|
||||
|
||||
let merge_derives = config.merge_derives();
|
||||
let skip_children = config.skip_children();
|
||||
|
||||
let used_options = config.used_options();
|
||||
let toml = used_options.to_toml().unwrap();
|
||||
assert_eq!(
|
||||
toml,
|
||||
format!(
|
||||
"merge_derives = {}\nskip_children = {}\n",
|
||||
merge_derives, skip_children,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_was_set() {
|
||||
let config = Config::from_toml("hard_tabs = true", Path::new("")).unwrap();
|
||||
|
||||
assert_eq!(config.was_set().hard_tabs(), true);
|
||||
assert_eq!(config.was_set().verbose(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_print_docs_exclude_unstable() {
|
||||
use self::mock::Config;
|
||||
|
||||
let mut output = Vec::new();
|
||||
Config::print_docs(&mut output, false);
|
||||
|
||||
let s = str::from_utf8(&output).unwrap();
|
||||
|
||||
assert_eq!(s.contains("stable_option"), true);
|
||||
assert_eq!(s.contains("unstable_option"), false);
|
||||
assert_eq!(s.contains("(unstable)"), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_print_docs_include_unstable() {
|
||||
use self::mock::Config;
|
||||
|
||||
let mut output = Vec::new();
|
||||
Config::print_docs(&mut output, true);
|
||||
|
||||
let s = str::from_utf8(&output).unwrap();
|
||||
assert_eq!(s.contains("stable_option"), true);
|
||||
assert_eq!(s.contains("unstable_option"), true);
|
||||
assert_eq!(s.contains("(unstable)"), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_string_license_template_path() {
|
||||
let toml = r#"license_template_path = """#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert!(config.license_template.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_license_template_path() {
|
||||
if !crate::is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let toml = r#"license_template_path = "tests/license-template/lt.txt""#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert!(config.license_template.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_existing_license_with_no_license() {
|
||||
if !crate::is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let toml = r#"license_template_path = "tests/license-template/lt.txt""#;
|
||||
let mut config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert!(config.license_template.is_some());
|
||||
config.override_value("license_template_path", "");
|
||||
assert!(config.license_template.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dump_default_config() {
|
||||
let default_config = format!(
|
||||
r#"max_width = 100
|
||||
hard_tabs = false
|
||||
tab_spaces = 4
|
||||
newline_style = "Auto"
|
||||
indent_style = "Block"
|
||||
use_small_heuristics = "Default"
|
||||
fn_call_width = 60
|
||||
attr_fn_like_width = 70
|
||||
struct_lit_width = 18
|
||||
struct_variant_width = 35
|
||||
array_width = 60
|
||||
chain_width = 60
|
||||
single_line_if_else_max_width = 50
|
||||
wrap_comments = false
|
||||
format_code_in_doc_comments = false
|
||||
comment_width = 80
|
||||
normalize_comments = false
|
||||
normalize_doc_attributes = false
|
||||
license_template_path = ""
|
||||
format_strings = false
|
||||
format_macro_matchers = false
|
||||
format_macro_bodies = true
|
||||
empty_item_single_line = true
|
||||
struct_lit_single_line = true
|
||||
fn_single_line = false
|
||||
where_single_line = false
|
||||
imports_indent = "Block"
|
||||
imports_layout = "Mixed"
|
||||
imports_granularity = "Preserve"
|
||||
group_imports = "Preserve"
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
reorder_impl_items = false
|
||||
type_punctuation_density = "Wide"
|
||||
space_before_colon = false
|
||||
space_after_colon = true
|
||||
spaces_around_ranges = false
|
||||
binop_separator = "Front"
|
||||
remove_nested_parens = true
|
||||
combine_control_expr = true
|
||||
overflow_delimited_expr = false
|
||||
struct_field_align_threshold = 0
|
||||
enum_discrim_align_threshold = 0
|
||||
match_arm_blocks = true
|
||||
match_arm_leading_pipes = "Never"
|
||||
force_multiline_blocks = false
|
||||
fn_args_layout = "Tall"
|
||||
brace_style = "SameLineWhere"
|
||||
control_brace_style = "AlwaysSameLine"
|
||||
trailing_semicolon = true
|
||||
trailing_comma = "Vertical"
|
||||
match_block_trailing_comma = false
|
||||
blank_lines_upper_bound = 1
|
||||
blank_lines_lower_bound = 0
|
||||
edition = "2015"
|
||||
version = "One"
|
||||
inline_attribute_width = 0
|
||||
merge_derives = true
|
||||
use_try_shorthand = false
|
||||
use_field_init_shorthand = false
|
||||
force_explicit_abi = true
|
||||
condense_wildcard_suffixes = false
|
||||
color = "Auto"
|
||||
required_version = "{}"
|
||||
unstable_features = false
|
||||
disable_all_formatting = false
|
||||
skip_children = false
|
||||
hide_parse_errors = false
|
||||
error_on_line_overflow = false
|
||||
error_on_unformatted = false
|
||||
report_todo = "Never"
|
||||
report_fixme = "Never"
|
||||
ignore = []
|
||||
emit_mode = "Files"
|
||||
make_backup = false
|
||||
"#,
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
let toml = Config::default().all_options().to_toml().unwrap();
|
||||
assert_eq!(&toml, &default_config);
|
||||
}
|
||||
|
||||
// FIXME(#2183): these tests cannot be run in parallel because they use env vars.
|
||||
// #[test]
|
||||
// fn test_as_not_nightly_channel() {
|
||||
// let mut config = Config::default();
|
||||
// assert_eq!(config.was_set().unstable_features(), false);
|
||||
// config.set().unstable_features(true);
|
||||
// assert_eq!(config.was_set().unstable_features(), false);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_as_nightly_channel() {
|
||||
// let v = ::std::env::var("CFG_RELEASE_CHANNEL").unwrap_or(String::from(""));
|
||||
// ::std::env::set_var("CFG_RELEASE_CHANNEL", "nightly");
|
||||
// let mut config = Config::default();
|
||||
// config.set().unstable_features(true);
|
||||
// assert_eq!(config.was_set().unstable_features(), false);
|
||||
// config.set().unstable_features(true);
|
||||
// assert_eq!(config.unstable_features(), true);
|
||||
// ::std::env::set_var("CFG_RELEASE_CHANNEL", v);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_unstable_from_toml() {
|
||||
// let mut config = Config::from_toml("unstable_features = true").unwrap();
|
||||
// assert_eq!(config.was_set().unstable_features(), false);
|
||||
// let v = ::std::env::var("CFG_RELEASE_CHANNEL").unwrap_or(String::from(""));
|
||||
// ::std::env::set_var("CFG_RELEASE_CHANNEL", "nightly");
|
||||
// config = Config::from_toml("unstable_features = true").unwrap();
|
||||
// assert_eq!(config.was_set().unstable_features(), true);
|
||||
// assert_eq!(config.unstable_features(), true);
|
||||
// ::std::env::set_var("CFG_RELEASE_CHANNEL", v);
|
||||
// }
|
||||
|
||||
#[cfg(test)]
|
||||
mod deprecated_option_merge_imports {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_old_option_set() {
|
||||
if !crate::is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let toml = r#"
|
||||
unstable_features = true
|
||||
merge_imports = true
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.imports_granularity(), ImportGranularity::Crate);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_both_set() {
|
||||
if !crate::is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let toml = r#"
|
||||
unstable_features = true
|
||||
merge_imports = true
|
||||
imports_granularity = "Preserve"
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_overridden() {
|
||||
if !crate::is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let toml = r#"
|
||||
unstable_features = true
|
||||
merge_imports = true
|
||||
"#;
|
||||
let mut config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
config.override_value("imports_granularity", "Preserve");
|
||||
assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_old_overridden() {
|
||||
if !crate::is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let toml = r#"
|
||||
unstable_features = true
|
||||
imports_granularity = "Module"
|
||||
"#;
|
||||
let mut config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
config.override_value("merge_imports", "true");
|
||||
// no effect: the new option always takes precedence
|
||||
assert_eq!(config.imports_granularity(), ImportGranularity::Module);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod use_small_heuristics {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_sets_correct_widths() {
|
||||
let toml = r#"
|
||||
use_small_heuristics = "Default"
|
||||
max_width = 200
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.array_width(), 120);
|
||||
assert_eq!(config.attr_fn_like_width(), 140);
|
||||
assert_eq!(config.chain_width(), 120);
|
||||
assert_eq!(config.fn_call_width(), 120);
|
||||
assert_eq!(config.single_line_if_else_max_width(), 100);
|
||||
assert_eq!(config.struct_lit_width(), 36);
|
||||
assert_eq!(config.struct_variant_width(), 70);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_sets_correct_widths() {
|
||||
let toml = r#"
|
||||
use_small_heuristics = "Max"
|
||||
max_width = 120
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.array_width(), 120);
|
||||
assert_eq!(config.attr_fn_like_width(), 120);
|
||||
assert_eq!(config.chain_width(), 120);
|
||||
assert_eq!(config.fn_call_width(), 120);
|
||||
assert_eq!(config.single_line_if_else_max_width(), 120);
|
||||
assert_eq!(config.struct_lit_width(), 120);
|
||||
assert_eq!(config.struct_variant_width(), 120);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_off_sets_correct_widths() {
|
||||
let toml = r#"
|
||||
use_small_heuristics = "Off"
|
||||
max_width = 100
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.array_width(), usize::max_value());
|
||||
assert_eq!(config.attr_fn_like_width(), usize::max_value());
|
||||
assert_eq!(config.chain_width(), usize::max_value());
|
||||
assert_eq!(config.fn_call_width(), usize::max_value());
|
||||
assert_eq!(config.single_line_if_else_max_width(), 0);
|
||||
assert_eq!(config.struct_lit_width(), 0);
|
||||
assert_eq!(config.struct_variant_width(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_works_with_default() {
|
||||
let toml = r#"
|
||||
use_small_heuristics = "Default"
|
||||
array_width = 20
|
||||
attr_fn_like_width = 40
|
||||
chain_width = 20
|
||||
fn_call_width = 90
|
||||
single_line_if_else_max_width = 40
|
||||
struct_lit_width = 30
|
||||
struct_variant_width = 34
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.array_width(), 20);
|
||||
assert_eq!(config.attr_fn_like_width(), 40);
|
||||
assert_eq!(config.chain_width(), 20);
|
||||
assert_eq!(config.fn_call_width(), 90);
|
||||
assert_eq!(config.single_line_if_else_max_width(), 40);
|
||||
assert_eq!(config.struct_lit_width(), 30);
|
||||
assert_eq!(config.struct_variant_width(), 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_with_max() {
|
||||
let toml = r#"
|
||||
use_small_heuristics = "Max"
|
||||
array_width = 20
|
||||
attr_fn_like_width = 40
|
||||
chain_width = 20
|
||||
fn_call_width = 90
|
||||
single_line_if_else_max_width = 40
|
||||
struct_lit_width = 30
|
||||
struct_variant_width = 34
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.array_width(), 20);
|
||||
assert_eq!(config.attr_fn_like_width(), 40);
|
||||
assert_eq!(config.chain_width(), 20);
|
||||
assert_eq!(config.fn_call_width(), 90);
|
||||
assert_eq!(config.single_line_if_else_max_width(), 40);
|
||||
assert_eq!(config.struct_lit_width(), 30);
|
||||
assert_eq!(config.struct_variant_width(), 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_with_off() {
|
||||
let toml = r#"
|
||||
use_small_heuristics = "Off"
|
||||
array_width = 20
|
||||
attr_fn_like_width = 40
|
||||
chain_width = 20
|
||||
fn_call_width = 90
|
||||
single_line_if_else_max_width = 40
|
||||
struct_lit_width = 30
|
||||
struct_variant_width = 34
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.array_width(), 20);
|
||||
assert_eq!(config.attr_fn_like_width(), 40);
|
||||
assert_eq!(config.chain_width(), 20);
|
||||
assert_eq!(config.fn_call_width(), 90);
|
||||
assert_eq!(config.single_line_if_else_max_width(), 40);
|
||||
assert_eq!(config.struct_lit_width(), 30);
|
||||
assert_eq!(config.struct_variant_width(), 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fn_call_width_config_exceeds_max_width() {
|
||||
let toml = r#"
|
||||
max_width = 90
|
||||
fn_call_width = 95
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.fn_call_width(), 90);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attr_fn_like_width_config_exceeds_max_width() {
|
||||
let toml = r#"
|
||||
max_width = 80
|
||||
attr_fn_like_width = 90
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.attr_fn_like_width(), 80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_lit_config_exceeds_max_width() {
|
||||
let toml = r#"
|
||||
max_width = 78
|
||||
struct_lit_width = 90
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.struct_lit_width(), 78);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_struct_variant_width_config_exceeds_max_width() {
|
||||
let toml = r#"
|
||||
max_width = 80
|
||||
struct_variant_width = 90
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.struct_variant_width(), 80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_array_width_config_exceeds_max_width() {
|
||||
let toml = r#"
|
||||
max_width = 60
|
||||
array_width = 80
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.array_width(), 60);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chain_width_config_exceeds_max_width() {
|
||||
let toml = r#"
|
||||
max_width = 80
|
||||
chain_width = 90
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.chain_width(), 80);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_line_if_else_max_width_config_exceeds_max_width() {
|
||||
let toml = r#"
|
||||
max_width = 70
|
||||
single_line_if_else_max_width = 90
|
||||
"#;
|
||||
let config = Config::from_toml(toml, Path::new("")).unwrap();
|
||||
assert_eq!(config.single_line_if_else_max_width(), 70);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_fn_call_width_exceeds_max_width() {
|
||||
let mut config = Config::default();
|
||||
config.override_value("fn_call_width", "101");
|
||||
assert_eq!(config.fn_call_width(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_attr_fn_like_width_exceeds_max_width() {
|
||||
let mut config = Config::default();
|
||||
config.override_value("attr_fn_like_width", "101");
|
||||
assert_eq!(config.attr_fn_like_width(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_struct_lit_exceeds_max_width() {
|
||||
let mut config = Config::default();
|
||||
config.override_value("struct_lit_width", "101");
|
||||
assert_eq!(config.struct_lit_width(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_struct_variant_width_exceeds_max_width() {
|
||||
let mut config = Config::default();
|
||||
config.override_value("struct_variant_width", "101");
|
||||
assert_eq!(config.struct_variant_width(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_array_width_exceeds_max_width() {
|
||||
let mut config = Config::default();
|
||||
config.override_value("array_width", "101");
|
||||
assert_eq!(config.array_width(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_chain_width_exceeds_max_width() {
|
||||
let mut config = Config::default();
|
||||
config.override_value("chain_width", "101");
|
||||
assert_eq!(config.chain_width(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_override_single_line_if_else_max_width_exceeds_max_width() {
|
||||
let mut config = Config::default();
|
||||
config.override_value("single_line_if_else_max_width", "101");
|
||||
assert_eq!(config.single_line_if_else_max_width(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
444
src/tools/rustfmt/src/config/options.rs
Normal file
444
src/tools/rustfmt/src/config/options.rs
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
use std::collections::{hash_set, HashSet};
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustfmt_config_proc_macro::config_type;
|
||||
use serde::de::{SeqAccess, Visitor};
|
||||
use serde::ser::SerializeSeq;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use crate::config::lists::*;
|
||||
use crate::config::Config;
|
||||
|
||||
#[config_type]
|
||||
pub enum NewlineStyle {
|
||||
/// Auto-detect based on the raw source input.
|
||||
Auto,
|
||||
/// Force CRLF (`\r\n`).
|
||||
Windows,
|
||||
/// Force CR (`\n).
|
||||
Unix,
|
||||
/// `\r\n` in Windows, `\n`` on other platforms.
|
||||
Native,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// Where to put the opening brace of items (`fn`, `impl`, etc.).
|
||||
pub enum BraceStyle {
|
||||
/// Put the opening brace on the next line.
|
||||
AlwaysNextLine,
|
||||
/// Put the opening brace on the same line, if possible.
|
||||
PreferSameLine,
|
||||
/// Prefer the same line except where there is a where-clause, in which
|
||||
/// case force the brace to be put on the next line.
|
||||
SameLineWhere,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// Where to put the opening brace of conditional expressions (`if`, `match`, etc.).
|
||||
pub enum ControlBraceStyle {
|
||||
/// K&R style, Rust community default
|
||||
AlwaysSameLine,
|
||||
/// Stroustrup style
|
||||
ClosingNextLine,
|
||||
/// Allman style
|
||||
AlwaysNextLine,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// How to indent.
|
||||
pub enum IndentStyle {
|
||||
/// First line on the same line as the opening brace, all lines aligned with
|
||||
/// the first line.
|
||||
Visual,
|
||||
/// First line is on a new line and all lines align with **block** indent.
|
||||
Block,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// How to place a list-like items.
|
||||
/// FIXME: Issue-3581: this should be renamed to ItemsLayout when publishing 2.0
|
||||
pub enum Density {
|
||||
/// Fit as much on one line as possible.
|
||||
Compressed,
|
||||
/// Items are placed horizontally if sufficient space, vertically otherwise.
|
||||
Tall,
|
||||
/// Place every item on a separate line.
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// Spacing around type combinators.
|
||||
pub enum TypeDensity {
|
||||
/// No spaces around "=" and "+"
|
||||
Compressed,
|
||||
/// Spaces around " = " and " + "
|
||||
Wide,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// Heuristic settings that can be used to simply
|
||||
/// the configuration of the granular width configurations
|
||||
/// like `struct_lit_width`, `array_width`, etc.
|
||||
pub enum Heuristics {
|
||||
/// Turn off any heuristics
|
||||
Off,
|
||||
/// Turn on max heuristics
|
||||
Max,
|
||||
/// Use scaled values based on the value of `max_width`
|
||||
Default,
|
||||
}
|
||||
|
||||
impl Density {
|
||||
pub fn to_list_tactic(self, len: usize) -> ListTactic {
|
||||
match self {
|
||||
Density::Compressed => ListTactic::Mixed,
|
||||
Density::Tall => ListTactic::HorizontalVertical,
|
||||
Density::Vertical if len == 1 => ListTactic::Horizontal,
|
||||
Density::Vertical => ListTactic::Vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// Configuration for import groups, i.e. sets of imports separated by newlines.
|
||||
pub enum GroupImportsTactic {
|
||||
/// Keep groups as they are.
|
||||
Preserve,
|
||||
/// Discard existing groups, and create new groups for
|
||||
/// 1. `std` / `core` / `alloc` imports
|
||||
/// 2. other imports
|
||||
/// 3. `self` / `crate` / `super` imports
|
||||
StdExternalCrate,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// How to merge imports.
|
||||
pub enum ImportGranularity {
|
||||
/// Do not merge imports.
|
||||
Preserve,
|
||||
/// Use one `use` statement per crate.
|
||||
Crate,
|
||||
/// Use one `use` statement per module.
|
||||
Module,
|
||||
/// Use one `use` statement per imported item.
|
||||
Item,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
pub enum ReportTactic {
|
||||
Always,
|
||||
Unnumbered,
|
||||
Never,
|
||||
}
|
||||
|
||||
/// What Rustfmt should emit. Mostly corresponds to the `--emit` command line
|
||||
/// option.
|
||||
#[config_type]
|
||||
pub enum EmitMode {
|
||||
/// Emits to files.
|
||||
Files,
|
||||
/// Writes the output to stdout.
|
||||
Stdout,
|
||||
/// Displays how much of the input file was processed
|
||||
Coverage,
|
||||
/// Unfancy stdout
|
||||
Checkstyle,
|
||||
/// Writes the resulting diffs in a JSON format. Returns an empty array
|
||||
/// `[]` if there were no diffs.
|
||||
Json,
|
||||
/// Output the changed lines (for internal value only)
|
||||
ModifiedLines,
|
||||
/// Checks if a diff can be generated. If so, rustfmt outputs a diff and
|
||||
/// quits with exit code 1.
|
||||
/// This option is designed to be run in CI where a non-zero exit signifies
|
||||
/// non-standard code formatting. Used for `--check`.
|
||||
Diff,
|
||||
}
|
||||
|
||||
/// Client-preference for coloured output.
|
||||
#[config_type]
|
||||
pub enum Color {
|
||||
/// Always use color, whether it is a piped or terminal output
|
||||
Always,
|
||||
/// Never use color
|
||||
Never,
|
||||
/// Automatically use color, if supported by terminal
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[config_type]
|
||||
/// rustfmt format style version.
|
||||
pub enum Version {
|
||||
/// 1.x.y. When specified, rustfmt will format in the same style as 1.0.0.
|
||||
One,
|
||||
/// 2.x.y. When specified, rustfmt will format in the the latest style.
|
||||
Two,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Whether we should use a coloured terminal.
|
||||
pub fn use_colored_tty(self) -> bool {
|
||||
match self {
|
||||
Color::Always | Color::Auto => true,
|
||||
Color::Never => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How chatty should Rustfmt be?
|
||||
#[config_type]
|
||||
pub enum Verbosity {
|
||||
/// Emit more.
|
||||
Verbose,
|
||||
/// Default.
|
||||
Normal,
|
||||
/// Emit as little as possible.
|
||||
Quiet,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
|
||||
pub struct WidthHeuristics {
|
||||
// Maximum width of the args of a function call before falling back
|
||||
// to vertical formatting.
|
||||
pub fn_call_width: usize,
|
||||
// Maximum width of the args of a function-like attributes before falling
|
||||
// back to vertical formatting.
|
||||
pub attr_fn_like_width: usize,
|
||||
// Maximum width in the body of a struct lit before falling back to
|
||||
// vertical formatting.
|
||||
pub struct_lit_width: usize,
|
||||
// Maximum width in the body of a struct variant before falling back
|
||||
// to vertical formatting.
|
||||
pub struct_variant_width: usize,
|
||||
// Maximum width of an array literal before falling back to vertical
|
||||
// formatting.
|
||||
pub array_width: usize,
|
||||
// Maximum length of a chain to fit on a single line.
|
||||
pub chain_width: usize,
|
||||
// Maximum line length for single line if-else expressions. A value
|
||||
// of zero means always break if-else expressions.
|
||||
pub single_line_if_else_max_width: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for WidthHeuristics {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WidthHeuristics {
|
||||
// Using this WidthHeuristics means we ignore heuristics.
|
||||
pub fn null() -> WidthHeuristics {
|
||||
WidthHeuristics {
|
||||
fn_call_width: usize::max_value(),
|
||||
attr_fn_like_width: usize::max_value(),
|
||||
struct_lit_width: 0,
|
||||
struct_variant_width: 0,
|
||||
array_width: usize::max_value(),
|
||||
chain_width: usize::max_value(),
|
||||
single_line_if_else_max_width: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(max_width: usize) -> WidthHeuristics {
|
||||
WidthHeuristics {
|
||||
fn_call_width: max_width,
|
||||
attr_fn_like_width: max_width,
|
||||
struct_lit_width: max_width,
|
||||
struct_variant_width: max_width,
|
||||
array_width: max_width,
|
||||
chain_width: max_width,
|
||||
single_line_if_else_max_width: max_width,
|
||||
}
|
||||
}
|
||||
|
||||
// scale the default WidthHeuristics according to max_width
|
||||
pub fn scaled(max_width: usize) -> WidthHeuristics {
|
||||
const DEFAULT_MAX_WIDTH: usize = 100;
|
||||
let max_width_ratio = if max_width > DEFAULT_MAX_WIDTH {
|
||||
let ratio = max_width as f32 / DEFAULT_MAX_WIDTH as f32;
|
||||
// round to the closest 0.1
|
||||
(ratio * 10.0).round() / 10.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
WidthHeuristics {
|
||||
fn_call_width: (60.0 * max_width_ratio).round() as usize,
|
||||
attr_fn_like_width: (70.0 * max_width_ratio).round() as usize,
|
||||
struct_lit_width: (18.0 * max_width_ratio).round() as usize,
|
||||
struct_variant_width: (35.0 * max_width_ratio).round() as usize,
|
||||
array_width: (60.0 * max_width_ratio).round() as usize,
|
||||
chain_width: (60.0 * max_width_ratio).round() as usize,
|
||||
single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::str::FromStr for WidthHeuristics {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(_: &str) -> Result<Self, Self::Err> {
|
||||
Err("WidthHeuristics is not parsable")
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmitMode {
|
||||
fn default() -> EmitMode {
|
||||
EmitMode::Files
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of directories, files and modules that rustfmt should ignore.
|
||||
#[derive(Default, Clone, Debug, PartialEq)]
|
||||
pub struct IgnoreList {
|
||||
/// A set of path specified in rustfmt.toml.
|
||||
path_set: HashSet<PathBuf>,
|
||||
/// A path to rustfmt.toml.
|
||||
rustfmt_toml_path: PathBuf,
|
||||
}
|
||||
|
||||
impl fmt::Display for IgnoreList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[{}]",
|
||||
self.path_set
|
||||
.iter()
|
||||
.format_with(", ", |path, f| f(&format_args!(
|
||||
"{}",
|
||||
path.to_string_lossy()
|
||||
)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for IgnoreList {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.path_set.len()))?;
|
||||
for e in &self.path_set {
|
||||
seq.serialize_element(e)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for IgnoreList {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct HashSetVisitor;
|
||||
impl<'v> Visitor<'v> for HashSetVisitor {
|
||||
type Value = HashSet<PathBuf>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a sequence of path")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'v>,
|
||||
{
|
||||
let mut path_set = HashSet::new();
|
||||
while let Some(elem) = seq.next_element()? {
|
||||
path_set.insert(elem);
|
||||
}
|
||||
Ok(path_set)
|
||||
}
|
||||
}
|
||||
Ok(IgnoreList {
|
||||
path_set: deserializer.deserialize_seq(HashSetVisitor)?,
|
||||
rustfmt_toml_path: PathBuf::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a IgnoreList {
|
||||
type Item = &'a PathBuf;
|
||||
type IntoIter = hash_set::Iter<'a, PathBuf>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.path_set.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IgnoreList {
|
||||
pub fn add_prefix(&mut self, dir: &Path) {
|
||||
self.rustfmt_toml_path = dir.to_path_buf();
|
||||
}
|
||||
|
||||
pub fn rustfmt_toml_path(&self) -> &Path {
|
||||
&self.rustfmt_toml_path
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IgnoreList {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(_: &str) -> Result<Self, Self::Err> {
|
||||
Err("IgnoreList is not parsable")
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps client-supplied options to Rustfmt's internals, mostly overriding
|
||||
/// values in a config with values from the command line.
|
||||
pub trait CliOptions {
|
||||
fn apply_to(self, config: &mut Config);
|
||||
fn config_path(&self) -> Option<&Path>;
|
||||
}
|
||||
|
||||
/// The edition of the syntax and semntics of code (RFC 2052).
|
||||
#[config_type]
|
||||
pub enum Edition {
|
||||
#[value = "2015"]
|
||||
#[doc_hint = "2015"]
|
||||
/// Edition 2015.
|
||||
Edition2015,
|
||||
#[value = "2018"]
|
||||
#[doc_hint = "2018"]
|
||||
/// Edition 2018.
|
||||
Edition2018,
|
||||
#[value = "2021"]
|
||||
#[doc_hint = "2021"]
|
||||
/// Edition 2021.
|
||||
Edition2021,
|
||||
}
|
||||
|
||||
impl Default for Edition {
|
||||
fn default() -> Edition {
|
||||
Edition::Edition2015
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Edition> for rustc_span::edition::Edition {
|
||||
fn from(edition: Edition) -> Self {
|
||||
match edition {
|
||||
Edition::Edition2015 => Self::Edition2015,
|
||||
Edition::Edition2018 => Self::Edition2018,
|
||||
Edition::Edition2021 => Self::Edition2021,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Edition {
|
||||
fn partial_cmp(&self, other: &Edition) -> Option<std::cmp::Ordering> {
|
||||
rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls how rustfmt should handle leading pipes on match arms.
|
||||
#[config_type]
|
||||
pub enum MatchArmLeadingPipe {
|
||||
/// Place leading pipes on all match arms
|
||||
Always,
|
||||
/// Never emit leading pipes on match arms
|
||||
Never,
|
||||
/// Preserve any existing leading pipes
|
||||
Preserve,
|
||||
}
|
||||
15
src/tools/rustfmt/src/coverage.rs
Normal file
15
src/tools/rustfmt/src/coverage.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use crate::{Config, EmitMode};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub(crate) fn transform_missing_snippet<'a>(config: &Config, string: &'a str) -> Cow<'a, str> {
|
||||
match config.emit_mode() {
|
||||
EmitMode::Coverage => Cow::from(replace_chars(string)),
|
||||
_ => Cow::from(string),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_chars(s: &str) -> String {
|
||||
s.chars()
|
||||
.map(|ch| if ch.is_whitespace() { ch } else { 'X' })
|
||||
.collect()
|
||||
}
|
||||
52
src/tools/rustfmt/src/emitter.rs
Normal file
52
src/tools/rustfmt/src/emitter.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
pub(crate) use self::checkstyle::*;
|
||||
pub(crate) use self::diff::*;
|
||||
pub(crate) use self::files::*;
|
||||
pub(crate) use self::files_with_backup::*;
|
||||
pub(crate) use self::json::*;
|
||||
pub(crate) use self::modified_lines::*;
|
||||
pub(crate) use self::stdout::*;
|
||||
use crate::FileName;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
mod checkstyle;
|
||||
mod diff;
|
||||
mod files;
|
||||
mod files_with_backup;
|
||||
mod json;
|
||||
mod modified_lines;
|
||||
mod stdout;
|
||||
|
||||
pub(crate) struct FormattedFile<'a> {
|
||||
pub(crate) filename: &'a FileName,
|
||||
pub(crate) original_text: &'a str,
|
||||
pub(crate) formatted_text: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub(crate) struct EmitterResult {
|
||||
pub(crate) has_diff: bool,
|
||||
}
|
||||
|
||||
pub(crate) trait Emitter {
|
||||
fn emit_formatted_file(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
formatted_file: FormattedFile<'_>,
|
||||
) -> Result<EmitterResult, io::Error>;
|
||||
|
||||
fn emit_header(&self, _output: &mut dyn Write) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_footer(&self, _output: &mut dyn Write) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_real_path(filename: &FileName) -> &Path {
|
||||
match *filename {
|
||||
FileName::Real(ref path) => path,
|
||||
_ => panic!("cannot format `{}` and emit to files", filename),
|
||||
}
|
||||
}
|
||||
148
src/tools/rustfmt/src/emitter/checkstyle.rs
Normal file
148
src/tools/rustfmt/src/emitter/checkstyle.rs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
use self::xml::XmlEscaped;
|
||||
use super::*;
|
||||
use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch};
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
mod xml;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct CheckstyleEmitter;
|
||||
|
||||
impl Emitter for CheckstyleEmitter {
|
||||
fn emit_header(&self, output: &mut dyn Write) -> Result<(), io::Error> {
|
||||
writeln!(output, r#"<?xml version="1.0" encoding="utf-8"?>"#)?;
|
||||
write!(output, r#"<checkstyle version="4.3">"#)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> {
|
||||
writeln!(output, "</checkstyle>")
|
||||
}
|
||||
|
||||
fn emit_formatted_file(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
FormattedFile {
|
||||
filename,
|
||||
original_text,
|
||||
formatted_text,
|
||||
}: FormattedFile<'_>,
|
||||
) -> Result<EmitterResult, io::Error> {
|
||||
const CONTEXT_SIZE: usize = 0;
|
||||
let filename = ensure_real_path(filename);
|
||||
let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE);
|
||||
output_checkstyle_file(output, filename, diff)?;
|
||||
Ok(EmitterResult::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn output_checkstyle_file<T>(
|
||||
mut writer: T,
|
||||
filename: &Path,
|
||||
diff: Vec<Mismatch>,
|
||||
) -> Result<(), io::Error>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
write!(writer, r#"<file name="{}">"#, filename.display())?;
|
||||
for mismatch in diff {
|
||||
let begin_line = mismatch.line_number;
|
||||
let mut current_line;
|
||||
let mut line_counter = 0;
|
||||
for line in mismatch.lines {
|
||||
// Do nothing with `DiffLine::Context` and `DiffLine::Resulting`.
|
||||
if let DiffLine::Expected(message) = line {
|
||||
current_line = begin_line + line_counter;
|
||||
line_counter += 1;
|
||||
write!(
|
||||
writer,
|
||||
r#"<error line="{}" severity="warning" message="Should be `{}`" />"#,
|
||||
current_line,
|
||||
XmlEscaped(&message)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
write!(writer, "</file>")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn emits_empty_record_on_file_with_no_mismatches() {
|
||||
let file_name = "src/well_formatted.rs";
|
||||
let mut writer = Vec::new();
|
||||
let _ = output_checkstyle_file(&mut writer, &PathBuf::from(file_name), vec![]);
|
||||
assert_eq!(
|
||||
&writer[..],
|
||||
format!(r#"<file name="{}"></file>"#, file_name).as_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/rust-lang/rustfmt/issues/1636
|
||||
#[test]
|
||||
fn emits_single_xml_tree_containing_all_files() {
|
||||
let bin_file = "src/bin.rs";
|
||||
let bin_original = vec!["fn main() {", "println!(\"Hello, world!\");", "}"];
|
||||
let bin_formatted = vec!["fn main() {", " println!(\"Hello, world!\");", "}"];
|
||||
let lib_file = "src/lib.rs";
|
||||
let lib_original = vec!["fn greet() {", "println!(\"Greetings!\");", "}"];
|
||||
let lib_formatted = vec!["fn greet() {", " println!(\"Greetings!\");", "}"];
|
||||
let mut writer = Vec::new();
|
||||
let mut emitter = CheckstyleEmitter::default();
|
||||
let _ = emitter.emit_header(&mut writer);
|
||||
let _ = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from(bin_file)),
|
||||
original_text: &bin_original.join("\n"),
|
||||
formatted_text: &bin_formatted.join("\n"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from(lib_file)),
|
||||
original_text: &lib_original.join("\n"),
|
||||
formatted_text: &lib_formatted.join("\n"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = emitter.emit_footer(&mut writer);
|
||||
let exp_bin_xml = vec![
|
||||
format!(r#"<file name="{}">"#, bin_file),
|
||||
format!(
|
||||
r#"<error line="2" severity="warning" message="Should be `{}`" />"#,
|
||||
XmlEscaped(&r#" println!("Hello, world!");"#),
|
||||
),
|
||||
String::from("</file>"),
|
||||
];
|
||||
let exp_lib_xml = vec![
|
||||
format!(r#"<file name="{}">"#, lib_file),
|
||||
format!(
|
||||
r#"<error line="2" severity="warning" message="Should be `{}`" />"#,
|
||||
XmlEscaped(&r#" println!("Greetings!");"#),
|
||||
),
|
||||
String::from("</file>"),
|
||||
];
|
||||
assert_eq!(
|
||||
String::from_utf8(writer).unwrap(),
|
||||
vec![
|
||||
r#"<?xml version="1.0" encoding="utf-8"?>"#,
|
||||
"\n",
|
||||
r#"<checkstyle version="4.3">"#,
|
||||
&format!("{}{}", exp_bin_xml.join(""), exp_lib_xml.join("")),
|
||||
"</checkstyle>\n",
|
||||
]
|
||||
.join(""),
|
||||
);
|
||||
}
|
||||
}
|
||||
52
src/tools/rustfmt/src/emitter/checkstyle/xml.rs
Normal file
52
src/tools/rustfmt/src/emitter/checkstyle/xml.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use std::fmt::{self, Display};
|
||||
|
||||
/// Convert special characters into XML entities.
|
||||
/// This is needed for checkstyle output.
|
||||
pub(super) struct XmlEscaped<'a>(pub(super) &'a str);
|
||||
|
||||
impl<'a> Display for XmlEscaped<'a> {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for char in self.0.chars() {
|
||||
match char {
|
||||
'<' => write!(formatter, "<"),
|
||||
'>' => write!(formatter, ">"),
|
||||
'"' => write!(formatter, """),
|
||||
'\'' => write!(formatter, "'"),
|
||||
'&' => write!(formatter, "&"),
|
||||
_ => write!(formatter, "{}", char),
|
||||
}?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn special_characters_are_escaped() {
|
||||
assert_eq!(
|
||||
"<>"'&",
|
||||
format!("{}", XmlEscaped(r#"<>"'&"#)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn special_characters_are_escaped_in_string_with_other_characters() {
|
||||
assert_eq!(
|
||||
"The quick brown "🦊" jumps <over> the lazy 🐶",
|
||||
format!(
|
||||
"{}",
|
||||
XmlEscaped(r#"The quick brown "🦊" jumps <over> the lazy 🐶"#)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_characters_are_not_escaped() {
|
||||
let string = "The quick brown 🦊 jumps over the lazy 🐶";
|
||||
assert_eq!(string, format!("{}", XmlEscaped(string)));
|
||||
}
|
||||
}
|
||||
138
src/tools/rustfmt/src/emitter/diff.rs
Normal file
138
src/tools/rustfmt/src/emitter/diff.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::rustfmt_diff::{make_diff, print_diff};
|
||||
|
||||
pub(crate) struct DiffEmitter {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl DiffEmitter {
|
||||
pub(crate) fn new(config: Config) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for DiffEmitter {
|
||||
fn emit_formatted_file(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
FormattedFile {
|
||||
filename,
|
||||
original_text,
|
||||
formatted_text,
|
||||
}: FormattedFile<'_>,
|
||||
) -> Result<EmitterResult, io::Error> {
|
||||
const CONTEXT_SIZE: usize = 3;
|
||||
let mismatch = make_diff(&original_text, formatted_text, CONTEXT_SIZE);
|
||||
let has_diff = !mismatch.is_empty();
|
||||
|
||||
if has_diff {
|
||||
if self.config.print_misformatted_file_names() {
|
||||
writeln!(output, "{}", ensure_real_path(filename).display())?;
|
||||
} else {
|
||||
print_diff(
|
||||
mismatch,
|
||||
|line_num| format!("Diff in {} at line {}:", filename, line_num),
|
||||
&self.config,
|
||||
);
|
||||
}
|
||||
} else if original_text != formatted_text {
|
||||
// This occurs when the only difference between the original and formatted values
|
||||
// is the newline style. This happens because The make_diff function compares the
|
||||
// original and formatted values line by line, independent of line endings.
|
||||
let file_path = ensure_real_path(filename);
|
||||
writeln!(output, "Incorrect newline style in {}", file_path.display())?;
|
||||
return Ok(EmitterResult { has_diff: true });
|
||||
}
|
||||
|
||||
return Ok(EmitterResult { has_diff });
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::Config;
|
||||
use crate::FileName;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn does_not_print_when_no_files_reformatted() {
|
||||
let mut writer = Vec::new();
|
||||
let config = Config::default();
|
||||
let mut emitter = DiffEmitter::new(config);
|
||||
let result = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from("src/lib.rs")),
|
||||
original_text: "fn empty() {}\n",
|
||||
formatted_text: "fn empty() {}\n",
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result.has_diff, false);
|
||||
assert_eq!(writer.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prints_file_names_when_config_is_enabled() {
|
||||
let bin_file = "src/bin.rs";
|
||||
let bin_original = "fn main() {\nprintln!(\"Hello, world!\");\n}";
|
||||
let bin_formatted = "fn main() {\n println!(\"Hello, world!\");\n}";
|
||||
let lib_file = "src/lib.rs";
|
||||
let lib_original = "fn greet() {\nprintln!(\"Greetings!\");\n}";
|
||||
let lib_formatted = "fn greet() {\n println!(\"Greetings!\");\n}";
|
||||
|
||||
let mut writer = Vec::new();
|
||||
let mut config = Config::default();
|
||||
config.set().print_misformatted_file_names(true);
|
||||
let mut emitter = DiffEmitter::new(config);
|
||||
let _ = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from(bin_file)),
|
||||
original_text: bin_original,
|
||||
formatted_text: bin_formatted,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from(lib_file)),
|
||||
original_text: lib_original,
|
||||
formatted_text: lib_formatted,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
String::from_utf8(writer).unwrap(),
|
||||
format!("{}\n{}\n", bin_file, lib_file),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prints_newline_message_with_only_newline_style_diff() {
|
||||
let mut writer = Vec::new();
|
||||
let config = Config::default();
|
||||
let mut emitter = DiffEmitter::new(config);
|
||||
let _ = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from("src/lib.rs")),
|
||||
original_text: "fn empty() {}\n",
|
||||
formatted_text: "fn empty() {}\r\n",
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
String::from_utf8(writer).unwrap(),
|
||||
String::from("Incorrect newline style in src/lib.rs\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
37
src/tools/rustfmt/src/emitter/files.rs
Normal file
37
src/tools/rustfmt/src/emitter/files.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct FilesEmitter {
|
||||
print_misformatted_file_names: bool,
|
||||
}
|
||||
|
||||
impl FilesEmitter {
|
||||
pub(crate) fn new(print_misformatted_file_names: bool) -> Self {
|
||||
Self {
|
||||
print_misformatted_file_names,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for FilesEmitter {
|
||||
fn emit_formatted_file(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
FormattedFile {
|
||||
filename,
|
||||
original_text,
|
||||
formatted_text,
|
||||
}: FormattedFile<'_>,
|
||||
) -> Result<EmitterResult, io::Error> {
|
||||
// Write text directly over original file if there is a diff.
|
||||
let filename = ensure_real_path(filename);
|
||||
if original_text != formatted_text {
|
||||
fs::write(filename, formatted_text)?;
|
||||
if self.print_misformatted_file_names {
|
||||
writeln!(output, "{}", filename.display())?;
|
||||
}
|
||||
}
|
||||
Ok(EmitterResult::default())
|
||||
}
|
||||
}
|
||||
31
src/tools/rustfmt/src/emitter/files_with_backup.rs
Normal file
31
src/tools/rustfmt/src/emitter/files_with_backup.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use super::*;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct FilesWithBackupEmitter;
|
||||
|
||||
impl Emitter for FilesWithBackupEmitter {
|
||||
fn emit_formatted_file(
|
||||
&mut self,
|
||||
_output: &mut dyn Write,
|
||||
FormattedFile {
|
||||
filename,
|
||||
original_text,
|
||||
formatted_text,
|
||||
}: FormattedFile<'_>,
|
||||
) -> Result<EmitterResult, io::Error> {
|
||||
let filename = ensure_real_path(filename);
|
||||
if original_text != formatted_text {
|
||||
// Do a little dance to make writing safer - write to a temp file
|
||||
// rename the original to a .bk, then rename the temp file to the
|
||||
// original.
|
||||
let tmp_name = filename.with_extension("tmp");
|
||||
let bk_name = filename.with_extension("bk");
|
||||
|
||||
fs::write(&tmp_name, formatted_text)?;
|
||||
fs::rename(filename, bk_name)?;
|
||||
fs::rename(tmp_name, filename)?;
|
||||
}
|
||||
Ok(EmitterResult::default())
|
||||
}
|
||||
}
|
||||
349
src/tools/rustfmt/src/emitter/json.rs
Normal file
349
src/tools/rustfmt/src/emitter/json.rs
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
use super::*;
|
||||
use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch};
|
||||
use serde::Serialize;
|
||||
use serde_json::to_string as to_json_string;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct JsonEmitter {
|
||||
num_files: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
struct MismatchedBlock {
|
||||
original_begin_line: u32,
|
||||
original_end_line: u32,
|
||||
expected_begin_line: u32,
|
||||
expected_end_line: u32,
|
||||
original: String,
|
||||
expected: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
struct MismatchedFile {
|
||||
name: String,
|
||||
mismatches: Vec<MismatchedBlock>,
|
||||
}
|
||||
|
||||
impl Emitter for JsonEmitter {
|
||||
fn emit_header(&self, output: &mut dyn Write) -> Result<(), io::Error> {
|
||||
write!(output, "[")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> {
|
||||
write!(output, "]")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_formatted_file(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
FormattedFile {
|
||||
filename,
|
||||
original_text,
|
||||
formatted_text,
|
||||
}: FormattedFile<'_>,
|
||||
) -> Result<EmitterResult, io::Error> {
|
||||
const CONTEXT_SIZE: usize = 0;
|
||||
let filename = ensure_real_path(filename);
|
||||
let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE);
|
||||
let has_diff = !diff.is_empty();
|
||||
|
||||
if has_diff {
|
||||
output_json_file(output, filename, diff, self.num_files)?;
|
||||
self.num_files += 1;
|
||||
}
|
||||
|
||||
Ok(EmitterResult { has_diff })
|
||||
}
|
||||
}
|
||||
|
||||
fn output_json_file<T>(
|
||||
mut writer: T,
|
||||
filename: &Path,
|
||||
diff: Vec<Mismatch>,
|
||||
num_emitted_files: u32,
|
||||
) -> Result<(), io::Error>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
let mut mismatches = vec![];
|
||||
for mismatch in diff {
|
||||
let original_begin_line = mismatch.line_number_orig;
|
||||
let expected_begin_line = mismatch.line_number;
|
||||
let mut original_end_line = original_begin_line;
|
||||
let mut expected_end_line = expected_begin_line;
|
||||
let mut original_line_counter = 0;
|
||||
let mut expected_line_counter = 0;
|
||||
let mut original_lines = vec![];
|
||||
let mut expected_lines = vec![];
|
||||
|
||||
for line in mismatch.lines {
|
||||
match line {
|
||||
DiffLine::Expected(msg) => {
|
||||
expected_end_line = expected_begin_line + expected_line_counter;
|
||||
expected_line_counter += 1;
|
||||
expected_lines.push(msg)
|
||||
}
|
||||
DiffLine::Resulting(msg) => {
|
||||
original_end_line = original_begin_line + original_line_counter;
|
||||
original_line_counter += 1;
|
||||
original_lines.push(msg)
|
||||
}
|
||||
DiffLine::Context(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
mismatches.push(MismatchedBlock {
|
||||
original_begin_line,
|
||||
original_end_line,
|
||||
expected_begin_line,
|
||||
expected_end_line,
|
||||
original: original_lines.join("\n"),
|
||||
expected: expected_lines.join("\n"),
|
||||
});
|
||||
}
|
||||
let json = to_json_string(&MismatchedFile {
|
||||
name: String::from(filename.to_str().unwrap()),
|
||||
mismatches,
|
||||
})?;
|
||||
let prefix = if num_emitted_files > 0 { "," } else { "" };
|
||||
write!(writer, "{}{}", prefix, &json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::FileName;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn expected_line_range_correct_when_single_line_split() {
|
||||
let file = "foo/bar.rs";
|
||||
let mismatched_file = MismatchedFile {
|
||||
name: String::from(file),
|
||||
mismatches: vec![MismatchedBlock {
|
||||
original_begin_line: 79,
|
||||
original_end_line: 79,
|
||||
expected_begin_line: 79,
|
||||
expected_end_line: 82,
|
||||
original: String::from("fn Foo<T>() where T: Bar {"),
|
||||
expected: String::from("fn Foo<T>()\nwhere\n T: Bar,\n{"),
|
||||
}],
|
||||
};
|
||||
let mismatch = Mismatch {
|
||||
line_number: 79,
|
||||
line_number_orig: 79,
|
||||
lines: vec![
|
||||
DiffLine::Resulting(String::from("fn Foo<T>() where T: Bar {")),
|
||||
DiffLine::Expected(String::from("fn Foo<T>()")),
|
||||
DiffLine::Expected(String::from("where")),
|
||||
DiffLine::Expected(String::from(" T: Bar,")),
|
||||
DiffLine::Expected(String::from("{")),
|
||||
],
|
||||
};
|
||||
|
||||
let mut writer = Vec::new();
|
||||
let exp_json = to_json_string(&mismatched_file).unwrap();
|
||||
let _ = output_json_file(&mut writer, &PathBuf::from(file), vec![mismatch], 0);
|
||||
assert_eq!(&writer[..], format!("{}", exp_json).as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn context_lines_ignored() {
|
||||
let file = "src/lib.rs";
|
||||
let mismatched_file = MismatchedFile {
|
||||
name: String::from(file),
|
||||
mismatches: vec![MismatchedBlock {
|
||||
original_begin_line: 5,
|
||||
original_end_line: 5,
|
||||
expected_begin_line: 5,
|
||||
expected_end_line: 5,
|
||||
original: String::from(
|
||||
"fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {",
|
||||
),
|
||||
expected: String::from(
|
||||
"fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {",
|
||||
),
|
||||
}],
|
||||
};
|
||||
let mismatch = Mismatch {
|
||||
line_number: 5,
|
||||
line_number_orig: 5,
|
||||
lines: vec![
|
||||
DiffLine::Context(String::new()),
|
||||
DiffLine::Resulting(String::from(
|
||||
"fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {",
|
||||
)),
|
||||
DiffLine::Context(String::new()),
|
||||
DiffLine::Expected(String::from(
|
||||
"fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {",
|
||||
)),
|
||||
DiffLine::Context(String::new()),
|
||||
],
|
||||
};
|
||||
|
||||
let mut writer = Vec::new();
|
||||
let exp_json = to_json_string(&mismatched_file).unwrap();
|
||||
let _ = output_json_file(&mut writer, &PathBuf::from(file), vec![mismatch], 0);
|
||||
assert_eq!(&writer[..], format!("{}", exp_json).as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emits_empty_array_on_no_diffs() {
|
||||
let mut writer = Vec::new();
|
||||
let mut emitter = JsonEmitter::default();
|
||||
let _ = emitter.emit_header(&mut writer);
|
||||
let result = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from("src/lib.rs")),
|
||||
original_text: "fn empty() {}\n",
|
||||
formatted_text: "fn empty() {}\n",
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = emitter.emit_footer(&mut writer);
|
||||
assert_eq!(result.has_diff, false);
|
||||
assert_eq!(&writer[..], "[]".as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emits_array_with_files_with_diffs() {
|
||||
let file_name = "src/bin.rs";
|
||||
let original = vec![
|
||||
"fn main() {",
|
||||
"println!(\"Hello, world!\");",
|
||||
"}",
|
||||
"",
|
||||
"#[cfg(test)]",
|
||||
"mod tests {",
|
||||
"#[test]",
|
||||
"fn it_works() {",
|
||||
" assert_eq!(2 + 2, 4);",
|
||||
"}",
|
||||
"}",
|
||||
];
|
||||
let formatted = vec![
|
||||
"fn main() {",
|
||||
" println!(\"Hello, world!\");",
|
||||
"}",
|
||||
"",
|
||||
"#[cfg(test)]",
|
||||
"mod tests {",
|
||||
" #[test]",
|
||||
" fn it_works() {",
|
||||
" assert_eq!(2 + 2, 4);",
|
||||
" }",
|
||||
"}",
|
||||
];
|
||||
let mut writer = Vec::new();
|
||||
let mut emitter = JsonEmitter::default();
|
||||
let _ = emitter.emit_header(&mut writer);
|
||||
let result = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from(file_name)),
|
||||
original_text: &original.join("\n"),
|
||||
formatted_text: &formatted.join("\n"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = emitter.emit_footer(&mut writer);
|
||||
let exp_json = to_json_string(&MismatchedFile {
|
||||
name: String::from(file_name),
|
||||
mismatches: vec![
|
||||
MismatchedBlock {
|
||||
original_begin_line: 2,
|
||||
original_end_line: 2,
|
||||
expected_begin_line: 2,
|
||||
expected_end_line: 2,
|
||||
original: String::from("println!(\"Hello, world!\");"),
|
||||
expected: String::from(" println!(\"Hello, world!\");"),
|
||||
},
|
||||
MismatchedBlock {
|
||||
original_begin_line: 7,
|
||||
original_end_line: 10,
|
||||
expected_begin_line: 7,
|
||||
expected_end_line: 10,
|
||||
original: String::from(
|
||||
"#[test]\nfn it_works() {\n assert_eq!(2 + 2, 4);\n}",
|
||||
),
|
||||
expected: String::from(
|
||||
" #[test]\n fn it_works() {\n assert_eq!(2 + 2, 4);\n }",
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(result.has_diff, true);
|
||||
assert_eq!(&writer[..], format!("[{}]", exp_json).as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emits_valid_json_with_multiple_files() {
|
||||
let bin_file = "src/bin.rs";
|
||||
let bin_original = vec!["fn main() {", "println!(\"Hello, world!\");", "}"];
|
||||
let bin_formatted = vec!["fn main() {", " println!(\"Hello, world!\");", "}"];
|
||||
let lib_file = "src/lib.rs";
|
||||
let lib_original = vec!["fn greet() {", "println!(\"Greetings!\");", "}"];
|
||||
let lib_formatted = vec!["fn greet() {", " println!(\"Greetings!\");", "}"];
|
||||
let mut writer = Vec::new();
|
||||
let mut emitter = JsonEmitter::default();
|
||||
let _ = emitter.emit_header(&mut writer);
|
||||
let _ = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from(bin_file)),
|
||||
original_text: &bin_original.join("\n"),
|
||||
formatted_text: &bin_formatted.join("\n"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = emitter
|
||||
.emit_formatted_file(
|
||||
&mut writer,
|
||||
FormattedFile {
|
||||
filename: &FileName::Real(PathBuf::from(lib_file)),
|
||||
original_text: &lib_original.join("\n"),
|
||||
formatted_text: &lib_formatted.join("\n"),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let _ = emitter.emit_footer(&mut writer);
|
||||
let exp_bin_json = to_json_string(&MismatchedFile {
|
||||
name: String::from(bin_file),
|
||||
mismatches: vec![MismatchedBlock {
|
||||
original_begin_line: 2,
|
||||
original_end_line: 2,
|
||||
expected_begin_line: 2,
|
||||
expected_end_line: 2,
|
||||
original: String::from("println!(\"Hello, world!\");"),
|
||||
expected: String::from(" println!(\"Hello, world!\");"),
|
||||
}],
|
||||
})
|
||||
.unwrap();
|
||||
let exp_lib_json = to_json_string(&MismatchedFile {
|
||||
name: String::from(lib_file),
|
||||
mismatches: vec![MismatchedBlock {
|
||||
original_begin_line: 2,
|
||||
original_end_line: 2,
|
||||
expected_begin_line: 2,
|
||||
expected_end_line: 2,
|
||||
original: String::from("println!(\"Greetings!\");"),
|
||||
expected: String::from(" println!(\"Greetings!\");"),
|
||||
}],
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
&writer[..],
|
||||
format!("[{},{}]", exp_bin_json, exp_lib_json).as_bytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
24
src/tools/rustfmt/src/emitter/modified_lines.rs
Normal file
24
src/tools/rustfmt/src/emitter/modified_lines.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use super::*;
|
||||
use crate::rustfmt_diff::{make_diff, ModifiedLines};
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ModifiedLinesEmitter;
|
||||
|
||||
impl Emitter for ModifiedLinesEmitter {
|
||||
fn emit_formatted_file(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
FormattedFile {
|
||||
original_text,
|
||||
formatted_text,
|
||||
..
|
||||
}: FormattedFile<'_>,
|
||||
) -> Result<EmitterResult, io::Error> {
|
||||
const CONTEXT_SIZE: usize = 0;
|
||||
let mismatch = make_diff(original_text, formatted_text, CONTEXT_SIZE);
|
||||
let has_diff = !mismatch.is_empty();
|
||||
write!(output, "{}", ModifiedLines::from(mismatch))?;
|
||||
Ok(EmitterResult { has_diff })
|
||||
}
|
||||
}
|
||||
32
src/tools/rustfmt/src/emitter/stdout.rs
Normal file
32
src/tools/rustfmt/src/emitter/stdout.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use super::*;
|
||||
use crate::config::Verbosity;
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct StdoutEmitter {
|
||||
verbosity: Verbosity,
|
||||
}
|
||||
|
||||
impl StdoutEmitter {
|
||||
pub(crate) fn new(verbosity: Verbosity) -> Self {
|
||||
Self { verbosity }
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for StdoutEmitter {
|
||||
fn emit_formatted_file(
|
||||
&mut self,
|
||||
output: &mut dyn Write,
|
||||
FormattedFile {
|
||||
filename,
|
||||
formatted_text,
|
||||
..
|
||||
}: FormattedFile<'_>,
|
||||
) -> Result<EmitterResult, io::Error> {
|
||||
if self.verbosity != Verbosity::Quiet {
|
||||
writeln!(output, "{}:\n", filename)?;
|
||||
}
|
||||
write!(output, "{}", formatted_text)?;
|
||||
Ok(EmitterResult::default())
|
||||
}
|
||||
}
|
||||
2076
src/tools/rustfmt/src/expr.rs
Normal file
2076
src/tools/rustfmt/src/expr.rs
Normal file
File diff suppressed because it is too large
Load diff
280
src/tools/rustfmt/src/format-diff/main.rs
Normal file
280
src/tools/rustfmt/src/format-diff/main.rs
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
// Inspired by Clang's clang-format-diff:
|
||||
//
|
||||
// https://github.com/llvm-mirror/clang/blob/master/tools/clang-format/clang-format-diff.py
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json as json;
|
||||
use thiserror::Error;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::{self, BufRead};
|
||||
use std::process;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use structopt::clap::AppSettings;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// The default pattern of files to format.
|
||||
///
|
||||
/// We only want to format rust files by default.
|
||||
const DEFAULT_PATTERN: &str = r".*\.rs";
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum FormatDiffError {
|
||||
#[error("{0}")]
|
||||
IncorrectOptions(#[from] getopts::Fail),
|
||||
#[error("{0}")]
|
||||
IncorrectFilter(#[from] regex::Error),
|
||||
#[error("{0}")]
|
||||
IoError(#[from] io::Error),
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(
|
||||
name = "rustfmt-format-diff",
|
||||
setting = AppSettings::DisableVersion,
|
||||
setting = AppSettings::NextLineHelp
|
||||
)]
|
||||
pub struct Opts {
|
||||
/// Skip the smallest prefix containing NUMBER slashes
|
||||
#[structopt(
|
||||
short = "p",
|
||||
long = "skip-prefix",
|
||||
value_name = "NUMBER",
|
||||
default_value = "0"
|
||||
)]
|
||||
skip_prefix: u32,
|
||||
|
||||
/// Custom pattern selecting file paths to reformat
|
||||
#[structopt(
|
||||
short = "f",
|
||||
long = "filter",
|
||||
value_name = "PATTERN",
|
||||
default_value = DEFAULT_PATTERN
|
||||
)]
|
||||
filter: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let opts = Opts::from_args();
|
||||
if let Err(e) = run(opts) {
|
||||
println!("{}", e);
|
||||
Opts::clap().print_help().expect("cannot write to stdout");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
struct Range {
|
||||
file: String,
|
||||
range: [u32; 2],
|
||||
}
|
||||
|
||||
fn run(opts: Opts) -> Result<(), FormatDiffError> {
|
||||
let (files, ranges) = scan_diff(io::stdin(), opts.skip_prefix, &opts.filter)?;
|
||||
run_rustfmt(&files, &ranges)
|
||||
}
|
||||
|
||||
fn run_rustfmt(files: &HashSet<String>, ranges: &[Range]) -> Result<(), FormatDiffError> {
|
||||
if files.is_empty() || ranges.is_empty() {
|
||||
debug!("No files to format found");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let ranges_as_json = json::to_string(ranges).unwrap();
|
||||
|
||||
debug!("Files: {:?}", files);
|
||||
debug!("Ranges: {:?}", ranges);
|
||||
|
||||
let rustfmt_var = env::var_os("RUSTFMT");
|
||||
let rustfmt = match &rustfmt_var {
|
||||
Some(rustfmt) => rustfmt,
|
||||
None => OsStr::new("rustfmt"),
|
||||
};
|
||||
let exit_status = process::Command::new(rustfmt)
|
||||
.args(files)
|
||||
.arg("--file-lines")
|
||||
.arg(ranges_as_json)
|
||||
.status()?;
|
||||
|
||||
if !exit_status.success() {
|
||||
return Err(FormatDiffError::IoError(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("rustfmt failed with {}", exit_status),
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Scans a diff from `from`, and returns the set of files found, and the ranges
|
||||
/// in those files.
|
||||
fn scan_diff<R>(
|
||||
from: R,
|
||||
skip_prefix: u32,
|
||||
file_filter: &str,
|
||||
) -> Result<(HashSet<String>, Vec<Range>), FormatDiffError>
|
||||
where
|
||||
R: io::Read,
|
||||
{
|
||||
let diff_pattern = format!(r"^\+\+\+\s(?:.*?/){{{}}}(\S*)", skip_prefix);
|
||||
let diff_pattern = Regex::new(&diff_pattern).unwrap();
|
||||
|
||||
let lines_pattern = Regex::new(r"^@@.*\+(\d+)(,(\d+))?").unwrap();
|
||||
|
||||
let file_filter = Regex::new(&format!("^{}$", file_filter))?;
|
||||
|
||||
let mut current_file = None;
|
||||
|
||||
let mut files = HashSet::new();
|
||||
let mut ranges = vec![];
|
||||
for line in io::BufReader::new(from).lines() {
|
||||
let line = line.unwrap();
|
||||
|
||||
if let Some(captures) = diff_pattern.captures(&line) {
|
||||
current_file = Some(captures.get(1).unwrap().as_str().to_owned());
|
||||
}
|
||||
|
||||
let file = match current_file {
|
||||
Some(ref f) => &**f,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// FIXME(emilio): We could avoid this most of the time if needed, but
|
||||
// it's not clear it's worth it.
|
||||
if !file_filter.is_match(file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let lines_captures = match lines_pattern.captures(&line) {
|
||||
Some(captures) => captures,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let start_line = lines_captures
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse::<u32>()
|
||||
.unwrap();
|
||||
let line_count = match lines_captures.get(3) {
|
||||
Some(line_count) => line_count.as_str().parse::<u32>().unwrap(),
|
||||
None => 1,
|
||||
};
|
||||
|
||||
if line_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let end_line = start_line + line_count - 1;
|
||||
files.insert(file.to_owned());
|
||||
ranges.push(Range {
|
||||
file: file.to_owned(),
|
||||
range: [start_line, end_line],
|
||||
});
|
||||
}
|
||||
|
||||
Ok((files, ranges))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_simple_git_diff() {
|
||||
const DIFF: &str = include_str!("test/bindgen.diff");
|
||||
let (files, ranges) = scan_diff(DIFF.as_bytes(), 1, r".*\.rs").expect("scan_diff failed?");
|
||||
|
||||
assert!(
|
||||
files.contains("src/ir/traversal.rs"),
|
||||
"Should've matched the filter"
|
||||
);
|
||||
|
||||
assert!(
|
||||
!files.contains("tests/headers/anon_enum.hpp"),
|
||||
"Shouldn't have matched the filter"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
&ranges,
|
||||
&[
|
||||
Range {
|
||||
file: "src/ir/item.rs".to_owned(),
|
||||
range: [148, 158],
|
||||
},
|
||||
Range {
|
||||
file: "src/ir/item.rs".to_owned(),
|
||||
range: [160, 170],
|
||||
},
|
||||
Range {
|
||||
file: "src/ir/traversal.rs".to_owned(),
|
||||
range: [9, 16],
|
||||
},
|
||||
Range {
|
||||
file: "src/ir/traversal.rs".to_owned(),
|
||||
range: [35, 43],
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cmd_line_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_options() {
|
||||
let empty: Vec<String> = vec![];
|
||||
let o = Opts::from_iter(&empty);
|
||||
assert_eq!(DEFAULT_PATTERN, o.filter);
|
||||
assert_eq!(0, o.skip_prefix);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn good_options() {
|
||||
let o = Opts::from_iter(&["test", "-p", "10", "-f", r".*\.hs"]);
|
||||
assert_eq!(r".*\.hs", o.filter);
|
||||
assert_eq!(10, o.skip_prefix);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_option() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "unexpected"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unexpected_flag() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "--flag"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overridden_option() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "-p", "10", "-p", "20"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_filter() {
|
||||
assert!(
|
||||
Opts::clap()
|
||||
.get_matches_from_safe(&["test", "-p", "-1"])
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
67
src/tools/rustfmt/src/format-diff/test/bindgen.diff
Normal file
67
src/tools/rustfmt/src/format-diff/test/bindgen.diff
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
diff --git a/src/ir/item.rs b/src/ir/item.rs
|
||||
index 7f3afefb..90d15e96 100644
|
||||
--- a/src/ir/item.rs
|
||||
+++ b/src/ir/item.rs
|
||||
@@ -148,7 +148,11 @@ impl<'a, 'b> Iterator for ItemAncestorsIter<'a, 'b>
|
||||
impl AsTemplateParam for ItemId {
|
||||
type Extra = ();
|
||||
|
||||
- fn as_template_param(&self, ctx: &BindgenContext, _: &()) -> Option<ItemId> {
|
||||
+ fn as_template_param(
|
||||
+ &self,
|
||||
+ ctx: &BindgenContext,
|
||||
+ _: &(),
|
||||
+ ) -> Option<ItemId> {
|
||||
ctx.resolve_item(*self).as_template_param(ctx, &())
|
||||
}
|
||||
}
|
||||
@@ -156,7 +160,11 @@ impl AsTemplateParam for ItemId {
|
||||
impl AsTemplateParam for Item {
|
||||
type Extra = ();
|
||||
|
||||
- fn as_template_param(&self, ctx: &BindgenContext, _: &()) -> Option<ItemId> {
|
||||
+ fn as_template_param(
|
||||
+ &self,
|
||||
+ ctx: &BindgenContext,
|
||||
+ _: &(),
|
||||
+ ) -> Option<ItemId> {
|
||||
self.kind.as_template_param(ctx, self)
|
||||
}
|
||||
}
|
||||
diff --git a/src/ir/traversal.rs b/src/ir/traversal.rs
|
||||
index 762a3e2d..b9c9dd4e 100644
|
||||
--- a/src/ir/traversal.rs
|
||||
+++ b/src/ir/traversal.rs
|
||||
@@ -9,6 +9,8 @@ use std::collections::{BTreeMap, VecDeque};
|
||||
///
|
||||
/// from --> to
|
||||
///
|
||||
+/// Random content to generate a diff.
|
||||
+///
|
||||
/// The `from` is left implicit: it is the concrete `Trace` implementer which
|
||||
/// yielded this outgoing edge.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@@ -33,7 +35,9 @@ impl Into<ItemId> for Edge {
|
||||
}
|
||||
}
|
||||
|
||||
-/// The kind of edge reference. This is useful when we wish to only consider
|
||||
+/// The kind of edge reference.
|
||||
+///
|
||||
+/// This is useful when we wish to only consider
|
||||
/// certain kinds of edges for a particular traversal or analysis.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum EdgeKind {
|
||||
diff --git a/tests/headers/anon_enum.hpp b/tests/headers/anon_enum.hpp
|
||||
index 1961fe6c..34759df3 100644
|
||||
--- a/tests/headers/anon_enum.hpp
|
||||
+++ b/tests/headers/anon_enum.hpp
|
||||
@@ -1,7 +1,7 @@
|
||||
struct Test {
|
||||
int foo;
|
||||
float bar;
|
||||
- enum { T_NONE };
|
||||
+ enum { T_NONE, T_SOME };
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
150
src/tools/rustfmt/src/format_report_formatter.rs
Normal file
150
src/tools/rustfmt/src/format_report_formatter.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
use crate::formatting::FormattingError;
|
||||
use crate::{ErrorKind, FormatReport};
|
||||
use annotate_snippets::display_list::{DisplayList, FormatOptions};
|
||||
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
/// A builder for [`FormatReportFormatter`].
|
||||
pub struct FormatReportFormatterBuilder<'a> {
|
||||
report: &'a FormatReport,
|
||||
enable_colors: bool,
|
||||
}
|
||||
|
||||
impl<'a> FormatReportFormatterBuilder<'a> {
|
||||
/// Creates a new [`FormatReportFormatterBuilder`].
|
||||
pub fn new(report: &'a FormatReport) -> Self {
|
||||
Self {
|
||||
report,
|
||||
enable_colors: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables colors and formatting in the output.
|
||||
pub fn enable_colors(self, enable_colors: bool) -> Self {
|
||||
Self {
|
||||
enable_colors,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`FormatReportFormatter`] from the settings in this builder.
|
||||
pub fn build(self) -> FormatReportFormatter<'a> {
|
||||
FormatReportFormatter {
|
||||
report: self.report,
|
||||
enable_colors: self.enable_colors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats the warnings/errors in a [`FormatReport`].
|
||||
///
|
||||
/// Can be created using a [`FormatReportFormatterBuilder`].
|
||||
pub struct FormatReportFormatter<'a> {
|
||||
report: &'a FormatReport,
|
||||
enable_colors: bool,
|
||||
}
|
||||
|
||||
impl<'a> Display for FormatReportFormatter<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let errors_by_file = &self.report.internal.borrow().0;
|
||||
|
||||
let opt = FormatOptions {
|
||||
color: self.enable_colors,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for (file, errors) in errors_by_file {
|
||||
for error in errors {
|
||||
let error_kind = error.kind.to_string();
|
||||
let title = Some(Annotation {
|
||||
id: if error.is_internal() {
|
||||
Some("internal")
|
||||
} else {
|
||||
None
|
||||
},
|
||||
label: Some(&error_kind),
|
||||
annotation_type: error_kind_to_snippet_annotation_type(&error.kind),
|
||||
});
|
||||
|
||||
let message_suffix = error.msg_suffix();
|
||||
let footer = if !message_suffix.is_empty() {
|
||||
Some(Annotation {
|
||||
id: None,
|
||||
label: Some(message_suffix),
|
||||
annotation_type: AnnotationType::Note,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let origin = format!("{}:{}", file, error.line);
|
||||
let slice = Slice {
|
||||
source: &error.line_buffer.clone(),
|
||||
line_start: error.line,
|
||||
origin: Some(origin.as_str()),
|
||||
fold: false,
|
||||
annotations: slice_annotation(error).into_iter().collect(),
|
||||
};
|
||||
|
||||
let snippet = Snippet {
|
||||
title,
|
||||
footer: footer.into_iter().collect(),
|
||||
slices: vec![slice],
|
||||
opt,
|
||||
};
|
||||
writeln!(f, "{}\n", DisplayList::from(snippet))?;
|
||||
}
|
||||
}
|
||||
|
||||
if !errors_by_file.is_empty() {
|
||||
let label = format!(
|
||||
"rustfmt has failed to format. See previous {} errors.",
|
||||
self.report.warning_count()
|
||||
);
|
||||
let snippet = Snippet {
|
||||
title: Some(Annotation {
|
||||
id: None,
|
||||
label: Some(&label),
|
||||
annotation_type: AnnotationType::Warning,
|
||||
}),
|
||||
footer: Vec::new(),
|
||||
slices: Vec::new(),
|
||||
opt,
|
||||
};
|
||||
writeln!(f, "{}", DisplayList::from(snippet))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn slice_annotation(error: &FormattingError) -> Option<SourceAnnotation<'_>> {
|
||||
let (range_start, range_length) = error.format_len();
|
||||
let range_end = range_start + range_length;
|
||||
|
||||
if range_length > 0 {
|
||||
Some(SourceAnnotation {
|
||||
annotation_type: AnnotationType::Error,
|
||||
range: (range_start, range_end),
|
||||
label: "",
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationType {
|
||||
match error_kind {
|
||||
ErrorKind::LineOverflow(..)
|
||||
| ErrorKind::TrailingWhitespace
|
||||
| ErrorKind::IoError(_)
|
||||
| ErrorKind::ModuleResolutionError(_)
|
||||
| ErrorKind::ParseError
|
||||
| ErrorKind::LostComment
|
||||
| ErrorKind::LicenseCheck
|
||||
| ErrorKind::BadAttr
|
||||
| ErrorKind::InvalidGlobPattern(_)
|
||||
| ErrorKind::VersionMismatch => AnnotationType::Error,
|
||||
ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => AnnotationType::Warning,
|
||||
}
|
||||
}
|
||||
610
src/tools/rustfmt/src/formatting.rs
Normal file
610
src/tools/rustfmt/src/formatting.rs
Normal file
|
|
@ -0,0 +1,610 @@
|
|||
// High level formatting functions.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::{self, Write};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rustc_ast::ast;
|
||||
use rustc_span::Span;
|
||||
|
||||
use self::newline_style::apply_newline_style;
|
||||
use crate::comment::{CharClasses, FullCodeCharKind};
|
||||
use crate::config::{Config, FileName, Verbosity};
|
||||
use crate::issues::BadIssueSeeker;
|
||||
use crate::modules::Module;
|
||||
use crate::syntux::parser::{DirectoryOwnership, Parser, ParserError};
|
||||
use crate::syntux::session::ParseSess;
|
||||
use crate::utils::count_newlines;
|
||||
use crate::visitor::FmtVisitor;
|
||||
use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session};
|
||||
|
||||
mod newline_style;
|
||||
|
||||
// A map of the files of a crate, with their new content
|
||||
pub(crate) type SourceFile = Vec<FileRecord>;
|
||||
pub(crate) type FileRecord = (FileName, String);
|
||||
|
||||
impl<'b, T: Write + 'b> Session<'b, T> {
|
||||
pub(crate) fn format_input_inner(
|
||||
&mut self,
|
||||
input: Input,
|
||||
is_macro_def: bool,
|
||||
) -> Result<FormatReport, ErrorKind> {
|
||||
if !self.config.version_meets_requirement() {
|
||||
return Err(ErrorKind::VersionMismatch);
|
||||
}
|
||||
|
||||
rustc_span::with_session_globals(self.config.edition().into(), || {
|
||||
if self.config.disable_all_formatting() {
|
||||
// When the input is from stdin, echo back the input.
|
||||
if let Input::Text(ref buf) = input {
|
||||
if let Err(e) = io::stdout().write_all(buf.as_bytes()) {
|
||||
return Err(From::from(e));
|
||||
}
|
||||
}
|
||||
return Ok(FormatReport::new());
|
||||
}
|
||||
|
||||
let config = &self.config.clone();
|
||||
let format_result = format_project(input, config, self, is_macro_def);
|
||||
|
||||
format_result.map(|report| {
|
||||
self.errors.add(&report.internal.borrow().1);
|
||||
report
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Format an entire crate (or subset of the module tree).
|
||||
fn format_project<T: FormatHandler>(
|
||||
input: Input,
|
||||
config: &Config,
|
||||
handler: &mut T,
|
||||
is_macro_def: bool,
|
||||
) -> Result<FormatReport, ErrorKind> {
|
||||
let mut timer = Timer::start();
|
||||
|
||||
let main_file = input.file_name();
|
||||
let input_is_stdin = main_file == FileName::Stdin;
|
||||
|
||||
let parse_session = ParseSess::new(config)?;
|
||||
if config.skip_children() && parse_session.ignore_file(&main_file) {
|
||||
return Ok(FormatReport::new());
|
||||
}
|
||||
|
||||
// Parse the crate.
|
||||
let mut report = FormatReport::new();
|
||||
let directory_ownership = input.to_directory_ownership();
|
||||
let krate = match Parser::parse_crate(input, &parse_session) {
|
||||
Ok(krate) => krate,
|
||||
// Surface parse error via Session (errors are merged there from report)
|
||||
Err(e) => {
|
||||
let forbid_verbose = input_is_stdin || e != ParserError::ParsePanicError;
|
||||
should_emit_verbose(forbid_verbose, config, || {
|
||||
eprintln!("The Rust parser panicked");
|
||||
});
|
||||
report.add_parsing_error();
|
||||
return Ok(report);
|
||||
}
|
||||
};
|
||||
|
||||
let mut context = FormatContext::new(&krate, report, parse_session, config, handler);
|
||||
let files = modules::ModResolver::new(
|
||||
&context.parse_session,
|
||||
directory_ownership.unwrap_or(DirectoryOwnership::UnownedViaBlock),
|
||||
!input_is_stdin && !config.skip_children(),
|
||||
)
|
||||
.visit_crate(&krate)?;
|
||||
|
||||
timer = timer.done_parsing();
|
||||
|
||||
// Suppress error output if we have to do any further parsing.
|
||||
context.parse_session.set_silent_emitter();
|
||||
|
||||
for (path, module) in files {
|
||||
let should_ignore = !input_is_stdin && context.ignore_file(&path);
|
||||
if (config.skip_children() && path != main_file) || should_ignore {
|
||||
continue;
|
||||
}
|
||||
should_emit_verbose(input_is_stdin, config, || println!("Formatting {}", path));
|
||||
context.format_file(path, &module, is_macro_def)?;
|
||||
}
|
||||
timer = timer.done_formatting();
|
||||
|
||||
should_emit_verbose(input_is_stdin, config, || {
|
||||
println!(
|
||||
"Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
|
||||
timer.get_parse_time(),
|
||||
timer.get_format_time(),
|
||||
)
|
||||
});
|
||||
|
||||
Ok(context.report)
|
||||
}
|
||||
|
||||
// Used for formatting files.
|
||||
#[derive(new)]
|
||||
struct FormatContext<'a, T: FormatHandler> {
|
||||
krate: &'a ast::Crate,
|
||||
report: FormatReport,
|
||||
parse_session: ParseSess,
|
||||
config: &'a Config,
|
||||
handler: &'a mut T,
|
||||
}
|
||||
|
||||
impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> {
|
||||
fn ignore_file(&self, path: &FileName) -> bool {
|
||||
self.parse_session.ignore_file(path)
|
||||
}
|
||||
|
||||
// Formats a single file/module.
|
||||
fn format_file(
|
||||
&mut self,
|
||||
path: FileName,
|
||||
module: &Module<'_>,
|
||||
is_macro_def: bool,
|
||||
) -> Result<(), ErrorKind> {
|
||||
let snippet_provider = self.parse_session.snippet_provider(module.span);
|
||||
let mut visitor = FmtVisitor::from_parse_sess(
|
||||
&self.parse_session,
|
||||
&self.config,
|
||||
&snippet_provider,
|
||||
self.report.clone(),
|
||||
);
|
||||
visitor.skip_context.update_with_attrs(&self.krate.attrs);
|
||||
visitor.is_macro_def = is_macro_def;
|
||||
visitor.last_pos = snippet_provider.start_pos();
|
||||
visitor.skip_empty_lines(snippet_provider.end_pos());
|
||||
visitor.format_separate_mod(module, snippet_provider.end_pos());
|
||||
|
||||
debug_assert_eq!(
|
||||
visitor.line_number,
|
||||
count_newlines(&visitor.buffer),
|
||||
"failed in format_file visitor.buffer:\n {:?}",
|
||||
&visitor.buffer
|
||||
);
|
||||
|
||||
// For some reason, the source_map does not include terminating
|
||||
// newlines so we must add one on for each file. This is sad.
|
||||
source_file::append_newline(&mut visitor.buffer);
|
||||
|
||||
format_lines(
|
||||
&mut visitor.buffer,
|
||||
&path,
|
||||
&visitor.skipped_range.borrow(),
|
||||
&self.config,
|
||||
&self.report,
|
||||
);
|
||||
|
||||
apply_newline_style(
|
||||
self.config.newline_style(),
|
||||
&mut visitor.buffer,
|
||||
snippet_provider.entire_snippet(),
|
||||
);
|
||||
|
||||
if visitor.macro_rewrite_failure {
|
||||
self.report.add_macro_format_failure();
|
||||
}
|
||||
self.report
|
||||
.add_non_formatted_ranges(visitor.skipped_range.borrow().clone());
|
||||
|
||||
self.handler.handle_formatted_file(
|
||||
&self.parse_session,
|
||||
path,
|
||||
visitor.buffer.to_owned(),
|
||||
&mut self.report,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the results of formatting.
|
||||
trait FormatHandler {
|
||||
fn handle_formatted_file(
|
||||
&mut self,
|
||||
parse_session: &ParseSess,
|
||||
path: FileName,
|
||||
result: String,
|
||||
report: &mut FormatReport,
|
||||
) -> Result<(), ErrorKind>;
|
||||
}
|
||||
|
||||
impl<'b, T: Write + 'b> FormatHandler for Session<'b, T> {
|
||||
// Called for each formatted file.
|
||||
fn handle_formatted_file(
|
||||
&mut self,
|
||||
parse_session: &ParseSess,
|
||||
path: FileName,
|
||||
result: String,
|
||||
report: &mut FormatReport,
|
||||
) -> Result<(), ErrorKind> {
|
||||
if let Some(ref mut out) = self.out {
|
||||
match source_file::write_file(
|
||||
Some(parse_session),
|
||||
&path,
|
||||
&result,
|
||||
out,
|
||||
&mut *self.emitter,
|
||||
self.config.newline_style(),
|
||||
) {
|
||||
Ok(ref result) if result.has_diff => report.add_diff(),
|
||||
Err(e) => {
|
||||
// Create a new error with path_str to help users see which files failed
|
||||
let err_msg = format!("{}: {}", path, e);
|
||||
return Err(io::Error::new(e.kind(), err_msg).into());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.source_file.push((path, result));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FormattingError {
|
||||
pub(crate) line: usize,
|
||||
pub(crate) kind: ErrorKind,
|
||||
is_comment: bool,
|
||||
is_string: bool,
|
||||
pub(crate) line_buffer: String,
|
||||
}
|
||||
|
||||
impl FormattingError {
|
||||
pub(crate) fn from_span(
|
||||
span: Span,
|
||||
parse_sess: &ParseSess,
|
||||
kind: ErrorKind,
|
||||
) -> FormattingError {
|
||||
FormattingError {
|
||||
line: parse_sess.line_of_byte_pos(span.lo()),
|
||||
is_comment: kind.is_comment(),
|
||||
kind,
|
||||
is_string: false,
|
||||
line_buffer: parse_sess.span_to_first_line_string(span),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_internal(&self) -> bool {
|
||||
match self.kind {
|
||||
ErrorKind::LineOverflow(..)
|
||||
| ErrorKind::TrailingWhitespace
|
||||
| ErrorKind::IoError(_)
|
||||
| ErrorKind::ParseError
|
||||
| ErrorKind::LostComment => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn msg_suffix(&self) -> &str {
|
||||
if self.is_comment || self.is_string {
|
||||
"set `error_on_unformatted = false` to suppress \
|
||||
the warning against comments or string literals\n"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
// (space, target)
|
||||
pub(crate) fn format_len(&self) -> (usize, usize) {
|
||||
match self.kind {
|
||||
ErrorKind::LineOverflow(found, max) => (max, found - max),
|
||||
ErrorKind::TrailingWhitespace
|
||||
| ErrorKind::DeprecatedAttr
|
||||
| ErrorKind::BadIssue(_)
|
||||
| ErrorKind::BadAttr
|
||||
| ErrorKind::LostComment
|
||||
| ErrorKind::LicenseCheck => {
|
||||
let trailing_ws_start = self
|
||||
.line_buffer
|
||||
.rfind(|c: char| !c.is_whitespace())
|
||||
.map(|pos| pos + 1)
|
||||
.unwrap_or(0);
|
||||
(
|
||||
trailing_ws_start,
|
||||
self.line_buffer.len() - trailing_ws_start,
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type FormatErrorMap = HashMap<FileName, Vec<FormattingError>>;
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
pub(crate) struct ReportedErrors {
|
||||
// Encountered e.g., an IO error.
|
||||
pub(crate) has_operational_errors: bool,
|
||||
|
||||
// Failed to reformat code because of parsing errors.
|
||||
pub(crate) has_parsing_errors: bool,
|
||||
|
||||
// Code is valid, but it is impossible to format it properly.
|
||||
pub(crate) has_formatting_errors: bool,
|
||||
|
||||
// Code contains macro call that was unable to format.
|
||||
pub(crate) has_macro_format_failure: bool,
|
||||
|
||||
// Failed a check, such as the license check or other opt-in checking.
|
||||
pub(crate) has_check_errors: bool,
|
||||
|
||||
/// Formatted code differs from existing code (--check only).
|
||||
pub(crate) has_diff: bool,
|
||||
|
||||
/// Formatted code missed something, like lost comments or extra trailing space
|
||||
pub(crate) has_unformatted_code_errors: bool,
|
||||
}
|
||||
|
||||
impl ReportedErrors {
|
||||
/// Combine two summaries together.
|
||||
pub(crate) fn add(&mut self, other: &ReportedErrors) {
|
||||
self.has_operational_errors |= other.has_operational_errors;
|
||||
self.has_parsing_errors |= other.has_parsing_errors;
|
||||
self.has_formatting_errors |= other.has_formatting_errors;
|
||||
self.has_macro_format_failure |= other.has_macro_format_failure;
|
||||
self.has_check_errors |= other.has_check_errors;
|
||||
self.has_diff |= other.has_diff;
|
||||
self.has_unformatted_code_errors |= other.has_unformatted_code_errors;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Timer {
|
||||
Disabled,
|
||||
Initialized(Instant),
|
||||
DoneParsing(Instant, Instant),
|
||||
DoneFormatting(Instant, Instant, Instant),
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
fn start() -> Timer {
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
Timer::Disabled
|
||||
} else {
|
||||
Timer::Initialized(Instant::now())
|
||||
}
|
||||
}
|
||||
fn done_parsing(self) -> Self {
|
||||
match self {
|
||||
Timer::Disabled => Timer::Disabled,
|
||||
Timer::Initialized(init_time) => Timer::DoneParsing(init_time, Instant::now()),
|
||||
_ => panic!("Timer can only transition to DoneParsing from Initialized state"),
|
||||
}
|
||||
}
|
||||
|
||||
fn done_formatting(self) -> Self {
|
||||
match self {
|
||||
Timer::Disabled => Timer::Disabled,
|
||||
Timer::DoneParsing(init_time, parse_time) => {
|
||||
Timer::DoneFormatting(init_time, parse_time, Instant::now())
|
||||
}
|
||||
_ => panic!("Timer can only transition to DoneFormatting from DoneParsing state"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the time it took to parse the source files in seconds.
|
||||
fn get_parse_time(&self) -> f32 {
|
||||
match *self {
|
||||
Timer::Disabled => panic!("this platform cannot time execution"),
|
||||
Timer::DoneParsing(init, parse_time) | Timer::DoneFormatting(init, parse_time, _) => {
|
||||
// This should never underflow since `Instant::now()` guarantees monotonicity.
|
||||
Self::duration_to_f32(parse_time.duration_since(init))
|
||||
}
|
||||
Timer::Initialized(..) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is
|
||||
/// not included.
|
||||
fn get_format_time(&self) -> f32 {
|
||||
match *self {
|
||||
Timer::Disabled => panic!("this platform cannot time execution"),
|
||||
Timer::DoneFormatting(_init, parse_time, format_time) => {
|
||||
Self::duration_to_f32(format_time.duration_since(parse_time))
|
||||
}
|
||||
Timer::DoneParsing(..) | Timer::Initialized(..) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn duration_to_f32(d: Duration) -> f32 {
|
||||
d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32
|
||||
}
|
||||
}
|
||||
|
||||
// Formatting done on a char by char or line by line basis.
|
||||
// FIXME(#20): other stuff for parity with make tidy.
|
||||
fn format_lines(
|
||||
text: &mut String,
|
||||
name: &FileName,
|
||||
skipped_range: &[(usize, usize)],
|
||||
config: &Config,
|
||||
report: &FormatReport,
|
||||
) {
|
||||
let mut formatter = FormatLines::new(name, skipped_range, config);
|
||||
formatter.check_license(text);
|
||||
formatter.iterate(text);
|
||||
|
||||
if formatter.newline_count > 1 {
|
||||
debug!("track truncate: {} {}", text.len(), formatter.newline_count);
|
||||
let line = text.len() - formatter.newline_count + 1;
|
||||
text.truncate(line);
|
||||
}
|
||||
|
||||
report.append(name.clone(), formatter.errors);
|
||||
}
|
||||
|
||||
struct FormatLines<'a> {
|
||||
name: &'a FileName,
|
||||
skipped_range: &'a [(usize, usize)],
|
||||
last_was_space: bool,
|
||||
line_len: usize,
|
||||
cur_line: usize,
|
||||
newline_count: usize,
|
||||
errors: Vec<FormattingError>,
|
||||
issue_seeker: BadIssueSeeker,
|
||||
line_buffer: String,
|
||||
current_line_contains_string_literal: bool,
|
||||
format_line: bool,
|
||||
allow_issue_seek: bool,
|
||||
config: &'a Config,
|
||||
}
|
||||
|
||||
impl<'a> FormatLines<'a> {
|
||||
fn new(
|
||||
name: &'a FileName,
|
||||
skipped_range: &'a [(usize, usize)],
|
||||
config: &'a Config,
|
||||
) -> FormatLines<'a> {
|
||||
let issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
|
||||
FormatLines {
|
||||
name,
|
||||
skipped_range,
|
||||
last_was_space: false,
|
||||
line_len: 0,
|
||||
cur_line: 1,
|
||||
newline_count: 0,
|
||||
errors: vec![],
|
||||
allow_issue_seek: !issue_seeker.is_disabled(),
|
||||
issue_seeker,
|
||||
line_buffer: String::with_capacity(config.max_width() * 2),
|
||||
current_line_contains_string_literal: false,
|
||||
format_line: config.file_lines().contains_line(name, 1),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_license(&mut self, text: &mut String) {
|
||||
if let Some(ref license_template) = self.config.license_template {
|
||||
if !license_template.is_match(text) {
|
||||
self.errors.push(FormattingError {
|
||||
line: self.cur_line,
|
||||
kind: ErrorKind::LicenseCheck,
|
||||
is_comment: false,
|
||||
is_string: false,
|
||||
line_buffer: String::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the chars in the file map.
|
||||
fn iterate(&mut self, text: &mut String) {
|
||||
for (kind, c) in CharClasses::new(text.chars()) {
|
||||
if c == '\r' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.allow_issue_seek && self.format_line {
|
||||
// Add warnings for bad todos/ fixmes
|
||||
if let Some(issue) = self.issue_seeker.inspect(c) {
|
||||
self.push_err(ErrorKind::BadIssue(issue), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
if c == '\n' {
|
||||
self.new_line(kind);
|
||||
} else {
|
||||
self.char(c, kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_line(&mut self, kind: FullCodeCharKind) {
|
||||
if self.format_line {
|
||||
// Check for (and record) trailing whitespace.
|
||||
if self.last_was_space {
|
||||
if self.should_report_error(kind, &ErrorKind::TrailingWhitespace)
|
||||
&& !self.is_skipped_line()
|
||||
{
|
||||
self.push_err(
|
||||
ErrorKind::TrailingWhitespace,
|
||||
kind.is_comment(),
|
||||
kind.is_string(),
|
||||
);
|
||||
}
|
||||
self.line_len -= 1;
|
||||
}
|
||||
|
||||
// Check for any line width errors we couldn't correct.
|
||||
let error_kind = ErrorKind::LineOverflow(self.line_len, self.config.max_width());
|
||||
if self.line_len > self.config.max_width()
|
||||
&& !self.is_skipped_line()
|
||||
&& self.should_report_error(kind, &error_kind)
|
||||
{
|
||||
let is_string = self.current_line_contains_string_literal;
|
||||
self.push_err(error_kind, kind.is_comment(), is_string);
|
||||
}
|
||||
}
|
||||
|
||||
self.line_len = 0;
|
||||
self.cur_line += 1;
|
||||
self.format_line = self
|
||||
.config
|
||||
.file_lines()
|
||||
.contains_line(self.name, self.cur_line);
|
||||
self.newline_count += 1;
|
||||
self.last_was_space = false;
|
||||
self.line_buffer.clear();
|
||||
self.current_line_contains_string_literal = false;
|
||||
}
|
||||
|
||||
fn char(&mut self, c: char, kind: FullCodeCharKind) {
|
||||
self.newline_count = 0;
|
||||
self.line_len += if c == '\t' {
|
||||
self.config.tab_spaces()
|
||||
} else {
|
||||
1
|
||||
};
|
||||
self.last_was_space = c.is_whitespace();
|
||||
self.line_buffer.push(c);
|
||||
if kind.is_string() {
|
||||
self.current_line_contains_string_literal = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn push_err(&mut self, kind: ErrorKind, is_comment: bool, is_string: bool) {
|
||||
self.errors.push(FormattingError {
|
||||
line: self.cur_line,
|
||||
kind,
|
||||
is_comment,
|
||||
is_string,
|
||||
line_buffer: self.line_buffer.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
fn should_report_error(&self, char_kind: FullCodeCharKind, error_kind: &ErrorKind) -> bool {
|
||||
let allow_error_report = if char_kind.is_comment()
|
||||
|| self.current_line_contains_string_literal
|
||||
|| error_kind.is_comment()
|
||||
{
|
||||
self.config.error_on_unformatted()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
match error_kind {
|
||||
ErrorKind::LineOverflow(..) => {
|
||||
self.config.error_on_line_overflow() && allow_error_report
|
||||
}
|
||||
ErrorKind::TrailingWhitespace | ErrorKind::LostComment => allow_error_report,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the line with the given line number was skipped by `#[rustfmt::skip]`.
|
||||
fn is_skipped_line(&self) -> bool {
|
||||
self.skipped_range
|
||||
.iter()
|
||||
.any(|&(lo, hi)| lo <= self.cur_line && self.cur_line <= hi)
|
||||
}
|
||||
}
|
||||
|
||||
fn should_emit_verbose<F>(forbid_verbose_output: bool, config: &Config, f: F)
|
||||
where
|
||||
F: Fn(),
|
||||
{
|
||||
if config.verbose() == Verbosity::Verbose && !forbid_verbose_output {
|
||||
f();
|
||||
}
|
||||
}
|
||||
250
src/tools/rustfmt/src/formatting/newline_style.rs
Normal file
250
src/tools/rustfmt/src/formatting/newline_style.rs
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
use crate::NewlineStyle;
|
||||
|
||||
/// Apply this newline style to the formatted text. When the style is set
|
||||
/// to `Auto`, the `raw_input_text` is used to detect the existing line
|
||||
/// endings.
|
||||
///
|
||||
/// If the style is set to `Auto` and `raw_input_text` contains no
|
||||
/// newlines, the `Native` style will be used.
|
||||
pub(crate) fn apply_newline_style(
|
||||
newline_style: NewlineStyle,
|
||||
formatted_text: &mut String,
|
||||
raw_input_text: &str,
|
||||
) {
|
||||
*formatted_text = match effective_newline_style(newline_style, raw_input_text) {
|
||||
EffectiveNewlineStyle::Windows => convert_to_windows_newlines(formatted_text),
|
||||
EffectiveNewlineStyle::Unix => convert_to_unix_newlines(formatted_text),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum EffectiveNewlineStyle {
|
||||
Windows,
|
||||
Unix,
|
||||
}
|
||||
|
||||
fn effective_newline_style(
|
||||
newline_style: NewlineStyle,
|
||||
raw_input_text: &str,
|
||||
) -> EffectiveNewlineStyle {
|
||||
match newline_style {
|
||||
NewlineStyle::Auto => auto_detect_newline_style(raw_input_text),
|
||||
NewlineStyle::Native => native_newline_style(),
|
||||
NewlineStyle::Windows => EffectiveNewlineStyle::Windows,
|
||||
NewlineStyle::Unix => EffectiveNewlineStyle::Unix,
|
||||
}
|
||||
}
|
||||
|
||||
const LINE_FEED: char = '\n';
|
||||
const CARRIAGE_RETURN: char = '\r';
|
||||
const WINDOWS_NEWLINE: &str = "\r\n";
|
||||
const UNIX_NEWLINE: &str = "\n";
|
||||
|
||||
fn auto_detect_newline_style(raw_input_text: &str) -> EffectiveNewlineStyle {
|
||||
let first_line_feed_pos = raw_input_text.chars().position(|ch| ch == LINE_FEED);
|
||||
match first_line_feed_pos {
|
||||
Some(first_line_feed_pos) => {
|
||||
let char_before_line_feed_pos = first_line_feed_pos.saturating_sub(1);
|
||||
let char_before_line_feed = raw_input_text.chars().nth(char_before_line_feed_pos);
|
||||
match char_before_line_feed {
|
||||
Some(CARRIAGE_RETURN) => EffectiveNewlineStyle::Windows,
|
||||
_ => EffectiveNewlineStyle::Unix,
|
||||
}
|
||||
}
|
||||
None => native_newline_style(),
|
||||
}
|
||||
}
|
||||
|
||||
fn native_newline_style() -> EffectiveNewlineStyle {
|
||||
if cfg!(windows) {
|
||||
EffectiveNewlineStyle::Windows
|
||||
} else {
|
||||
EffectiveNewlineStyle::Unix
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_windows_newlines(formatted_text: &String) -> String {
|
||||
let mut transformed = String::with_capacity(2 * formatted_text.capacity());
|
||||
let mut chars = formatted_text.chars().peekable();
|
||||
while let Some(current_char) = chars.next() {
|
||||
let next_char = chars.peek();
|
||||
match current_char {
|
||||
LINE_FEED => transformed.push_str(WINDOWS_NEWLINE),
|
||||
CARRIAGE_RETURN if next_char == Some(&LINE_FEED) => {}
|
||||
current_char => transformed.push(current_char),
|
||||
}
|
||||
}
|
||||
transformed
|
||||
}
|
||||
|
||||
fn convert_to_unix_newlines(formatted_text: &String) -> String {
|
||||
formatted_text.replace(WINDOWS_NEWLINE, UNIX_NEWLINE)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn auto_detects_unix_newlines() {
|
||||
assert_eq!(
|
||||
EffectiveNewlineStyle::Unix,
|
||||
auto_detect_newline_style("One\nTwo\nThree")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_detects_windows_newlines() {
|
||||
assert_eq!(
|
||||
EffectiveNewlineStyle::Windows,
|
||||
auto_detect_newline_style("One\r\nTwo\r\nThree")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_detects_windows_newlines_with_multibyte_char_on_first_line() {
|
||||
assert_eq!(
|
||||
EffectiveNewlineStyle::Windows,
|
||||
auto_detect_newline_style("A 🎢 of a first line\r\nTwo\r\nThree")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn falls_back_to_native_newlines_if_no_newlines_are_found() {
|
||||
let expected_newline_style = if cfg!(windows) {
|
||||
EffectiveNewlineStyle::Windows
|
||||
} else {
|
||||
EffectiveNewlineStyle::Unix
|
||||
};
|
||||
assert_eq!(
|
||||
expected_newline_style,
|
||||
auto_detect_newline_style("One Two Three")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_detects_and_applies_unix_newlines() {
|
||||
let formatted_text = "One\nTwo\nThree";
|
||||
let raw_input_text = "One\nTwo\nThree";
|
||||
|
||||
let mut out = String::from(formatted_text);
|
||||
apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
|
||||
assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_detects_and_applies_windows_newlines() {
|
||||
let formatted_text = "One\nTwo\nThree";
|
||||
let raw_input_text = "One\r\nTwo\r\nThree";
|
||||
|
||||
let mut out = String::from(formatted_text);
|
||||
apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
|
||||
assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_detects_and_applies_native_newlines() {
|
||||
let formatted_text = "One\nTwo\nThree";
|
||||
let raw_input_text = "One Two Three";
|
||||
|
||||
let mut out = String::from(formatted_text);
|
||||
apply_newline_style(NewlineStyle::Auto, &mut out, raw_input_text);
|
||||
|
||||
if cfg!(windows) {
|
||||
assert_eq!(
|
||||
"One\r\nTwo\r\nThree", &out,
|
||||
"auto-native-windows should detect 'crlf'"
|
||||
);
|
||||
} else {
|
||||
assert_eq!(
|
||||
"One\nTwo\nThree", &out,
|
||||
"auto-native-unix should detect 'lf'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applies_unix_newlines() {
|
||||
test_newlines_are_applied_correctly(
|
||||
"One\r\nTwo\nThree",
|
||||
"One\nTwo\nThree",
|
||||
NewlineStyle::Unix,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applying_unix_newlines_changes_nothing_for_unix_newlines() {
|
||||
let formatted_text = "One\nTwo\nThree";
|
||||
test_newlines_are_applied_correctly(formatted_text, formatted_text, NewlineStyle::Unix);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applies_unix_newlines_to_string_with_unix_and_windows_newlines() {
|
||||
test_newlines_are_applied_correctly(
|
||||
"One\r\nTwo\r\nThree\nFour",
|
||||
"One\nTwo\nThree\nFour",
|
||||
NewlineStyle::Unix,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applies_windows_newlines_to_string_with_unix_and_windows_newlines() {
|
||||
test_newlines_are_applied_correctly(
|
||||
"One\nTwo\nThree\r\nFour",
|
||||
"One\r\nTwo\r\nThree\r\nFour",
|
||||
NewlineStyle::Windows,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applying_windows_newlines_changes_nothing_for_windows_newlines() {
|
||||
let formatted_text = "One\r\nTwo\r\nThree";
|
||||
test_newlines_are_applied_correctly(formatted_text, formatted_text, NewlineStyle::Windows);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_carriage_returns_when_applying_windows_newlines_to_str_with_unix_newlines() {
|
||||
test_newlines_are_applied_correctly(
|
||||
"One\nTwo\nThree\rDrei",
|
||||
"One\r\nTwo\r\nThree\rDrei",
|
||||
NewlineStyle::Windows,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_carriage_returns_when_applying_unix_newlines_to_str_with_unix_newlines() {
|
||||
test_newlines_are_applied_correctly(
|
||||
"One\nTwo\nThree\rDrei",
|
||||
"One\nTwo\nThree\rDrei",
|
||||
NewlineStyle::Unix,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_carriage_returns_when_applying_windows_newlines_to_str_with_windows_newlines() {
|
||||
test_newlines_are_applied_correctly(
|
||||
"One\r\nTwo\r\nThree\rDrei",
|
||||
"One\r\nTwo\r\nThree\rDrei",
|
||||
NewlineStyle::Windows,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_carriage_returns_when_applying_unix_newlines_to_str_with_windows_newlines() {
|
||||
test_newlines_are_applied_correctly(
|
||||
"One\r\nTwo\r\nThree\rDrei",
|
||||
"One\nTwo\nThree\rDrei",
|
||||
NewlineStyle::Unix,
|
||||
);
|
||||
}
|
||||
|
||||
fn test_newlines_are_applied_correctly(
|
||||
input: &str,
|
||||
expected: &str,
|
||||
newline_style: NewlineStyle,
|
||||
) {
|
||||
let mut out = String::from(input);
|
||||
apply_newline_style(newline_style, &mut out, input);
|
||||
assert_eq!(expected, &out);
|
||||
}
|
||||
}
|
||||
192
src/tools/rustfmt/src/git-rustfmt/main.rs
Normal file
192
src/tools/rustfmt/src/git-rustfmt/main.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::env;
|
||||
use std::io::stdout;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
|
||||
use getopts::{Matches, Options};
|
||||
use rustfmt_nightly as rustfmt;
|
||||
|
||||
use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session};
|
||||
|
||||
fn prune_files(files: Vec<&str>) -> Vec<&str> {
|
||||
let prefixes: Vec<_> = files
|
||||
.iter()
|
||||
.filter(|f| f.ends_with("mod.rs") || f.ends_with("lib.rs"))
|
||||
.map(|f| &f[..f.len() - 6])
|
||||
.collect();
|
||||
|
||||
let mut pruned_prefixes = vec![];
|
||||
for p1 in prefixes {
|
||||
if p1.starts_with("src/bin/") || pruned_prefixes.iter().all(|p2| !p1.starts_with(p2)) {
|
||||
pruned_prefixes.push(p1);
|
||||
}
|
||||
}
|
||||
debug!("prefixes: {:?}", pruned_prefixes);
|
||||
|
||||
files
|
||||
.into_iter()
|
||||
.filter(|f| {
|
||||
if f.ends_with("mod.rs") || f.ends_with("lib.rs") || f.starts_with("src/bin/") {
|
||||
return true;
|
||||
}
|
||||
pruned_prefixes.iter().all(|pp| !f.starts_with(pp))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn git_diff(commits: &str) -> String {
|
||||
let mut cmd = Command::new("git");
|
||||
cmd.arg("diff");
|
||||
if commits != "0" {
|
||||
cmd.arg(format!("HEAD~{}", commits));
|
||||
}
|
||||
let output = cmd.output().expect("Couldn't execute `git diff`");
|
||||
String::from_utf8_lossy(&output.stdout).into_owned()
|
||||
}
|
||||
|
||||
fn get_files(input: &str) -> Vec<&str> {
|
||||
input
|
||||
.lines()
|
||||
.filter(|line| line.starts_with("+++ b/") && line.ends_with(".rs"))
|
||||
.map(|line| &line[6..])
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn fmt_files(files: &[&str]) -> i32 {
|
||||
let (config, _) =
|
||||
load_config::<NullOptions>(Some(Path::new(".")), None).expect("couldn't load config");
|
||||
|
||||
let mut exit_code = 0;
|
||||
let mut out = stdout();
|
||||
let mut session = Session::new(config, Some(&mut out));
|
||||
for file in files {
|
||||
let report = session.format(Input::File(PathBuf::from(file))).unwrap();
|
||||
if report.has_warnings() {
|
||||
eprintln!("{}", FormatReportFormatterBuilder::new(&report).build());
|
||||
}
|
||||
if !session.has_no_errors() {
|
||||
exit_code = 1;
|
||||
}
|
||||
}
|
||||
exit_code
|
||||
}
|
||||
|
||||
struct NullOptions;
|
||||
|
||||
impl CliOptions for NullOptions {
|
||||
fn apply_to(self, _: &mut rustfmt::Config) {
|
||||
unreachable!();
|
||||
}
|
||||
fn config_path(&self) -> Option<&Path> {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
fn uncommitted_files() -> Vec<String> {
|
||||
let mut cmd = Command::new("git");
|
||||
cmd.arg("ls-files");
|
||||
cmd.arg("--others");
|
||||
cmd.arg("--modified");
|
||||
cmd.arg("--exclude-standard");
|
||||
let output = cmd.output().expect("Couldn't execute Git");
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
stdout
|
||||
.lines()
|
||||
.filter(|s| s.ends_with(".rs"))
|
||||
.map(std::borrow::ToOwned::to_owned)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn check_uncommitted() {
|
||||
let uncommitted = uncommitted_files();
|
||||
debug!("uncommitted files: {:?}", uncommitted);
|
||||
if !uncommitted.is_empty() {
|
||||
println!("Found untracked changes:");
|
||||
for f in &uncommitted {
|
||||
println!(" {}", f);
|
||||
}
|
||||
println!("Commit your work, or run with `-u`.");
|
||||
println!("Exiting.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn make_opts() -> Options {
|
||||
let mut opts = Options::new();
|
||||
opts.optflag("h", "help", "show this message");
|
||||
opts.optflag("c", "check", "check only, don't format (unimplemented)");
|
||||
opts.optflag("u", "uncommitted", "format uncommitted files");
|
||||
opts
|
||||
}
|
||||
|
||||
struct Config {
|
||||
commits: String,
|
||||
uncommitted: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn from_args(matches: &Matches, opts: &Options) -> Config {
|
||||
// `--help` display help message and quit
|
||||
if matches.opt_present("h") {
|
||||
let message = format!(
|
||||
"\nusage: {} <commits> [options]\n\n\
|
||||
commits: number of commits to format, default: 1",
|
||||
env::args_os().next().unwrap().to_string_lossy()
|
||||
);
|
||||
println!("{}", opts.usage(&message));
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let mut config = Config {
|
||||
commits: "1".to_owned(),
|
||||
uncommitted: false,
|
||||
};
|
||||
|
||||
if matches.opt_present("c") {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
if matches.opt_present("u") {
|
||||
config.uncommitted = true;
|
||||
}
|
||||
|
||||
if matches.free.len() > 1 {
|
||||
panic!("unknown arguments, use `-h` for usage");
|
||||
}
|
||||
if matches.free.len() == 1 {
|
||||
let commits = matches.free[0].trim();
|
||||
if u32::from_str(commits).is_err() {
|
||||
panic!("Couldn't parse number of commits");
|
||||
}
|
||||
config.commits = commits.to_owned();
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let opts = make_opts();
|
||||
let matches = opts
|
||||
.parse(env::args().skip(1))
|
||||
.expect("Couldn't parse command line");
|
||||
let config = Config::from_args(&matches, &opts);
|
||||
|
||||
if !config.uncommitted {
|
||||
check_uncommitted();
|
||||
}
|
||||
|
||||
let stdout = git_diff(&config.commits);
|
||||
let files = get_files(&stdout);
|
||||
debug!("files: {:?}", files);
|
||||
let files = prune_files(files);
|
||||
debug!("pruned files: {:?}", files);
|
||||
let exit_code = fmt_files(&files);
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
57
src/tools/rustfmt/src/ignore_path.rs
Normal file
57
src/tools/rustfmt/src/ignore_path.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use ignore::{self, gitignore};
|
||||
|
||||
use crate::config::{FileName, IgnoreList};
|
||||
|
||||
pub(crate) struct IgnorePathSet {
|
||||
ignore_set: gitignore::Gitignore,
|
||||
}
|
||||
|
||||
impl IgnorePathSet {
|
||||
pub(crate) fn from_ignore_list(ignore_list: &IgnoreList) -> Result<Self, ignore::Error> {
|
||||
let mut ignore_builder = gitignore::GitignoreBuilder::new(ignore_list.rustfmt_toml_path());
|
||||
|
||||
for ignore_path in ignore_list {
|
||||
ignore_builder.add_line(None, ignore_path.to_str().unwrap())?;
|
||||
}
|
||||
|
||||
Ok(IgnorePathSet {
|
||||
ignore_set: ignore_builder.build()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_match(&self, file_name: &FileName) -> bool {
|
||||
match file_name {
|
||||
FileName::Stdin => false,
|
||||
FileName::Real(p) => self
|
||||
.ignore_set
|
||||
.matched_path_or_any_parents(p, false)
|
||||
.is_ignore(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::config::{Config, FileName};
|
||||
use crate::ignore_path::IgnorePathSet;
|
||||
|
||||
#[test]
|
||||
fn test_ignore_path_set() {
|
||||
match option_env!("CFG_RELEASE_CHANNEL") {
|
||||
// this test requires nightly
|
||||
None | Some("nightly") => {
|
||||
let config =
|
||||
Config::from_toml(r#"ignore = ["foo.rs", "bar_dir/*"]"#, Path::new(""))
|
||||
.unwrap();
|
||||
let ignore_path_set = IgnorePathSet::from_ignore_list(&config.ignore()).unwrap();
|
||||
|
||||
assert!(ignore_path_set.is_match(&FileName::Real(PathBuf::from("src/foo.rs"))));
|
||||
assert!(ignore_path_set.is_match(&FileName::Real(PathBuf::from("bar_dir/baz.rs"))));
|
||||
assert!(!ignore_path_set.is_match(&FileName::Real(PathBuf::from("src/bar.rs"))));
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
1210
src/tools/rustfmt/src/imports.rs
Normal file
1210
src/tools/rustfmt/src/imports.rs
Normal file
File diff suppressed because it is too large
Load diff
317
src/tools/rustfmt/src/issues.rs
Normal file
317
src/tools/rustfmt/src/issues.rs
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
// Objects for seeking through a char stream for occurrences of TODO and FIXME.
|
||||
// Depending on the loaded configuration, may also check that these have an
|
||||
// associated issue number.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::config::ReportTactic;
|
||||
|
||||
const TO_DO_CHARS: &[char] = &['t', 'o', 'd', 'o'];
|
||||
const FIX_ME_CHARS: &[char] = &['f', 'i', 'x', 'm', 'e'];
|
||||
|
||||
// Enabled implementation detail is here because it is
|
||||
// irrelevant outside the issues module
|
||||
fn is_enabled(report_tactic: ReportTactic) -> bool {
|
||||
report_tactic != ReportTactic::Never
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Seeking {
|
||||
Issue { todo_idx: usize, fixme_idx: usize },
|
||||
Number { issue: Issue, part: NumberPart },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum NumberPart {
|
||||
OpenParen,
|
||||
Pound,
|
||||
Number,
|
||||
CloseParen,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub struct Issue {
|
||||
issue_type: IssueType,
|
||||
// Indicates whether we're looking for issues with missing numbers, or
|
||||
// all issues of this type.
|
||||
missing_number: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for Issue {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
let msg = match self.issue_type {
|
||||
IssueType::Todo => "TODO",
|
||||
IssueType::Fixme => "FIXME",
|
||||
};
|
||||
let details = if self.missing_number {
|
||||
" without issue number"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
write!(fmt, "{}{}", msg, details)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
enum IssueType {
|
||||
Todo,
|
||||
Fixme,
|
||||
}
|
||||
|
||||
enum IssueClassification {
|
||||
Good,
|
||||
Bad(Issue),
|
||||
None,
|
||||
}
|
||||
|
||||
pub(crate) struct BadIssueSeeker {
|
||||
state: Seeking,
|
||||
report_todo: ReportTactic,
|
||||
report_fixme: ReportTactic,
|
||||
}
|
||||
|
||||
impl BadIssueSeeker {
|
||||
pub(crate) fn new(report_todo: ReportTactic, report_fixme: ReportTactic) -> BadIssueSeeker {
|
||||
BadIssueSeeker {
|
||||
state: Seeking::Issue {
|
||||
todo_idx: 0,
|
||||
fixme_idx: 0,
|
||||
},
|
||||
report_todo,
|
||||
report_fixme,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_disabled(&self) -> bool {
|
||||
!is_enabled(self.report_todo) && !is_enabled(self.report_fixme)
|
||||
}
|
||||
|
||||
// Check whether or not the current char is conclusive evidence for an
|
||||
// unnumbered TO-DO or FIX-ME.
|
||||
pub(crate) fn inspect(&mut self, c: char) -> Option<Issue> {
|
||||
match self.state {
|
||||
Seeking::Issue {
|
||||
todo_idx,
|
||||
fixme_idx,
|
||||
} => {
|
||||
self.state = self.inspect_issue(c, todo_idx, fixme_idx);
|
||||
}
|
||||
Seeking::Number { issue, part } => {
|
||||
let result = self.inspect_number(c, issue, part);
|
||||
|
||||
if let IssueClassification::None = result {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.state = Seeking::Issue {
|
||||
todo_idx: 0,
|
||||
fixme_idx: 0,
|
||||
};
|
||||
|
||||
if let IssueClassification::Bad(issue) = result {
|
||||
return Some(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn inspect_issue(&mut self, c: char, mut todo_idx: usize, mut fixme_idx: usize) -> Seeking {
|
||||
if let Some(lower_case_c) = c.to_lowercase().next() {
|
||||
if is_enabled(self.report_todo) && lower_case_c == TO_DO_CHARS[todo_idx] {
|
||||
todo_idx += 1;
|
||||
if todo_idx == TO_DO_CHARS.len() {
|
||||
return Seeking::Number {
|
||||
issue: Issue {
|
||||
issue_type: IssueType::Todo,
|
||||
missing_number: if let ReportTactic::Unnumbered = self.report_todo {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
},
|
||||
},
|
||||
part: NumberPart::OpenParen,
|
||||
};
|
||||
}
|
||||
fixme_idx = 0;
|
||||
} else if is_enabled(self.report_fixme) && lower_case_c == FIX_ME_CHARS[fixme_idx] {
|
||||
// Exploit the fact that the character sets of todo and fixme
|
||||
// are disjoint by adding else.
|
||||
fixme_idx += 1;
|
||||
if fixme_idx == FIX_ME_CHARS.len() {
|
||||
return Seeking::Number {
|
||||
issue: Issue {
|
||||
issue_type: IssueType::Fixme,
|
||||
missing_number: if let ReportTactic::Unnumbered = self.report_fixme {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
},
|
||||
},
|
||||
part: NumberPart::OpenParen,
|
||||
};
|
||||
}
|
||||
todo_idx = 0;
|
||||
} else {
|
||||
todo_idx = 0;
|
||||
fixme_idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Seeking::Issue {
|
||||
todo_idx,
|
||||
fixme_idx,
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect_number(
|
||||
&mut self,
|
||||
c: char,
|
||||
issue: Issue,
|
||||
mut part: NumberPart,
|
||||
) -> IssueClassification {
|
||||
if !issue.missing_number || c == '\n' {
|
||||
return IssueClassification::Bad(issue);
|
||||
} else if c == ')' {
|
||||
return if let NumberPart::CloseParen = part {
|
||||
IssueClassification::Good
|
||||
} else {
|
||||
IssueClassification::Bad(issue)
|
||||
};
|
||||
}
|
||||
|
||||
match part {
|
||||
NumberPart::OpenParen => {
|
||||
if c != '(' {
|
||||
return IssueClassification::Bad(issue);
|
||||
} else {
|
||||
part = NumberPart::Pound;
|
||||
}
|
||||
}
|
||||
NumberPart::Pound => {
|
||||
if c == '#' {
|
||||
part = NumberPart::Number;
|
||||
}
|
||||
}
|
||||
NumberPart::Number => {
|
||||
if c >= '0' && c <= '9' {
|
||||
part = NumberPart::CloseParen;
|
||||
} else {
|
||||
return IssueClassification::Bad(issue);
|
||||
}
|
||||
}
|
||||
NumberPart::CloseParen => {}
|
||||
}
|
||||
|
||||
self.state = Seeking::Number { part, issue };
|
||||
|
||||
IssueClassification::None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_unnumbered_issue() {
|
||||
fn check_fail(text: &str, failing_pos: usize) {
|
||||
let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
|
||||
assert_eq!(
|
||||
Some(failing_pos),
|
||||
text.find(|c| seeker.inspect(c).is_some())
|
||||
);
|
||||
}
|
||||
|
||||
fn check_pass(text: &str) {
|
||||
let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
|
||||
assert_eq!(None, text.find(|c| seeker.inspect(c).is_some()));
|
||||
}
|
||||
|
||||
check_fail("TODO\n", 4);
|
||||
check_pass(" TO FIX DOME\n");
|
||||
check_fail(" \n FIXME\n", 8);
|
||||
check_fail("FIXME(\n", 6);
|
||||
check_fail("FIXME(#\n", 7);
|
||||
check_fail("FIXME(#1\n", 8);
|
||||
check_fail("FIXME(#)1\n", 7);
|
||||
check_pass("FIXME(#1222)\n");
|
||||
check_fail("FIXME(#12\n22)\n", 9);
|
||||
check_pass("FIXME(@maintainer, #1222, hello)\n");
|
||||
check_fail("TODO(#22) FIXME\n", 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_issue() {
|
||||
fn is_bad_issue(text: &str, report_todo: ReportTactic, report_fixme: ReportTactic) -> bool {
|
||||
let mut seeker = BadIssueSeeker::new(report_todo, report_fixme);
|
||||
text.chars().any(|c| seeker.inspect(c).is_some())
|
||||
}
|
||||
|
||||
assert!(is_bad_issue(
|
||||
"TODO(@maintainer, #1222, hello)\n",
|
||||
ReportTactic::Always,
|
||||
ReportTactic::Never,
|
||||
));
|
||||
|
||||
assert!(!is_bad_issue(
|
||||
"TODO: no number\n",
|
||||
ReportTactic::Never,
|
||||
ReportTactic::Always,
|
||||
));
|
||||
|
||||
assert!(!is_bad_issue(
|
||||
"Todo: mixed case\n",
|
||||
ReportTactic::Never,
|
||||
ReportTactic::Always,
|
||||
));
|
||||
|
||||
assert!(is_bad_issue(
|
||||
"This is a FIXME(#1)\n",
|
||||
ReportTactic::Never,
|
||||
ReportTactic::Always,
|
||||
));
|
||||
|
||||
assert!(is_bad_issue(
|
||||
"This is a FixMe(#1) mixed case\n",
|
||||
ReportTactic::Never,
|
||||
ReportTactic::Always,
|
||||
));
|
||||
|
||||
assert!(!is_bad_issue(
|
||||
"bad FIXME\n",
|
||||
ReportTactic::Always,
|
||||
ReportTactic::Never,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_type() {
|
||||
let mut seeker = BadIssueSeeker::new(ReportTactic::Always, ReportTactic::Never);
|
||||
let expected = Some(Issue {
|
||||
issue_type: IssueType::Todo,
|
||||
missing_number: false,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
expected,
|
||||
"TODO(#100): more awesomeness"
|
||||
.chars()
|
||||
.map(|c| seeker.inspect(c))
|
||||
.find(Option::is_some)
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let mut seeker = BadIssueSeeker::new(ReportTactic::Never, ReportTactic::Unnumbered);
|
||||
let expected = Some(Issue {
|
||||
issue_type: IssueType::Fixme,
|
||||
missing_number: true,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
expected,
|
||||
"Test. FIXME: bad, bad, not good"
|
||||
.chars()
|
||||
.map(|c| seeker.inspect(c))
|
||||
.find(Option::is_some)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
3291
src/tools/rustfmt/src/items.rs
Normal file
3291
src/tools/rustfmt/src/items.rs
Normal file
File diff suppressed because it is too large
Load diff
670
src/tools/rustfmt/src/lib.rs
Normal file
670
src/tools/rustfmt/src/lib.rs
Normal file
|
|
@ -0,0 +1,670 @@
|
|||
#![feature(rustc_private)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![warn(unreachable_pub)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate derive_new;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
// N.B. these crates are loaded from the sysroot, so they need extern crate.
|
||||
extern crate rustc_ast;
|
||||
extern crate rustc_ast_pretty;
|
||||
extern crate rustc_data_structures;
|
||||
extern crate rustc_errors;
|
||||
extern crate rustc_expand;
|
||||
extern crate rustc_parse;
|
||||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
use std::panic;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ignore;
|
||||
use rustc_ast::ast;
|
||||
use rustc_span::symbol;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::comment::LineClasses;
|
||||
use crate::emitter::Emitter;
|
||||
use crate::formatting::{FormatErrorMap, FormattingError, ReportedErrors, SourceFile};
|
||||
use crate::issues::Issue;
|
||||
use crate::modules::ModuleResolutionError;
|
||||
use crate::shape::Indent;
|
||||
use crate::syntux::parser::DirectoryOwnership;
|
||||
use crate::utils::indent_next_line;
|
||||
|
||||
pub use crate::config::{
|
||||
load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName, NewlineStyle,
|
||||
Range, Verbosity,
|
||||
};
|
||||
|
||||
pub use crate::format_report_formatter::{FormatReportFormatter, FormatReportFormatterBuilder};
|
||||
|
||||
pub use crate::rustfmt_diff::{ModifiedChunk, ModifiedLines};
|
||||
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
|
||||
mod attr;
|
||||
mod chains;
|
||||
mod closures;
|
||||
mod comment;
|
||||
pub(crate) mod config;
|
||||
mod coverage;
|
||||
mod emitter;
|
||||
mod expr;
|
||||
mod format_report_formatter;
|
||||
pub(crate) mod formatting;
|
||||
mod ignore_path;
|
||||
mod imports;
|
||||
mod issues;
|
||||
mod items;
|
||||
mod lists;
|
||||
mod macros;
|
||||
mod matches;
|
||||
mod missed_spans;
|
||||
pub(crate) mod modules;
|
||||
mod overflow;
|
||||
mod pairs;
|
||||
mod patterns;
|
||||
mod release_channel;
|
||||
mod reorder;
|
||||
mod rewrite;
|
||||
pub(crate) mod rustfmt_diff;
|
||||
mod shape;
|
||||
mod skip;
|
||||
pub(crate) mod source_file;
|
||||
pub(crate) mod source_map;
|
||||
mod spanned;
|
||||
mod stmt;
|
||||
mod string;
|
||||
mod syntux;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
mod types;
|
||||
mod vertical;
|
||||
pub(crate) mod visitor;
|
||||
|
||||
/// The various errors that can occur during formatting. Note that not all of
|
||||
/// these can currently be propagated to clients.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ErrorKind {
|
||||
/// Line has exceeded character limit (found, maximum).
|
||||
#[error(
|
||||
"line formatted, but exceeded maximum width \
|
||||
(maximum: {1} (see `max_width` option), found: {0})"
|
||||
)]
|
||||
LineOverflow(usize, usize),
|
||||
/// Line ends in whitespace.
|
||||
#[error("left behind trailing whitespace")]
|
||||
TrailingWhitespace,
|
||||
/// TODO or FIXME item without an issue number.
|
||||
#[error("found {0}")]
|
||||
BadIssue(Issue),
|
||||
/// License check has failed.
|
||||
#[error("license check failed")]
|
||||
LicenseCheck,
|
||||
/// Used deprecated skip attribute.
|
||||
#[error("`rustfmt_skip` is deprecated; use `rustfmt::skip`")]
|
||||
DeprecatedAttr,
|
||||
/// Used a rustfmt:: attribute other than skip or skip::macros.
|
||||
#[error("invalid attribute")]
|
||||
BadAttr,
|
||||
/// An io error during reading or writing.
|
||||
#[error("io error: {0}")]
|
||||
IoError(io::Error),
|
||||
/// Error during module resolution.
|
||||
#[error("{0}")]
|
||||
ModuleResolutionError(#[from] ModuleResolutionError),
|
||||
/// Parse error occurred when parsing the input.
|
||||
#[error("parse error")]
|
||||
ParseError,
|
||||
/// The user mandated a version and the current version of Rustfmt does not
|
||||
/// satisfy that requirement.
|
||||
#[error("version mismatch")]
|
||||
VersionMismatch,
|
||||
/// If we had formatted the given node, then we would have lost a comment.
|
||||
#[error("not formatted because a comment would be lost")]
|
||||
LostComment,
|
||||
/// Invalid glob pattern in `ignore` configuration option.
|
||||
#[error("Invalid glob pattern found in ignore list: {0}")]
|
||||
InvalidGlobPattern(ignore::Error),
|
||||
}
|
||||
|
||||
impl ErrorKind {
|
||||
fn is_comment(&self) -> bool {
|
||||
match self {
|
||||
ErrorKind::LostComment => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ErrorKind {
|
||||
fn from(e: io::Error) -> ErrorKind {
|
||||
ErrorKind::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of formatting a snippet of code along with ranges of lines that didn't get formatted,
|
||||
/// i.e., that got returned as they were originally.
|
||||
#[derive(Debug)]
|
||||
struct FormattedSnippet {
|
||||
snippet: String,
|
||||
non_formatted_ranges: Vec<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl FormattedSnippet {
|
||||
/// In case the snippet needed to be wrapped in a function, this shifts down the ranges of
|
||||
/// non-formatted code.
|
||||
fn unwrap_code_block(&mut self) {
|
||||
self.non_formatted_ranges
|
||||
.iter_mut()
|
||||
.for_each(|(low, high)| {
|
||||
*low -= 1;
|
||||
*high -= 1;
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns `true` if the line n did not get formatted.
|
||||
fn is_line_non_formatted(&self, n: usize) -> bool {
|
||||
self.non_formatted_ranges
|
||||
.iter()
|
||||
.any(|(low, high)| *low <= n && n <= *high)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports on any issues that occurred during a run of Rustfmt.
|
||||
///
|
||||
/// Can be reported to the user using the `Display` impl on [`FormatReportFormatter`].
|
||||
#[derive(Clone)]
|
||||
pub struct FormatReport {
|
||||
// Maps stringified file paths to their associated formatting errors.
|
||||
internal: Rc<RefCell<(FormatErrorMap, ReportedErrors)>>,
|
||||
non_formatted_ranges: Vec<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl FormatReport {
|
||||
fn new() -> FormatReport {
|
||||
FormatReport {
|
||||
internal: Rc::new(RefCell::new((HashMap::new(), ReportedErrors::default()))),
|
||||
non_formatted_ranges: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_non_formatted_ranges(&mut self, mut ranges: Vec<(usize, usize)>) {
|
||||
self.non_formatted_ranges.append(&mut ranges);
|
||||
}
|
||||
|
||||
fn append(&self, f: FileName, mut v: Vec<FormattingError>) {
|
||||
self.track_errors(&v);
|
||||
self.internal
|
||||
.borrow_mut()
|
||||
.0
|
||||
.entry(f)
|
||||
.and_modify(|fe| fe.append(&mut v))
|
||||
.or_insert(v);
|
||||
}
|
||||
|
||||
fn track_errors(&self, new_errors: &[FormattingError]) {
|
||||
let errs = &mut self.internal.borrow_mut().1;
|
||||
if !new_errors.is_empty() {
|
||||
errs.has_formatting_errors = true;
|
||||
}
|
||||
if errs.has_operational_errors && errs.has_check_errors && errs.has_unformatted_code_errors
|
||||
{
|
||||
return;
|
||||
}
|
||||
for err in new_errors {
|
||||
match err.kind {
|
||||
ErrorKind::LineOverflow(..) => {
|
||||
errs.has_operational_errors = true;
|
||||
}
|
||||
ErrorKind::TrailingWhitespace => {
|
||||
errs.has_operational_errors = true;
|
||||
errs.has_unformatted_code_errors = true;
|
||||
}
|
||||
ErrorKind::LostComment => {
|
||||
errs.has_unformatted_code_errors = true;
|
||||
}
|
||||
ErrorKind::BadIssue(_)
|
||||
| ErrorKind::LicenseCheck
|
||||
| ErrorKind::DeprecatedAttr
|
||||
| ErrorKind::BadAttr
|
||||
| ErrorKind::VersionMismatch => {
|
||||
errs.has_check_errors = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_diff(&mut self) {
|
||||
self.internal.borrow_mut().1.has_diff = true;
|
||||
}
|
||||
|
||||
fn add_macro_format_failure(&mut self) {
|
||||
self.internal.borrow_mut().1.has_macro_format_failure = true;
|
||||
}
|
||||
|
||||
fn add_parsing_error(&mut self) {
|
||||
self.internal.borrow_mut().1.has_parsing_errors = true;
|
||||
}
|
||||
|
||||
fn warning_count(&self) -> usize {
|
||||
self.internal
|
||||
.borrow()
|
||||
.0
|
||||
.iter()
|
||||
.map(|(_, errors)| errors.len())
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// Whether any warnings or errors are present in the report.
|
||||
pub fn has_warnings(&self) -> bool {
|
||||
self.internal.borrow().1.has_formatting_errors
|
||||
}
|
||||
|
||||
/// Print the report to a terminal using colours and potentially other
|
||||
/// fancy output.
|
||||
#[deprecated(note = "Use FormatReportFormatter with colors enabled instead")]
|
||||
pub fn fancy_print(
|
||||
&self,
|
||||
mut t: Box<dyn term::Terminal<Output = io::Stderr>>,
|
||||
) -> Result<(), term::Error> {
|
||||
writeln!(
|
||||
t,
|
||||
"{}",
|
||||
FormatReportFormatterBuilder::new(&self)
|
||||
.enable_colors(true)
|
||||
.build()
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Deprecated - Use FormatReportFormatter instead
|
||||
// https://github.com/rust-lang/rust/issues/78625
|
||||
// https://github.com/rust-lang/rust/issues/39935
|
||||
impl fmt::Display for FormatReport {
|
||||
// Prints all the formatting errors.
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{}", FormatReportFormatterBuilder::new(&self).build())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the given snippet. The snippet is expected to be *complete* code.
|
||||
/// When we cannot parse the given snippet, this function returns `None`.
|
||||
fn format_snippet(snippet: &str, config: &Config, is_macro_def: bool) -> Option<FormattedSnippet> {
|
||||
let mut config = config.clone();
|
||||
panic::catch_unwind(|| {
|
||||
let mut out: Vec<u8> = Vec::with_capacity(snippet.len() * 2);
|
||||
config.set().emit_mode(config::EmitMode::Stdout);
|
||||
config.set().verbose(Verbosity::Quiet);
|
||||
config.set().hide_parse_errors(true);
|
||||
if is_macro_def {
|
||||
config.set().error_on_unformatted(true);
|
||||
}
|
||||
|
||||
let (formatting_error, result) = {
|
||||
let input = Input::Text(snippet.into());
|
||||
let mut session = Session::new(config, Some(&mut out));
|
||||
let result = session.format_input_inner(input, is_macro_def);
|
||||
(
|
||||
session.errors.has_macro_format_failure
|
||||
|| session.out.as_ref().unwrap().is_empty() && !snippet.is_empty()
|
||||
|| result.is_err()
|
||||
|| (is_macro_def && session.has_unformatted_code_errors()),
|
||||
result,
|
||||
)
|
||||
};
|
||||
if formatting_error {
|
||||
None
|
||||
} else {
|
||||
String::from_utf8(out).ok().map(|snippet| FormattedSnippet {
|
||||
snippet,
|
||||
non_formatted_ranges: result.unwrap().non_formatted_ranges,
|
||||
})
|
||||
}
|
||||
})
|
||||
// Discard panics encountered while formatting the snippet
|
||||
// The ? operator is needed to remove the extra Option
|
||||
.ok()?
|
||||
}
|
||||
|
||||
/// Format the given code block. Mainly targeted for code block in comment.
|
||||
/// The code block may be incomplete (i.e., parser may be unable to parse it).
|
||||
/// To avoid panic in parser, we wrap the code block with a dummy function.
|
||||
/// The returned code block does **not** end with newline.
|
||||
fn format_code_block(
|
||||
code_snippet: &str,
|
||||
config: &Config,
|
||||
is_macro_def: bool,
|
||||
) -> Option<FormattedSnippet> {
|
||||
const FN_MAIN_PREFIX: &str = "fn main() {\n";
|
||||
|
||||
fn enclose_in_main_block(s: &str, config: &Config) -> String {
|
||||
let indent = Indent::from_width(config, config.tab_spaces());
|
||||
let mut result = String::with_capacity(s.len() * 2);
|
||||
result.push_str(FN_MAIN_PREFIX);
|
||||
let mut need_indent = true;
|
||||
for (kind, line) in LineClasses::new(s) {
|
||||
if need_indent {
|
||||
result.push_str(&indent.to_string(config));
|
||||
}
|
||||
result.push_str(&line);
|
||||
result.push('\n');
|
||||
need_indent = indent_next_line(kind, &line, config);
|
||||
}
|
||||
result.push('}');
|
||||
result
|
||||
}
|
||||
|
||||
// Wrap the given code block with `fn main()` if it does not have one.
|
||||
let snippet = enclose_in_main_block(code_snippet, config);
|
||||
let mut result = String::with_capacity(snippet.len());
|
||||
let mut is_first = true;
|
||||
|
||||
// While formatting the code, ignore the config's newline style setting and always use "\n"
|
||||
// instead of "\r\n" for the newline characters. This is ok because the output here is
|
||||
// not directly outputted by rustfmt command, but used by the comment formatter's input.
|
||||
// We have output-file-wide "\n" ==> "\r\n" conversion process after here if it's necessary.
|
||||
let mut config_with_unix_newline = config.clone();
|
||||
config_with_unix_newline
|
||||
.set()
|
||||
.newline_style(NewlineStyle::Unix);
|
||||
let mut formatted = format_snippet(&snippet, &config_with_unix_newline, is_macro_def)?;
|
||||
// Remove wrapping main block
|
||||
formatted.unwrap_code_block();
|
||||
|
||||
// Trim "fn main() {" on the first line and "}" on the last line,
|
||||
// then unindent the whole code block.
|
||||
let block_len = formatted
|
||||
.snippet
|
||||
.rfind('}')
|
||||
.unwrap_or_else(|| formatted.snippet.len());
|
||||
let mut is_indented = true;
|
||||
let indent_str = Indent::from_width(config, config.tab_spaces()).to_string(config);
|
||||
for (kind, ref line) in LineClasses::new(&formatted.snippet[FN_MAIN_PREFIX.len()..block_len]) {
|
||||
if !is_first {
|
||||
result.push('\n');
|
||||
} else {
|
||||
is_first = false;
|
||||
}
|
||||
let trimmed_line = if !is_indented {
|
||||
line
|
||||
} else if line.len() > config.max_width() {
|
||||
// If there are lines that are larger than max width, we cannot tell
|
||||
// whether we have succeeded but have some comments or strings that
|
||||
// are too long, or we have failed to format code block. We will be
|
||||
// conservative and just return `None` in this case.
|
||||
return None;
|
||||
} else if line.len() > indent_str.len() {
|
||||
// Make sure that the line has leading whitespaces.
|
||||
if line.starts_with(indent_str.as_ref()) {
|
||||
let offset = if config.hard_tabs() {
|
||||
1
|
||||
} else {
|
||||
config.tab_spaces()
|
||||
};
|
||||
&line[offset..]
|
||||
} else {
|
||||
line
|
||||
}
|
||||
} else {
|
||||
line
|
||||
};
|
||||
result.push_str(trimmed_line);
|
||||
is_indented = indent_next_line(kind, line, config);
|
||||
}
|
||||
Some(FormattedSnippet {
|
||||
snippet: result,
|
||||
non_formatted_ranges: formatted.non_formatted_ranges,
|
||||
})
|
||||
}
|
||||
|
||||
/// A session is a run of rustfmt across a single or multiple inputs.
|
||||
pub struct Session<'b, T: Write> {
|
||||
pub config: Config,
|
||||
pub out: Option<&'b mut T>,
|
||||
pub(crate) errors: ReportedErrors,
|
||||
source_file: SourceFile,
|
||||
emitter: Box<dyn Emitter + 'b>,
|
||||
}
|
||||
|
||||
impl<'b, T: Write + 'b> Session<'b, T> {
|
||||
pub fn new(config: Config, mut out: Option<&'b mut T>) -> Session<'b, T> {
|
||||
let emitter = create_emitter(&config);
|
||||
|
||||
if let Some(ref mut out) = out {
|
||||
let _ = emitter.emit_header(out);
|
||||
}
|
||||
|
||||
Session {
|
||||
config,
|
||||
out,
|
||||
emitter,
|
||||
errors: ReportedErrors::default(),
|
||||
source_file: SourceFile::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The main entry point for Rustfmt. Formats the given input according to the
|
||||
/// given config. `out` is only necessary if required by the configuration.
|
||||
pub fn format(&mut self, input: Input) -> Result<FormatReport, ErrorKind> {
|
||||
self.format_input_inner(input, false)
|
||||
}
|
||||
|
||||
pub fn override_config<F, U>(&mut self, mut config: Config, f: F) -> U
|
||||
where
|
||||
F: FnOnce(&mut Session<'b, T>) -> U,
|
||||
{
|
||||
mem::swap(&mut config, &mut self.config);
|
||||
let result = f(self);
|
||||
mem::swap(&mut config, &mut self.config);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn add_operational_error(&mut self) {
|
||||
self.errors.has_operational_errors = true;
|
||||
}
|
||||
|
||||
pub fn has_operational_errors(&self) -> bool {
|
||||
self.errors.has_operational_errors
|
||||
}
|
||||
|
||||
pub fn has_parsing_errors(&self) -> bool {
|
||||
self.errors.has_parsing_errors
|
||||
}
|
||||
|
||||
pub fn has_formatting_errors(&self) -> bool {
|
||||
self.errors.has_formatting_errors
|
||||
}
|
||||
|
||||
pub fn has_check_errors(&self) -> bool {
|
||||
self.errors.has_check_errors
|
||||
}
|
||||
|
||||
pub fn has_diff(&self) -> bool {
|
||||
self.errors.has_diff
|
||||
}
|
||||
|
||||
pub fn has_unformatted_code_errors(&self) -> bool {
|
||||
self.errors.has_unformatted_code_errors
|
||||
}
|
||||
|
||||
pub fn has_no_errors(&self) -> bool {
|
||||
!(self.has_operational_errors()
|
||||
|| self.has_parsing_errors()
|
||||
|| self.has_formatting_errors()
|
||||
|| self.has_check_errors()
|
||||
|| self.has_diff()
|
||||
|| self.has_unformatted_code_errors()
|
||||
|| self.errors.has_macro_format_failure)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_emitter<'a>(config: &Config) -> Box<dyn Emitter + 'a> {
|
||||
match config.emit_mode() {
|
||||
EmitMode::Files if config.make_backup() => {
|
||||
Box::new(emitter::FilesWithBackupEmitter::default())
|
||||
}
|
||||
EmitMode::Files => Box::new(emitter::FilesEmitter::new(
|
||||
config.print_misformatted_file_names(),
|
||||
)),
|
||||
EmitMode::Stdout | EmitMode::Coverage => {
|
||||
Box::new(emitter::StdoutEmitter::new(config.verbose()))
|
||||
}
|
||||
EmitMode::Json => Box::new(emitter::JsonEmitter::default()),
|
||||
EmitMode::ModifiedLines => Box::new(emitter::ModifiedLinesEmitter::default()),
|
||||
EmitMode::Checkstyle => Box::new(emitter::CheckstyleEmitter::default()),
|
||||
EmitMode::Diff => Box::new(emitter::DiffEmitter::new(config.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, T: Write + 'b> Drop for Session<'b, T> {
|
||||
fn drop(&mut self) {
|
||||
if let Some(ref mut out) = self.out {
|
||||
let _ = self.emitter.emit_footer(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Input {
|
||||
File(PathBuf),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
impl Input {
|
||||
fn file_name(&self) -> FileName {
|
||||
match *self {
|
||||
Input::File(ref file) => FileName::Real(file.clone()),
|
||||
Input::Text(..) => FileName::Stdin,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_directory_ownership(&self) -> Option<DirectoryOwnership> {
|
||||
match self {
|
||||
Input::File(ref file) => {
|
||||
// If there exists a directory with the same name as an input,
|
||||
// then the input should be parsed as a sub module.
|
||||
let file_stem = file.file_stem()?;
|
||||
if file.parent()?.to_path_buf().join(file_stem).is_dir() {
|
||||
Some(DirectoryOwnership::Owned {
|
||||
relative: file_stem.to_str().map(symbol::Ident::from_str),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_no_panic_on_format_snippet_and_format_code_block() {
|
||||
// `format_snippet()` and `format_code_block()` should not panic
|
||||
// even when we cannot parse the given snippet.
|
||||
let snippet = "let";
|
||||
assert!(format_snippet(snippet, &Config::default(), false).is_none());
|
||||
assert!(format_code_block(snippet, &Config::default(), false).is_none());
|
||||
}
|
||||
|
||||
fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool
|
||||
where
|
||||
F: Fn(&str, &Config, bool) -> Option<FormattedSnippet>,
|
||||
{
|
||||
let output = formatter(input, &Config::default(), false);
|
||||
output.is_some() && output.unwrap().snippet == expected
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_snippet() {
|
||||
let snippet = "fn main() { println!(\"hello, world\"); }";
|
||||
#[cfg(not(windows))]
|
||||
let expected = "fn main() {\n \
|
||||
println!(\"hello, world\");\n\
|
||||
}\n";
|
||||
#[cfg(windows)]
|
||||
let expected = "fn main() {\r\n \
|
||||
println!(\"hello, world\");\r\n\
|
||||
}\r\n";
|
||||
assert!(test_format_inner(format_snippet, snippet, expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_code_block_fail() {
|
||||
#[rustfmt::skip]
|
||||
let code_block = "this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(x, y, z);";
|
||||
assert!(format_code_block(code_block, &Config::default(), false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_code_block() {
|
||||
// simple code block
|
||||
let code_block = "let x=3;";
|
||||
let expected = "let x = 3;";
|
||||
assert!(test_format_inner(format_code_block, code_block, expected));
|
||||
|
||||
// more complex code block, taken from chains.rs.
|
||||
let code_block =
|
||||
"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
|
||||
(
|
||||
chain_indent(context, shape.add_offset(parent_rewrite.len())),
|
||||
context.config.indent_style() == IndentStyle::Visual || is_small_parent,
|
||||
)
|
||||
} else if is_block_expr(context, &parent, &parent_rewrite) {
|
||||
match context.config.indent_style() {
|
||||
// Try to put the first child on the same line with parent's last line
|
||||
IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
|
||||
// The parent is a block, so align the rest of the chain with the closing
|
||||
// brace.
|
||||
IndentStyle::Visual => (parent_shape, false),
|
||||
}
|
||||
} else {
|
||||
(
|
||||
chain_indent(context, shape.add_offset(parent_rewrite.len())),
|
||||
false,
|
||||
)
|
||||
};
|
||||
";
|
||||
let expected =
|
||||
"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
|
||||
(
|
||||
chain_indent(context, shape.add_offset(parent_rewrite.len())),
|
||||
context.config.indent_style() == IndentStyle::Visual || is_small_parent,
|
||||
)
|
||||
} else if is_block_expr(context, &parent, &parent_rewrite) {
|
||||
match context.config.indent_style() {
|
||||
// Try to put the first child on the same line with parent's last line
|
||||
IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
|
||||
// The parent is a block, so align the rest of the chain with the closing
|
||||
// brace.
|
||||
IndentStyle::Visual => (parent_shape, false),
|
||||
}
|
||||
} else {
|
||||
(
|
||||
chain_indent(context, shape.add_offset(parent_rewrite.len())),
|
||||
false,
|
||||
)
|
||||
};";
|
||||
assert!(test_format_inner(format_code_block, code_block, expected));
|
||||
}
|
||||
}
|
||||
930
src/tools/rustfmt/src/lists.rs
Normal file
930
src/tools/rustfmt/src/lists.rs
Normal file
|
|
@ -0,0 +1,930 @@
|
|||
//! Format list-like expressions and items.
|
||||
|
||||
use std::cmp;
|
||||
use std::iter::Peekable;
|
||||
|
||||
use rustc_span::BytePos;
|
||||
|
||||
use crate::comment::{find_comment_end, rewrite_comment, FindUncommented};
|
||||
use crate::config::lists::*;
|
||||
use crate::config::{Config, IndentStyle};
|
||||
use crate::rewrite::RewriteContext;
|
||||
use crate::shape::{Indent, Shape};
|
||||
use crate::utils::{
|
||||
count_newlines, first_line_width, last_line_width, mk_sp, starts_with_newline,
|
||||
unicode_str_width,
|
||||
};
|
||||
use crate::visitor::SnippetProvider;
|
||||
|
||||
pub(crate) struct ListFormatting<'a> {
|
||||
tactic: DefinitiveListTactic,
|
||||
separator: &'a str,
|
||||
trailing_separator: SeparatorTactic,
|
||||
separator_place: SeparatorPlace,
|
||||
shape: Shape,
|
||||
// Non-expressions, e.g., items, will have a new line at the end of the list.
|
||||
// Important for comment styles.
|
||||
ends_with_newline: bool,
|
||||
// Remove newlines between list elements for expressions.
|
||||
preserve_newline: bool,
|
||||
// Nested import lists get some special handling for the "Mixed" list type
|
||||
nested: bool,
|
||||
// Whether comments should be visually aligned.
|
||||
align_comments: bool,
|
||||
config: &'a Config,
|
||||
}
|
||||
|
||||
impl<'a> ListFormatting<'a> {
|
||||
pub(crate) fn new(shape: Shape, config: &'a Config) -> Self {
|
||||
ListFormatting {
|
||||
tactic: DefinitiveListTactic::Vertical,
|
||||
separator: ",",
|
||||
trailing_separator: SeparatorTactic::Never,
|
||||
separator_place: SeparatorPlace::Back,
|
||||
shape,
|
||||
ends_with_newline: true,
|
||||
preserve_newline: false,
|
||||
nested: false,
|
||||
align_comments: true,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tactic(mut self, tactic: DefinitiveListTactic) -> Self {
|
||||
self.tactic = tactic;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn separator(mut self, separator: &'a str) -> Self {
|
||||
self.separator = separator;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn trailing_separator(mut self, trailing_separator: SeparatorTactic) -> Self {
|
||||
self.trailing_separator = trailing_separator;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn separator_place(mut self, separator_place: SeparatorPlace) -> Self {
|
||||
self.separator_place = separator_place;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn ends_with_newline(mut self, ends_with_newline: bool) -> Self {
|
||||
self.ends_with_newline = ends_with_newline;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn preserve_newline(mut self, preserve_newline: bool) -> Self {
|
||||
self.preserve_newline = preserve_newline;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn nested(mut self, nested: bool) -> Self {
|
||||
self.nested = nested;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn align_comments(mut self, align_comments: bool) -> Self {
|
||||
self.align_comments = align_comments;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn needs_trailing_separator(&self) -> bool {
|
||||
match self.trailing_separator {
|
||||
// We always put separator in front.
|
||||
SeparatorTactic::Always => true,
|
||||
SeparatorTactic::Vertical => self.tactic == DefinitiveListTactic::Vertical,
|
||||
SeparatorTactic::Never => {
|
||||
self.tactic == DefinitiveListTactic::Vertical && self.separator_place.is_front()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<ListItem> for ListItem {
|
||||
fn as_ref(&self) -> &ListItem {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
pub(crate) enum ListItemCommentStyle {
|
||||
// Try to keep the comment on the same line with the item.
|
||||
SameLine,
|
||||
// Put the comment on the previous or the next line of the item.
|
||||
DifferentLine,
|
||||
// No comment available.
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ListItem {
|
||||
// None for comments mean that they are not present.
|
||||
pub(crate) pre_comment: Option<String>,
|
||||
pub(crate) pre_comment_style: ListItemCommentStyle,
|
||||
// Item should include attributes and doc comments. None indicates a failed
|
||||
// rewrite.
|
||||
pub(crate) item: Option<String>,
|
||||
pub(crate) post_comment: Option<String>,
|
||||
// Whether there is extra whitespace before this item.
|
||||
pub(crate) new_lines: bool,
|
||||
}
|
||||
|
||||
impl ListItem {
|
||||
pub(crate) fn empty() -> ListItem {
|
||||
ListItem {
|
||||
pre_comment: None,
|
||||
pre_comment_style: ListItemCommentStyle::None,
|
||||
item: None,
|
||||
post_comment: None,
|
||||
new_lines: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inner_as_ref(&self) -> &str {
|
||||
self.item.as_ref().map_or("", |s| s)
|
||||
}
|
||||
|
||||
pub(crate) fn is_different_group(&self) -> bool {
|
||||
self.inner_as_ref().contains('\n')
|
||||
|| self.pre_comment.is_some()
|
||||
|| self
|
||||
.post_comment
|
||||
.as_ref()
|
||||
.map_or(false, |s| s.contains('\n'))
|
||||
}
|
||||
|
||||
pub(crate) fn is_multiline(&self) -> bool {
|
||||
self.inner_as_ref().contains('\n')
|
||||
|| self
|
||||
.pre_comment
|
||||
.as_ref()
|
||||
.map_or(false, |s| s.contains('\n'))
|
||||
|| self
|
||||
.post_comment
|
||||
.as_ref()
|
||||
.map_or(false, |s| s.contains('\n'))
|
||||
}
|
||||
|
||||
pub(crate) fn has_single_line_comment(&self) -> bool {
|
||||
self.pre_comment
|
||||
.as_ref()
|
||||
.map_or(false, |comment| comment.trim_start().starts_with("//"))
|
||||
|| self
|
||||
.post_comment
|
||||
.as_ref()
|
||||
.map_or(false, |comment| comment.trim_start().starts_with("//"))
|
||||
}
|
||||
|
||||
pub(crate) fn has_comment(&self) -> bool {
|
||||
self.pre_comment.is_some() || self.post_comment.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn from_str<S: Into<String>>(s: S) -> ListItem {
|
||||
ListItem {
|
||||
pre_comment: None,
|
||||
pre_comment_style: ListItemCommentStyle::None,
|
||||
item: Some(s.into()),
|
||||
post_comment: None,
|
||||
new_lines: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns `true` if the item causes something to be written.
|
||||
fn is_substantial(&self) -> bool {
|
||||
fn empty(s: &Option<String>) -> bool {
|
||||
match *s {
|
||||
Some(ref s) if !s.is_empty() => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
!(empty(&self.pre_comment) && empty(&self.item) && empty(&self.post_comment))
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of separator for lists.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub(crate) enum Separator {
|
||||
Comma,
|
||||
VerticalBar,
|
||||
}
|
||||
|
||||
impl Separator {
|
||||
pub(crate) fn len(self) -> usize {
|
||||
match self {
|
||||
// 2 = `, `
|
||||
Separator::Comma => 2,
|
||||
// 3 = ` | `
|
||||
Separator::VerticalBar => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn definitive_tactic<I, T>(
|
||||
items: I,
|
||||
tactic: ListTactic,
|
||||
sep: Separator,
|
||||
width: usize,
|
||||
) -> DefinitiveListTactic
|
||||
where
|
||||
I: IntoIterator<Item = T> + Clone,
|
||||
T: AsRef<ListItem>,
|
||||
{
|
||||
let pre_line_comments = items
|
||||
.clone()
|
||||
.into_iter()
|
||||
.any(|item| item.as_ref().has_single_line_comment());
|
||||
|
||||
let limit = match tactic {
|
||||
_ if pre_line_comments => return DefinitiveListTactic::Vertical,
|
||||
ListTactic::Horizontal => return DefinitiveListTactic::Horizontal,
|
||||
ListTactic::Vertical => return DefinitiveListTactic::Vertical,
|
||||
ListTactic::LimitedHorizontalVertical(limit) => ::std::cmp::min(width, limit),
|
||||
ListTactic::Mixed | ListTactic::HorizontalVertical => width,
|
||||
};
|
||||
|
||||
let (sep_count, total_width) = calculate_width(items.clone());
|
||||
let total_sep_len = sep.len() * sep_count.saturating_sub(1);
|
||||
let real_total = total_width + total_sep_len;
|
||||
|
||||
if real_total <= limit && !items.into_iter().any(|item| item.as_ref().is_multiline()) {
|
||||
DefinitiveListTactic::Horizontal
|
||||
} else {
|
||||
match tactic {
|
||||
ListTactic::Mixed => DefinitiveListTactic::Mixed,
|
||||
_ => DefinitiveListTactic::Vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format a list of commented items into a string.
|
||||
pub(crate) fn write_list<I, T>(items: I, formatting: &ListFormatting<'_>) -> Option<String>
|
||||
where
|
||||
I: IntoIterator<Item = T> + Clone,
|
||||
T: AsRef<ListItem>,
|
||||
{
|
||||
let tactic = formatting.tactic;
|
||||
let sep_len = formatting.separator.len();
|
||||
|
||||
// Now that we know how we will layout, we can decide for sure if there
|
||||
// will be a trailing separator.
|
||||
let mut trailing_separator = formatting.needs_trailing_separator();
|
||||
let mut result = String::with_capacity(128);
|
||||
let cloned_items = items.clone();
|
||||
let mut iter = items.into_iter().enumerate().peekable();
|
||||
let mut item_max_width: Option<usize> = None;
|
||||
let sep_place =
|
||||
SeparatorPlace::from_tactic(formatting.separator_place, tactic, formatting.separator);
|
||||
let mut prev_item_had_post_comment = false;
|
||||
let mut prev_item_is_nested_import = false;
|
||||
|
||||
let mut line_len = 0;
|
||||
let indent_str = &formatting.shape.indent.to_string(formatting.config);
|
||||
while let Some((i, item)) = iter.next() {
|
||||
let item = item.as_ref();
|
||||
let inner_item = item.item.as_ref()?;
|
||||
let first = i == 0;
|
||||
let last = iter.peek().is_none();
|
||||
let mut separate = match sep_place {
|
||||
SeparatorPlace::Front => !first,
|
||||
SeparatorPlace::Back => !last || trailing_separator,
|
||||
};
|
||||
let item_sep_len = if separate { sep_len } else { 0 };
|
||||
|
||||
// Item string may be multi-line. Its length (used for block comment alignment)
|
||||
// should be only the length of the last line.
|
||||
let item_last_line = if item.is_multiline() {
|
||||
inner_item.lines().last().unwrap_or("")
|
||||
} else {
|
||||
inner_item.as_ref()
|
||||
};
|
||||
let mut item_last_line_width = item_last_line.len() + item_sep_len;
|
||||
if item_last_line.starts_with(&**indent_str) {
|
||||
item_last_line_width -= indent_str.len();
|
||||
}
|
||||
|
||||
if !item.is_substantial() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match tactic {
|
||||
DefinitiveListTactic::Horizontal if !first => {
|
||||
result.push(' ');
|
||||
}
|
||||
DefinitiveListTactic::SpecialMacro(num_args_before) => {
|
||||
if i == 0 {
|
||||
// Nothing
|
||||
} else if i < num_args_before {
|
||||
result.push(' ');
|
||||
} else if i <= num_args_before + 1 {
|
||||
result.push('\n');
|
||||
result.push_str(indent_str);
|
||||
} else {
|
||||
result.push(' ');
|
||||
}
|
||||
}
|
||||
DefinitiveListTactic::Vertical
|
||||
if !first && !inner_item.is_empty() && !result.is_empty() =>
|
||||
{
|
||||
result.push('\n');
|
||||
result.push_str(indent_str);
|
||||
}
|
||||
DefinitiveListTactic::Mixed => {
|
||||
let total_width = total_item_width(item) + item_sep_len;
|
||||
|
||||
// 1 is space between separator and item.
|
||||
if (line_len > 0 && line_len + 1 + total_width > formatting.shape.width)
|
||||
|| prev_item_had_post_comment
|
||||
|| (formatting.nested
|
||||
&& (prev_item_is_nested_import || (!first && inner_item.contains("::"))))
|
||||
{
|
||||
result.push('\n');
|
||||
result.push_str(indent_str);
|
||||
line_len = 0;
|
||||
if formatting.ends_with_newline {
|
||||
trailing_separator = true;
|
||||
}
|
||||
} else if line_len > 0 {
|
||||
result.push(' ');
|
||||
line_len += 1;
|
||||
}
|
||||
|
||||
if last && formatting.ends_with_newline {
|
||||
separate = formatting.trailing_separator != SeparatorTactic::Never;
|
||||
}
|
||||
|
||||
line_len += total_width;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Pre-comments
|
||||
if let Some(ref comment) = item.pre_comment {
|
||||
// Block style in non-vertical mode.
|
||||
let block_mode = tactic == DefinitiveListTactic::Horizontal;
|
||||
// Width restriction is only relevant in vertical mode.
|
||||
let comment =
|
||||
rewrite_comment(comment, block_mode, formatting.shape, formatting.config)?;
|
||||
result.push_str(&comment);
|
||||
|
||||
if !inner_item.is_empty() {
|
||||
if tactic == DefinitiveListTactic::Vertical || tactic == DefinitiveListTactic::Mixed
|
||||
{
|
||||
// We cannot keep pre-comments on the same line if the comment if normalized.
|
||||
let keep_comment = if formatting.config.normalize_comments()
|
||||
|| item.pre_comment_style == ListItemCommentStyle::DifferentLine
|
||||
{
|
||||
false
|
||||
} else {
|
||||
// We will try to keep the comment on the same line with the item here.
|
||||
// 1 = ` `
|
||||
let total_width = total_item_width(item) + item_sep_len + 1;
|
||||
total_width <= formatting.shape.width
|
||||
};
|
||||
if keep_comment {
|
||||
result.push(' ');
|
||||
} else {
|
||||
result.push('\n');
|
||||
result.push_str(indent_str);
|
||||
// This is the width of the item (without comments).
|
||||
line_len = item.item.as_ref().map_or(0, |s| unicode_str_width(&s));
|
||||
}
|
||||
} else {
|
||||
result.push(' ');
|
||||
}
|
||||
}
|
||||
item_max_width = None;
|
||||
}
|
||||
|
||||
if separate && sep_place.is_front() && !first {
|
||||
result.push_str(formatting.separator.trim());
|
||||
result.push(' ');
|
||||
}
|
||||
result.push_str(inner_item);
|
||||
|
||||
// Post-comments
|
||||
if tactic == DefinitiveListTactic::Horizontal && item.post_comment.is_some() {
|
||||
let comment = item.post_comment.as_ref().unwrap();
|
||||
let formatted_comment = rewrite_comment(
|
||||
comment,
|
||||
true,
|
||||
Shape::legacy(formatting.shape.width, Indent::empty()),
|
||||
formatting.config,
|
||||
)?;
|
||||
|
||||
result.push(' ');
|
||||
result.push_str(&formatted_comment);
|
||||
}
|
||||
|
||||
if separate && sep_place.is_back() {
|
||||
result.push_str(formatting.separator);
|
||||
}
|
||||
|
||||
if tactic != DefinitiveListTactic::Horizontal && item.post_comment.is_some() {
|
||||
let comment = item.post_comment.as_ref().unwrap();
|
||||
let overhead = last_line_width(&result) + first_line_width(comment.trim());
|
||||
|
||||
let rewrite_post_comment = |item_max_width: &mut Option<usize>| {
|
||||
if item_max_width.is_none() && !last && !inner_item.contains('\n') {
|
||||
*item_max_width = Some(max_width_of_item_with_post_comment(
|
||||
&cloned_items,
|
||||
i,
|
||||
overhead,
|
||||
formatting.config.max_width(),
|
||||
));
|
||||
}
|
||||
let overhead = if starts_with_newline(comment) {
|
||||
0
|
||||
} else if let Some(max_width) = *item_max_width {
|
||||
max_width + 2
|
||||
} else {
|
||||
// 1 = space between item and comment.
|
||||
item_last_line_width + 1
|
||||
};
|
||||
let width = formatting.shape.width.checked_sub(overhead).unwrap_or(1);
|
||||
let offset = formatting.shape.indent + overhead;
|
||||
let comment_shape = Shape::legacy(width, offset);
|
||||
|
||||
// Use block-style only for the last item or multiline comments.
|
||||
let block_style = !formatting.ends_with_newline && last
|
||||
|| comment.trim().contains('\n')
|
||||
|| comment.trim().len() > width;
|
||||
|
||||
rewrite_comment(
|
||||
comment.trim_start(),
|
||||
block_style,
|
||||
comment_shape,
|
||||
formatting.config,
|
||||
)
|
||||
};
|
||||
|
||||
let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?;
|
||||
|
||||
if !starts_with_newline(comment) {
|
||||
if formatting.align_comments {
|
||||
let mut comment_alignment =
|
||||
post_comment_alignment(item_max_width, inner_item.len());
|
||||
if first_line_width(&formatted_comment)
|
||||
+ last_line_width(&result)
|
||||
+ comment_alignment
|
||||
+ 1
|
||||
> formatting.config.max_width()
|
||||
{
|
||||
item_max_width = None;
|
||||
formatted_comment = rewrite_post_comment(&mut item_max_width)?;
|
||||
comment_alignment =
|
||||
post_comment_alignment(item_max_width, inner_item.len());
|
||||
}
|
||||
for _ in 0..=comment_alignment {
|
||||
result.push(' ');
|
||||
}
|
||||
}
|
||||
// An additional space for the missing trailing separator (or
|
||||
// if we skipped alignment above).
|
||||
if !formatting.align_comments
|
||||
|| (last
|
||||
&& item_max_width.is_some()
|
||||
&& !separate
|
||||
&& !formatting.separator.is_empty())
|
||||
{
|
||||
result.push(' ');
|
||||
}
|
||||
} else {
|
||||
result.push('\n');
|
||||
result.push_str(indent_str);
|
||||
}
|
||||
if formatted_comment.contains('\n') {
|
||||
item_max_width = None;
|
||||
}
|
||||
result.push_str(&formatted_comment);
|
||||
} else {
|
||||
item_max_width = None;
|
||||
}
|
||||
|
||||
if formatting.preserve_newline
|
||||
&& !last
|
||||
&& tactic == DefinitiveListTactic::Vertical
|
||||
&& item.new_lines
|
||||
{
|
||||
item_max_width = None;
|
||||
result.push('\n');
|
||||
}
|
||||
|
||||
prev_item_had_post_comment = item.post_comment.is_some();
|
||||
prev_item_is_nested_import = inner_item.contains("::");
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn max_width_of_item_with_post_comment<I, T>(
|
||||
items: &I,
|
||||
i: usize,
|
||||
overhead: usize,
|
||||
max_budget: usize,
|
||||
) -> usize
|
||||
where
|
||||
I: IntoIterator<Item = T> + Clone,
|
||||
T: AsRef<ListItem>,
|
||||
{
|
||||
let mut max_width = 0;
|
||||
let mut first = true;
|
||||
for item in items.clone().into_iter().skip(i) {
|
||||
let item = item.as_ref();
|
||||
let inner_item_width = item.inner_as_ref().len();
|
||||
if !first
|
||||
&& (item.is_different_group()
|
||||
|| item.post_comment.is_none()
|
||||
|| inner_item_width + overhead > max_budget)
|
||||
{
|
||||
return max_width;
|
||||
}
|
||||
if max_width < inner_item_width {
|
||||
max_width = inner_item_width;
|
||||
}
|
||||
if item.new_lines {
|
||||
return max_width;
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
max_width
|
||||
}
|
||||
|
||||
fn post_comment_alignment(item_max_width: Option<usize>, inner_item_len: usize) -> usize {
|
||||
item_max_width.unwrap_or(0).saturating_sub(inner_item_len)
|
||||
}
|
||||
|
||||
pub(crate) struct ListItems<'a, I, F1, F2, F3>
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
snippet_provider: &'a SnippetProvider,
|
||||
inner: Peekable<I>,
|
||||
get_lo: F1,
|
||||
get_hi: F2,
|
||||
get_item_string: F3,
|
||||
prev_span_end: BytePos,
|
||||
next_span_start: BytePos,
|
||||
terminator: &'a str,
|
||||
separator: &'a str,
|
||||
leave_last: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn extract_pre_comment(pre_snippet: &str) -> (Option<String>, ListItemCommentStyle) {
|
||||
let trimmed_pre_snippet = pre_snippet.trim();
|
||||
// Both start and end are checked to support keeping a block comment inline with
|
||||
// the item, even if there are preceeding line comments, while still supporting
|
||||
// a snippet that starts with a block comment but also contains one or more
|
||||
// trailing single line comments.
|
||||
// https://github.com/rust-lang/rustfmt/issues/3025
|
||||
// https://github.com/rust-lang/rustfmt/pull/3048
|
||||
// https://github.com/rust-lang/rustfmt/issues/3839
|
||||
let starts_with_block_comment = trimmed_pre_snippet.starts_with("/*");
|
||||
let ends_with_block_comment = trimmed_pre_snippet.ends_with("*/");
|
||||
let starts_with_single_line_comment = trimmed_pre_snippet.starts_with("//");
|
||||
if ends_with_block_comment {
|
||||
let comment_end = pre_snippet.rfind(|c| c == '/').unwrap();
|
||||
if pre_snippet[comment_end..].contains('\n') {
|
||||
(
|
||||
Some(trimmed_pre_snippet.to_owned()),
|
||||
ListItemCommentStyle::DifferentLine,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Some(trimmed_pre_snippet.to_owned()),
|
||||
ListItemCommentStyle::SameLine,
|
||||
)
|
||||
}
|
||||
} else if starts_with_single_line_comment || starts_with_block_comment {
|
||||
(
|
||||
Some(trimmed_pre_snippet.to_owned()),
|
||||
ListItemCommentStyle::DifferentLine,
|
||||
)
|
||||
} else {
|
||||
(None, ListItemCommentStyle::None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extract_post_comment(
|
||||
post_snippet: &str,
|
||||
comment_end: usize,
|
||||
separator: &str,
|
||||
) -> Option<String> {
|
||||
let white_space: &[_] = &[' ', '\t'];
|
||||
|
||||
// Cleanup post-comment: strip separators and whitespace.
|
||||
let post_snippet = post_snippet[..comment_end].trim();
|
||||
let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') {
|
||||
post_snippet[1..].trim_matches(white_space)
|
||||
} else if post_snippet.starts_with(separator) {
|
||||
post_snippet[separator.len()..].trim_matches(white_space)
|
||||
}
|
||||
// not comment or over two lines
|
||||
else if post_snippet.ends_with(',')
|
||||
&& (!post_snippet.trim().starts_with("//") || post_snippet.trim().contains('\n'))
|
||||
{
|
||||
post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
|
||||
} else {
|
||||
post_snippet
|
||||
};
|
||||
// FIXME(#3441): post_snippet includes 'const' now
|
||||
// it should not include here
|
||||
let removed_newline_snippet = post_snippet_trimmed.trim();
|
||||
if !post_snippet_trimmed.is_empty()
|
||||
&& (removed_newline_snippet.starts_with("//") || removed_newline_snippet.starts_with("/*"))
|
||||
{
|
||||
Some(post_snippet_trimmed.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_comment_end(
|
||||
post_snippet: &str,
|
||||
separator: &str,
|
||||
terminator: &str,
|
||||
is_last: bool,
|
||||
) -> usize {
|
||||
if is_last {
|
||||
return post_snippet
|
||||
.find_uncommented(terminator)
|
||||
.unwrap_or_else(|| post_snippet.len());
|
||||
}
|
||||
|
||||
let mut block_open_index = post_snippet.find("/*");
|
||||
// check if it really is a block comment (and not `//*` or a nested comment)
|
||||
if let Some(i) = block_open_index {
|
||||
match post_snippet.find('/') {
|
||||
Some(j) if j < i => block_open_index = None,
|
||||
_ if post_snippet[..i].ends_with('/') => block_open_index = None,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let newline_index = post_snippet.find('\n');
|
||||
if let Some(separator_index) = post_snippet.find_uncommented(separator) {
|
||||
match (block_open_index, newline_index) {
|
||||
// Separator before comment, with the next item on same line.
|
||||
// Comment belongs to next item.
|
||||
(Some(i), None) if i > separator_index => separator_index + 1,
|
||||
// Block-style post-comment before the separator.
|
||||
(Some(i), None) => cmp::max(
|
||||
find_comment_end(&post_snippet[i..]).unwrap() + i,
|
||||
separator_index + 1,
|
||||
),
|
||||
// Block-style post-comment. Either before or after the separator.
|
||||
(Some(i), Some(j)) if i < j => cmp::max(
|
||||
find_comment_end(&post_snippet[i..]).unwrap() + i,
|
||||
separator_index + 1,
|
||||
),
|
||||
// Potential *single* line comment.
|
||||
(_, Some(j)) if j > separator_index => j + 1,
|
||||
_ => post_snippet.len(),
|
||||
}
|
||||
} else if let Some(newline_index) = newline_index {
|
||||
// Match arms may not have trailing comma. In any case, for match arms,
|
||||
// we will assume that the post comment belongs to the next arm if they
|
||||
// do not end with trailing comma.
|
||||
newline_index + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// Account for extra whitespace between items. This is fiddly
|
||||
// because of the way we divide pre- and post- comments.
|
||||
pub(crate) fn has_extra_newline(post_snippet: &str, comment_end: usize) -> bool {
|
||||
if post_snippet.is_empty() || comment_end == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let len_last = post_snippet[..comment_end]
|
||||
.chars()
|
||||
.last()
|
||||
.unwrap()
|
||||
.len_utf8();
|
||||
// Everything from the separator to the next item.
|
||||
let test_snippet = &post_snippet[comment_end - len_last..];
|
||||
let first_newline = test_snippet
|
||||
.find('\n')
|
||||
.unwrap_or_else(|| test_snippet.len());
|
||||
// From the end of the first line of comments.
|
||||
let test_snippet = &test_snippet[first_newline..];
|
||||
let first = test_snippet
|
||||
.find(|c: char| !c.is_whitespace())
|
||||
.unwrap_or_else(|| test_snippet.len());
|
||||
// From the end of the first line of comments to the next non-whitespace char.
|
||||
let test_snippet = &test_snippet[..first];
|
||||
|
||||
// There were multiple line breaks which got trimmed to nothing.
|
||||
count_newlines(test_snippet) > 1
|
||||
}
|
||||
|
||||
impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
|
||||
where
|
||||
I: Iterator<Item = T>,
|
||||
F1: Fn(&T) -> BytePos,
|
||||
F2: Fn(&T) -> BytePos,
|
||||
F3: Fn(&T) -> Option<String>,
|
||||
{
|
||||
type Item = ListItem;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|item| {
|
||||
// Pre-comment
|
||||
let pre_snippet = self
|
||||
.snippet_provider
|
||||
.span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item)))
|
||||
.unwrap_or("");
|
||||
let (pre_comment, pre_comment_style) = extract_pre_comment(pre_snippet);
|
||||
|
||||
// Post-comment
|
||||
let next_start = match self.inner.peek() {
|
||||
Some(next_item) => (self.get_lo)(next_item),
|
||||
None => self.next_span_start,
|
||||
};
|
||||
let post_snippet = self
|
||||
.snippet_provider
|
||||
.span_to_snippet(mk_sp((self.get_hi)(&item), next_start))
|
||||
.unwrap_or("");
|
||||
let comment_end = get_comment_end(
|
||||
post_snippet,
|
||||
self.separator,
|
||||
self.terminator,
|
||||
self.inner.peek().is_none(),
|
||||
);
|
||||
let new_lines = has_extra_newline(post_snippet, comment_end);
|
||||
let post_comment = extract_post_comment(post_snippet, comment_end, self.separator);
|
||||
|
||||
self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
|
||||
|
||||
ListItem {
|
||||
pre_comment,
|
||||
pre_comment_style,
|
||||
item: if self.inner.peek().is_none() && self.leave_last {
|
||||
None
|
||||
} else {
|
||||
(self.get_item_string)(&item)
|
||||
},
|
||||
post_comment,
|
||||
new_lines,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
// Creates an iterator over a list's items with associated comments.
|
||||
pub(crate) fn itemize_list<'a, T, I, F1, F2, F3>(
|
||||
snippet_provider: &'a SnippetProvider,
|
||||
inner: I,
|
||||
terminator: &'a str,
|
||||
separator: &'a str,
|
||||
get_lo: F1,
|
||||
get_hi: F2,
|
||||
get_item_string: F3,
|
||||
prev_span_end: BytePos,
|
||||
next_span_start: BytePos,
|
||||
leave_last: bool,
|
||||
) -> ListItems<'a, I, F1, F2, F3>
|
||||
where
|
||||
I: Iterator<Item = T>,
|
||||
F1: Fn(&T) -> BytePos,
|
||||
F2: Fn(&T) -> BytePos,
|
||||
F3: Fn(&T) -> Option<String>,
|
||||
{
|
||||
ListItems {
|
||||
snippet_provider,
|
||||
inner: inner.peekable(),
|
||||
get_lo,
|
||||
get_hi,
|
||||
get_item_string,
|
||||
prev_span_end,
|
||||
next_span_start,
|
||||
terminator,
|
||||
separator,
|
||||
leave_last,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the count and total width of the list items.
|
||||
fn calculate_width<I, T>(items: I) -> (usize, usize)
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: AsRef<ListItem>,
|
||||
{
|
||||
items
|
||||
.into_iter()
|
||||
.map(|item| total_item_width(item.as_ref()))
|
||||
.fold((0, 0), |acc, l| (acc.0 + 1, acc.1 + l))
|
||||
}
|
||||
|
||||
pub(crate) fn total_item_width(item: &ListItem) -> usize {
|
||||
comment_len(item.pre_comment.as_ref().map(|x| &(*x)[..]))
|
||||
+ comment_len(item.post_comment.as_ref().map(|x| &(*x)[..]))
|
||||
+ &item.item.as_ref().map_or(0, |s| unicode_str_width(&s))
|
||||
}
|
||||
|
||||
fn comment_len(comment: Option<&str>) -> usize {
|
||||
match comment {
|
||||
Some(s) => {
|
||||
let text_len = s.trim().len();
|
||||
if text_len > 0 {
|
||||
// We'll put " /*" before and " */" after inline comments.
|
||||
text_len + 6
|
||||
} else {
|
||||
text_len
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Compute horizontal and vertical shapes for a struct-lit-like thing.
|
||||
pub(crate) fn struct_lit_shape(
|
||||
shape: Shape,
|
||||
context: &RewriteContext<'_>,
|
||||
prefix_width: usize,
|
||||
suffix_width: usize,
|
||||
) -> Option<(Option<Shape>, Shape)> {
|
||||
let v_shape = match context.config.indent_style() {
|
||||
IndentStyle::Visual => shape
|
||||
.visual_indent(0)
|
||||
.shrink_left(prefix_width)?
|
||||
.sub_width(suffix_width)?,
|
||||
IndentStyle::Block => {
|
||||
let shape = shape.block_indent(context.config.tab_spaces());
|
||||
Shape {
|
||||
width: context.budget(shape.indent.width()),
|
||||
..shape
|
||||
}
|
||||
}
|
||||
};
|
||||
let shape_width = shape.width.checked_sub(prefix_width + suffix_width);
|
||||
if let Some(w) = shape_width {
|
||||
let shape_width = cmp::min(w, context.config.struct_lit_width());
|
||||
Some((Some(Shape::legacy(shape_width, shape.indent)), v_shape))
|
||||
} else {
|
||||
Some((None, v_shape))
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the tactic for the internals of a struct-lit-like thing.
|
||||
pub(crate) fn struct_lit_tactic(
|
||||
h_shape: Option<Shape>,
|
||||
context: &RewriteContext<'_>,
|
||||
items: &[ListItem],
|
||||
) -> DefinitiveListTactic {
|
||||
if let Some(h_shape) = h_shape {
|
||||
let prelim_tactic = match (context.config.indent_style(), items.len()) {
|
||||
(IndentStyle::Visual, 1) => ListTactic::HorizontalVertical,
|
||||
_ if context.config.struct_lit_single_line() => ListTactic::HorizontalVertical,
|
||||
_ => ListTactic::Vertical,
|
||||
};
|
||||
definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width)
|
||||
} else {
|
||||
DefinitiveListTactic::Vertical
|
||||
}
|
||||
}
|
||||
|
||||
// Given a tactic and possible shapes for horizontal and vertical layout,
|
||||
// come up with the actual shape to use.
|
||||
pub(crate) fn shape_for_tactic(
|
||||
tactic: DefinitiveListTactic,
|
||||
h_shape: Option<Shape>,
|
||||
v_shape: Shape,
|
||||
) -> Shape {
|
||||
match tactic {
|
||||
DefinitiveListTactic::Horizontal => h_shape.unwrap(),
|
||||
_ => v_shape,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a ListFormatting object for formatting the internals of a
|
||||
// struct-lit-like thing, that is a series of fields.
|
||||
pub(crate) fn struct_lit_formatting<'a>(
|
||||
shape: Shape,
|
||||
tactic: DefinitiveListTactic,
|
||||
context: &'a RewriteContext<'_>,
|
||||
force_no_trailing_comma: bool,
|
||||
) -> ListFormatting<'a> {
|
||||
let ends_with_newline = context.config.indent_style() != IndentStyle::Visual
|
||||
&& tactic == DefinitiveListTactic::Vertical;
|
||||
ListFormatting {
|
||||
tactic,
|
||||
separator: ",",
|
||||
trailing_separator: if force_no_trailing_comma {
|
||||
SeparatorTactic::Never
|
||||
} else {
|
||||
context.config.trailing_comma()
|
||||
},
|
||||
separator_place: SeparatorPlace::Back,
|
||||
shape,
|
||||
ends_with_newline,
|
||||
preserve_newline: true,
|
||||
nested: false,
|
||||
align_comments: true,
|
||||
config: context.config,
|
||||
}
|
||||
}
|
||||
1612
src/tools/rustfmt/src/macros.rs
Normal file
1612
src/tools/rustfmt/src/macros.rs
Normal file
File diff suppressed because it is too large
Load diff
596
src/tools/rustfmt/src/matches.rs
Normal file
596
src/tools/rustfmt/src/matches.rs
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
//! Format match expression.
|
||||
|
||||
use std::iter::repeat;
|
||||
|
||||
use rustc_ast::{ast, ptr};
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
use crate::comment::{combine_strs_with_missing_comments, rewrite_comment};
|
||||
use crate::config::lists::*;
|
||||
use crate::config::{Config, ControlBraceStyle, IndentStyle, MatchArmLeadingPipe, Version};
|
||||
use crate::expr::{
|
||||
format_expr, is_empty_block, is_simple_block, is_unsafe_block, prefer_next_line, rewrite_cond,
|
||||
ExprType, RhsTactics,
|
||||
};
|
||||
use crate::lists::{itemize_list, write_list, ListFormatting};
|
||||
use crate::rewrite::{Rewrite, RewriteContext};
|
||||
use crate::shape::Shape;
|
||||
use crate::source_map::SpanUtils;
|
||||
use crate::spanned::Spanned;
|
||||
use crate::utils::{
|
||||
contains_skip, extra_offset, first_line_width, inner_attributes, last_line_extendable, mk_sp,
|
||||
mk_sp_lo_plus_one, semicolon_for_expr, trimmed_last_line_width, unicode_str_width,
|
||||
};
|
||||
|
||||
/// A simple wrapper type against `ast::Arm`. Used inside `write_list()`.
|
||||
struct ArmWrapper<'a> {
|
||||
arm: &'a ast::Arm,
|
||||
/// `true` if the arm is the last one in match expression. Used to decide on whether we should
|
||||
/// add trailing comma to the match arm when `config.trailing_comma() == Never`.
|
||||
is_last: bool,
|
||||
/// Holds a byte position of `|` at the beginning of the arm pattern, if available.
|
||||
beginning_vert: Option<BytePos>,
|
||||
}
|
||||
|
||||
impl<'a> ArmWrapper<'a> {
|
||||
fn new(arm: &'a ast::Arm, is_last: bool, beginning_vert: Option<BytePos>) -> ArmWrapper<'a> {
|
||||
ArmWrapper {
|
||||
arm,
|
||||
is_last,
|
||||
beginning_vert,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spanned for ArmWrapper<'a> {
|
||||
fn span(&self) -> Span {
|
||||
if let Some(lo) = self.beginning_vert {
|
||||
let lo = std::cmp::min(lo, self.arm.span().lo());
|
||||
mk_sp(lo, self.arm.span().hi())
|
||||
} else {
|
||||
self.arm.span()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Rewrite for ArmWrapper<'a> {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
rewrite_match_arm(
|
||||
context,
|
||||
self.arm,
|
||||
shape,
|
||||
self.is_last,
|
||||
self.beginning_vert.is_some(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rewrite_match(
|
||||
context: &RewriteContext<'_>,
|
||||
cond: &ast::Expr,
|
||||
arms: &[ast::Arm],
|
||||
shape: Shape,
|
||||
span: Span,
|
||||
attrs: &[ast::Attribute],
|
||||
) -> Option<String> {
|
||||
// Do not take the rhs overhead from the upper expressions into account
|
||||
// when rewriting match condition.
|
||||
let cond_shape = Shape {
|
||||
width: context.budget(shape.used_width()),
|
||||
..shape
|
||||
};
|
||||
// 6 = `match `
|
||||
let cond_shape = match context.config.indent_style() {
|
||||
IndentStyle::Visual => cond_shape.shrink_left(6)?,
|
||||
IndentStyle::Block => cond_shape.offset_left(6)?,
|
||||
};
|
||||
let cond_str = cond.rewrite(context, cond_shape)?;
|
||||
let alt_block_sep = &shape.indent.to_string_with_newline(context.config);
|
||||
let block_sep = match context.config.control_brace_style() {
|
||||
ControlBraceStyle::AlwaysNextLine => alt_block_sep,
|
||||
_ if last_line_extendable(&cond_str) => " ",
|
||||
// 2 = ` {`
|
||||
_ if cond_str.contains('\n') || cond_str.len() + 2 > cond_shape.width => alt_block_sep,
|
||||
_ => " ",
|
||||
};
|
||||
|
||||
let nested_indent_str = shape
|
||||
.indent
|
||||
.block_indent(context.config)
|
||||
.to_string(context.config);
|
||||
// Inner attributes.
|
||||
let inner_attrs = &inner_attributes(attrs);
|
||||
let inner_attrs_str = if inner_attrs.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
inner_attrs
|
||||
.rewrite(context, shape)
|
||||
.map(|s| format!("{}{}\n", nested_indent_str, s))?
|
||||
};
|
||||
|
||||
let open_brace_pos = if inner_attrs.is_empty() {
|
||||
let hi = if arms.is_empty() {
|
||||
span.hi()
|
||||
} else {
|
||||
arms[0].span().lo()
|
||||
};
|
||||
context
|
||||
.snippet_provider
|
||||
.span_after(mk_sp(cond.span.hi(), hi), "{")
|
||||
} else {
|
||||
inner_attrs[inner_attrs.len() - 1].span.hi()
|
||||
};
|
||||
|
||||
if arms.is_empty() {
|
||||
let snippet = context.snippet(mk_sp(open_brace_pos, span.hi() - BytePos(1)));
|
||||
if snippet.trim().is_empty() {
|
||||
Some(format!("match {} {{}}", cond_str))
|
||||
} else {
|
||||
// Empty match with comments or inner attributes? We are not going to bother, sorry ;)
|
||||
Some(context.snippet(span).to_owned())
|
||||
}
|
||||
} else {
|
||||
let span_after_cond = mk_sp(cond.span.hi(), span.hi());
|
||||
Some(format!(
|
||||
"match {}{}{{\n{}{}{}\n{}}}",
|
||||
cond_str,
|
||||
block_sep,
|
||||
inner_attrs_str,
|
||||
nested_indent_str,
|
||||
rewrite_match_arms(context, arms, shape, span_after_cond, open_brace_pos)?,
|
||||
shape.indent.to_string(context.config),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn arm_comma(config: &Config, body: &ast::Expr, is_last: bool) -> &'static str {
|
||||
if is_last && config.trailing_comma() == SeparatorTactic::Never {
|
||||
""
|
||||
} else if config.match_block_trailing_comma() {
|
||||
","
|
||||
} else if let ast::ExprKind::Block(ref block, _) = body.kind {
|
||||
if let ast::BlockCheckMode::Default = block.rules {
|
||||
""
|
||||
} else {
|
||||
","
|
||||
}
|
||||
} else {
|
||||
","
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect a byte position of the beginning `|` for each arm, if available.
|
||||
fn collect_beginning_verts(
|
||||
context: &RewriteContext<'_>,
|
||||
arms: &[ast::Arm],
|
||||
) -> Vec<Option<BytePos>> {
|
||||
arms.iter()
|
||||
.map(|a| {
|
||||
context
|
||||
.snippet_provider
|
||||
.opt_span_before(mk_sp_lo_plus_one(a.pat.span.lo()), "|")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn rewrite_match_arms(
|
||||
context: &RewriteContext<'_>,
|
||||
arms: &[ast::Arm],
|
||||
shape: Shape,
|
||||
span: Span,
|
||||
open_brace_pos: BytePos,
|
||||
) -> Option<String> {
|
||||
let arm_shape = shape
|
||||
.block_indent(context.config.tab_spaces())
|
||||
.with_max_width(context.config);
|
||||
|
||||
let arm_len = arms.len();
|
||||
let is_last_iter = repeat(false)
|
||||
.take(arm_len.saturating_sub(1))
|
||||
.chain(repeat(true));
|
||||
let beginning_verts = collect_beginning_verts(context, arms);
|
||||
let items = itemize_list(
|
||||
context.snippet_provider,
|
||||
arms.iter()
|
||||
.zip(is_last_iter)
|
||||
.zip(beginning_verts.into_iter())
|
||||
.map(|((arm, is_last), beginning_vert)| ArmWrapper::new(arm, is_last, beginning_vert)),
|
||||
"}",
|
||||
"|",
|
||||
|arm| arm.span().lo(),
|
||||
|arm| arm.span().hi(),
|
||||
|arm| arm.rewrite(context, arm_shape),
|
||||
open_brace_pos,
|
||||
span.hi(),
|
||||
false,
|
||||
);
|
||||
let arms_vec: Vec<_> = items.collect();
|
||||
// We will add/remove commas inside `arm.rewrite()`, and hence no separator here.
|
||||
let fmt = ListFormatting::new(arm_shape, context.config)
|
||||
.separator("")
|
||||
.preserve_newline(true);
|
||||
|
||||
write_list(&arms_vec, &fmt)
|
||||
}
|
||||
|
||||
fn rewrite_match_arm(
|
||||
context: &RewriteContext<'_>,
|
||||
arm: &ast::Arm,
|
||||
shape: Shape,
|
||||
is_last: bool,
|
||||
has_leading_pipe: bool,
|
||||
) -> Option<String> {
|
||||
let (missing_span, attrs_str) = if !arm.attrs.is_empty() {
|
||||
if contains_skip(&arm.attrs) {
|
||||
let (_, body) = flatten_arm_body(context, &arm.body, None);
|
||||
// `arm.span()` does not include trailing comma, add it manually.
|
||||
return Some(format!(
|
||||
"{}{}",
|
||||
context.snippet(arm.span()),
|
||||
arm_comma(context.config, body, is_last),
|
||||
));
|
||||
}
|
||||
let missing_span = mk_sp(arm.attrs[arm.attrs.len() - 1].span.hi(), arm.pat.span.lo());
|
||||
(missing_span, arm.attrs.rewrite(context, shape)?)
|
||||
} else {
|
||||
(mk_sp(arm.span().lo(), arm.span().lo()), String::new())
|
||||
};
|
||||
|
||||
// Leading pipe offset
|
||||
// 2 = `| `
|
||||
let (pipe_offset, pipe_str) = match context.config.match_arm_leading_pipes() {
|
||||
MatchArmLeadingPipe::Never => (0, ""),
|
||||
MatchArmLeadingPipe::Preserve if !has_leading_pipe => (0, ""),
|
||||
MatchArmLeadingPipe::Preserve | MatchArmLeadingPipe::Always => (2, "| "),
|
||||
};
|
||||
|
||||
// Patterns
|
||||
// 5 = ` => {`
|
||||
let pat_shape = shape.sub_width(5)?.offset_left(pipe_offset)?;
|
||||
let pats_str = arm.pat.rewrite(context, pat_shape)?;
|
||||
|
||||
// Guard
|
||||
let block_like_pat = trimmed_last_line_width(&pats_str) <= context.config.tab_spaces();
|
||||
let new_line_guard = pats_str.contains('\n') && !block_like_pat;
|
||||
let guard_str = rewrite_guard(
|
||||
context,
|
||||
&arm.guard,
|
||||
shape,
|
||||
trimmed_last_line_width(&pats_str),
|
||||
new_line_guard,
|
||||
)?;
|
||||
|
||||
let lhs_str = combine_strs_with_missing_comments(
|
||||
context,
|
||||
&attrs_str,
|
||||
&format!("{}{}{}", pipe_str, pats_str, guard_str),
|
||||
missing_span,
|
||||
shape,
|
||||
false,
|
||||
)?;
|
||||
|
||||
let arrow_span = mk_sp(arm.pat.span.hi(), arm.body.span().lo());
|
||||
rewrite_match_body(
|
||||
context,
|
||||
&arm.body,
|
||||
&lhs_str,
|
||||
shape,
|
||||
guard_str.contains('\n'),
|
||||
arrow_span,
|
||||
is_last,
|
||||
)
|
||||
}
|
||||
|
||||
fn stmt_is_expr_mac(stmt: &ast::Stmt) -> bool {
|
||||
if let ast::StmtKind::Expr(expr) = &stmt.kind {
|
||||
if let ast::ExprKind::MacCall(_) = &expr.kind {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn block_can_be_flattened<'a>(
|
||||
context: &RewriteContext<'_>,
|
||||
expr: &'a ast::Expr,
|
||||
) -> Option<&'a ast::Block> {
|
||||
match expr.kind {
|
||||
ast::ExprKind::Block(ref block, _)
|
||||
if !is_unsafe_block(block)
|
||||
&& !context.inside_macro()
|
||||
&& is_simple_block(context, block, Some(&expr.attrs))
|
||||
&& !stmt_is_expr_mac(&block.stmts[0]) =>
|
||||
{
|
||||
Some(&*block)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// (extend, body)
|
||||
// @extend: true if the arm body can be put next to `=>`
|
||||
// @body: flattened body, if the body is block with a single expression
|
||||
fn flatten_arm_body<'a>(
|
||||
context: &'a RewriteContext<'_>,
|
||||
body: &'a ast::Expr,
|
||||
opt_shape: Option<Shape>,
|
||||
) -> (bool, &'a ast::Expr) {
|
||||
let can_extend =
|
||||
|expr| !context.config.force_multiline_blocks() && can_flatten_block_around_this(expr);
|
||||
|
||||
if let Some(ref block) = block_can_be_flattened(context, body) {
|
||||
if let ast::StmtKind::Expr(ref expr) = block.stmts[0].kind {
|
||||
if let ast::ExprKind::Block(..) = expr.kind {
|
||||
flatten_arm_body(context, expr, None)
|
||||
} else {
|
||||
let cond_becomes_muti_line = opt_shape
|
||||
.and_then(|shape| rewrite_cond(context, expr, shape))
|
||||
.map_or(false, |cond| cond.contains('\n'));
|
||||
if cond_becomes_muti_line {
|
||||
(false, &*body)
|
||||
} else {
|
||||
(can_extend(expr), &*expr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(false, &*body)
|
||||
}
|
||||
} else {
|
||||
(can_extend(body), &*body)
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_match_body(
|
||||
context: &RewriteContext<'_>,
|
||||
body: &ptr::P<ast::Expr>,
|
||||
pats_str: &str,
|
||||
shape: Shape,
|
||||
has_guard: bool,
|
||||
arrow_span: Span,
|
||||
is_last: bool,
|
||||
) -> Option<String> {
|
||||
let (extend, body) = flatten_arm_body(
|
||||
context,
|
||||
body,
|
||||
shape.offset_left(extra_offset(pats_str, shape) + 4),
|
||||
);
|
||||
let (is_block, is_empty_block) = if let ast::ExprKind::Block(ref block, _) = body.kind {
|
||||
(true, is_empty_block(context, block, Some(&body.attrs)))
|
||||
} else {
|
||||
(false, false)
|
||||
};
|
||||
|
||||
let comma = arm_comma(context.config, body, is_last);
|
||||
let alt_block_sep = &shape.indent.to_string_with_newline(context.config);
|
||||
|
||||
let combine_orig_body = |body_str: &str| {
|
||||
let block_sep = match context.config.control_brace_style() {
|
||||
ControlBraceStyle::AlwaysNextLine if is_block => alt_block_sep,
|
||||
_ => " ",
|
||||
};
|
||||
|
||||
Some(format!("{} =>{}{}{}", pats_str, block_sep, body_str, comma))
|
||||
};
|
||||
|
||||
let next_line_indent = if !is_block || is_empty_block {
|
||||
shape.indent.block_indent(context.config)
|
||||
} else {
|
||||
shape.indent
|
||||
};
|
||||
|
||||
let forbid_same_line =
|
||||
(has_guard && pats_str.contains('\n') && !is_empty_block) || !body.attrs.is_empty();
|
||||
|
||||
// Look for comments between `=>` and the start of the body.
|
||||
let arrow_comment = {
|
||||
let arrow_snippet = context.snippet(arrow_span).trim();
|
||||
// search for the arrow starting from the end of the snippet since there may be a match
|
||||
// expression within the guard
|
||||
let arrow_index = arrow_snippet.rfind("=>").unwrap();
|
||||
// 2 = `=>`
|
||||
let comment_str = arrow_snippet[arrow_index + 2..].trim();
|
||||
if comment_str.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
rewrite_comment(comment_str, false, shape, &context.config)?
|
||||
}
|
||||
};
|
||||
|
||||
let combine_next_line_body = |body_str: &str| {
|
||||
let nested_indent_str = next_line_indent.to_string_with_newline(context.config);
|
||||
|
||||
if is_block {
|
||||
let mut result = pats_str.to_owned();
|
||||
result.push_str(" =>");
|
||||
if !arrow_comment.is_empty() {
|
||||
result.push_str(&nested_indent_str);
|
||||
result.push_str(&arrow_comment);
|
||||
}
|
||||
result.push_str(&nested_indent_str);
|
||||
result.push_str(&body_str);
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
let indent_str = shape.indent.to_string_with_newline(context.config);
|
||||
let (body_prefix, body_suffix) =
|
||||
if context.config.match_arm_blocks() && !context.inside_macro() {
|
||||
let comma = if context.config.match_block_trailing_comma() {
|
||||
","
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let semicolon = if context.config.version() == Version::One {
|
||||
""
|
||||
} else {
|
||||
if semicolon_for_expr(context, body) {
|
||||
";"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
};
|
||||
("{", format!("{}{}}}{}", semicolon, indent_str, comma))
|
||||
} else {
|
||||
("", String::from(","))
|
||||
};
|
||||
|
||||
let block_sep = match context.config.control_brace_style() {
|
||||
ControlBraceStyle::AlwaysNextLine => format!("{}{}", alt_block_sep, body_prefix),
|
||||
_ if body_prefix.is_empty() => "".to_owned(),
|
||||
_ if forbid_same_line || !arrow_comment.is_empty() => {
|
||||
format!("{}{}", alt_block_sep, body_prefix)
|
||||
}
|
||||
_ => format!(" {}", body_prefix),
|
||||
} + &nested_indent_str;
|
||||
|
||||
let mut result = pats_str.to_owned();
|
||||
result.push_str(" =>");
|
||||
if !arrow_comment.is_empty() {
|
||||
result.push_str(&indent_str);
|
||||
result.push_str(&arrow_comment);
|
||||
}
|
||||
result.push_str(&block_sep);
|
||||
result.push_str(&body_str);
|
||||
result.push_str(&body_suffix);
|
||||
Some(result)
|
||||
};
|
||||
|
||||
// Let's try and get the arm body on the same line as the condition.
|
||||
// 4 = ` => `.len()
|
||||
let orig_body_shape = shape
|
||||
.offset_left(extra_offset(pats_str, shape) + 4)
|
||||
.and_then(|shape| shape.sub_width(comma.len()));
|
||||
let orig_body = if forbid_same_line || !arrow_comment.is_empty() {
|
||||
None
|
||||
} else if let Some(body_shape) = orig_body_shape {
|
||||
let rewrite = nop_block_collapse(
|
||||
format_expr(body, ExprType::Statement, context, body_shape),
|
||||
body_shape.width,
|
||||
);
|
||||
|
||||
match rewrite {
|
||||
Some(ref body_str)
|
||||
if is_block
|
||||
|| (!body_str.contains('\n')
|
||||
&& unicode_str_width(body_str) <= body_shape.width) =>
|
||||
{
|
||||
return combine_orig_body(body_str);
|
||||
}
|
||||
_ => rewrite,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let orig_budget = orig_body_shape.map_or(0, |shape| shape.width);
|
||||
|
||||
// Try putting body on the next line and see if it looks better.
|
||||
let next_line_body_shape = Shape::indented(next_line_indent, context.config);
|
||||
let next_line_body = nop_block_collapse(
|
||||
format_expr(body, ExprType::Statement, context, next_line_body_shape),
|
||||
next_line_body_shape.width,
|
||||
);
|
||||
match (orig_body, next_line_body) {
|
||||
(Some(ref orig_str), Some(ref next_line_str))
|
||||
if prefer_next_line(orig_str, next_line_str, RhsTactics::Default) =>
|
||||
{
|
||||
combine_next_line_body(next_line_str)
|
||||
}
|
||||
(Some(ref orig_str), _) if extend && first_line_width(orig_str) <= orig_budget => {
|
||||
combine_orig_body(orig_str)
|
||||
}
|
||||
(Some(ref orig_str), Some(ref next_line_str)) if orig_str.contains('\n') => {
|
||||
combine_next_line_body(next_line_str)
|
||||
}
|
||||
(None, Some(ref next_line_str)) => combine_next_line_body(next_line_str),
|
||||
(None, None) => None,
|
||||
(Some(ref orig_str), _) => combine_orig_body(orig_str),
|
||||
}
|
||||
}
|
||||
|
||||
// The `if ...` guard on a match arm.
|
||||
fn rewrite_guard(
|
||||
context: &RewriteContext<'_>,
|
||||
guard: &Option<ptr::P<ast::Expr>>,
|
||||
shape: Shape,
|
||||
// The amount of space used up on this line for the pattern in
|
||||
// the arm (excludes offset).
|
||||
pattern_width: usize,
|
||||
multiline_pattern: bool,
|
||||
) -> Option<String> {
|
||||
if let Some(ref guard) = *guard {
|
||||
// First try to fit the guard string on the same line as the pattern.
|
||||
// 4 = ` if `, 5 = ` => {`
|
||||
let cond_shape = shape
|
||||
.offset_left(pattern_width + 4)
|
||||
.and_then(|s| s.sub_width(5));
|
||||
if !multiline_pattern {
|
||||
if let Some(cond_shape) = cond_shape {
|
||||
if let Some(cond_str) = guard.rewrite(context, cond_shape) {
|
||||
if !cond_str.contains('\n') || pattern_width <= context.config.tab_spaces() {
|
||||
return Some(format!(" if {}", cond_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not enough space to put the guard after the pattern, try a newline.
|
||||
// 3 = `if `, 5 = ` => {`
|
||||
let cond_shape = Shape::indented(shape.indent.block_indent(context.config), context.config)
|
||||
.offset_left(3)
|
||||
.and_then(|s| s.sub_width(5));
|
||||
if let Some(cond_shape) = cond_shape {
|
||||
if let Some(cond_str) = guard.rewrite(context, cond_shape) {
|
||||
return Some(format!(
|
||||
"{}if {}",
|
||||
cond_shape.indent.to_string_with_newline(context.config),
|
||||
cond_str
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
} else {
|
||||
Some(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
fn nop_block_collapse(block_str: Option<String>, budget: usize) -> Option<String> {
|
||||
debug!("nop_block_collapse {:?} {}", block_str, budget);
|
||||
block_str.map(|block_str| {
|
||||
if block_str.starts_with('{')
|
||||
&& budget >= 2
|
||||
&& (block_str[1..].find(|c: char| !c.is_whitespace()).unwrap() == block_str.len() - 2)
|
||||
{
|
||||
String::from("{}")
|
||||
} else {
|
||||
block_str
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn can_flatten_block_around_this(body: &ast::Expr) -> bool {
|
||||
match body.kind {
|
||||
// We do not allow `if` to stay on the same line, since we could easily mistake
|
||||
// `pat => if cond { ... }` and `pat if cond => { ... }`.
|
||||
ast::ExprKind::If(..) => false,
|
||||
// We do not allow collapsing a block around expression with condition
|
||||
// to avoid it being cluttered with match arm.
|
||||
ast::ExprKind::ForLoop(..) | ast::ExprKind::While(..) => false,
|
||||
ast::ExprKind::Loop(..)
|
||||
| ast::ExprKind::Match(..)
|
||||
| ast::ExprKind::Block(..)
|
||||
| ast::ExprKind::Closure(..)
|
||||
| ast::ExprKind::Array(..)
|
||||
| ast::ExprKind::Call(..)
|
||||
| ast::ExprKind::MethodCall(..)
|
||||
| ast::ExprKind::MacCall(..)
|
||||
| ast::ExprKind::Struct(..)
|
||||
| ast::ExprKind::Tup(..) => true,
|
||||
ast::ExprKind::AddrOf(_, _, ref expr)
|
||||
| ast::ExprKind::Box(ref expr)
|
||||
| ast::ExprKind::Try(ref expr)
|
||||
| ast::ExprKind::Unary(_, ref expr)
|
||||
| ast::ExprKind::Index(ref expr, _)
|
||||
| ast::ExprKind::Cast(ref expr, _) => can_flatten_block_around_this(expr),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
361
src/tools/rustfmt/src/missed_spans.rs
Normal file
361
src/tools/rustfmt/src/missed_spans.rs
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
use rustc_span::{BytePos, Pos, Span};
|
||||
|
||||
use crate::comment::{is_last_comment_block, rewrite_comment, CodeCharKind, CommentCodeSlices};
|
||||
use crate::config::file_lines::FileLines;
|
||||
use crate::config::FileName;
|
||||
use crate::config::Version;
|
||||
use crate::coverage::transform_missing_snippet;
|
||||
use crate::shape::{Indent, Shape};
|
||||
use crate::source_map::LineRangeUtils;
|
||||
use crate::utils::{count_lf_crlf, count_newlines, last_line_width, mk_sp};
|
||||
use crate::visitor::FmtVisitor;
|
||||
|
||||
struct SnippetStatus {
|
||||
/// An offset to the current line from the beginning of the original snippet.
|
||||
line_start: usize,
|
||||
/// A length of trailing whitespaces on the current line.
|
||||
last_wspace: Option<usize>,
|
||||
/// The current line number.
|
||||
cur_line: usize,
|
||||
}
|
||||
|
||||
impl SnippetStatus {
|
||||
fn new(cur_line: usize) -> Self {
|
||||
SnippetStatus {
|
||||
line_start: 0,
|
||||
last_wspace: None,
|
||||
cur_line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FmtVisitor<'a> {
|
||||
fn output_at_start(&self) -> bool {
|
||||
self.buffer.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn format_missing(&mut self, end: BytePos) {
|
||||
// HACK(topecongiro): we use `format_missing()` to extract a missing comment between
|
||||
// a macro (or similar) and a trailing semicolon. Here we just try to avoid calling
|
||||
// `format_missing_inner` in the common case where there is no such comment.
|
||||
// This is a hack, ideally we should fix a possible bug in `format_missing_inner`
|
||||
// or refactor `visit_mac` and `rewrite_macro`, but this should suffice to fix the
|
||||
// issue (#2727).
|
||||
let missing_snippet = self.snippet(mk_sp(self.last_pos, end));
|
||||
if missing_snippet.trim() == ";" {
|
||||
self.push_str(";");
|
||||
self.last_pos = end;
|
||||
return;
|
||||
}
|
||||
self.format_missing_inner(end, |this, last_snippet, _| this.push_str(last_snippet))
|
||||
}
|
||||
|
||||
pub(crate) fn format_missing_with_indent(&mut self, end: BytePos) {
|
||||
let config = self.config;
|
||||
self.format_missing_inner(end, |this, last_snippet, snippet| {
|
||||
this.push_str(last_snippet.trim_end());
|
||||
if last_snippet == snippet && !this.output_at_start() {
|
||||
// No new lines in the snippet.
|
||||
this.push_str("\n");
|
||||
}
|
||||
let indent = this.block_indent.to_string(config);
|
||||
this.push_str(&indent);
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn format_missing_no_indent(&mut self, end: BytePos) {
|
||||
self.format_missing_inner(end, |this, last_snippet, _| {
|
||||
this.push_str(last_snippet.trim_end());
|
||||
})
|
||||
}
|
||||
|
||||
fn format_missing_inner<F: Fn(&mut FmtVisitor<'_>, &str, &str)>(
|
||||
&mut self,
|
||||
end: BytePos,
|
||||
process_last_snippet: F,
|
||||
) {
|
||||
let start = self.last_pos;
|
||||
|
||||
if start == end {
|
||||
// Do nothing if this is the beginning of the file.
|
||||
if !self.output_at_start() {
|
||||
process_last_snippet(self, "", "");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert!(
|
||||
start < end,
|
||||
"Request to format inverted span: {}",
|
||||
self.parse_sess.span_to_debug_info(mk_sp(start, end)),
|
||||
);
|
||||
|
||||
self.last_pos = end;
|
||||
let span = mk_sp(start, end);
|
||||
let snippet = self.snippet(span);
|
||||
|
||||
// Do nothing for spaces in the beginning of the file
|
||||
if start == BytePos(0) && end.0 as usize == snippet.len() && snippet.trim().is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if snippet.trim().is_empty() && !out_of_file_lines_range!(self, span) {
|
||||
// Keep vertical spaces within range.
|
||||
self.push_vertical_spaces(count_newlines(snippet));
|
||||
process_last_snippet(self, "", snippet);
|
||||
} else {
|
||||
self.write_snippet(span, &process_last_snippet);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_vertical_spaces(&mut self, mut newline_count: usize) {
|
||||
let offset = self.buffer.chars().rev().take_while(|c| *c == '\n').count();
|
||||
let newline_upper_bound = self.config.blank_lines_upper_bound() + 1;
|
||||
let newline_lower_bound = self.config.blank_lines_lower_bound() + 1;
|
||||
|
||||
if newline_count + offset > newline_upper_bound {
|
||||
if offset >= newline_upper_bound {
|
||||
newline_count = 0;
|
||||
} else {
|
||||
newline_count = newline_upper_bound - offset;
|
||||
}
|
||||
} else if newline_count + offset < newline_lower_bound {
|
||||
if offset >= newline_lower_bound {
|
||||
newline_count = 0;
|
||||
} else {
|
||||
newline_count = newline_lower_bound - offset;
|
||||
}
|
||||
}
|
||||
|
||||
let blank_lines = "\n".repeat(newline_count);
|
||||
self.push_str(&blank_lines);
|
||||
}
|
||||
|
||||
fn write_snippet<F>(&mut self, span: Span, process_last_snippet: F)
|
||||
where
|
||||
F: Fn(&mut FmtVisitor<'_>, &str, &str),
|
||||
{
|
||||
// Get a snippet from the file start to the span's hi without allocating.
|
||||
// We need it to determine what precedes the current comment. If the comment
|
||||
// follows code on the same line, we won't touch it.
|
||||
let big_span_lo = self.snippet_provider.start_pos();
|
||||
let big_snippet = self.snippet_provider.entire_snippet();
|
||||
let big_diff = (span.lo() - big_span_lo).to_usize();
|
||||
|
||||
let snippet = self.snippet(span);
|
||||
|
||||
debug!("write_snippet `{}`", snippet);
|
||||
|
||||
self.write_snippet_inner(big_snippet, snippet, big_diff, span, process_last_snippet);
|
||||
}
|
||||
|
||||
fn write_snippet_inner<F>(
|
||||
&mut self,
|
||||
big_snippet: &str,
|
||||
old_snippet: &str,
|
||||
big_diff: usize,
|
||||
span: Span,
|
||||
process_last_snippet: F,
|
||||
) where
|
||||
F: Fn(&mut FmtVisitor<'_>, &str, &str),
|
||||
{
|
||||
// Trim whitespace from the right hand side of each line.
|
||||
// Annoyingly, the library functions for splitting by lines etc. are not
|
||||
// quite right, so we must do it ourselves.
|
||||
let line = self.parse_sess.line_of_byte_pos(span.lo());
|
||||
let file_name = &self.parse_sess.span_to_filename(span);
|
||||
let mut status = SnippetStatus::new(line);
|
||||
|
||||
let snippet = &*transform_missing_snippet(self.config, old_snippet);
|
||||
|
||||
let slice_within_file_lines_range =
|
||||
|file_lines: FileLines, cur_line, s| -> (usize, usize, bool) {
|
||||
let (lf_count, crlf_count) = count_lf_crlf(s);
|
||||
let newline_count = lf_count + crlf_count;
|
||||
let within_file_lines_range = file_lines.contains_range(
|
||||
file_name,
|
||||
cur_line,
|
||||
// if a newline character is at the end of the slice, then the number of
|
||||
// newlines needs to be decreased by 1 so that the range checked against
|
||||
// the file_lines is the visual range one would expect.
|
||||
cur_line + newline_count - if s.ends_with('\n') { 1 } else { 0 },
|
||||
);
|
||||
(lf_count, crlf_count, within_file_lines_range)
|
||||
};
|
||||
for (kind, offset, subslice) in CommentCodeSlices::new(snippet) {
|
||||
debug!("{:?}: {:?}", kind, subslice);
|
||||
|
||||
let (lf_count, crlf_count, within_file_lines_range) =
|
||||
slice_within_file_lines_range(self.config.file_lines(), status.cur_line, subslice);
|
||||
let newline_count = lf_count + crlf_count;
|
||||
if CodeCharKind::Comment == kind && within_file_lines_range {
|
||||
// 1: comment.
|
||||
self.process_comment(
|
||||
&mut status,
|
||||
snippet,
|
||||
&big_snippet[..(offset + big_diff)],
|
||||
offset,
|
||||
subslice,
|
||||
);
|
||||
} else if subslice.trim().is_empty() && newline_count > 0 && within_file_lines_range {
|
||||
// 2: blank lines.
|
||||
self.push_vertical_spaces(newline_count);
|
||||
status.cur_line += newline_count;
|
||||
status.line_start = offset + lf_count + crlf_count * 2;
|
||||
} else {
|
||||
// 3: code which we failed to format or which is not within file-lines range.
|
||||
self.process_missing_code(&mut status, snippet, subslice, offset, file_name);
|
||||
}
|
||||
}
|
||||
|
||||
let last_snippet = &snippet[status.line_start..];
|
||||
let (_, _, within_file_lines_range) =
|
||||
slice_within_file_lines_range(self.config.file_lines(), status.cur_line, last_snippet);
|
||||
if within_file_lines_range {
|
||||
process_last_snippet(self, last_snippet, snippet);
|
||||
} else {
|
||||
// just append what's left
|
||||
self.push_str(last_snippet);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_comment(
|
||||
&mut self,
|
||||
status: &mut SnippetStatus,
|
||||
snippet: &str,
|
||||
big_snippet: &str,
|
||||
offset: usize,
|
||||
subslice: &str,
|
||||
) {
|
||||
let last_char = big_snippet
|
||||
.chars()
|
||||
.rev()
|
||||
.skip_while(|rev_c| [' ', '\t'].contains(rev_c))
|
||||
.next();
|
||||
|
||||
let fix_indent = last_char.map_or(true, |rev_c| ['{', '\n'].contains(&rev_c));
|
||||
let mut on_same_line = false;
|
||||
|
||||
let comment_indent = if fix_indent {
|
||||
if let Some('{') = last_char {
|
||||
self.push_str("\n");
|
||||
}
|
||||
let indent_str = self.block_indent.to_string(self.config);
|
||||
self.push_str(&indent_str);
|
||||
self.block_indent
|
||||
} else if self.config.version() == Version::Two && !snippet.starts_with('\n') {
|
||||
// The comment appears on the same line as the previous formatted code.
|
||||
// Assuming that comment is logically associated with that code, we want to keep it on
|
||||
// the same level and avoid mixing it with possible other comment.
|
||||
on_same_line = true;
|
||||
self.push_str(" ");
|
||||
self.block_indent
|
||||
} else {
|
||||
self.push_str(" ");
|
||||
Indent::from_width(self.config, last_line_width(&self.buffer))
|
||||
};
|
||||
|
||||
let comment_width = ::std::cmp::min(
|
||||
self.config.comment_width(),
|
||||
self.config.max_width() - self.block_indent.width(),
|
||||
);
|
||||
let comment_shape = Shape::legacy(comment_width, comment_indent);
|
||||
|
||||
if on_same_line {
|
||||
match subslice.find("\n") {
|
||||
None => {
|
||||
self.push_str(subslice);
|
||||
}
|
||||
Some(offset) if offset + 1 == subslice.len() => {
|
||||
self.push_str(&subslice[..offset]);
|
||||
}
|
||||
Some(offset) => {
|
||||
// keep first line as is: if it were too long and wrapped, it may get mixed
|
||||
// with the other lines.
|
||||
let first_line = &subslice[..offset];
|
||||
self.push_str(first_line);
|
||||
self.push_str(&comment_indent.to_string_with_newline(self.config));
|
||||
|
||||
let other_lines = &subslice[offset + 1..];
|
||||
let comment_str =
|
||||
rewrite_comment(other_lines, false, comment_shape, self.config)
|
||||
.unwrap_or_else(|| String::from(other_lines));
|
||||
self.push_str(&comment_str);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let comment_str = rewrite_comment(subslice, false, comment_shape, self.config)
|
||||
.unwrap_or_else(|| String::from(subslice));
|
||||
self.push_str(&comment_str);
|
||||
}
|
||||
|
||||
status.last_wspace = None;
|
||||
status.line_start = offset + subslice.len();
|
||||
|
||||
// Add a newline:
|
||||
// - if there isn't one already
|
||||
// - otherwise, only if the last line is a line comment
|
||||
if status.line_start <= snippet.len() {
|
||||
match snippet[status.line_start..]
|
||||
.chars()
|
||||
// skip trailing whitespaces
|
||||
.skip_while(|c| *c == ' ' || *c == '\t')
|
||||
.next()
|
||||
{
|
||||
Some('\n') | Some('\r') => {
|
||||
if !is_last_comment_block(subslice) {
|
||||
self.push_str("\n");
|
||||
}
|
||||
}
|
||||
_ => self.push_str("\n"),
|
||||
}
|
||||
}
|
||||
|
||||
status.cur_line += count_newlines(subslice);
|
||||
}
|
||||
|
||||
fn process_missing_code(
|
||||
&mut self,
|
||||
status: &mut SnippetStatus,
|
||||
snippet: &str,
|
||||
subslice: &str,
|
||||
offset: usize,
|
||||
file_name: &FileName,
|
||||
) {
|
||||
for (mut i, c) in subslice.char_indices() {
|
||||
i += offset;
|
||||
|
||||
if c == '\n' {
|
||||
let skip_this_line = !self
|
||||
.config
|
||||
.file_lines()
|
||||
.contains_line(file_name, status.cur_line);
|
||||
if skip_this_line {
|
||||
status.last_wspace = None;
|
||||
}
|
||||
|
||||
if let Some(lw) = status.last_wspace {
|
||||
self.push_str(&snippet[status.line_start..lw]);
|
||||
self.push_str("\n");
|
||||
status.last_wspace = None;
|
||||
} else {
|
||||
self.push_str(&snippet[status.line_start..=i]);
|
||||
}
|
||||
|
||||
status.cur_line += 1;
|
||||
status.line_start = i + 1;
|
||||
} else if c.is_whitespace() && status.last_wspace.is_none() {
|
||||
status.last_wspace = Some(i);
|
||||
} else {
|
||||
status.last_wspace = None;
|
||||
}
|
||||
}
|
||||
|
||||
let remaining = snippet[status.line_start..subslice.len() + offset].trim();
|
||||
if !remaining.is_empty() {
|
||||
self.push_str(&self.block_indent.to_string(self.config));
|
||||
self.push_str(remaining);
|
||||
status.line_start = subslice.len() + offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
552
src/tools/rustfmt/src/modules.rs
Normal file
552
src/tools/rustfmt/src/modules.rs
Normal file
|
|
@ -0,0 +1,552 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rustc_ast::ast;
|
||||
use rustc_ast::visit::Visitor;
|
||||
use rustc_ast::AstLike;
|
||||
use rustc_span::symbol::{self, sym, Symbol};
|
||||
use rustc_span::Span;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::attr::MetaVisitor;
|
||||
use crate::config::FileName;
|
||||
use crate::items::is_mod_decl;
|
||||
use crate::syntux::parser::{
|
||||
Directory, DirectoryOwnership, ModError, ModulePathSuccess, Parser, ParserError,
|
||||
};
|
||||
use crate::syntux::session::ParseSess;
|
||||
use crate::utils::contains_skip;
|
||||
|
||||
mod visitor;
|
||||
|
||||
type FileModMap<'ast> = BTreeMap<FileName, Module<'ast>>;
|
||||
|
||||
/// Represents module with its inner attributes.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Module<'a> {
|
||||
ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
|
||||
pub(crate) items: Cow<'a, Vec<rustc_ast::ptr::P<ast::Item>>>,
|
||||
attrs: Cow<'a, Vec<ast::Attribute>>,
|
||||
inner_attr: Vec<ast::Attribute>,
|
||||
pub(crate) span: Span,
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
pub(crate) fn new(
|
||||
mod_span: Span,
|
||||
ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
|
||||
mod_items: Cow<'a, Vec<rustc_ast::ptr::P<ast::Item>>>,
|
||||
mod_attrs: Cow<'a, Vec<ast::Attribute>>,
|
||||
) -> Self {
|
||||
let inner_attr = mod_attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.style == ast::AttrStyle::Inner)
|
||||
.cloned()
|
||||
.collect();
|
||||
Module {
|
||||
items: mod_items,
|
||||
attrs: mod_attrs,
|
||||
inner_attr,
|
||||
span: mod_span,
|
||||
ast_mod_kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AstLike for Module<'a> {
|
||||
const SUPPORTS_CUSTOM_INNER_ATTRS: bool = true;
|
||||
fn attrs(&self) -> &[ast::Attribute] {
|
||||
&self.inner_attr
|
||||
}
|
||||
fn visit_attrs(&mut self, f: impl FnOnce(&mut Vec<ast::Attribute>)) {
|
||||
f(&mut self.inner_attr)
|
||||
}
|
||||
fn tokens_mut(&mut self) -> Option<&mut Option<rustc_ast::tokenstream::LazyTokenStream>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps each module to the corresponding file.
|
||||
pub(crate) struct ModResolver<'ast, 'sess> {
|
||||
parse_sess: &'sess ParseSess,
|
||||
directory: Directory,
|
||||
file_map: FileModMap<'ast>,
|
||||
recursive: bool,
|
||||
}
|
||||
|
||||
/// Represents errors while trying to resolve modules.
|
||||
#[derive(Debug, Error)]
|
||||
#[error("failed to resolve mod `{module}`: {kind}")]
|
||||
pub struct ModuleResolutionError {
|
||||
pub(crate) module: String,
|
||||
pub(crate) kind: ModuleResolutionErrorKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum ModuleResolutionErrorKind {
|
||||
/// Find a file that cannot be parsed.
|
||||
#[error("cannot parse {file}")]
|
||||
ParseError { file: PathBuf },
|
||||
/// File cannot be found.
|
||||
#[error("{file} does not exist")]
|
||||
NotFound { file: PathBuf },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum SubModKind<'a, 'ast> {
|
||||
/// `mod foo;`
|
||||
External(PathBuf, DirectoryOwnership, Module<'ast>),
|
||||
/// `mod foo;` with multiple sources.
|
||||
MultiExternal(Vec<(PathBuf, DirectoryOwnership, Module<'ast>)>),
|
||||
/// `mod foo {}`
|
||||
Internal(&'a ast::Item),
|
||||
}
|
||||
|
||||
impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
|
||||
/// Creates a new `ModResolver`.
|
||||
pub(crate) fn new(
|
||||
parse_sess: &'sess ParseSess,
|
||||
directory_ownership: DirectoryOwnership,
|
||||
recursive: bool,
|
||||
) -> Self {
|
||||
ModResolver {
|
||||
directory: Directory {
|
||||
path: PathBuf::new(),
|
||||
ownership: directory_ownership,
|
||||
},
|
||||
file_map: BTreeMap::new(),
|
||||
parse_sess,
|
||||
recursive,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a map that maps a file name to the module in AST.
|
||||
pub(crate) fn visit_crate(
|
||||
mut self,
|
||||
krate: &'ast ast::Crate,
|
||||
) -> Result<FileModMap<'ast>, ModuleResolutionError> {
|
||||
let root_filename = self.parse_sess.span_to_filename(krate.span);
|
||||
self.directory.path = match root_filename {
|
||||
FileName::Real(ref p) => p.parent().unwrap_or(Path::new("")).to_path_buf(),
|
||||
_ => PathBuf::new(),
|
||||
};
|
||||
|
||||
// Skip visiting sub modules when the input is from stdin.
|
||||
if self.recursive {
|
||||
self.visit_mod_from_ast(&krate.items)?;
|
||||
}
|
||||
|
||||
self.file_map.insert(
|
||||
root_filename,
|
||||
Module::new(
|
||||
krate.span,
|
||||
None,
|
||||
Cow::Borrowed(&krate.items),
|
||||
Cow::Borrowed(&krate.attrs),
|
||||
),
|
||||
);
|
||||
Ok(self.file_map)
|
||||
}
|
||||
|
||||
/// Visit `cfg_if` macro and look for module declarations.
|
||||
fn visit_cfg_if(&mut self, item: Cow<'ast, ast::Item>) -> Result<(), ModuleResolutionError> {
|
||||
let mut visitor = visitor::CfgIfVisitor::new(self.parse_sess);
|
||||
visitor.visit_item(&item);
|
||||
for module_item in visitor.mods() {
|
||||
if let ast::ItemKind::Mod(_, ref sub_mod_kind) = module_item.item.kind {
|
||||
self.visit_sub_mod(
|
||||
&module_item.item,
|
||||
Module::new(
|
||||
module_item.item.span,
|
||||
Some(Cow::Owned(sub_mod_kind.clone())),
|
||||
Cow::Owned(vec![]),
|
||||
Cow::Owned(vec![]),
|
||||
),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Visit modules defined inside macro calls.
|
||||
fn visit_mod_outside_ast(
|
||||
&mut self,
|
||||
items: Vec<rustc_ast::ptr::P<ast::Item>>,
|
||||
) -> Result<(), ModuleResolutionError> {
|
||||
for item in items {
|
||||
if is_cfg_if(&item) {
|
||||
self.visit_cfg_if(Cow::Owned(item.into_inner()))?;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
|
||||
let span = item.span;
|
||||
self.visit_sub_mod(
|
||||
&item,
|
||||
Module::new(
|
||||
span,
|
||||
Some(Cow::Owned(sub_mod_kind.clone())),
|
||||
Cow::Owned(vec![]),
|
||||
Cow::Owned(vec![]),
|
||||
),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Visit modules from AST.
|
||||
fn visit_mod_from_ast(
|
||||
&mut self,
|
||||
items: &'ast Vec<rustc_ast::ptr::P<ast::Item>>,
|
||||
) -> Result<(), ModuleResolutionError> {
|
||||
for item in items {
|
||||
if is_cfg_if(item) {
|
||||
self.visit_cfg_if(Cow::Borrowed(item))?;
|
||||
}
|
||||
|
||||
if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
|
||||
let span = item.span;
|
||||
self.visit_sub_mod(
|
||||
item,
|
||||
Module::new(
|
||||
span,
|
||||
Some(Cow::Borrowed(sub_mod_kind)),
|
||||
Cow::Owned(vec![]),
|
||||
Cow::Borrowed(&item.attrs),
|
||||
),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_sub_mod(
|
||||
&mut self,
|
||||
item: &'c ast::Item,
|
||||
sub_mod: Module<'ast>,
|
||||
) -> Result<(), ModuleResolutionError> {
|
||||
let old_directory = self.directory.clone();
|
||||
let sub_mod_kind = self.peek_sub_mod(item, &sub_mod)?;
|
||||
if let Some(sub_mod_kind) = sub_mod_kind {
|
||||
self.insert_sub_mod(sub_mod_kind.clone())?;
|
||||
self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?;
|
||||
}
|
||||
self.directory = old_directory;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inspect the given sub-module which we are about to visit and returns its kind.
|
||||
fn peek_sub_mod(
|
||||
&self,
|
||||
item: &'c ast::Item,
|
||||
sub_mod: &Module<'ast>,
|
||||
) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
|
||||
if contains_skip(&item.attrs) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if is_mod_decl(item) {
|
||||
// mod foo;
|
||||
// Look for an extern file.
|
||||
self.find_external_module(item.ident, &item.attrs, sub_mod)
|
||||
} else {
|
||||
// An internal module (`mod foo { /* ... */ }`);
|
||||
Ok(Some(SubModKind::Internal(item)))
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_sub_mod(
|
||||
&mut self,
|
||||
sub_mod_kind: SubModKind<'c, 'ast>,
|
||||
) -> Result<(), ModuleResolutionError> {
|
||||
match sub_mod_kind {
|
||||
SubModKind::External(mod_path, _, sub_mod) => {
|
||||
self.file_map
|
||||
.entry(FileName::Real(mod_path))
|
||||
.or_insert(sub_mod);
|
||||
}
|
||||
SubModKind::MultiExternal(mods) => {
|
||||
for (mod_path, _, sub_mod) in mods {
|
||||
self.file_map
|
||||
.entry(FileName::Real(mod_path))
|
||||
.or_insert(sub_mod);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_sub_mod_inner(
|
||||
&mut self,
|
||||
sub_mod: Module<'ast>,
|
||||
sub_mod_kind: SubModKind<'c, 'ast>,
|
||||
) -> Result<(), ModuleResolutionError> {
|
||||
match sub_mod_kind {
|
||||
SubModKind::External(mod_path, directory_ownership, sub_mod) => {
|
||||
let directory = Directory {
|
||||
path: mod_path.parent().unwrap().to_path_buf(),
|
||||
ownership: directory_ownership,
|
||||
};
|
||||
self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))
|
||||
}
|
||||
SubModKind::Internal(ref item) => {
|
||||
self.push_inline_mod_directory(item.ident, &item.attrs);
|
||||
self.visit_sub_mod_after_directory_update(sub_mod, None)
|
||||
}
|
||||
SubModKind::MultiExternal(mods) => {
|
||||
for (mod_path, directory_ownership, sub_mod) in mods {
|
||||
let directory = Directory {
|
||||
path: mod_path.parent().unwrap().to_path_buf(),
|
||||
ownership: directory_ownership,
|
||||
};
|
||||
self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_sub_mod_after_directory_update(
|
||||
&mut self,
|
||||
sub_mod: Module<'ast>,
|
||||
directory: Option<Directory>,
|
||||
) -> Result<(), ModuleResolutionError> {
|
||||
if let Some(directory) = directory {
|
||||
self.directory = directory;
|
||||
}
|
||||
match (sub_mod.ast_mod_kind, sub_mod.items) {
|
||||
(Some(Cow::Borrowed(ast::ModKind::Loaded(items, ast::Inline::No, _))), _) => {
|
||||
self.visit_mod_from_ast(&items)
|
||||
}
|
||||
(Some(Cow::Owned(..)), Cow::Owned(items)) => self.visit_mod_outside_ast(items),
|
||||
(_, _) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Find a file path in the filesystem which corresponds to the given module.
|
||||
fn find_external_module(
|
||||
&self,
|
||||
mod_name: symbol::Ident,
|
||||
attrs: &[ast::Attribute],
|
||||
sub_mod: &Module<'ast>,
|
||||
) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
|
||||
let relative = match self.directory.ownership {
|
||||
DirectoryOwnership::Owned { relative } => relative,
|
||||
DirectoryOwnership::UnownedViaBlock => None,
|
||||
};
|
||||
if let Some(path) = Parser::submod_path_from_attr(attrs, &self.directory.path) {
|
||||
if self.parse_sess.is_file_parsed(&path) {
|
||||
return Ok(None);
|
||||
}
|
||||
return match Parser::parse_file_as_module(self.parse_sess, &path, sub_mod.span) {
|
||||
Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
|
||||
Ok((attrs, items, span)) => Ok(Some(SubModKind::External(
|
||||
path,
|
||||
DirectoryOwnership::Owned { relative: None },
|
||||
Module::new(
|
||||
span,
|
||||
Some(Cow::Owned(ast::ModKind::Unloaded)),
|
||||
Cow::Owned(items),
|
||||
Cow::Owned(attrs),
|
||||
),
|
||||
))),
|
||||
Err(ParserError::ParseError) => Err(ModuleResolutionError {
|
||||
module: mod_name.to_string(),
|
||||
kind: ModuleResolutionErrorKind::ParseError { file: path },
|
||||
}),
|
||||
Err(..) => Err(ModuleResolutionError {
|
||||
module: mod_name.to_string(),
|
||||
kind: ModuleResolutionErrorKind::NotFound { file: path },
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// Look for nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`.
|
||||
let mut mods_outside_ast = self.find_mods_outside_of_ast(attrs, sub_mod);
|
||||
|
||||
match self
|
||||
.parse_sess
|
||||
.default_submod_path(mod_name, relative, &self.directory.path)
|
||||
{
|
||||
Ok(ModulePathSuccess {
|
||||
file_path,
|
||||
dir_ownership,
|
||||
..
|
||||
}) => {
|
||||
let outside_mods_empty = mods_outside_ast.is_empty();
|
||||
let should_insert = !mods_outside_ast
|
||||
.iter()
|
||||
.any(|(outside_path, _, _)| outside_path == &file_path);
|
||||
if self.parse_sess.is_file_parsed(&file_path) {
|
||||
if outside_mods_empty {
|
||||
return Ok(None);
|
||||
} else {
|
||||
if should_insert {
|
||||
mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
|
||||
}
|
||||
return Ok(Some(SubModKind::MultiExternal(mods_outside_ast)));
|
||||
}
|
||||
}
|
||||
match Parser::parse_file_as_module(self.parse_sess, &file_path, sub_mod.span) {
|
||||
Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
|
||||
Ok((attrs, items, span)) if outside_mods_empty => {
|
||||
Ok(Some(SubModKind::External(
|
||||
file_path,
|
||||
dir_ownership,
|
||||
Module::new(
|
||||
span,
|
||||
Some(Cow::Owned(ast::ModKind::Unloaded)),
|
||||
Cow::Owned(items),
|
||||
Cow::Owned(attrs),
|
||||
),
|
||||
)))
|
||||
}
|
||||
Ok((attrs, items, span)) => {
|
||||
mods_outside_ast.push((
|
||||
file_path.clone(),
|
||||
dir_ownership,
|
||||
Module::new(
|
||||
span,
|
||||
Some(Cow::Owned(ast::ModKind::Unloaded)),
|
||||
Cow::Owned(items),
|
||||
Cow::Owned(attrs),
|
||||
),
|
||||
));
|
||||
if should_insert {
|
||||
mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
|
||||
}
|
||||
Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
|
||||
}
|
||||
Err(ParserError::ParseError) => Err(ModuleResolutionError {
|
||||
module: mod_name.to_string(),
|
||||
kind: ModuleResolutionErrorKind::ParseError { file: file_path },
|
||||
}),
|
||||
Err(..) if outside_mods_empty => Err(ModuleResolutionError {
|
||||
module: mod_name.to_string(),
|
||||
kind: ModuleResolutionErrorKind::NotFound { file: file_path },
|
||||
}),
|
||||
Err(..) => {
|
||||
if should_insert {
|
||||
mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
|
||||
}
|
||||
Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(mod_err) if !mods_outside_ast.is_empty() => {
|
||||
if let ModError::ParserError(mut e) = mod_err {
|
||||
e.cancel();
|
||||
}
|
||||
Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
|
||||
}
|
||||
Err(_) => Err(ModuleResolutionError {
|
||||
module: mod_name.to_string(),
|
||||
kind: ModuleResolutionErrorKind::NotFound {
|
||||
file: self.directory.path.clone(),
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_inline_mod_directory(&mut self, id: symbol::Ident, attrs: &[ast::Attribute]) {
|
||||
if let Some(path) = find_path_value(attrs) {
|
||||
self.directory.path.push(&*path.as_str());
|
||||
self.directory.ownership = DirectoryOwnership::Owned { relative: None };
|
||||
} else {
|
||||
// We have to push on the current module name in the case of relative
|
||||
// paths in order to ensure that any additional module paths from inline
|
||||
// `mod x { ... }` come after the relative extension.
|
||||
//
|
||||
// For example, a `mod z { ... }` inside `x/y.rs` should set the current
|
||||
// directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
|
||||
if let DirectoryOwnership::Owned { relative } = &mut self.directory.ownership {
|
||||
if let Some(ident) = relative.take() {
|
||||
// remove the relative offset
|
||||
self.directory.path.push(&*ident.as_str());
|
||||
}
|
||||
}
|
||||
self.directory.path.push(&*id.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
fn find_mods_outside_of_ast(
|
||||
&self,
|
||||
attrs: &[ast::Attribute],
|
||||
sub_mod: &Module<'ast>,
|
||||
) -> Vec<(PathBuf, DirectoryOwnership, Module<'ast>)> {
|
||||
// Filter nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`.
|
||||
let mut path_visitor = visitor::PathVisitor::default();
|
||||
for attr in attrs.iter() {
|
||||
if let Some(meta) = attr.meta() {
|
||||
path_visitor.visit_meta_item(&meta)
|
||||
}
|
||||
}
|
||||
let mut result = vec![];
|
||||
for path in path_visitor.paths() {
|
||||
let mut actual_path = self.directory.path.clone();
|
||||
actual_path.push(&path);
|
||||
if !actual_path.exists() {
|
||||
continue;
|
||||
}
|
||||
if self.parse_sess.is_file_parsed(&actual_path) {
|
||||
// If the specified file is already parsed, then we just use that.
|
||||
result.push((
|
||||
actual_path,
|
||||
DirectoryOwnership::Owned { relative: None },
|
||||
sub_mod.clone(),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
let (attrs, items, span) =
|
||||
match Parser::parse_file_as_module(self.parse_sess, &actual_path, sub_mod.span) {
|
||||
Ok((ref attrs, _, _)) if contains_skip(attrs) => continue,
|
||||
Ok(m) => m,
|
||||
Err(..) => continue,
|
||||
};
|
||||
|
||||
result.push((
|
||||
actual_path,
|
||||
DirectoryOwnership::Owned { relative: None },
|
||||
Module::new(
|
||||
span,
|
||||
Some(Cow::Owned(ast::ModKind::Unloaded)),
|
||||
Cow::Owned(items),
|
||||
Cow::Owned(attrs),
|
||||
),
|
||||
))
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn path_value(attr: &ast::Attribute) -> Option<Symbol> {
|
||||
if attr.has_name(sym::path) {
|
||||
attr.value_str()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// N.B., even when there are multiple `#[path = ...]` attributes, we just need to
|
||||
// examine the first one, since rustc ignores the second and the subsequent ones
|
||||
// as unused attributes.
|
||||
fn find_path_value(attrs: &[ast::Attribute]) -> Option<Symbol> {
|
||||
attrs.iter().flat_map(path_value).next()
|
||||
}
|
||||
|
||||
fn is_cfg_if(item: &ast::Item) -> bool {
|
||||
match item.kind {
|
||||
ast::ItemKind::MacCall(ref mac) => {
|
||||
if let Some(first_segment) = mac.path.segments.first() {
|
||||
if first_segment.ident.name == Symbol::intern("cfg_if") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
108
src/tools/rustfmt/src/modules/visitor.rs
Normal file
108
src/tools/rustfmt/src/modules/visitor.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use rustc_ast::ast;
|
||||
use rustc_ast::visit::Visitor;
|
||||
use rustc_span::Symbol;
|
||||
|
||||
use crate::attr::MetaVisitor;
|
||||
use crate::syntux::parser::Parser;
|
||||
use crate::syntux::session::ParseSess;
|
||||
|
||||
pub(crate) struct ModItem {
|
||||
pub(crate) item: ast::Item,
|
||||
}
|
||||
|
||||
/// Traverse `cfg_if!` macro and fetch modules.
|
||||
pub(crate) struct CfgIfVisitor<'a> {
|
||||
parse_sess: &'a ParseSess,
|
||||
mods: Vec<ModItem>,
|
||||
}
|
||||
|
||||
impl<'a> CfgIfVisitor<'a> {
|
||||
pub(crate) fn new(parse_sess: &'a ParseSess) -> CfgIfVisitor<'a> {
|
||||
CfgIfVisitor {
|
||||
mods: vec![],
|
||||
parse_sess,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mods(self) -> Vec<ModItem> {
|
||||
self.mods
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'ast: 'a> Visitor<'ast> for CfgIfVisitor<'a> {
|
||||
fn visit_mac_call(&mut self, mac: &'ast ast::MacCall) {
|
||||
match self.visit_mac_inner(mac) {
|
||||
Ok(()) => (),
|
||||
Err(e) => debug!("{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'ast: 'a> CfgIfVisitor<'a> {
|
||||
fn visit_mac_inner(&mut self, mac: &'ast ast::MacCall) -> Result<(), &'static str> {
|
||||
// Support both:
|
||||
// ```
|
||||
// extern crate cfg_if;
|
||||
// cfg_if::cfg_if! {..}
|
||||
// ```
|
||||
// And:
|
||||
// ```
|
||||
// #[macro_use]
|
||||
// extern crate cfg_if;
|
||||
// cfg_if! {..}
|
||||
// ```
|
||||
match mac.path.segments.first() {
|
||||
Some(first_segment) => {
|
||||
if first_segment.ident.name != Symbol::intern("cfg_if") {
|
||||
return Err("Expected cfg_if");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err("Expected cfg_if");
|
||||
}
|
||||
};
|
||||
|
||||
let items = Parser::parse_cfg_if(self.parse_sess, mac)?;
|
||||
self.mods
|
||||
.append(&mut items.into_iter().map(|item| ModItem { item }).collect());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts `path = "foo.rs"` from attributes.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PathVisitor {
|
||||
/// A list of path defined in attributes.
|
||||
paths: Vec<String>,
|
||||
}
|
||||
|
||||
impl PathVisitor {
|
||||
pub(crate) fn paths(self) -> Vec<String> {
|
||||
self.paths
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> MetaVisitor<'ast> for PathVisitor {
|
||||
fn visit_meta_name_value(&mut self, meta_item: &'ast ast::MetaItem, lit: &'ast ast::Lit) {
|
||||
if meta_item.has_name(Symbol::intern("path")) && lit.kind.is_str() {
|
||||
self.paths.push(lit_to_str(lit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn lit_to_str(lit: &ast::Lit) -> String {
|
||||
match lit.kind {
|
||||
ast::LitKind::Str(symbol, ..) => symbol.to_string(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn lit_to_str(lit: &ast::Lit) -> String {
|
||||
match lit.kind {
|
||||
ast::LitKind::Str(symbol, ..) => symbol.as_str().replace("/", "\\"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
786
src/tools/rustfmt/src/overflow.rs
Normal file
786
src/tools/rustfmt/src/overflow.rs
Normal file
|
|
@ -0,0 +1,786 @@
|
|||
//! Rewrite a list some items with overflow.
|
||||
|
||||
use std::cmp::min;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::token::DelimToken;
|
||||
use rustc_ast::{ast, ptr};
|
||||
use rustc_span::Span;
|
||||
|
||||
use crate::closures;
|
||||
use crate::config::lists::*;
|
||||
use crate::config::Version;
|
||||
use crate::expr::{
|
||||
can_be_overflowed_expr, is_every_expr_simple, is_method_call, is_nested_call, is_simple_expr,
|
||||
rewrite_cond,
|
||||
};
|
||||
use crate::lists::{
|
||||
definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator,
|
||||
};
|
||||
use crate::macros::MacroArg;
|
||||
use crate::patterns::{can_be_overflowed_pat, TuplePatField};
|
||||
use crate::rewrite::{Rewrite, RewriteContext};
|
||||
use crate::shape::Shape;
|
||||
use crate::source_map::SpanUtils;
|
||||
use crate::spanned::Spanned;
|
||||
use crate::types::{can_be_overflowed_type, SegmentParam};
|
||||
use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp};
|
||||
|
||||
const SHORT_ITEM_THRESHOLD: usize = 10;
|
||||
|
||||
/// A list of `format!`-like macros, that take a long format string and a list of arguments to
|
||||
/// format.
|
||||
///
|
||||
/// Organized as a list of `(&str, usize)` tuples, giving the name of the macro and the number of
|
||||
/// arguments before the format string (none for `format!("format", ...)`, one for `assert!(result,
|
||||
/// "format", ...)`, two for `assert_eq!(left, right, "format", ...)`).
|
||||
const SPECIAL_MACRO_WHITELIST: &[(&str, usize)] = &[
|
||||
// format! like macros
|
||||
// From the Rust Standard Library.
|
||||
("eprint!", 0),
|
||||
("eprintln!", 0),
|
||||
("format!", 0),
|
||||
("format_args!", 0),
|
||||
("print!", 0),
|
||||
("println!", 0),
|
||||
("panic!", 0),
|
||||
("unreachable!", 0),
|
||||
// From the `log` crate.
|
||||
("debug!", 0),
|
||||
("error!", 0),
|
||||
("info!", 0),
|
||||
("warn!", 0),
|
||||
// write! like macros
|
||||
("assert!", 1),
|
||||
("debug_assert!", 1),
|
||||
("write!", 1),
|
||||
("writeln!", 1),
|
||||
// assert_eq! like macros
|
||||
("assert_eq!", 2),
|
||||
("assert_ne!", 2),
|
||||
("debug_assert_eq!", 2),
|
||||
("debug_assert_ne!", 2),
|
||||
];
|
||||
|
||||
const SPECIAL_ATTR_WHITELIST: &[(&str, usize)] = &[
|
||||
// From the `failure` crate.
|
||||
("fail", 0),
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum OverflowableItem<'a> {
|
||||
Expr(&'a ast::Expr),
|
||||
GenericParam(&'a ast::GenericParam),
|
||||
MacroArg(&'a MacroArg),
|
||||
NestedMetaItem(&'a ast::NestedMetaItem),
|
||||
SegmentParam(&'a SegmentParam<'a>),
|
||||
FieldDef(&'a ast::FieldDef),
|
||||
TuplePatField(&'a TuplePatField<'a>),
|
||||
Ty(&'a ast::Ty),
|
||||
}
|
||||
|
||||
impl<'a> Rewrite for OverflowableItem<'a> {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
self.map(|item| item.rewrite(context, shape))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spanned for OverflowableItem<'a> {
|
||||
fn span(&self) -> Span {
|
||||
self.map(|item| item.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OverflowableItem<'a> {
|
||||
fn has_attrs(&self) -> bool {
|
||||
match self {
|
||||
OverflowableItem::Expr(ast::Expr { attrs, .. })
|
||||
| OverflowableItem::GenericParam(ast::GenericParam { attrs, .. }) => !attrs.is_empty(),
|
||||
OverflowableItem::FieldDef(ast::FieldDef { attrs, .. }) => !attrs.is_empty(),
|
||||
OverflowableItem::MacroArg(MacroArg::Expr(expr)) => !expr.attrs.is_empty(),
|
||||
OverflowableItem::MacroArg(MacroArg::Item(item)) => !item.attrs.is_empty(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map<F, T>(&self, f: F) -> T
|
||||
where
|
||||
F: Fn(&dyn IntoOverflowableItem<'a>) -> T,
|
||||
{
|
||||
match self {
|
||||
OverflowableItem::Expr(expr) => f(*expr),
|
||||
OverflowableItem::GenericParam(gp) => f(*gp),
|
||||
OverflowableItem::MacroArg(macro_arg) => f(*macro_arg),
|
||||
OverflowableItem::NestedMetaItem(nmi) => f(*nmi),
|
||||
OverflowableItem::SegmentParam(sp) => f(*sp),
|
||||
OverflowableItem::FieldDef(sf) => f(*sf),
|
||||
OverflowableItem::TuplePatField(pat) => f(*pat),
|
||||
OverflowableItem::Ty(ty) => f(*ty),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_simple(&self) -> bool {
|
||||
match self {
|
||||
OverflowableItem::Expr(expr) => is_simple_expr(expr),
|
||||
OverflowableItem::MacroArg(MacroArg::Keyword(..)) => true,
|
||||
OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_simple_expr(expr),
|
||||
OverflowableItem::NestedMetaItem(nested_meta_item) => match nested_meta_item {
|
||||
ast::NestedMetaItem::Literal(..) => true,
|
||||
ast::NestedMetaItem::MetaItem(ref meta_item) => match meta_item.kind {
|
||||
ast::MetaItemKind::Word => true,
|
||||
_ => false,
|
||||
},
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_expr(&self) -> bool {
|
||||
match self {
|
||||
OverflowableItem::Expr(..) => true,
|
||||
OverflowableItem::MacroArg(MacroArg::Expr(..)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_nested_call(&self) -> bool {
|
||||
match self {
|
||||
OverflowableItem::Expr(expr) => is_nested_call(expr),
|
||||
OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_nested_call(expr),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_expr(&self) -> Option<&'a ast::Expr> {
|
||||
match self {
|
||||
OverflowableItem::Expr(expr) => Some(expr),
|
||||
OverflowableItem::MacroArg(macro_arg) => match macro_arg {
|
||||
MacroArg::Expr(ref expr) => Some(expr),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn can_be_overflowed(&self, context: &RewriteContext<'_>, len: usize) -> bool {
|
||||
match self {
|
||||
OverflowableItem::Expr(expr) => can_be_overflowed_expr(context, expr, len),
|
||||
OverflowableItem::MacroArg(macro_arg) => match macro_arg {
|
||||
MacroArg::Expr(ref expr) => can_be_overflowed_expr(context, expr, len),
|
||||
MacroArg::Ty(ref ty) => can_be_overflowed_type(context, ty, len),
|
||||
MacroArg::Pat(..) => false,
|
||||
MacroArg::Item(..) => len == 1,
|
||||
MacroArg::Keyword(..) => false,
|
||||
},
|
||||
OverflowableItem::NestedMetaItem(nested_meta_item) if len == 1 => {
|
||||
match nested_meta_item {
|
||||
ast::NestedMetaItem::Literal(..) => false,
|
||||
ast::NestedMetaItem::MetaItem(..) => true,
|
||||
}
|
||||
}
|
||||
OverflowableItem::SegmentParam(seg) => match seg {
|
||||
SegmentParam::Type(ty) => can_be_overflowed_type(context, ty, len),
|
||||
_ => false,
|
||||
},
|
||||
OverflowableItem::TuplePatField(pat) => can_be_overflowed_pat(context, pat, len),
|
||||
OverflowableItem::Ty(ty) => can_be_overflowed_type(context, ty, len),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn whitelist(&self) -> &'static [(&'static str, usize)] {
|
||||
match self {
|
||||
OverflowableItem::MacroArg(..) => SPECIAL_MACRO_WHITELIST,
|
||||
OverflowableItem::NestedMetaItem(..) => SPECIAL_ATTR_WHITELIST,
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait IntoOverflowableItem<'a>: Rewrite + Spanned {
|
||||
fn into_overflowable_item(&'a self) -> OverflowableItem<'a>;
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + IntoOverflowableItem<'a>> IntoOverflowableItem<'a> for ptr::P<T> {
|
||||
fn into_overflowable_item(&'a self) -> OverflowableItem<'a> {
|
||||
(**self).into_overflowable_item()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_overflowable_item_for_ast_node {
|
||||
($($ast_node:ident),*) => {
|
||||
$(
|
||||
impl<'a> IntoOverflowableItem<'a> for ast::$ast_node {
|
||||
fn into_overflowable_item(&'a self) -> OverflowableItem<'a> {
|
||||
OverflowableItem::$ast_node(self)
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_overflowable_item_for_rustfmt_types {
|
||||
([$($ty:ident),*], [$($ty_with_lifetime:ident),*]) => {
|
||||
$(
|
||||
impl<'a> IntoOverflowableItem<'a> for $ty {
|
||||
fn into_overflowable_item(&'a self) -> OverflowableItem<'a> {
|
||||
OverflowableItem::$ty(self)
|
||||
}
|
||||
}
|
||||
)*
|
||||
$(
|
||||
impl<'a> IntoOverflowableItem<'a> for $ty_with_lifetime<'a> {
|
||||
fn into_overflowable_item(&'a self) -> OverflowableItem<'a> {
|
||||
OverflowableItem::$ty_with_lifetime(self)
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl_into_overflowable_item_for_ast_node!(Expr, GenericParam, NestedMetaItem, FieldDef, Ty);
|
||||
impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]);
|
||||
|
||||
pub(crate) fn into_overflowable_list<'a, T>(
|
||||
iter: impl Iterator<Item = &'a T>,
|
||||
) -> impl Iterator<Item = OverflowableItem<'a>>
|
||||
where
|
||||
T: 'a + IntoOverflowableItem<'a>,
|
||||
{
|
||||
iter.map(|x| IntoOverflowableItem::into_overflowable_item(x))
|
||||
}
|
||||
|
||||
pub(crate) fn rewrite_with_parens<'a, T: 'a + IntoOverflowableItem<'a>>(
|
||||
context: &'a RewriteContext<'_>,
|
||||
ident: &'a str,
|
||||
items: impl Iterator<Item = &'a T>,
|
||||
shape: Shape,
|
||||
span: Span,
|
||||
item_max_width: usize,
|
||||
force_separator_tactic: Option<SeparatorTactic>,
|
||||
) -> Option<String> {
|
||||
Context::new(
|
||||
context,
|
||||
items,
|
||||
ident,
|
||||
shape,
|
||||
span,
|
||||
"(",
|
||||
")",
|
||||
item_max_width,
|
||||
force_separator_tactic,
|
||||
None,
|
||||
)
|
||||
.rewrite(shape)
|
||||
}
|
||||
|
||||
pub(crate) fn rewrite_with_angle_brackets<'a, T: 'a + IntoOverflowableItem<'a>>(
|
||||
context: &'a RewriteContext<'_>,
|
||||
ident: &'a str,
|
||||
items: impl Iterator<Item = &'a T>,
|
||||
shape: Shape,
|
||||
span: Span,
|
||||
) -> Option<String> {
|
||||
Context::new(
|
||||
context,
|
||||
items,
|
||||
ident,
|
||||
shape,
|
||||
span,
|
||||
"<",
|
||||
">",
|
||||
context.config.max_width(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.rewrite(shape)
|
||||
}
|
||||
|
||||
pub(crate) fn rewrite_with_square_brackets<'a, T: 'a + IntoOverflowableItem<'a>>(
|
||||
context: &'a RewriteContext<'_>,
|
||||
name: &'a str,
|
||||
items: impl Iterator<Item = &'a T>,
|
||||
shape: Shape,
|
||||
span: Span,
|
||||
force_separator_tactic: Option<SeparatorTactic>,
|
||||
delim_token: Option<DelimToken>,
|
||||
) -> Option<String> {
|
||||
let (lhs, rhs) = match delim_token {
|
||||
Some(DelimToken::Paren) => ("(", ")"),
|
||||
Some(DelimToken::Brace) => ("{", "}"),
|
||||
_ => ("[", "]"),
|
||||
};
|
||||
Context::new(
|
||||
context,
|
||||
items,
|
||||
name,
|
||||
shape,
|
||||
span,
|
||||
lhs,
|
||||
rhs,
|
||||
context.config.array_width(),
|
||||
force_separator_tactic,
|
||||
Some(("[", "]")),
|
||||
)
|
||||
.rewrite(shape)
|
||||
}
|
||||
|
||||
struct Context<'a> {
|
||||
context: &'a RewriteContext<'a>,
|
||||
items: Vec<OverflowableItem<'a>>,
|
||||
ident: &'a str,
|
||||
prefix: &'static str,
|
||||
suffix: &'static str,
|
||||
one_line_shape: Shape,
|
||||
nested_shape: Shape,
|
||||
span: Span,
|
||||
item_max_width: usize,
|
||||
one_line_width: usize,
|
||||
force_separator_tactic: Option<SeparatorTactic>,
|
||||
custom_delims: Option<(&'a str, &'a str)>,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
fn new<T: 'a + IntoOverflowableItem<'a>>(
|
||||
context: &'a RewriteContext<'_>,
|
||||
items: impl Iterator<Item = &'a T>,
|
||||
ident: &'a str,
|
||||
shape: Shape,
|
||||
span: Span,
|
||||
prefix: &'static str,
|
||||
suffix: &'static str,
|
||||
item_max_width: usize,
|
||||
force_separator_tactic: Option<SeparatorTactic>,
|
||||
custom_delims: Option<(&'a str, &'a str)>,
|
||||
) -> Context<'a> {
|
||||
let used_width = extra_offset(ident, shape);
|
||||
// 1 = `()`
|
||||
let one_line_width = shape.width.saturating_sub(used_width + 2);
|
||||
|
||||
// 1 = "(" or ")"
|
||||
let one_line_shape = shape
|
||||
.offset_left(last_line_width(ident) + 1)
|
||||
.and_then(|shape| shape.sub_width(1))
|
||||
.unwrap_or(Shape { width: 0, ..shape });
|
||||
let nested_shape = shape_from_indent_style(context, shape, used_width + 2, used_width + 1);
|
||||
Context {
|
||||
context,
|
||||
items: into_overflowable_list(items).collect(),
|
||||
ident,
|
||||
one_line_shape,
|
||||
nested_shape,
|
||||
span,
|
||||
prefix,
|
||||
suffix,
|
||||
item_max_width,
|
||||
one_line_width,
|
||||
force_separator_tactic,
|
||||
custom_delims,
|
||||
}
|
||||
}
|
||||
|
||||
fn last_item(&self) -> Option<&OverflowableItem<'_>> {
|
||||
self.items.last()
|
||||
}
|
||||
|
||||
fn items_span(&self) -> Span {
|
||||
let span_lo = self
|
||||
.context
|
||||
.snippet_provider
|
||||
.span_after(self.span, self.prefix);
|
||||
mk_sp(span_lo, self.span.hi())
|
||||
}
|
||||
|
||||
fn rewrite_last_item_with_overflow(
|
||||
&self,
|
||||
last_list_item: &mut ListItem,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
let last_item = self.last_item()?;
|
||||
let rewrite = match last_item {
|
||||
OverflowableItem::Expr(ref expr) => {
|
||||
match expr.kind {
|
||||
// When overflowing the closure which consists of a single control flow
|
||||
// expression, force to use block if its condition uses multi line.
|
||||
ast::ExprKind::Closure(..) => {
|
||||
// If the argument consists of multiple closures, we do not overflow
|
||||
// the last closure.
|
||||
if closures::args_have_many_closure(&self.items) {
|
||||
None
|
||||
} else {
|
||||
closures::rewrite_last_closure(self.context, expr, shape)
|
||||
}
|
||||
}
|
||||
|
||||
// When overflowing the expressions which consists of a control flow
|
||||
// expression, avoid condition to use multi line.
|
||||
ast::ExprKind::If(..)
|
||||
| ast::ExprKind::ForLoop(..)
|
||||
| ast::ExprKind::Loop(..)
|
||||
| ast::ExprKind::While(..)
|
||||
| ast::ExprKind::Match(..) => {
|
||||
let multi_line = rewrite_cond(self.context, expr, shape)
|
||||
.map_or(false, |cond| cond.contains('\n'));
|
||||
|
||||
if multi_line {
|
||||
None
|
||||
} else {
|
||||
expr.rewrite(self.context, shape)
|
||||
}
|
||||
}
|
||||
|
||||
_ => expr.rewrite(self.context, shape),
|
||||
}
|
||||
}
|
||||
item => item.rewrite(self.context, shape),
|
||||
};
|
||||
|
||||
if let Some(rewrite) = rewrite {
|
||||
// splitn(2, *).next().unwrap() is always safe.
|
||||
let rewrite_first_line = Some(rewrite.splitn(2, '\n').next().unwrap().to_owned());
|
||||
last_list_item.item = rewrite_first_line;
|
||||
Some(rewrite)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn default_tactic(&self, list_items: &[ListItem]) -> DefinitiveListTactic {
|
||||
definitive_tactic(
|
||||
list_items,
|
||||
ListTactic::LimitedHorizontalVertical(self.item_max_width),
|
||||
Separator::Comma,
|
||||
self.one_line_width,
|
||||
)
|
||||
}
|
||||
|
||||
fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveListTactic {
|
||||
// 1 = "("
|
||||
let combine_arg_with_callee = self.items.len() == 1
|
||||
&& self.items[0].is_expr()
|
||||
&& !self.items[0].has_attrs()
|
||||
&& self.ident.len() < self.context.config.tab_spaces();
|
||||
let overflow_last = combine_arg_with_callee || can_be_overflowed(self.context, &self.items);
|
||||
|
||||
// Replace the last item with its first line to see if it fits with
|
||||
// first arguments.
|
||||
let placeholder = if overflow_last {
|
||||
let old_value = self.context.force_one_line_chain.get();
|
||||
match self.last_item() {
|
||||
Some(OverflowableItem::Expr(expr))
|
||||
if !combine_arg_with_callee && is_method_call(expr) =>
|
||||
{
|
||||
self.context.force_one_line_chain.replace(true);
|
||||
}
|
||||
Some(OverflowableItem::MacroArg(MacroArg::Expr(expr)))
|
||||
if !combine_arg_with_callee
|
||||
&& is_method_call(expr)
|
||||
&& self.context.config.version() == Version::Two =>
|
||||
{
|
||||
self.context.force_one_line_chain.replace(true);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let result = last_item_shape(
|
||||
&self.items,
|
||||
list_items,
|
||||
self.one_line_shape,
|
||||
self.item_max_width,
|
||||
)
|
||||
.and_then(|arg_shape| {
|
||||
self.rewrite_last_item_with_overflow(
|
||||
&mut list_items[self.items.len() - 1],
|
||||
arg_shape,
|
||||
)
|
||||
});
|
||||
self.context.force_one_line_chain.replace(old_value);
|
||||
result
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut tactic = definitive_tactic(
|
||||
&*list_items,
|
||||
ListTactic::LimitedHorizontalVertical(self.item_max_width),
|
||||
Separator::Comma,
|
||||
self.one_line_width,
|
||||
);
|
||||
|
||||
// Replace the stub with the full overflowing last argument if the rewrite
|
||||
// succeeded and its first line fits with the other arguments.
|
||||
match (overflow_last, tactic, placeholder) {
|
||||
(true, DefinitiveListTactic::Horizontal, Some(ref overflowed))
|
||||
if self.items.len() == 1 =>
|
||||
{
|
||||
// When we are rewriting a nested function call, we restrict the
|
||||
// budget for the inner function to avoid them being deeply nested.
|
||||
// However, when the inner function has a prefix or a suffix
|
||||
// (e.g., `foo() as u32`), this budget reduction may produce poorly
|
||||
// formatted code, where a prefix or a suffix being left on its own
|
||||
// line. Here we explicitlly check those cases.
|
||||
if count_newlines(overflowed) == 1 {
|
||||
let rw = self
|
||||
.items
|
||||
.last()
|
||||
.and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
|
||||
let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n'));
|
||||
if no_newline {
|
||||
list_items[self.items.len() - 1].item = rw;
|
||||
} else {
|
||||
list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
|
||||
}
|
||||
} else {
|
||||
list_items[self.items.len() - 1].item = Some(overflowed.to_owned());
|
||||
}
|
||||
}
|
||||
(true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => {
|
||||
list_items[self.items.len() - 1].item = placeholder;
|
||||
}
|
||||
_ if !self.items.is_empty() => {
|
||||
list_items[self.items.len() - 1].item = self
|
||||
.items
|
||||
.last()
|
||||
.and_then(|last_item| last_item.rewrite(self.context, self.nested_shape));
|
||||
|
||||
// Use horizontal layout for a function with a single argument as long as
|
||||
// everything fits in a single line.
|
||||
// `self.one_line_width == 0` means vertical layout is forced.
|
||||
if self.items.len() == 1
|
||||
&& self.one_line_width != 0
|
||||
&& !list_items[0].has_comment()
|
||||
&& !list_items[0].inner_as_ref().contains('\n')
|
||||
&& crate::lists::total_item_width(&list_items[0]) <= self.one_line_width
|
||||
{
|
||||
tactic = DefinitiveListTactic::Horizontal;
|
||||
} else {
|
||||
tactic = self.default_tactic(list_items);
|
||||
|
||||
if tactic == DefinitiveListTactic::Vertical {
|
||||
if let Some((all_simple, num_args_before)) =
|
||||
maybe_get_args_offset(self.ident, &self.items)
|
||||
{
|
||||
let one_line = all_simple
|
||||
&& definitive_tactic(
|
||||
&list_items[..num_args_before],
|
||||
ListTactic::HorizontalVertical,
|
||||
Separator::Comma,
|
||||
self.nested_shape.width,
|
||||
) == DefinitiveListTactic::Horizontal
|
||||
&& definitive_tactic(
|
||||
&list_items[num_args_before + 1..],
|
||||
ListTactic::HorizontalVertical,
|
||||
Separator::Comma,
|
||||
self.nested_shape.width,
|
||||
) == DefinitiveListTactic::Horizontal;
|
||||
|
||||
if one_line {
|
||||
tactic = DefinitiveListTactic::SpecialMacro(num_args_before);
|
||||
};
|
||||
} else if is_every_expr_simple(&self.items) && no_long_items(list_items) {
|
||||
tactic = DefinitiveListTactic::Mixed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
tactic
|
||||
}
|
||||
|
||||
fn rewrite_items(&self) -> Option<(bool, String)> {
|
||||
let span = self.items_span();
|
||||
let items = itemize_list(
|
||||
self.context.snippet_provider,
|
||||
self.items.iter(),
|
||||
self.suffix,
|
||||
",",
|
||||
|item| item.span().lo(),
|
||||
|item| item.span().hi(),
|
||||
|item| item.rewrite(self.context, self.nested_shape),
|
||||
span.lo(),
|
||||
span.hi(),
|
||||
true,
|
||||
);
|
||||
let mut list_items: Vec<_> = items.collect();
|
||||
|
||||
// Try letting the last argument overflow to the next line with block
|
||||
// indentation. If its first line fits on one line with the other arguments,
|
||||
// we format the function arguments horizontally.
|
||||
let tactic = self.try_overflow_last_item(&mut list_items);
|
||||
let trailing_separator = if let Some(tactic) = self.force_separator_tactic {
|
||||
tactic
|
||||
} else if !self.context.use_block_indent() {
|
||||
SeparatorTactic::Never
|
||||
} else {
|
||||
self.context.config.trailing_comma()
|
||||
};
|
||||
let ends_with_newline = match tactic {
|
||||
DefinitiveListTactic::Vertical | DefinitiveListTactic::Mixed => {
|
||||
self.context.use_block_indent()
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let fmt = ListFormatting::new(self.nested_shape, self.context.config)
|
||||
.tactic(tactic)
|
||||
.trailing_separator(trailing_separator)
|
||||
.ends_with_newline(ends_with_newline);
|
||||
|
||||
write_list(&list_items, &fmt)
|
||||
.map(|items_str| (tactic == DefinitiveListTactic::Horizontal, items_str))
|
||||
}
|
||||
|
||||
fn wrap_items(&self, items_str: &str, shape: Shape, is_extendable: bool) -> String {
|
||||
let shape = Shape {
|
||||
width: shape.width.saturating_sub(last_line_width(self.ident)),
|
||||
..shape
|
||||
};
|
||||
|
||||
let (prefix, suffix) = match self.custom_delims {
|
||||
Some((lhs, rhs)) => (lhs, rhs),
|
||||
_ => (self.prefix, self.suffix),
|
||||
};
|
||||
|
||||
let extend_width = if items_str.is_empty() {
|
||||
2
|
||||
} else {
|
||||
first_line_width(items_str) + 1
|
||||
};
|
||||
let nested_indent_str = self
|
||||
.nested_shape
|
||||
.indent
|
||||
.to_string_with_newline(self.context.config);
|
||||
let indent_str = shape
|
||||
.block()
|
||||
.indent
|
||||
.to_string_with_newline(self.context.config);
|
||||
let mut result = String::with_capacity(
|
||||
self.ident.len() + items_str.len() + 2 + indent_str.len() + nested_indent_str.len(),
|
||||
);
|
||||
result.push_str(self.ident);
|
||||
result.push_str(prefix);
|
||||
let force_single_line = if self.context.config.version() == Version::Two {
|
||||
!self.context.use_block_indent() || (is_extendable && extend_width <= shape.width)
|
||||
} else {
|
||||
// 2 = `()`
|
||||
let fits_one_line = items_str.len() + 2 <= shape.width;
|
||||
!self.context.use_block_indent()
|
||||
|| (self.context.inside_macro() && !items_str.contains('\n') && fits_one_line)
|
||||
|| (is_extendable && extend_width <= shape.width)
|
||||
};
|
||||
if force_single_line {
|
||||
result.push_str(items_str);
|
||||
} else {
|
||||
if !items_str.is_empty() {
|
||||
result.push_str(&nested_indent_str);
|
||||
result.push_str(items_str);
|
||||
}
|
||||
result.push_str(&indent_str);
|
||||
}
|
||||
result.push_str(suffix);
|
||||
result
|
||||
}
|
||||
|
||||
fn rewrite(&self, shape: Shape) -> Option<String> {
|
||||
let (extendable, items_str) = self.rewrite_items()?;
|
||||
|
||||
// If we are using visual indent style and failed to format, retry with block indent.
|
||||
if !self.context.use_block_indent()
|
||||
&& need_block_indent(&items_str, self.nested_shape)
|
||||
&& !extendable
|
||||
{
|
||||
self.context.use_block.replace(true);
|
||||
let result = self.rewrite(shape);
|
||||
self.context.use_block.replace(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
Some(self.wrap_items(&items_str, shape, extendable))
|
||||
}
|
||||
}
|
||||
|
||||
fn need_block_indent(s: &str, shape: Shape) -> bool {
|
||||
s.lines().skip(1).any(|s| {
|
||||
s.find(|c| !char::is_whitespace(c))
|
||||
.map_or(false, |w| w + 1 < shape.indent.width())
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_overflowed(context: &RewriteContext<'_>, items: &[OverflowableItem<'_>]) -> bool {
|
||||
items
|
||||
.last()
|
||||
.map_or(false, |x| x.can_be_overflowed(context, items.len()))
|
||||
}
|
||||
|
||||
/// Returns a shape for the last argument which is going to be overflowed.
|
||||
fn last_item_shape(
|
||||
lists: &[OverflowableItem<'_>],
|
||||
items: &[ListItem],
|
||||
shape: Shape,
|
||||
args_max_width: usize,
|
||||
) -> Option<Shape> {
|
||||
if items.len() == 1 && !lists.get(0)?.is_nested_call() {
|
||||
return Some(shape);
|
||||
}
|
||||
let offset = items
|
||||
.iter()
|
||||
.dropping_back(1)
|
||||
.map(|i| {
|
||||
// 2 = ", "
|
||||
2 + i.inner_as_ref().len()
|
||||
})
|
||||
.sum();
|
||||
Shape {
|
||||
width: min(args_max_width, shape.width),
|
||||
..shape
|
||||
}
|
||||
.offset_left(offset)
|
||||
}
|
||||
|
||||
fn shape_from_indent_style(
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
overhead: usize,
|
||||
offset: usize,
|
||||
) -> Shape {
|
||||
let (shape, overhead) = if context.use_block_indent() {
|
||||
let shape = shape
|
||||
.block()
|
||||
.block_indent(context.config.tab_spaces())
|
||||
.with_max_width(context.config);
|
||||
(shape, 1) // 1 = ","
|
||||
} else {
|
||||
(shape.visual_indent(offset), overhead)
|
||||
};
|
||||
Shape {
|
||||
width: shape.width.saturating_sub(overhead),
|
||||
..shape
|
||||
}
|
||||
}
|
||||
|
||||
fn no_long_items(list: &[ListItem]) -> bool {
|
||||
list.iter()
|
||||
.all(|item| item.inner_as_ref().len() <= SHORT_ITEM_THRESHOLD)
|
||||
}
|
||||
|
||||
/// In case special-case style is required, returns an offset from which we start horizontal layout.
|
||||
pub(crate) fn maybe_get_args_offset(
|
||||
callee_str: &str,
|
||||
args: &[OverflowableItem<'_>],
|
||||
) -> Option<(bool, usize)> {
|
||||
if let Some(&(_, num_args_before)) = args
|
||||
.get(0)?
|
||||
.whitelist()
|
||||
.iter()
|
||||
.find(|&&(s, _)| s == callee_str)
|
||||
{
|
||||
let all_simple = args.len() > num_args_before
|
||||
&& is_every_expr_simple(&args[0..num_args_before])
|
||||
&& is_every_expr_simple(&args[num_args_before + 1..]);
|
||||
|
||||
Some((all_simple, num_args_before))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
318
src/tools/rustfmt/src/pairs.rs
Normal file
318
src/tools/rustfmt/src/pairs.rs
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
use rustc_ast::ast;
|
||||
|
||||
use crate::config::lists::*;
|
||||
use crate::config::IndentStyle;
|
||||
use crate::rewrite::{Rewrite, RewriteContext};
|
||||
use crate::shape::Shape;
|
||||
use crate::utils::{
|
||||
first_line_width, is_single_line, last_line_width, trimmed_last_line_width, wrap_str,
|
||||
};
|
||||
|
||||
/// Sigils that decorate a binop pair.
|
||||
#[derive(new, Clone, Copy)]
|
||||
pub(crate) struct PairParts<'a> {
|
||||
prefix: &'a str,
|
||||
infix: &'a str,
|
||||
suffix: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> PairParts<'a> {
|
||||
pub(crate) fn infix(infix: &'a str) -> PairParts<'a> {
|
||||
PairParts {
|
||||
prefix: "",
|
||||
infix,
|
||||
suffix: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flattens a tree of pairs into a list and tries to rewrite them all at once.
|
||||
// FIXME would be nice to reuse the lists API for this, but because each separator
|
||||
// can be different, we can't.
|
||||
pub(crate) fn rewrite_all_pairs(
|
||||
expr: &ast::Expr,
|
||||
shape: Shape,
|
||||
context: &RewriteContext<'_>,
|
||||
) -> Option<String> {
|
||||
expr.flatten(context, shape).and_then(|list| {
|
||||
// First we try formatting on one line.
|
||||
rewrite_pairs_one_line(&list, shape, context)
|
||||
.or_else(|| rewrite_pairs_multiline(&list, shape, context))
|
||||
})
|
||||
}
|
||||
|
||||
// This may return a multi-line result since we allow the last expression to go
|
||||
// multiline in a 'single line' formatting.
|
||||
fn rewrite_pairs_one_line<T: Rewrite>(
|
||||
list: &PairList<'_, '_, T>,
|
||||
shape: Shape,
|
||||
context: &RewriteContext<'_>,
|
||||
) -> Option<String> {
|
||||
assert!(list.list.len() >= 2, "Not a pair?");
|
||||
|
||||
let mut result = String::new();
|
||||
let base_shape = shape.block();
|
||||
|
||||
for ((_, rewrite), s) in list.list.iter().zip(list.separators.iter()) {
|
||||
if let Some(rewrite) = rewrite {
|
||||
if !is_single_line(&rewrite) || result.len() > shape.width {
|
||||
return None;
|
||||
}
|
||||
|
||||
result.push_str(&rewrite);
|
||||
result.push(' ');
|
||||
result.push_str(s);
|
||||
result.push(' ');
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let prefix_len = result.len();
|
||||
let last = list.list.last()?.0;
|
||||
let cur_shape = base_shape.offset_left(last_line_width(&result))?;
|
||||
let last_rewrite = last.rewrite(context, cur_shape)?;
|
||||
result.push_str(&last_rewrite);
|
||||
|
||||
if first_line_width(&result) > shape.width {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check the last expression in the list. We sometimes let this expression
|
||||
// go over multiple lines, but we check for some ugly conditions.
|
||||
if !(is_single_line(&result) || last_rewrite.starts_with('{'))
|
||||
&& (last_rewrite.starts_with('(') || prefix_len > context.config.tab_spaces())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
wrap_str(result, context.config.max_width(), shape)
|
||||
}
|
||||
|
||||
fn rewrite_pairs_multiline<T: Rewrite>(
|
||||
list: &PairList<'_, '_, T>,
|
||||
shape: Shape,
|
||||
context: &RewriteContext<'_>,
|
||||
) -> Option<String> {
|
||||
let rhs_offset = shape.rhs_overhead(&context.config);
|
||||
let nested_shape = (match context.config.indent_style() {
|
||||
IndentStyle::Visual => shape.visual_indent(0),
|
||||
IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
|
||||
})
|
||||
.with_max_width(&context.config)
|
||||
.sub_width(rhs_offset)?;
|
||||
|
||||
let indent_str = nested_shape.indent.to_string_with_newline(context.config);
|
||||
let mut result = String::new();
|
||||
|
||||
result.push_str(&list.list[0].1.as_ref()?);
|
||||
|
||||
for ((e, default_rw), s) in list.list[1..].iter().zip(list.separators.iter()) {
|
||||
// The following test checks if we should keep two subexprs on the same
|
||||
// line. We do this if not doing so would create an orphan and there is
|
||||
// enough space to do so.
|
||||
let offset = if result.contains('\n') {
|
||||
0
|
||||
} else {
|
||||
shape.used_width()
|
||||
};
|
||||
if last_line_width(&result) + offset <= nested_shape.used_width() {
|
||||
// We must snuggle the next line onto the previous line to avoid an orphan.
|
||||
if let Some(line_shape) =
|
||||
shape.offset_left(s.len() + 2 + trimmed_last_line_width(&result))
|
||||
{
|
||||
if let Some(rewrite) = e.rewrite(context, line_shape) {
|
||||
result.push(' ');
|
||||
result.push_str(s);
|
||||
result.push(' ');
|
||||
result.push_str(&rewrite);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match context.config.binop_separator() {
|
||||
SeparatorPlace::Back => {
|
||||
result.push(' ');
|
||||
result.push_str(s);
|
||||
result.push_str(&indent_str);
|
||||
}
|
||||
SeparatorPlace::Front => {
|
||||
result.push_str(&indent_str);
|
||||
result.push_str(s);
|
||||
result.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
result.push_str(&default_rw.as_ref()?);
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
// Rewrites a single pair.
|
||||
pub(crate) fn rewrite_pair<LHS, RHS>(
|
||||
lhs: &LHS,
|
||||
rhs: &RHS,
|
||||
pp: PairParts<'_>,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
separator_place: SeparatorPlace,
|
||||
) -> Option<String>
|
||||
where
|
||||
LHS: Rewrite,
|
||||
RHS: Rewrite,
|
||||
{
|
||||
let tab_spaces = context.config.tab_spaces();
|
||||
let lhs_overhead = match separator_place {
|
||||
SeparatorPlace::Back => shape.used_width() + pp.prefix.len() + pp.infix.trim_end().len(),
|
||||
SeparatorPlace::Front => shape.used_width(),
|
||||
};
|
||||
let lhs_shape = Shape {
|
||||
width: context.budget(lhs_overhead),
|
||||
..shape
|
||||
};
|
||||
let lhs_result = lhs
|
||||
.rewrite(context, lhs_shape)
|
||||
.map(|lhs_str| format!("{}{}", pp.prefix, lhs_str))?;
|
||||
|
||||
// Try to put both lhs and rhs on the same line.
|
||||
let rhs_orig_result = shape
|
||||
.offset_left(last_line_width(&lhs_result) + pp.infix.len())
|
||||
.and_then(|s| s.sub_width(pp.suffix.len()))
|
||||
.and_then(|rhs_shape| rhs.rewrite(context, rhs_shape));
|
||||
if let Some(ref rhs_result) = rhs_orig_result {
|
||||
// If the length of the lhs is equal to or shorter than the tab width or
|
||||
// the rhs looks like block expression, we put the rhs on the same
|
||||
// line with the lhs even if the rhs is multi-lined.
|
||||
let allow_same_line = lhs_result.len() <= tab_spaces
|
||||
|| rhs_result
|
||||
.lines()
|
||||
.next()
|
||||
.map(|first_line| first_line.ends_with('{'))
|
||||
.unwrap_or(false);
|
||||
if !rhs_result.contains('\n') || allow_same_line {
|
||||
let one_line_width = last_line_width(&lhs_result)
|
||||
+ pp.infix.len()
|
||||
+ first_line_width(rhs_result)
|
||||
+ pp.suffix.len();
|
||||
if one_line_width <= shape.width {
|
||||
return Some(format!(
|
||||
"{}{}{}{}",
|
||||
lhs_result, pp.infix, rhs_result, pp.suffix
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have to use multiple lines.
|
||||
// Re-evaluate the rhs because we have more space now:
|
||||
let mut rhs_shape = match context.config.indent_style() {
|
||||
IndentStyle::Visual => shape
|
||||
.sub_width(pp.suffix.len() + pp.prefix.len())?
|
||||
.visual_indent(pp.prefix.len()),
|
||||
IndentStyle::Block => {
|
||||
// Try to calculate the initial constraint on the right hand side.
|
||||
let rhs_overhead = shape.rhs_overhead(context.config);
|
||||
Shape::indented(shape.indent.block_indent(context.config), context.config)
|
||||
.sub_width(rhs_overhead)?
|
||||
}
|
||||
};
|
||||
let infix = match separator_place {
|
||||
SeparatorPlace::Back => pp.infix.trim_end(),
|
||||
SeparatorPlace::Front => pp.infix.trim_start(),
|
||||
};
|
||||
if separator_place == SeparatorPlace::Front {
|
||||
rhs_shape = rhs_shape.offset_left(infix.len())?;
|
||||
}
|
||||
let rhs_result = rhs.rewrite(context, rhs_shape)?;
|
||||
let indent_str = rhs_shape.indent.to_string_with_newline(context.config);
|
||||
let infix_with_sep = match separator_place {
|
||||
SeparatorPlace::Back => format!("{}{}", infix, indent_str),
|
||||
SeparatorPlace::Front => format!("{}{}", indent_str, infix),
|
||||
};
|
||||
Some(format!(
|
||||
"{}{}{}{}",
|
||||
lhs_result, infix_with_sep, rhs_result, pp.suffix
|
||||
))
|
||||
}
|
||||
|
||||
// A pair which forms a tree and can be flattened (e.g., binops).
|
||||
trait FlattenPair: Rewrite + Sized {
|
||||
fn flatten(&self, _: &RewriteContext<'_>, _: Shape) -> Option<PairList<'_, '_, Self>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct PairList<'a, 'b, T: Rewrite> {
|
||||
list: Vec<(&'b T, Option<String>)>,
|
||||
separators: Vec<&'a str>,
|
||||
}
|
||||
|
||||
impl FlattenPair for ast::Expr {
|
||||
fn flatten(
|
||||
&self,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<PairList<'_, '_, ast::Expr>> {
|
||||
let top_op = match self.kind {
|
||||
ast::ExprKind::Binary(op, _, _) => op.node,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let default_rewrite = |node: &ast::Expr, sep: usize, is_first: bool| {
|
||||
if is_first {
|
||||
return node.rewrite(context, shape);
|
||||
}
|
||||
let nested_overhead = sep + 1;
|
||||
let rhs_offset = shape.rhs_overhead(&context.config);
|
||||
let nested_shape = (match context.config.indent_style() {
|
||||
IndentStyle::Visual => shape.visual_indent(0),
|
||||
IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
|
||||
})
|
||||
.with_max_width(&context.config)
|
||||
.sub_width(rhs_offset)?;
|
||||
let default_shape = match context.config.binop_separator() {
|
||||
SeparatorPlace::Back => nested_shape.sub_width(nested_overhead)?,
|
||||
SeparatorPlace::Front => nested_shape.offset_left(nested_overhead)?,
|
||||
};
|
||||
node.rewrite(context, default_shape)
|
||||
};
|
||||
|
||||
// Turn a tree of binop expressions into a list using a depth-first,
|
||||
// in-order traversal.
|
||||
let mut stack = vec![];
|
||||
let mut list = vec![];
|
||||
let mut separators = vec![];
|
||||
let mut node = self;
|
||||
loop {
|
||||
match node.kind {
|
||||
ast::ExprKind::Binary(op, ref lhs, _) if op.node == top_op => {
|
||||
stack.push(node);
|
||||
node = lhs;
|
||||
}
|
||||
_ => {
|
||||
let op_len = separators.last().map_or(0, |s: &&str| s.len());
|
||||
let rw = default_rewrite(node, op_len, list.is_empty());
|
||||
list.push((node, rw));
|
||||
if let Some(pop) = stack.pop() {
|
||||
match pop.kind {
|
||||
ast::ExprKind::Binary(op, _, ref rhs) => {
|
||||
separators.push(op.node.to_string());
|
||||
node = rhs;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(list.len() - 1, separators.len());
|
||||
Some(PairList { list, separators })
|
||||
}
|
||||
}
|
||||
|
||||
impl FlattenPair for ast::Ty {}
|
||||
impl FlattenPair for ast::Pat {}
|
||||
525
src/tools/rustfmt/src/patterns.rs
Normal file
525
src/tools/rustfmt/src/patterns.rs
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
use rustc_ast::ast::{self, BindingMode, Pat, PatField, PatKind, RangeEnd, RangeSyntax};
|
||||
use rustc_ast::ptr;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
use crate::comment::{combine_strs_with_missing_comments, FindUncommented};
|
||||
use crate::config::lists::*;
|
||||
use crate::expr::{can_be_overflowed_expr, rewrite_unary_prefix, wrap_struct_field};
|
||||
use crate::lists::{
|
||||
definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape,
|
||||
struct_lit_tactic, write_list, ListFormatting, ListItem, Separator,
|
||||
};
|
||||
use crate::macros::{rewrite_macro, MacroPosition};
|
||||
use crate::overflow;
|
||||
use crate::pairs::{rewrite_pair, PairParts};
|
||||
use crate::rewrite::{Rewrite, RewriteContext};
|
||||
use crate::shape::Shape;
|
||||
use crate::source_map::SpanUtils;
|
||||
use crate::spanned::Spanned;
|
||||
use crate::types::{rewrite_path, PathContext};
|
||||
use crate::utils::{format_mutability, mk_sp, mk_sp_lo_plus_one, rewrite_ident};
|
||||
|
||||
/// Returns `true` if the given pattern is "short".
|
||||
/// A short pattern is defined by the following grammar:
|
||||
///
|
||||
/// [small, ntp]:
|
||||
/// - single token
|
||||
/// - `&[single-line, ntp]`
|
||||
///
|
||||
/// [small]:
|
||||
/// - `[small, ntp]`
|
||||
/// - unary tuple constructor `([small, ntp])`
|
||||
/// - `&[small]`
|
||||
pub(crate) fn is_short_pattern(pat: &ast::Pat, pat_str: &str) -> bool {
|
||||
// We also require that the pattern is reasonably 'small' with its literal width.
|
||||
pat_str.len() <= 20 && !pat_str.contains('\n') && is_short_pattern_inner(pat)
|
||||
}
|
||||
|
||||
fn is_short_pattern_inner(pat: &ast::Pat) -> bool {
|
||||
match pat.kind {
|
||||
ast::PatKind::Rest | ast::PatKind::Wild | ast::PatKind::Lit(_) => true,
|
||||
ast::PatKind::Ident(_, _, ref pat) => pat.is_none(),
|
||||
ast::PatKind::Struct(..)
|
||||
| ast::PatKind::MacCall(..)
|
||||
| ast::PatKind::Slice(..)
|
||||
| ast::PatKind::Path(..)
|
||||
| ast::PatKind::Range(..) => false,
|
||||
ast::PatKind::Tuple(ref subpats) => subpats.len() <= 1,
|
||||
ast::PatKind::TupleStruct(ref path, ref subpats) => {
|
||||
path.segments.len() <= 1 && subpats.len() <= 1
|
||||
}
|
||||
ast::PatKind::Box(ref p) | ast::PatKind::Ref(ref p, _) | ast::PatKind::Paren(ref p) => {
|
||||
is_short_pattern_inner(&*p)
|
||||
}
|
||||
PatKind::Or(ref pats) => pats.iter().all(|p| is_short_pattern_inner(p)),
|
||||
}
|
||||
}
|
||||
|
||||
struct RangeOperand<'a>(&'a Option<ptr::P<ast::Expr>>);
|
||||
|
||||
impl<'a> Rewrite for RangeOperand<'a> {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
match &self.0 {
|
||||
None => Some("".to_owned()),
|
||||
Some(ref exp) => exp.rewrite(context, shape),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rewrite for Pat {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
match self.kind {
|
||||
PatKind::Or(ref pats) => {
|
||||
let pat_strs = pats
|
||||
.iter()
|
||||
.map(|p| p.rewrite(context, shape))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
let use_mixed_layout = pats
|
||||
.iter()
|
||||
.zip(pat_strs.iter())
|
||||
.all(|(pat, pat_str)| is_short_pattern(pat, pat_str));
|
||||
let items: Vec<_> = pat_strs.into_iter().map(ListItem::from_str).collect();
|
||||
let tactic = if use_mixed_layout {
|
||||
DefinitiveListTactic::Mixed
|
||||
} else {
|
||||
definitive_tactic(
|
||||
&items,
|
||||
ListTactic::HorizontalVertical,
|
||||
Separator::VerticalBar,
|
||||
shape.width,
|
||||
)
|
||||
};
|
||||
let fmt = ListFormatting::new(shape, context.config)
|
||||
.tactic(tactic)
|
||||
.separator(" |")
|
||||
.separator_place(context.config.binop_separator())
|
||||
.ends_with_newline(false);
|
||||
write_list(&items, &fmt)
|
||||
}
|
||||
PatKind::Box(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, shape),
|
||||
PatKind::Ident(binding_mode, ident, ref sub_pat) => {
|
||||
let (prefix, mutability) = match binding_mode {
|
||||
BindingMode::ByRef(mutability) => ("ref", mutability),
|
||||
BindingMode::ByValue(mutability) => ("", mutability),
|
||||
};
|
||||
let mut_infix = format_mutability(mutability).trim();
|
||||
let id_str = rewrite_ident(context, ident);
|
||||
let sub_pat = match *sub_pat {
|
||||
Some(ref p) => {
|
||||
// 2 - `@ `.
|
||||
let width = shape
|
||||
.width
|
||||
.checked_sub(prefix.len() + mut_infix.len() + id_str.len() + 2)?;
|
||||
let lo = context.snippet_provider.span_after(self.span, "@");
|
||||
combine_strs_with_missing_comments(
|
||||
context,
|
||||
"@",
|
||||
&p.rewrite(context, Shape::legacy(width, shape.indent))?,
|
||||
mk_sp(lo, p.span.lo()),
|
||||
shape,
|
||||
true,
|
||||
)?
|
||||
}
|
||||
None => "".to_owned(),
|
||||
};
|
||||
|
||||
// combine prefix and mut
|
||||
let (first_lo, first) = if !prefix.is_empty() && !mut_infix.is_empty() {
|
||||
let hi = context.snippet_provider.span_before(self.span, "mut");
|
||||
let lo = context.snippet_provider.span_after(self.span, "ref");
|
||||
(
|
||||
context.snippet_provider.span_after(self.span, "mut"),
|
||||
combine_strs_with_missing_comments(
|
||||
context,
|
||||
prefix,
|
||||
mut_infix,
|
||||
mk_sp(lo, hi),
|
||||
shape,
|
||||
true,
|
||||
)?,
|
||||
)
|
||||
} else if !prefix.is_empty() {
|
||||
(
|
||||
context.snippet_provider.span_after(self.span, "ref"),
|
||||
prefix.to_owned(),
|
||||
)
|
||||
} else if !mut_infix.is_empty() {
|
||||
(
|
||||
context.snippet_provider.span_after(self.span, "mut"),
|
||||
mut_infix.to_owned(),
|
||||
)
|
||||
} else {
|
||||
(self.span.lo(), "".to_owned())
|
||||
};
|
||||
|
||||
let next = if !sub_pat.is_empty() {
|
||||
let hi = context.snippet_provider.span_before(self.span, "@");
|
||||
combine_strs_with_missing_comments(
|
||||
context,
|
||||
id_str,
|
||||
&sub_pat,
|
||||
mk_sp(ident.span.hi(), hi),
|
||||
shape,
|
||||
true,
|
||||
)?
|
||||
} else {
|
||||
id_str.to_owned()
|
||||
};
|
||||
|
||||
combine_strs_with_missing_comments(
|
||||
context,
|
||||
&first,
|
||||
&next,
|
||||
mk_sp(first_lo, ident.span.lo()),
|
||||
shape,
|
||||
true,
|
||||
)
|
||||
}
|
||||
PatKind::Wild => {
|
||||
if 1 <= shape.width {
|
||||
Some("_".to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatKind::Rest => {
|
||||
if 1 <= shape.width {
|
||||
Some("..".to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
PatKind::Range(ref lhs, ref rhs, ref end_kind) => {
|
||||
let infix = match end_kind.node {
|
||||
RangeEnd::Included(RangeSyntax::DotDotDot) => "...",
|
||||
RangeEnd::Included(RangeSyntax::DotDotEq) => "..=",
|
||||
RangeEnd::Excluded => "..",
|
||||
};
|
||||
let infix = if context.config.spaces_around_ranges() {
|
||||
let lhs_spacing = match lhs {
|
||||
None => "",
|
||||
Some(_) => " ",
|
||||
};
|
||||
let rhs_spacing = match rhs {
|
||||
None => "",
|
||||
Some(_) => " ",
|
||||
};
|
||||
format!("{}{}{}", lhs_spacing, infix, rhs_spacing)
|
||||
} else {
|
||||
infix.to_owned()
|
||||
};
|
||||
rewrite_pair(
|
||||
&RangeOperand(lhs),
|
||||
&RangeOperand(rhs),
|
||||
PairParts::infix(&infix),
|
||||
context,
|
||||
shape,
|
||||
SeparatorPlace::Front,
|
||||
)
|
||||
}
|
||||
PatKind::Ref(ref pat, mutability) => {
|
||||
let prefix = format!("&{}", format_mutability(mutability));
|
||||
rewrite_unary_prefix(context, &prefix, &**pat, shape)
|
||||
}
|
||||
PatKind::Tuple(ref items) => rewrite_tuple_pat(items, None, self.span, context, shape),
|
||||
PatKind::Path(ref q_self, ref path) => {
|
||||
rewrite_path(context, PathContext::Expr, q_self.as_ref(), path, shape)
|
||||
}
|
||||
PatKind::TupleStruct(ref path, ref pat_vec) => {
|
||||
let path_str = rewrite_path(context, PathContext::Expr, None, path, shape)?;
|
||||
rewrite_tuple_pat(pat_vec, Some(path_str), self.span, context, shape)
|
||||
}
|
||||
PatKind::Lit(ref expr) => expr.rewrite(context, shape),
|
||||
PatKind::Slice(ref slice_pat) => {
|
||||
let rw: Vec<String> = slice_pat
|
||||
.iter()
|
||||
.map(|p| {
|
||||
if let Some(rw) = p.rewrite(context, shape) {
|
||||
rw
|
||||
} else {
|
||||
format!("{}", context.snippet(p.span))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(format!("[{}]", rw.join(", ")))
|
||||
}
|
||||
PatKind::Struct(ref path, ref fields, ellipsis) => {
|
||||
rewrite_struct_pat(path, fields, ellipsis, self.span, context, shape)
|
||||
}
|
||||
PatKind::MacCall(ref mac) => {
|
||||
rewrite_macro(mac, None, context, shape, MacroPosition::Pat)
|
||||
}
|
||||
PatKind::Paren(ref pat) => pat
|
||||
.rewrite(context, shape.offset_left(1)?.sub_width(1)?)
|
||||
.map(|inner_pat| format!("({})", inner_pat)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_struct_pat(
|
||||
path: &ast::Path,
|
||||
fields: &[ast::PatField],
|
||||
ellipsis: bool,
|
||||
span: Span,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
// 2 = ` {`
|
||||
let path_shape = shape.sub_width(2)?;
|
||||
let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?;
|
||||
|
||||
if fields.is_empty() && !ellipsis {
|
||||
return Some(format!("{} {{}}", path_str));
|
||||
}
|
||||
|
||||
let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") };
|
||||
|
||||
// 3 = ` { `, 2 = ` }`.
|
||||
let (h_shape, v_shape) =
|
||||
struct_lit_shape(shape, context, path_str.len() + 3, ellipsis_str.len() + 2)?;
|
||||
|
||||
let items = itemize_list(
|
||||
context.snippet_provider,
|
||||
fields.iter(),
|
||||
terminator,
|
||||
",",
|
||||
|f| {
|
||||
if f.attrs.is_empty() {
|
||||
f.span.lo()
|
||||
} else {
|
||||
f.attrs.first().unwrap().span.lo()
|
||||
}
|
||||
},
|
||||
|f| f.span.hi(),
|
||||
|f| f.rewrite(context, v_shape),
|
||||
context.snippet_provider.span_after(span, "{"),
|
||||
span.hi(),
|
||||
false,
|
||||
);
|
||||
let item_vec = items.collect::<Vec<_>>();
|
||||
|
||||
let tactic = struct_lit_tactic(h_shape, context, &item_vec);
|
||||
let nested_shape = shape_for_tactic(tactic, h_shape, v_shape);
|
||||
let fmt = struct_lit_formatting(nested_shape, tactic, context, false);
|
||||
|
||||
let mut fields_str = write_list(&item_vec, &fmt)?;
|
||||
let one_line_width = h_shape.map_or(0, |shape| shape.width);
|
||||
|
||||
if ellipsis {
|
||||
if fields_str.contains('\n') || fields_str.len() > one_line_width {
|
||||
// Add a missing trailing comma.
|
||||
if context.config.trailing_comma() == SeparatorTactic::Never {
|
||||
fields_str.push_str(",");
|
||||
}
|
||||
fields_str.push_str("\n");
|
||||
fields_str.push_str(&nested_shape.indent.to_string(context.config));
|
||||
fields_str.push_str("..");
|
||||
} else {
|
||||
if !fields_str.is_empty() {
|
||||
// there are preceding struct fields being matched on
|
||||
if tactic == DefinitiveListTactic::Vertical {
|
||||
// if the tactic is Vertical, write_list already added a trailing ,
|
||||
fields_str.push_str(" ");
|
||||
} else {
|
||||
fields_str.push_str(", ");
|
||||
}
|
||||
}
|
||||
fields_str.push_str("..");
|
||||
}
|
||||
}
|
||||
|
||||
// ast::Pat doesn't have attrs so use &[]
|
||||
let fields_str = wrap_struct_field(context, &[], &fields_str, shape, v_shape, one_line_width)?;
|
||||
Some(format!("{} {{{}}}", path_str, fields_str))
|
||||
}
|
||||
|
||||
impl Rewrite for PatField {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
let hi_pos = if let Some(last) = self.attrs.last() {
|
||||
last.span.hi()
|
||||
} else {
|
||||
self.pat.span.lo()
|
||||
};
|
||||
|
||||
let attrs_str = if self.attrs.is_empty() {
|
||||
String::from("")
|
||||
} else {
|
||||
self.attrs.rewrite(context, shape)?
|
||||
};
|
||||
|
||||
let pat_str = self.pat.rewrite(context, shape)?;
|
||||
if self.is_shorthand {
|
||||
combine_strs_with_missing_comments(
|
||||
context,
|
||||
&attrs_str,
|
||||
&pat_str,
|
||||
mk_sp(hi_pos, self.pat.span.lo()),
|
||||
shape,
|
||||
false,
|
||||
)
|
||||
} else {
|
||||
let nested_shape = shape.block_indent(context.config.tab_spaces());
|
||||
let id_str = rewrite_ident(context, self.ident);
|
||||
let one_line_width = id_str.len() + 2 + pat_str.len();
|
||||
let pat_and_id_str = if one_line_width <= shape.width {
|
||||
format!("{}: {}", id_str, pat_str)
|
||||
} else {
|
||||
format!(
|
||||
"{}:\n{}{}",
|
||||
id_str,
|
||||
nested_shape.indent.to_string(context.config),
|
||||
self.pat.rewrite(context, nested_shape)?
|
||||
)
|
||||
};
|
||||
combine_strs_with_missing_comments(
|
||||
context,
|
||||
&attrs_str,
|
||||
&pat_and_id_str,
|
||||
mk_sp(hi_pos, self.pat.span.lo()),
|
||||
nested_shape,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum TuplePatField<'a> {
|
||||
Pat(&'a ptr::P<ast::Pat>),
|
||||
Dotdot(Span),
|
||||
}
|
||||
|
||||
impl<'a> Rewrite for TuplePatField<'a> {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
match *self {
|
||||
TuplePatField::Pat(p) => p.rewrite(context, shape),
|
||||
TuplePatField::Dotdot(_) => Some("..".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spanned for TuplePatField<'a> {
|
||||
fn span(&self) -> Span {
|
||||
match *self {
|
||||
TuplePatField::Pat(p) => p.span(),
|
||||
TuplePatField::Dotdot(span) => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TuplePatField<'a> {
|
||||
fn is_dotdot(&self) -> bool {
|
||||
match self {
|
||||
TuplePatField::Pat(pat) => match pat.kind {
|
||||
ast::PatKind::Rest => true,
|
||||
_ => false,
|
||||
},
|
||||
TuplePatField::Dotdot(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn can_be_overflowed_pat(
|
||||
context: &RewriteContext<'_>,
|
||||
pat: &TuplePatField<'_>,
|
||||
len: usize,
|
||||
) -> bool {
|
||||
match *pat {
|
||||
TuplePatField::Pat(pat) => match pat.kind {
|
||||
ast::PatKind::Path(..)
|
||||
| ast::PatKind::Tuple(..)
|
||||
| ast::PatKind::Struct(..)
|
||||
| ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1,
|
||||
ast::PatKind::Ref(ref p, _) | ast::PatKind::Box(ref p) => {
|
||||
can_be_overflowed_pat(context, &TuplePatField::Pat(p), len)
|
||||
}
|
||||
ast::PatKind::Lit(ref expr) => can_be_overflowed_expr(context, expr, len),
|
||||
_ => false,
|
||||
},
|
||||
TuplePatField::Dotdot(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_tuple_pat(
|
||||
pats: &[ptr::P<ast::Pat>],
|
||||
path_str: Option<String>,
|
||||
span: Span,
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
let mut pat_vec: Vec<_> = pats.iter().map(|x| TuplePatField::Pat(x)).collect();
|
||||
|
||||
if pat_vec.is_empty() {
|
||||
return Some(format!("{}()", path_str.unwrap_or_default()));
|
||||
}
|
||||
let wildcard_suffix_len = count_wildcard_suffix_len(context, &pat_vec, span, shape);
|
||||
let (pat_vec, span) = if context.config.condense_wildcard_suffixes() && wildcard_suffix_len >= 2
|
||||
{
|
||||
let new_item_count = 1 + pat_vec.len() - wildcard_suffix_len;
|
||||
let sp = pat_vec[new_item_count - 1].span();
|
||||
let snippet = context.snippet(sp);
|
||||
let lo = sp.lo() + BytePos(snippet.find_uncommented("_").unwrap() as u32);
|
||||
pat_vec[new_item_count - 1] = TuplePatField::Dotdot(mk_sp_lo_plus_one(lo));
|
||||
(
|
||||
&pat_vec[..new_item_count],
|
||||
mk_sp(span.lo(), lo + BytePos(1)),
|
||||
)
|
||||
} else {
|
||||
(&pat_vec[..], span)
|
||||
};
|
||||
|
||||
let is_last_pat_dotdot = pat_vec.last().map_or(false, |p| p.is_dotdot());
|
||||
let add_comma = path_str.is_none() && pat_vec.len() == 1 && !is_last_pat_dotdot;
|
||||
let path_str = path_str.unwrap_or_default();
|
||||
|
||||
overflow::rewrite_with_parens(
|
||||
&context,
|
||||
&path_str,
|
||||
pat_vec.iter(),
|
||||
shape,
|
||||
span,
|
||||
context.config.max_width(),
|
||||
if add_comma {
|
||||
Some(SeparatorTactic::Always)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn count_wildcard_suffix_len(
|
||||
context: &RewriteContext<'_>,
|
||||
patterns: &[TuplePatField<'_>],
|
||||
span: Span,
|
||||
shape: Shape,
|
||||
) -> usize {
|
||||
let mut suffix_len = 0;
|
||||
|
||||
let items: Vec<_> = itemize_list(
|
||||
context.snippet_provider,
|
||||
patterns.iter(),
|
||||
")",
|
||||
",",
|
||||
|item| item.span().lo(),
|
||||
|item| item.span().hi(),
|
||||
|item| item.rewrite(context, shape),
|
||||
context.snippet_provider.span_after(span, "("),
|
||||
span.hi() - BytePos(1),
|
||||
false,
|
||||
)
|
||||
.collect();
|
||||
|
||||
for item in items.iter().rev().take_while(|i| match i.item {
|
||||
Some(ref internal_string) if internal_string == "_" => true,
|
||||
_ => false,
|
||||
}) {
|
||||
suffix_len += 1;
|
||||
|
||||
if item.has_comment() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
suffix_len
|
||||
}
|
||||
16
src/tools/rustfmt/src/release_channel.rs
Normal file
16
src/tools/rustfmt/src/release_channel.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/// Checks if we're in a nightly build.
|
||||
///
|
||||
/// The environment variable `CFG_RELEASE_CHANNEL` is set during the rustc bootstrap
|
||||
/// to "stable", "beta", or "nightly" depending on what toolchain is being built.
|
||||
/// If we are being built as part of the stable or beta toolchains, we want
|
||||
/// to disable unstable configuration options.
|
||||
///
|
||||
/// If we're being built by cargo (e.g., `cargo +nightly install rustfmt-nightly`),
|
||||
/// `CFG_RELEASE_CHANNEL` is not set. As we only support being built against the
|
||||
/// nightly compiler when installed from crates.io, default to nightly mode.
|
||||
#[macro_export]
|
||||
macro_rules! is_nightly_channel {
|
||||
() => {
|
||||
option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev")
|
||||
};
|
||||
}
|
||||
332
src/tools/rustfmt/src/reorder.rs
Normal file
332
src/tools/rustfmt/src/reorder.rs
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
//! Reorder items.
|
||||
//!
|
||||
//! `mod`, `extern crate` and `use` declarations are reordered in alphabetical
|
||||
//! order. Trait items are reordered in pre-determined order (associated types
|
||||
//! and constants comes before methods).
|
||||
|
||||
// FIXME(#2455): Reorder trait items.
|
||||
|
||||
use std::cmp::{Ord, Ordering};
|
||||
|
||||
use rustc_ast::ast;
|
||||
use rustc_span::{symbol::sym, Span};
|
||||
|
||||
use crate::config::{Config, GroupImportsTactic, ImportGranularity};
|
||||
use crate::imports::{flatten_use_trees, merge_use_trees, SharedPrefix, UseSegment, UseTree};
|
||||
use crate::items::{is_mod_decl, rewrite_extern_crate, rewrite_mod};
|
||||
use crate::lists::{itemize_list, write_list, ListFormatting, ListItem};
|
||||
use crate::rewrite::RewriteContext;
|
||||
use crate::shape::Shape;
|
||||
use crate::source_map::LineRangeUtils;
|
||||
use crate::spanned::Spanned;
|
||||
use crate::utils::{contains_skip, mk_sp};
|
||||
use crate::visitor::FmtVisitor;
|
||||
|
||||
/// Choose the ordering between the given two items.
|
||||
fn compare_items(a: &ast::Item, b: &ast::Item) -> Ordering {
|
||||
match (&a.kind, &b.kind) {
|
||||
(&ast::ItemKind::Mod(..), &ast::ItemKind::Mod(..)) => {
|
||||
a.ident.as_str().cmp(&b.ident.as_str())
|
||||
}
|
||||
(&ast::ItemKind::ExternCrate(ref a_name), &ast::ItemKind::ExternCrate(ref b_name)) => {
|
||||
// `extern crate foo as bar;`
|
||||
// ^^^ Comparing this.
|
||||
let a_orig_name = a_name.map_or_else(|| a.ident.as_str(), rustc_span::Symbol::as_str);
|
||||
let b_orig_name = b_name.map_or_else(|| b.ident.as_str(), rustc_span::Symbol::as_str);
|
||||
let result = a_orig_name.cmp(&b_orig_name);
|
||||
if result != Ordering::Equal {
|
||||
return result;
|
||||
}
|
||||
|
||||
// `extern crate foo as bar;`
|
||||
// ^^^ Comparing this.
|
||||
match (a_name, b_name) {
|
||||
(Some(..), None) => Ordering::Greater,
|
||||
(None, Some(..)) => Ordering::Less,
|
||||
(None, None) => Ordering::Equal,
|
||||
(Some(..), Some(..)) => a.ident.as_str().cmp(&b.ident.as_str()),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_reorderable_items(
|
||||
context: &RewriteContext<'_>,
|
||||
list_items: &[ListItem],
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
let fmt = ListFormatting::new(shape, context.config)
|
||||
.separator("")
|
||||
.align_comments(false);
|
||||
write_list(list_items, &fmt)
|
||||
}
|
||||
|
||||
fn rewrite_reorderable_item(
|
||||
context: &RewriteContext<'_>,
|
||||
item: &ast::Item,
|
||||
shape: Shape,
|
||||
) -> Option<String> {
|
||||
match item.kind {
|
||||
ast::ItemKind::ExternCrate(..) => rewrite_extern_crate(context, item, shape),
|
||||
ast::ItemKind::Mod(..) => rewrite_mod(context, item, shape),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewrite a list of items with reordering and/or regrouping. Every item
|
||||
/// in `items` must have the same `ast::ItemKind`. Whether reordering, regrouping,
|
||||
/// or both are done is determined from the `context`.
|
||||
fn rewrite_reorderable_or_regroupable_items(
|
||||
context: &RewriteContext<'_>,
|
||||
reorderable_items: &[&ast::Item],
|
||||
shape: Shape,
|
||||
span: Span,
|
||||
) -> Option<String> {
|
||||
match reorderable_items[0].kind {
|
||||
// FIXME: Remove duplicated code.
|
||||
ast::ItemKind::Use(..) => {
|
||||
let mut normalized_items: Vec<_> = reorderable_items
|
||||
.iter()
|
||||
.filter_map(|item| UseTree::from_ast_with_normalization(context, item))
|
||||
.collect();
|
||||
let cloned = normalized_items.clone();
|
||||
// Add comments before merging.
|
||||
let list_items = itemize_list(
|
||||
context.snippet_provider,
|
||||
cloned.iter(),
|
||||
"",
|
||||
";",
|
||||
|item| item.span().lo(),
|
||||
|item| item.span().hi(),
|
||||
|_item| Some("".to_owned()),
|
||||
span.lo(),
|
||||
span.hi(),
|
||||
false,
|
||||
);
|
||||
for (item, list_item) in normalized_items.iter_mut().zip(list_items) {
|
||||
item.list_item = Some(list_item.clone());
|
||||
}
|
||||
normalized_items = match context.config.imports_granularity() {
|
||||
ImportGranularity::Crate => merge_use_trees(normalized_items, SharedPrefix::Crate),
|
||||
ImportGranularity::Module => {
|
||||
merge_use_trees(normalized_items, SharedPrefix::Module)
|
||||
}
|
||||
ImportGranularity::Item => flatten_use_trees(normalized_items),
|
||||
ImportGranularity::Preserve => normalized_items,
|
||||
};
|
||||
|
||||
let mut regrouped_items = match context.config.group_imports() {
|
||||
GroupImportsTactic::Preserve => vec![normalized_items],
|
||||
GroupImportsTactic::StdExternalCrate => group_imports(normalized_items),
|
||||
};
|
||||
|
||||
if context.config.reorder_imports() {
|
||||
regrouped_items.iter_mut().for_each(|items| items.sort())
|
||||
}
|
||||
|
||||
// 4 = "use ", 1 = ";"
|
||||
let nested_shape = shape.offset_left(4)?.sub_width(1)?;
|
||||
let item_vec: Vec<_> = regrouped_items
|
||||
.into_iter()
|
||||
.filter(|use_group| !use_group.is_empty())
|
||||
.map(|use_group| {
|
||||
let item_vec: Vec<_> = use_group
|
||||
.into_iter()
|
||||
.map(|use_tree| ListItem {
|
||||
item: use_tree.rewrite_top_level(context, nested_shape),
|
||||
..use_tree.list_item.unwrap_or_else(ListItem::empty)
|
||||
})
|
||||
.collect();
|
||||
wrap_reorderable_items(context, &item_vec, nested_shape)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
let join_string = format!("\n\n{}", shape.indent.to_string(context.config));
|
||||
Some(item_vec.join(&join_string))
|
||||
}
|
||||
_ => {
|
||||
let list_items = itemize_list(
|
||||
context.snippet_provider,
|
||||
reorderable_items.iter(),
|
||||
"",
|
||||
";",
|
||||
|item| item.span().lo(),
|
||||
|item| item.span().hi(),
|
||||
|item| rewrite_reorderable_item(context, item, shape),
|
||||
span.lo(),
|
||||
span.hi(),
|
||||
false,
|
||||
);
|
||||
|
||||
let mut item_pair_vec: Vec<_> = list_items.zip(reorderable_items.iter()).collect();
|
||||
item_pair_vec.sort_by(|a, b| compare_items(a.1, b.1));
|
||||
let item_vec: Vec<_> = item_pair_vec.into_iter().map(|pair| pair.0).collect();
|
||||
|
||||
wrap_reorderable_items(context, &item_vec, shape)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_macro_use_attr(item: &ast::Item) -> bool {
|
||||
crate::attr::contains_name(&item.attrs, sym::macro_use)
|
||||
}
|
||||
|
||||
/// Divides imports into three groups, corresponding to standard, external
|
||||
/// and local imports. Sorts each subgroup.
|
||||
fn group_imports(uts: Vec<UseTree>) -> Vec<Vec<UseTree>> {
|
||||
let mut std_imports = Vec::new();
|
||||
let mut external_imports = Vec::new();
|
||||
let mut local_imports = Vec::new();
|
||||
|
||||
for ut in uts.into_iter() {
|
||||
if ut.path.is_empty() {
|
||||
external_imports.push(ut);
|
||||
continue;
|
||||
}
|
||||
match &ut.path[0] {
|
||||
UseSegment::Ident(id, _) => match id.as_ref() {
|
||||
"std" | "alloc" | "core" => std_imports.push(ut),
|
||||
_ => external_imports.push(ut),
|
||||
},
|
||||
UseSegment::Slf(_) | UseSegment::Super(_) | UseSegment::Crate(_) => {
|
||||
local_imports.push(ut)
|
||||
}
|
||||
// These are probably illegal here
|
||||
UseSegment::Glob | UseSegment::List(_) => external_imports.push(ut),
|
||||
}
|
||||
}
|
||||
|
||||
vec![std_imports, external_imports, local_imports]
|
||||
}
|
||||
|
||||
/// A simplified version of `ast::ItemKind`.
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
enum ReorderableItemKind {
|
||||
ExternCrate,
|
||||
Mod,
|
||||
Use,
|
||||
/// An item that cannot be reordered. Either has an unreorderable item kind
|
||||
/// or an `macro_use` attribute.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl ReorderableItemKind {
|
||||
fn from(item: &ast::Item) -> Self {
|
||||
match item.kind {
|
||||
_ if contains_macro_use_attr(item) | contains_skip(&item.attrs) => {
|
||||
ReorderableItemKind::Other
|
||||
}
|
||||
ast::ItemKind::ExternCrate(..) => ReorderableItemKind::ExternCrate,
|
||||
ast::ItemKind::Mod(..) if is_mod_decl(item) => ReorderableItemKind::Mod,
|
||||
ast::ItemKind::Use(..) => ReorderableItemKind::Use,
|
||||
_ => ReorderableItemKind::Other,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_same_item_kind(self, item: &ast::Item) -> bool {
|
||||
ReorderableItemKind::from(item) == self
|
||||
}
|
||||
|
||||
fn is_reorderable(self, config: &Config) -> bool {
|
||||
match self {
|
||||
ReorderableItemKind::ExternCrate => config.reorder_imports(),
|
||||
ReorderableItemKind::Mod => config.reorder_modules(),
|
||||
ReorderableItemKind::Use => config.reorder_imports(),
|
||||
ReorderableItemKind::Other => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_regroupable(self, config: &Config) -> bool {
|
||||
match self {
|
||||
ReorderableItemKind::ExternCrate
|
||||
| ReorderableItemKind::Mod
|
||||
| ReorderableItemKind::Other => false,
|
||||
ReorderableItemKind::Use => config.group_imports() != GroupImportsTactic::Preserve,
|
||||
}
|
||||
}
|
||||
|
||||
fn in_group(self, config: &Config) -> bool {
|
||||
match self {
|
||||
ReorderableItemKind::ExternCrate | ReorderableItemKind::Mod => true,
|
||||
ReorderableItemKind::Use => config.group_imports() == GroupImportsTactic::Preserve,
|
||||
ReorderableItemKind::Other => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, 'a: 'b> FmtVisitor<'a> {
|
||||
/// Format items with the same item kind and reorder them, regroup them, or
|
||||
/// both. If `in_group` is `true`, then the items separated by an empty line
|
||||
/// will not be reordered together.
|
||||
fn walk_reorderable_or_regroupable_items(
|
||||
&mut self,
|
||||
items: &[&ast::Item],
|
||||
item_kind: ReorderableItemKind,
|
||||
in_group: bool,
|
||||
) -> usize {
|
||||
let mut last = self.parse_sess.lookup_line_range(items[0].span());
|
||||
let item_length = items
|
||||
.iter()
|
||||
.take_while(|ppi| {
|
||||
item_kind.is_same_item_kind(&***ppi)
|
||||
&& (!in_group || {
|
||||
let current = self.parse_sess.lookup_line_range(ppi.span());
|
||||
let in_same_group = current.lo < last.hi + 2;
|
||||
last = current;
|
||||
in_same_group
|
||||
})
|
||||
})
|
||||
.count();
|
||||
let items = &items[..item_length];
|
||||
|
||||
let at_least_one_in_file_lines = items
|
||||
.iter()
|
||||
.any(|item| !out_of_file_lines_range!(self, item.span));
|
||||
|
||||
if at_least_one_in_file_lines && !items.is_empty() {
|
||||
let lo = items.first().unwrap().span().lo();
|
||||
let hi = items.last().unwrap().span().hi();
|
||||
let span = mk_sp(lo, hi);
|
||||
let rw = rewrite_reorderable_or_regroupable_items(
|
||||
&self.get_context(),
|
||||
items,
|
||||
self.shape(),
|
||||
span,
|
||||
);
|
||||
self.push_rewrite(span, rw);
|
||||
} else {
|
||||
for item in items {
|
||||
self.push_rewrite(item.span, None);
|
||||
}
|
||||
}
|
||||
|
||||
item_length
|
||||
}
|
||||
|
||||
/// Visits and format the given items. Items are reordered If they are
|
||||
/// consecutive and reorderable.
|
||||
pub(crate) fn visit_items_with_reordering(&mut self, mut items: &[&ast::Item]) {
|
||||
while !items.is_empty() {
|
||||
// If the next item is a `use`, `extern crate` or `mod`, then extract it and any
|
||||
// subsequent items that have the same item kind to be reordered within
|
||||
// `walk_reorderable_items`. Otherwise, just format the next item for output.
|
||||
let item_kind = ReorderableItemKind::from(items[0]);
|
||||
if item_kind.is_reorderable(self.config) || item_kind.is_regroupable(self.config) {
|
||||
let visited_items_num = self.walk_reorderable_or_regroupable_items(
|
||||
items,
|
||||
item_kind,
|
||||
item_kind.in_group(self.config),
|
||||
);
|
||||
let (_, rest) = items.split_at(visited_items_num);
|
||||
items = rest;
|
||||
} else {
|
||||
// Reaching here means items were not reordered. There must be at least
|
||||
// one item left in `items`, so calling `unwrap()` here is safe.
|
||||
let (item, rest) = items.split_first().unwrap();
|
||||
self.visit_item(item);
|
||||
items = rest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/tools/rustfmt/src/rewrite.rs
Normal file
98
src/tools/rustfmt/src/rewrite.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// A generic trait to abstract the rewriting of an element (of the AST).
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
use rustc_ast::ptr;
|
||||
use rustc_span::Span;
|
||||
|
||||
use crate::config::{Config, IndentStyle};
|
||||
use crate::shape::Shape;
|
||||
use crate::skip::SkipContext;
|
||||
use crate::syntux::session::ParseSess;
|
||||
use crate::visitor::SnippetProvider;
|
||||
use crate::FormatReport;
|
||||
|
||||
pub(crate) trait Rewrite {
|
||||
/// Rewrite self into shape.
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String>;
|
||||
}
|
||||
|
||||
impl<T: Rewrite> Rewrite for ptr::P<T> {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
(**self).rewrite(context, shape)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct RewriteContext<'a> {
|
||||
pub(crate) parse_sess: &'a ParseSess,
|
||||
pub(crate) config: &'a Config,
|
||||
pub(crate) inside_macro: Rc<Cell<bool>>,
|
||||
// Force block indent style even if we are using visual indent style.
|
||||
pub(crate) use_block: Cell<bool>,
|
||||
// When `is_if_else_block` is true, unindent the comment on top
|
||||
// of the `else` or `else if`.
|
||||
pub(crate) is_if_else_block: Cell<bool>,
|
||||
// When rewriting chain, veto going multi line except the last element
|
||||
pub(crate) force_one_line_chain: Cell<bool>,
|
||||
pub(crate) snippet_provider: &'a SnippetProvider,
|
||||
// Used for `format_snippet`
|
||||
pub(crate) macro_rewrite_failure: Cell<bool>,
|
||||
pub(crate) is_macro_def: bool,
|
||||
pub(crate) report: FormatReport,
|
||||
pub(crate) skip_context: SkipContext,
|
||||
pub(crate) skipped_range: Rc<RefCell<Vec<(usize, usize)>>>,
|
||||
}
|
||||
|
||||
pub(crate) struct InsideMacroGuard {
|
||||
is_nested_macro_context: bool,
|
||||
inside_macro_ref: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
impl InsideMacroGuard {
|
||||
pub(crate) fn is_nested(&self) -> bool {
|
||||
self.is_nested_macro_context
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InsideMacroGuard {
|
||||
fn drop(&mut self) {
|
||||
self.inside_macro_ref.replace(self.is_nested_macro_context);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RewriteContext<'a> {
|
||||
pub(crate) fn snippet(&self, span: Span) -> &str {
|
||||
self.snippet_provider.span_to_snippet(span).unwrap()
|
||||
}
|
||||
|
||||
/// Returns `true` if we should use block indent style for rewriting function call.
|
||||
pub(crate) fn use_block_indent(&self) -> bool {
|
||||
self.config.indent_style() == IndentStyle::Block || self.use_block.get()
|
||||
}
|
||||
|
||||
pub(crate) fn budget(&self, used_width: usize) -> usize {
|
||||
self.config.max_width().saturating_sub(used_width)
|
||||
}
|
||||
|
||||
pub(crate) fn inside_macro(&self) -> bool {
|
||||
self.inside_macro.get()
|
||||
}
|
||||
|
||||
pub(crate) fn enter_macro(&self) -> InsideMacroGuard {
|
||||
let is_nested_macro_context = self.inside_macro.replace(true);
|
||||
InsideMacroGuard {
|
||||
is_nested_macro_context,
|
||||
inside_macro_ref: self.inside_macro.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn leave_macro(&self) {
|
||||
self.inside_macro.replace(false);
|
||||
}
|
||||
|
||||
pub(crate) fn is_if_else_block(&self) -> bool {
|
||||
self.is_if_else_block.get()
|
||||
}
|
||||
}
|
||||
403
src/tools/rustfmt/src/rustfmt_diff.rs
Normal file
403
src/tools/rustfmt/src/rustfmt_diff.rs
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::config::{Color, Config, Verbosity};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DiffLine {
|
||||
Context(String),
|
||||
Expected(String),
|
||||
Resulting(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Mismatch {
|
||||
/// The line number in the formatted version.
|
||||
pub line_number: u32,
|
||||
/// The line number in the original version.
|
||||
pub line_number_orig: u32,
|
||||
/// The set of lines (context and old/new) in the mismatch.
|
||||
pub lines: Vec<DiffLine>,
|
||||
}
|
||||
|
||||
impl Mismatch {
|
||||
fn new(line_number: u32, line_number_orig: u32) -> Mismatch {
|
||||
Mismatch {
|
||||
line_number,
|
||||
line_number_orig,
|
||||
lines: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single span of changed lines, with 0 or more removed lines
|
||||
/// and a vector of 0 or more inserted lines.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ModifiedChunk {
|
||||
/// The first to be removed from the original text
|
||||
pub line_number_orig: u32,
|
||||
/// The number of lines which have been replaced
|
||||
pub lines_removed: u32,
|
||||
/// The new lines
|
||||
pub lines: Vec<String>,
|
||||
}
|
||||
|
||||
/// Set of changed sections of a file.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ModifiedLines {
|
||||
/// The set of changed chunks.
|
||||
pub chunks: Vec<ModifiedChunk>,
|
||||
}
|
||||
|
||||
impl From<Vec<Mismatch>> for ModifiedLines {
|
||||
fn from(mismatches: Vec<Mismatch>) -> ModifiedLines {
|
||||
let chunks = mismatches.into_iter().map(|mismatch| {
|
||||
let lines = mismatch.lines.iter();
|
||||
let num_removed = lines
|
||||
.filter(|line| match line {
|
||||
DiffLine::Resulting(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
.count();
|
||||
|
||||
let new_lines = mismatch.lines.into_iter().filter_map(|line| match line {
|
||||
DiffLine::Context(_) | DiffLine::Resulting(_) => None,
|
||||
DiffLine::Expected(str) => Some(str),
|
||||
});
|
||||
|
||||
ModifiedChunk {
|
||||
line_number_orig: mismatch.line_number_orig,
|
||||
lines_removed: num_removed as u32,
|
||||
lines: new_lines.collect(),
|
||||
}
|
||||
});
|
||||
|
||||
ModifiedLines {
|
||||
chunks: chunks.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a `Mismatch` into a serialized form, which just includes
|
||||
// enough information to modify the original file.
|
||||
// Each section starts with a line with three integers, space separated:
|
||||
// lineno num_removed num_added
|
||||
// followed by (`num_added`) lines of added text. The line numbers are
|
||||
// relative to the original file.
|
||||
impl fmt::Display for ModifiedLines {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for chunk in &self.chunks {
|
||||
writeln!(
|
||||
f,
|
||||
"{} {} {}",
|
||||
chunk.line_number_orig,
|
||||
chunk.lines_removed,
|
||||
chunk.lines.iter().count()
|
||||
)?;
|
||||
|
||||
for line in &chunk.lines {
|
||||
writeln!(f, "{}", line)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Allows to convert `Display`ed `ModifiedLines` back to the structural data.
|
||||
impl std::str::FromStr for ModifiedLines {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<ModifiedLines, ()> {
|
||||
let mut chunks = vec![];
|
||||
|
||||
let mut lines = s.lines();
|
||||
while let Some(header) = lines.next() {
|
||||
let mut header = header.split_whitespace();
|
||||
let (orig, rem, new_lines) = match (header.next(), header.next(), header.next()) {
|
||||
(Some(orig), Some(removed), Some(added)) => (orig, removed, added),
|
||||
_ => return Err(()),
|
||||
};
|
||||
let (orig, rem, new_lines): (u32, u32, usize) =
|
||||
match (orig.parse(), rem.parse(), new_lines.parse()) {
|
||||
(Ok(a), Ok(b), Ok(c)) => (a, b, c),
|
||||
_ => return Err(()),
|
||||
};
|
||||
let lines = lines.by_ref().take(new_lines);
|
||||
let lines: Vec<_> = lines.map(ToOwned::to_owned).collect();
|
||||
if lines.len() != new_lines {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
chunks.push(ModifiedChunk {
|
||||
line_number_orig: orig,
|
||||
lines_removed: rem,
|
||||
lines,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(ModifiedLines { chunks })
|
||||
}
|
||||
}
|
||||
|
||||
// This struct handles writing output to stdout and abstracts away the logic
|
||||
// of printing in color, if it's possible in the executing environment.
|
||||
pub(crate) struct OutputWriter {
|
||||
terminal: Option<Box<dyn term::Terminal<Output = io::Stdout>>>,
|
||||
}
|
||||
|
||||
impl OutputWriter {
|
||||
// Create a new OutputWriter instance based on the caller's preference
|
||||
// for colorized output and the capabilities of the terminal.
|
||||
pub(crate) fn new(color: Color) -> Self {
|
||||
if let Some(t) = term::stdout() {
|
||||
if color.use_colored_tty() && t.supports_color() {
|
||||
return OutputWriter { terminal: Some(t) };
|
||||
}
|
||||
}
|
||||
OutputWriter { terminal: None }
|
||||
}
|
||||
|
||||
// Write output in the optionally specified color. The output is written
|
||||
// in the specified color if this OutputWriter instance contains a
|
||||
// Terminal in its `terminal` field.
|
||||
pub(crate) fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) {
|
||||
match &mut self.terminal {
|
||||
Some(ref mut t) => {
|
||||
if let Some(color) = color {
|
||||
t.fg(color).unwrap();
|
||||
}
|
||||
writeln!(t, "{}", msg).unwrap();
|
||||
if color.is_some() {
|
||||
t.reset().unwrap();
|
||||
}
|
||||
}
|
||||
None => println!("{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Produces a diff between the expected output and actual output of rustfmt.
|
||||
pub(crate) fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
|
||||
let mut line_number = 1;
|
||||
let mut line_number_orig = 1;
|
||||
let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
|
||||
let mut lines_since_mismatch = context_size + 1;
|
||||
let mut results = Vec::new();
|
||||
let mut mismatch = Mismatch::new(0, 0);
|
||||
|
||||
for result in diff::lines(expected, actual) {
|
||||
match result {
|
||||
diff::Result::Left(str) => {
|
||||
if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
|
||||
results.push(mismatch);
|
||||
mismatch = Mismatch::new(
|
||||
line_number - context_queue.len() as u32,
|
||||
line_number_orig - context_queue.len() as u32,
|
||||
);
|
||||
}
|
||||
|
||||
while let Some(line) = context_queue.pop_front() {
|
||||
mismatch.lines.push(DiffLine::Context(line.to_owned()));
|
||||
}
|
||||
|
||||
mismatch.lines.push(DiffLine::Resulting(str.to_owned()));
|
||||
line_number_orig += 1;
|
||||
lines_since_mismatch = 0;
|
||||
}
|
||||
diff::Result::Right(str) => {
|
||||
if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
|
||||
results.push(mismatch);
|
||||
mismatch = Mismatch::new(
|
||||
line_number - context_queue.len() as u32,
|
||||
line_number_orig - context_queue.len() as u32,
|
||||
);
|
||||
}
|
||||
|
||||
while let Some(line) = context_queue.pop_front() {
|
||||
mismatch.lines.push(DiffLine::Context(line.to_owned()));
|
||||
}
|
||||
|
||||
mismatch.lines.push(DiffLine::Expected(str.to_owned()));
|
||||
line_number += 1;
|
||||
lines_since_mismatch = 0;
|
||||
}
|
||||
diff::Result::Both(str, _) => {
|
||||
if context_queue.len() >= context_size {
|
||||
let _ = context_queue.pop_front();
|
||||
}
|
||||
|
||||
if lines_since_mismatch < context_size {
|
||||
mismatch.lines.push(DiffLine::Context(str.to_owned()));
|
||||
} else if context_size > 0 {
|
||||
context_queue.push_back(str);
|
||||
}
|
||||
|
||||
line_number += 1;
|
||||
line_number_orig += 1;
|
||||
lines_since_mismatch += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.push(mismatch);
|
||||
results.remove(0);
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
pub(crate) fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, config: &Config)
|
||||
where
|
||||
F: Fn(u32) -> String,
|
||||
{
|
||||
let color = config.color();
|
||||
let line_terminator = if config.verbose() == Verbosity::Verbose {
|
||||
"⏎"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let mut writer = OutputWriter::new(color);
|
||||
|
||||
for mismatch in diff {
|
||||
let title = get_section_title(mismatch.line_number_orig);
|
||||
writer.writeln(&title, None);
|
||||
|
||||
for line in mismatch.lines {
|
||||
match line {
|
||||
DiffLine::Context(ref str) => {
|
||||
writer.writeln(&format!(" {}{}", str, line_terminator), None)
|
||||
}
|
||||
DiffLine::Expected(ref str) => writer.writeln(
|
||||
&format!("+{}{}", str, line_terminator),
|
||||
Some(term::color::GREEN),
|
||||
),
|
||||
DiffLine::Resulting(ref str) => writer.writeln(
|
||||
&format!("-{}{}", str, line_terminator),
|
||||
Some(term::color::RED),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::DiffLine::*;
|
||||
use super::{make_diff, Mismatch};
|
||||
use super::{ModifiedChunk, ModifiedLines};
|
||||
|
||||
#[test]
|
||||
fn diff_simple() {
|
||||
let src = "one\ntwo\nthree\nfour\nfive\n";
|
||||
let dest = "one\ntwo\ntrois\nfour\nfive\n";
|
||||
let diff = make_diff(src, dest, 1);
|
||||
assert_eq!(
|
||||
diff,
|
||||
vec![Mismatch {
|
||||
line_number: 2,
|
||||
line_number_orig: 2,
|
||||
lines: vec![
|
||||
Context("two".to_owned()),
|
||||
Resulting("three".to_owned()),
|
||||
Expected("trois".to_owned()),
|
||||
Context("four".to_owned()),
|
||||
],
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_simple2() {
|
||||
let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n";
|
||||
let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n";
|
||||
let diff = make_diff(src, dest, 1);
|
||||
assert_eq!(
|
||||
diff,
|
||||
vec![
|
||||
Mismatch {
|
||||
line_number: 2,
|
||||
line_number_orig: 2,
|
||||
lines: vec![
|
||||
Context("two".to_owned()),
|
||||
Resulting("three".to_owned()),
|
||||
Expected("trois".to_owned()),
|
||||
Context("four".to_owned()),
|
||||
],
|
||||
},
|
||||
Mismatch {
|
||||
line_number: 5,
|
||||
line_number_orig: 5,
|
||||
lines: vec![
|
||||
Resulting("five".to_owned()),
|
||||
Expected("cinq".to_owned()),
|
||||
Context("six".to_owned()),
|
||||
],
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_zerocontext() {
|
||||
let src = "one\ntwo\nthree\nfour\nfive\n";
|
||||
let dest = "one\ntwo\ntrois\nfour\nfive\n";
|
||||
let diff = make_diff(src, dest, 0);
|
||||
assert_eq!(
|
||||
diff,
|
||||
vec![Mismatch {
|
||||
line_number: 3,
|
||||
line_number_orig: 3,
|
||||
lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())],
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_trailing_newline() {
|
||||
let src = "one\ntwo\nthree\nfour\nfive";
|
||||
let dest = "one\ntwo\nthree\nfour\nfive\n";
|
||||
let diff = make_diff(src, dest, 1);
|
||||
assert_eq!(
|
||||
diff,
|
||||
vec![Mismatch {
|
||||
line_number: 5,
|
||||
line_number_orig: 5,
|
||||
lines: vec![Context("five".to_owned()), Expected("".to_owned())],
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modified_lines_from_str() {
|
||||
use std::str::FromStr;
|
||||
|
||||
let src = "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n struct Test {}";
|
||||
let lines = ModifiedLines::from_str(src).unwrap();
|
||||
assert_eq!(
|
||||
lines,
|
||||
ModifiedLines {
|
||||
chunks: vec![
|
||||
ModifiedChunk {
|
||||
line_number_orig: 1,
|
||||
lines_removed: 6,
|
||||
lines: vec!["fn some() {}".to_owned(), "fn main() {}".to_owned(),]
|
||||
},
|
||||
ModifiedChunk {
|
||||
line_number_orig: 25,
|
||||
lines_removed: 3,
|
||||
lines: vec![" struct Test {}".to_owned()]
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
let src = "1 5 3";
|
||||
assert_eq!(ModifiedLines::from_str(src), Err(()));
|
||||
|
||||
let src = "1 5 3\na\nb";
|
||||
assert_eq!(ModifiedLines::from_str(src), Err(()));
|
||||
}
|
||||
}
|
||||
373
src/tools/rustfmt/src/shape.rs
Normal file
373
src/tools/rustfmt/src/shape.rs
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
use std::borrow::Cow;
|
||||
use std::cmp::min;
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
use crate::Config;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct Indent {
|
||||
// Width of the block indent, in characters. Must be a multiple of
|
||||
// Config::tab_spaces.
|
||||
pub(crate) block_indent: usize,
|
||||
// Alignment in characters.
|
||||
pub(crate) alignment: usize,
|
||||
}
|
||||
|
||||
// INDENT_BUFFER.len() = 81
|
||||
const INDENT_BUFFER_LEN: usize = 80;
|
||||
const INDENT_BUFFER: &str =
|
||||
"\n ";
|
||||
|
||||
impl Indent {
|
||||
pub(crate) fn new(block_indent: usize, alignment: usize) -> Indent {
|
||||
Indent {
|
||||
block_indent,
|
||||
alignment,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_width(config: &Config, width: usize) -> Indent {
|
||||
if config.hard_tabs() {
|
||||
let tab_num = width / config.tab_spaces();
|
||||
let alignment = width % config.tab_spaces();
|
||||
Indent::new(config.tab_spaces() * tab_num, alignment)
|
||||
} else {
|
||||
Indent::new(width, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn empty() -> Indent {
|
||||
Indent::new(0, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn block_only(&self) -> Indent {
|
||||
Indent {
|
||||
block_indent: self.block_indent,
|
||||
alignment: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn block_indent(mut self, config: &Config) -> Indent {
|
||||
self.block_indent += config.tab_spaces();
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn block_unindent(mut self, config: &Config) -> Indent {
|
||||
if self.block_indent < config.tab_spaces() {
|
||||
Indent::new(self.block_indent, 0)
|
||||
} else {
|
||||
self.block_indent -= config.tab_spaces();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn width(&self) -> usize {
|
||||
self.block_indent + self.alignment
|
||||
}
|
||||
|
||||
pub(crate) fn to_string(&self, config: &Config) -> Cow<'static, str> {
|
||||
self.to_string_inner(config, 1)
|
||||
}
|
||||
|
||||
pub(crate) fn to_string_with_newline(&self, config: &Config) -> Cow<'static, str> {
|
||||
self.to_string_inner(config, 0)
|
||||
}
|
||||
|
||||
fn to_string_inner(&self, config: &Config, offset: usize) -> Cow<'static, str> {
|
||||
let (num_tabs, num_spaces) = if config.hard_tabs() {
|
||||
(self.block_indent / config.tab_spaces(), self.alignment)
|
||||
} else {
|
||||
(0, self.width())
|
||||
};
|
||||
let num_chars = num_tabs + num_spaces;
|
||||
if num_tabs == 0 && num_chars + offset <= INDENT_BUFFER_LEN {
|
||||
Cow::from(&INDENT_BUFFER[offset..=num_chars])
|
||||
} else {
|
||||
let mut indent = String::with_capacity(num_chars + if offset == 0 { 1 } else { 0 });
|
||||
if offset == 0 {
|
||||
indent.push('\n');
|
||||
}
|
||||
for _ in 0..num_tabs {
|
||||
indent.push('\t')
|
||||
}
|
||||
for _ in 0..num_spaces {
|
||||
indent.push(' ')
|
||||
}
|
||||
Cow::from(indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Indent {
|
||||
type Output = Indent;
|
||||
|
||||
fn add(self, rhs: Indent) -> Indent {
|
||||
Indent {
|
||||
block_indent: self.block_indent + rhs.block_indent,
|
||||
alignment: self.alignment + rhs.alignment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Indent {
|
||||
type Output = Indent;
|
||||
|
||||
fn sub(self, rhs: Indent) -> Indent {
|
||||
Indent::new(
|
||||
self.block_indent - rhs.block_indent,
|
||||
self.alignment - rhs.alignment,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<usize> for Indent {
|
||||
type Output = Indent;
|
||||
|
||||
fn add(self, rhs: usize) -> Indent {
|
||||
Indent::new(self.block_indent, self.alignment + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<usize> for Indent {
|
||||
type Output = Indent;
|
||||
|
||||
fn sub(self, rhs: usize) -> Indent {
|
||||
Indent::new(self.block_indent, self.alignment - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
// 8096 is close enough to infinite for rustfmt.
|
||||
const INFINITE_SHAPE_WIDTH: usize = 8096;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct Shape {
|
||||
pub(crate) width: usize,
|
||||
// The current indentation of code.
|
||||
pub(crate) indent: Indent,
|
||||
// Indentation + any already emitted text on the first line of the current
|
||||
// statement.
|
||||
pub(crate) offset: usize,
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
/// `indent` is the indentation of the first line. The next lines
|
||||
/// should begin with at least `indent` spaces (except backwards
|
||||
/// indentation). The first line should not begin with indentation.
|
||||
/// `width` is the maximum number of characters on the last line
|
||||
/// (excluding `indent`). The width of other lines is not limited by
|
||||
/// `width`.
|
||||
/// Note that in reality, we sometimes use width for lines other than the
|
||||
/// last (i.e., we are conservative).
|
||||
// .......*-------*
|
||||
// | |
|
||||
// | *-*
|
||||
// *-----|
|
||||
// |<------------>| max width
|
||||
// |<---->| indent
|
||||
// |<--->| width
|
||||
pub(crate) fn legacy(width: usize, indent: Indent) -> Shape {
|
||||
Shape {
|
||||
width,
|
||||
indent,
|
||||
offset: indent.alignment,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn indented(indent: Indent, config: &Config) -> Shape {
|
||||
Shape {
|
||||
width: config.max_width().saturating_sub(indent.width()),
|
||||
indent,
|
||||
offset: indent.alignment,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_max_width(&self, config: &Config) -> Shape {
|
||||
Shape {
|
||||
width: config.max_width().saturating_sub(self.indent.width()),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn visual_indent(&self, extra_width: usize) -> Shape {
|
||||
let alignment = self.offset + extra_width;
|
||||
Shape {
|
||||
width: self.width,
|
||||
indent: Indent::new(self.indent.block_indent, alignment),
|
||||
offset: alignment,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn block_indent(&self, extra_width: usize) -> Shape {
|
||||
if self.indent.alignment == 0 {
|
||||
Shape {
|
||||
width: self.width,
|
||||
indent: Indent::new(self.indent.block_indent + extra_width, 0),
|
||||
offset: 0,
|
||||
}
|
||||
} else {
|
||||
Shape {
|
||||
width: self.width,
|
||||
indent: self.indent + extra_width,
|
||||
offset: self.indent.alignment + extra_width,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn block_left(&self, width: usize) -> Option<Shape> {
|
||||
self.block_indent(width).sub_width(width)
|
||||
}
|
||||
|
||||
pub(crate) fn add_offset(&self, extra_width: usize) -> Shape {
|
||||
Shape {
|
||||
offset: self.offset + extra_width,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn block(&self) -> Shape {
|
||||
Shape {
|
||||
indent: self.indent.block_only(),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn saturating_sub_width(&self, width: usize) -> Shape {
|
||||
self.sub_width(width).unwrap_or(Shape { width: 0, ..*self })
|
||||
}
|
||||
|
||||
pub(crate) fn sub_width(&self, width: usize) -> Option<Shape> {
|
||||
Some(Shape {
|
||||
width: self.width.checked_sub(width)?,
|
||||
..*self
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn shrink_left(&self, width: usize) -> Option<Shape> {
|
||||
Some(Shape {
|
||||
width: self.width.checked_sub(width)?,
|
||||
indent: self.indent + width,
|
||||
offset: self.offset + width,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn offset_left(&self, width: usize) -> Option<Shape> {
|
||||
self.add_offset(width).sub_width(width)
|
||||
}
|
||||
|
||||
pub(crate) fn used_width(&self) -> usize {
|
||||
self.indent.block_indent + self.offset
|
||||
}
|
||||
|
||||
pub(crate) fn rhs_overhead(&self, config: &Config) -> usize {
|
||||
config
|
||||
.max_width()
|
||||
.saturating_sub(self.used_width() + self.width)
|
||||
}
|
||||
|
||||
pub(crate) fn comment(&self, config: &Config) -> Shape {
|
||||
let width = min(
|
||||
self.width,
|
||||
config.comment_width().saturating_sub(self.indent.width()),
|
||||
);
|
||||
Shape { width, ..*self }
|
||||
}
|
||||
|
||||
pub(crate) fn to_string_with_newline(&self, config: &Config) -> Cow<'static, str> {
|
||||
let mut offset_indent = self.indent;
|
||||
offset_indent.alignment = self.offset;
|
||||
offset_indent.to_string_inner(config, 0)
|
||||
}
|
||||
|
||||
/// Creates a `Shape` with a virtually infinite width.
|
||||
pub(crate) fn infinite_width(&self) -> Shape {
|
||||
Shape {
|
||||
width: INFINITE_SHAPE_WIDTH,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn indent_add_sub() {
|
||||
let indent = Indent::new(4, 8) + Indent::new(8, 12);
|
||||
assert_eq!(12, indent.block_indent);
|
||||
assert_eq!(20, indent.alignment);
|
||||
|
||||
let indent = indent - Indent::new(4, 4);
|
||||
assert_eq!(8, indent.block_indent);
|
||||
assert_eq!(16, indent.alignment);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent_add_sub_alignment() {
|
||||
let indent = Indent::new(4, 8) + 4;
|
||||
assert_eq!(4, indent.block_indent);
|
||||
assert_eq!(12, indent.alignment);
|
||||
|
||||
let indent = indent - 4;
|
||||
assert_eq!(4, indent.block_indent);
|
||||
assert_eq!(8, indent.alignment);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent_to_string_spaces() {
|
||||
let config = Config::default();
|
||||
let indent = Indent::new(4, 8);
|
||||
|
||||
// 12 spaces
|
||||
assert_eq!(" ", indent.to_string(&config));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent_to_string_hard_tabs() {
|
||||
let mut config = Config::default();
|
||||
config.set().hard_tabs(true);
|
||||
let indent = Indent::new(8, 4);
|
||||
|
||||
// 2 tabs + 4 spaces
|
||||
assert_eq!("\t\t ", indent.to_string(&config));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_visual_indent() {
|
||||
let config = Config::default();
|
||||
let indent = Indent::new(4, 8);
|
||||
let shape = Shape::legacy(config.max_width(), indent);
|
||||
let shape = shape.visual_indent(20);
|
||||
|
||||
assert_eq!(config.max_width(), shape.width);
|
||||
assert_eq!(4, shape.indent.block_indent);
|
||||
assert_eq!(28, shape.indent.alignment);
|
||||
assert_eq!(28, shape.offset);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_block_indent_without_alignment() {
|
||||
let config = Config::default();
|
||||
let indent = Indent::new(4, 0);
|
||||
let shape = Shape::legacy(config.max_width(), indent);
|
||||
let shape = shape.block_indent(20);
|
||||
|
||||
assert_eq!(config.max_width(), shape.width);
|
||||
assert_eq!(24, shape.indent.block_indent);
|
||||
assert_eq!(0, shape.indent.alignment);
|
||||
assert_eq!(0, shape.offset);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_block_indent_with_alignment() {
|
||||
let config = Config::default();
|
||||
let indent = Indent::new(4, 8);
|
||||
let shape = Shape::legacy(config.max_width(), indent);
|
||||
let shape = shape.block_indent(20);
|
||||
|
||||
assert_eq!(config.max_width(), shape.width);
|
||||
assert_eq!(4, shape.indent.block_indent);
|
||||
assert_eq!(28, shape.indent.alignment);
|
||||
assert_eq!(28, shape.offset);
|
||||
}
|
||||
}
|
||||
76
src/tools/rustfmt/src/skip.rs
Normal file
76
src/tools/rustfmt/src/skip.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
//! Module that contains skip related stuffs.
|
||||
|
||||
use rustc_ast::ast;
|
||||
use rustc_ast_pretty::pprust;
|
||||
|
||||
/// Take care of skip name stack. You can update it by attributes slice or
|
||||
/// by other context. Query this context to know if you need skip a block.
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct SkipContext {
|
||||
macros: Vec<String>,
|
||||
attributes: Vec<String>,
|
||||
}
|
||||
|
||||
impl SkipContext {
|
||||
pub(crate) fn update_with_attrs(&mut self, attrs: &[ast::Attribute]) {
|
||||
self.macros.append(&mut get_skip_names("macros", attrs));
|
||||
self.attributes
|
||||
.append(&mut get_skip_names("attributes", attrs));
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self, mut other: SkipContext) {
|
||||
self.macros.append(&mut other.macros);
|
||||
self.attributes.append(&mut other.attributes);
|
||||
}
|
||||
|
||||
pub(crate) fn skip_macro(&self, name: &str) -> bool {
|
||||
self.macros.iter().any(|n| n == name)
|
||||
}
|
||||
|
||||
pub(crate) fn skip_attribute(&self, name: &str) -> bool {
|
||||
self.attributes.iter().any(|n| n == name)
|
||||
}
|
||||
}
|
||||
|
||||
static RUSTFMT: &'static str = "rustfmt";
|
||||
static SKIP: &'static str = "skip";
|
||||
|
||||
/// Say if you're playing with `rustfmt`'s skip attribute
|
||||
pub(crate) fn is_skip_attr(segments: &[ast::PathSegment]) -> bool {
|
||||
if segments.len() < 2 || segments[0].ident.to_string() != RUSTFMT {
|
||||
return false;
|
||||
}
|
||||
match segments.len() {
|
||||
2 => segments[1].ident.to_string() == SKIP,
|
||||
3 => {
|
||||
segments[1].ident.to_string() == SKIP
|
||||
&& ["macros", "attributes"]
|
||||
.iter()
|
||||
.any(|&n| n == &pprust::path_segment_to_string(&segments[2]))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_skip_names(kind: &str, attrs: &[ast::Attribute]) -> Vec<String> {
|
||||
let mut skip_names = vec![];
|
||||
let path = format!("{}::{}::{}", RUSTFMT, SKIP, kind);
|
||||
for attr in attrs {
|
||||
// rustc_ast::ast::Path is implemented partialEq
|
||||
// but it is designed for segments.len() == 1
|
||||
if let ast::AttrKind::Normal(attr_item, _) = &attr.kind {
|
||||
if pprust::path_to_string(&attr_item.path) != path {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(list) = attr.meta_item_list() {
|
||||
for nested_meta_item in list {
|
||||
if let Some(name) = nested_meta_item.ident() {
|
||||
skip_names.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
skip_names
|
||||
}
|
||||
105
src/tools/rustfmt/src/source_file.rs
Normal file
105
src/tools/rustfmt/src/source_file.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::config::FileName;
|
||||
use crate::emitter::{self, Emitter};
|
||||
use crate::syntux::session::ParseSess;
|
||||
use crate::NewlineStyle;
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::config::Config;
|
||||
#[cfg(test)]
|
||||
use crate::create_emitter;
|
||||
#[cfg(test)]
|
||||
use crate::formatting::FileRecord;
|
||||
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
|
||||
// Append a newline to the end of each file.
|
||||
pub(crate) fn append_newline(s: &mut String) {
|
||||
s.push_str("\n");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn write_all_files<T>(
|
||||
source_file: &[FileRecord],
|
||||
out: &mut T,
|
||||
config: &Config,
|
||||
) -> Result<(), io::Error>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
let mut emitter = create_emitter(config);
|
||||
|
||||
emitter.emit_header(out)?;
|
||||
for &(ref filename, ref text) in source_file {
|
||||
write_file(
|
||||
None,
|
||||
filename,
|
||||
text,
|
||||
out,
|
||||
&mut *emitter,
|
||||
config.newline_style(),
|
||||
)?;
|
||||
}
|
||||
emitter.emit_footer(out)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn write_file<T>(
|
||||
parse_sess: Option<&ParseSess>,
|
||||
filename: &FileName,
|
||||
formatted_text: &str,
|
||||
out: &mut T,
|
||||
emitter: &mut dyn Emitter,
|
||||
newline_style: NewlineStyle,
|
||||
) -> Result<emitter::EmitterResult, io::Error>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
fn ensure_real_path(filename: &FileName) -> &Path {
|
||||
match *filename {
|
||||
FileName::Real(ref path) => path,
|
||||
_ => panic!("cannot format `{}` and emit to files", filename),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FileName> for rustc_span::FileName {
|
||||
fn from(filename: &FileName) -> rustc_span::FileName {
|
||||
match filename {
|
||||
FileName::Real(path) => {
|
||||
rustc_span::FileName::Real(rustc_span::RealFileName::LocalPath(path.to_owned()))
|
||||
}
|
||||
FileName::Stdin => rustc_span::FileName::Custom("stdin".to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SourceFile's in the SourceMap will always have Unix-style line endings
|
||||
// See: https://github.com/rust-lang/rustfmt/issues/3850
|
||||
// So if the user has explicitly overridden the rustfmt `newline_style`
|
||||
// config and `filename` is FileName::Real, then we must check the file system
|
||||
// to get the original file value in order to detect newline_style conflicts.
|
||||
// Otherwise, parse session is around (cfg(not(test))) and newline_style has been
|
||||
// left as the default value, then try getting source from the parse session
|
||||
// source map instead of hitting the file system. This also supports getting
|
||||
// original text for `FileName::Stdin`.
|
||||
let original_text = if newline_style != NewlineStyle::Auto && *filename != FileName::Stdin {
|
||||
Lrc::new(fs::read_to_string(ensure_real_path(filename))?)
|
||||
} else {
|
||||
match parse_sess.and_then(|sess| sess.get_original_snippet(filename)) {
|
||||
Some(ori) => ori,
|
||||
None => Lrc::new(fs::read_to_string(ensure_real_path(filename))?),
|
||||
}
|
||||
};
|
||||
|
||||
let formatted_file = emitter::FormattedFile {
|
||||
filename,
|
||||
original_text: original_text.as_str(),
|
||||
formatted_text,
|
||||
};
|
||||
|
||||
emitter.emit_formatted_file(out, formatted_file)
|
||||
}
|
||||
82
src/tools/rustfmt/src/source_map.rs
Normal file
82
src/tools/rustfmt/src/source_map.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
//! This module contains utilities that work with the `SourceMap` from `libsyntax`/`syntex_syntax`.
|
||||
//! This includes extension traits and methods for looking up spans and line ranges for AST nodes.
|
||||
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
use crate::comment::FindUncommented;
|
||||
use crate::config::file_lines::LineRange;
|
||||
use crate::visitor::SnippetProvider;
|
||||
|
||||
pub(crate) trait SpanUtils {
|
||||
fn span_after(&self, original: Span, needle: &str) -> BytePos;
|
||||
fn span_after_last(&self, original: Span, needle: &str) -> BytePos;
|
||||
fn span_before(&self, original: Span, needle: &str) -> BytePos;
|
||||
fn span_before_last(&self, original: Span, needle: &str) -> BytePos;
|
||||
fn opt_span_after(&self, original: Span, needle: &str) -> Option<BytePos>;
|
||||
fn opt_span_before(&self, original: Span, needle: &str) -> Option<BytePos>;
|
||||
}
|
||||
|
||||
pub(crate) trait LineRangeUtils {
|
||||
/// Returns the `LineRange` that corresponds to `span` in `self`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `span` crosses a file boundary, which shouldn't happen.
|
||||
fn lookup_line_range(&self, span: Span) -> LineRange;
|
||||
}
|
||||
|
||||
impl SpanUtils for SnippetProvider {
|
||||
fn span_after(&self, original: Span, needle: &str) -> BytePos {
|
||||
self.opt_span_after(original, needle).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"bad span: `{}`: `{}`",
|
||||
needle,
|
||||
self.span_to_snippet(original).unwrap()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn span_after_last(&self, original: Span, needle: &str) -> BytePos {
|
||||
let snippet = self.span_to_snippet(original).unwrap();
|
||||
let mut offset = 0;
|
||||
|
||||
while let Some(additional_offset) = snippet[offset..].find_uncommented(needle) {
|
||||
offset += additional_offset + needle.len();
|
||||
}
|
||||
|
||||
original.lo() + BytePos(offset as u32)
|
||||
}
|
||||
|
||||
fn span_before(&self, original: Span, needle: &str) -> BytePos {
|
||||
self.opt_span_before(original, needle).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"bad span: `{}`: `{}`",
|
||||
needle,
|
||||
self.span_to_snippet(original).unwrap()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn span_before_last(&self, original: Span, needle: &str) -> BytePos {
|
||||
let snippet = self.span_to_snippet(original).unwrap();
|
||||
let mut offset = 0;
|
||||
|
||||
while let Some(additional_offset) = snippet[offset..].find_uncommented(needle) {
|
||||
offset += additional_offset + needle.len();
|
||||
}
|
||||
|
||||
original.lo() + BytePos(offset as u32 - 1)
|
||||
}
|
||||
|
||||
fn opt_span_after(&self, original: Span, needle: &str) -> Option<BytePos> {
|
||||
self.opt_span_before(original, needle)
|
||||
.map(|bytepos| bytepos + BytePos(needle.len() as u32))
|
||||
}
|
||||
|
||||
fn opt_span_before(&self, original: Span, needle: &str) -> Option<BytePos> {
|
||||
let snippet = self.span_to_snippet(original)?;
|
||||
let offset = snippet.find_uncommented(needle)?;
|
||||
|
||||
Some(original.lo() + BytePos(offset as u32))
|
||||
}
|
||||
}
|
||||
206
src/tools/rustfmt/src/spanned.rs
Normal file
206
src/tools/rustfmt/src/spanned.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
use std::cmp::max;
|
||||
|
||||
use rustc_ast::{ast, ptr};
|
||||
use rustc_span::{source_map, Span};
|
||||
|
||||
use crate::macros::MacroArg;
|
||||
use crate::utils::{mk_sp, outer_attributes};
|
||||
|
||||
/// Spanned returns a span including attributes, if available.
|
||||
pub(crate) trait Spanned {
|
||||
fn span(&self) -> Span;
|
||||
}
|
||||
|
||||
impl<T: Spanned> Spanned for ptr::P<T> {
|
||||
fn span(&self) -> Span {
|
||||
(**self).span()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Spanned for source_map::Spanned<T> {
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! span_with_attrs_lo_hi {
|
||||
($this:ident, $lo:expr, $hi:expr) => {{
|
||||
let attrs = outer_attributes(&$this.attrs);
|
||||
if attrs.is_empty() {
|
||||
mk_sp($lo, $hi)
|
||||
} else {
|
||||
mk_sp(attrs[0].span.lo(), $hi)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! span_with_attrs {
|
||||
($this:ident) => {
|
||||
span_with_attrs_lo_hi!($this, $this.span.lo(), $this.span.hi())
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! implement_spanned {
|
||||
($this:ty) => {
|
||||
impl Spanned for $this {
|
||||
fn span(&self) -> Span {
|
||||
span_with_attrs!(self)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Implement `Spanned` for structs with `attrs` field.
|
||||
implement_spanned!(ast::AssocItem);
|
||||
implement_spanned!(ast::Expr);
|
||||
implement_spanned!(ast::ExprField);
|
||||
implement_spanned!(ast::ForeignItem);
|
||||
implement_spanned!(ast::Item);
|
||||
implement_spanned!(ast::Local);
|
||||
|
||||
impl Spanned for ast::Stmt {
|
||||
fn span(&self) -> Span {
|
||||
match self.kind {
|
||||
ast::StmtKind::Local(ref local) => mk_sp(local.span().lo(), self.span.hi()),
|
||||
ast::StmtKind::Item(ref item) => mk_sp(item.span().lo(), self.span.hi()),
|
||||
ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => {
|
||||
mk_sp(expr.span().lo(), self.span.hi())
|
||||
}
|
||||
ast::StmtKind::MacCall(ref mac_stmt) => {
|
||||
if mac_stmt.attrs.is_empty() {
|
||||
self.span
|
||||
} else {
|
||||
mk_sp(mac_stmt.attrs[0].span.lo(), self.span.hi())
|
||||
}
|
||||
}
|
||||
ast::StmtKind::Empty => self.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::Pat {
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::Ty {
|
||||
fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::Arm {
|
||||
fn span(&self) -> Span {
|
||||
let lo = if self.attrs.is_empty() {
|
||||
self.pat.span.lo()
|
||||
} else {
|
||||
self.attrs[0].span.lo()
|
||||
};
|
||||
span_with_attrs_lo_hi!(self, lo, self.body.span.hi())
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::Param {
|
||||
fn span(&self) -> Span {
|
||||
if crate::items::is_named_param(self) {
|
||||
mk_sp(self.pat.span.lo(), self.ty.span.hi())
|
||||
} else {
|
||||
self.ty.span
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::GenericParam {
|
||||
fn span(&self) -> Span {
|
||||
let lo = if let ast::GenericParamKind::Const {
|
||||
ty: _,
|
||||
kw_span,
|
||||
default: _,
|
||||
} = self.kind
|
||||
{
|
||||
kw_span.lo()
|
||||
} else if self.attrs.is_empty() {
|
||||
self.ident.span.lo()
|
||||
} else {
|
||||
self.attrs[0].span.lo()
|
||||
};
|
||||
let hi = if self.bounds.is_empty() {
|
||||
self.ident.span.hi()
|
||||
} else {
|
||||
self.bounds.last().unwrap().span().hi()
|
||||
};
|
||||
let ty_hi = if let ast::GenericParamKind::Type {
|
||||
default: Some(ref ty),
|
||||
}
|
||||
| ast::GenericParamKind::Const { ref ty, .. } = self.kind
|
||||
{
|
||||
ty.span().hi()
|
||||
} else {
|
||||
hi
|
||||
};
|
||||
mk_sp(lo, max(hi, ty_hi))
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::FieldDef {
|
||||
fn span(&self) -> Span {
|
||||
span_with_attrs_lo_hi!(self, self.span.lo(), self.ty.span.hi())
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::WherePredicate {
|
||||
fn span(&self) -> Span {
|
||||
match *self {
|
||||
ast::WherePredicate::BoundPredicate(ref p) => p.span,
|
||||
ast::WherePredicate::RegionPredicate(ref p) => p.span,
|
||||
ast::WherePredicate::EqPredicate(ref p) => p.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::FnRetTy {
|
||||
fn span(&self) -> Span {
|
||||
match *self {
|
||||
ast::FnRetTy::Default(span) => span,
|
||||
ast::FnRetTy::Ty(ref ty) => ty.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::GenericArg {
|
||||
fn span(&self) -> Span {
|
||||
match *self {
|
||||
ast::GenericArg::Lifetime(ref lt) => lt.ident.span,
|
||||
ast::GenericArg::Type(ref ty) => ty.span(),
|
||||
ast::GenericArg::Const(ref _const) => _const.value.span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::GenericBound {
|
||||
fn span(&self) -> Span {
|
||||
match *self {
|
||||
ast::GenericBound::Trait(ref ptr, _) => ptr.span,
|
||||
ast::GenericBound::Outlives(ref l) => l.ident.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for MacroArg {
|
||||
fn span(&self) -> Span {
|
||||
match *self {
|
||||
MacroArg::Expr(ref expr) => expr.span(),
|
||||
MacroArg::Ty(ref ty) => ty.span(),
|
||||
MacroArg::Pat(ref pat) => pat.span(),
|
||||
MacroArg::Item(ref item) => item.span(),
|
||||
MacroArg::Keyword(_, span) => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned for ast::NestedMetaItem {
|
||||
fn span(&self) -> Span {
|
||||
self.span()
|
||||
}
|
||||
}
|
||||
116
src/tools/rustfmt/src/stmt.rs
Normal file
116
src/tools/rustfmt/src/stmt.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
use rustc_ast::ast;
|
||||
use rustc_span::Span;
|
||||
|
||||
use crate::comment::recover_comment_removed;
|
||||
use crate::config::Version;
|
||||
use crate::expr::{format_expr, ExprType};
|
||||
use crate::rewrite::{Rewrite, RewriteContext};
|
||||
use crate::shape::Shape;
|
||||
use crate::source_map::LineRangeUtils;
|
||||
use crate::spanned::Spanned;
|
||||
use crate::utils::semicolon_for_stmt;
|
||||
|
||||
pub(crate) struct Stmt<'a> {
|
||||
inner: &'a ast::Stmt,
|
||||
is_last: bool,
|
||||
}
|
||||
|
||||
impl<'a> Spanned for Stmt<'a> {
|
||||
fn span(&self) -> Span {
|
||||
self.inner.span()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Stmt<'a> {
|
||||
pub(crate) fn as_ast_node(&self) -> &ast::Stmt {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn to_item(&self) -> Option<&ast::Item> {
|
||||
match self.inner.kind {
|
||||
ast::StmtKind::Item(ref item) => Some(&**item),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_ast_node(inner: &'a ast::Stmt, is_last: bool) -> Self {
|
||||
Stmt { inner, is_last }
|
||||
}
|
||||
|
||||
pub(crate) fn from_ast_nodes<I>(iter: I) -> Vec<Self>
|
||||
where
|
||||
I: Iterator<Item = &'a ast::Stmt>,
|
||||
{
|
||||
let mut result = vec![];
|
||||
let mut iter = iter.peekable();
|
||||
while iter.peek().is_some() {
|
||||
result.push(Stmt {
|
||||
inner: iter.next().unwrap(),
|
||||
is_last: iter.peek().is_none(),
|
||||
})
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
matches!(self.inner.kind, ast::StmtKind::Empty)
|
||||
}
|
||||
|
||||
fn is_last_expr(&self) -> bool {
|
||||
if !self.is_last {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.as_ast_node().kind {
|
||||
ast::StmtKind::Expr(ref expr) => match expr.kind {
|
||||
ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => {
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Rewrite for Stmt<'a> {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
let expr_type = if context.config.version() == Version::Two && self.is_last_expr() {
|
||||
ExprType::SubExpression
|
||||
} else {
|
||||
ExprType::Statement
|
||||
};
|
||||
format_stmt(context, shape, self.as_ast_node(), expr_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rewrite for ast::Stmt {
|
||||
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
|
||||
format_stmt(context, shape, self, ExprType::Statement)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_stmt(
|
||||
context: &RewriteContext<'_>,
|
||||
shape: Shape,
|
||||
stmt: &ast::Stmt,
|
||||
expr_type: ExprType,
|
||||
) -> Option<String> {
|
||||
skip_out_of_file_lines_range!(context, stmt.span());
|
||||
|
||||
let result = match stmt.kind {
|
||||
ast::StmtKind::Local(ref local) => local.rewrite(context, shape),
|
||||
ast::StmtKind::Expr(ref ex) | ast::StmtKind::Semi(ref ex) => {
|
||||
let suffix = if semicolon_for_stmt(context, stmt) {
|
||||
";"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let shape = shape.sub_width(suffix.len())?;
|
||||
format_expr(ex, expr_type, context, shape).map(|s| s + suffix)
|
||||
}
|
||||
ast::StmtKind::MacCall(..) | ast::StmtKind::Item(..) | ast::StmtKind::Empty => None,
|
||||
};
|
||||
result.and_then(|res| recover_comment_removed(res, stmt.span(), context))
|
||||
}
|
||||
691
src/tools/rustfmt/src/string.rs
Normal file
691
src/tools/rustfmt/src/string.rs
Normal file
|
|
@ -0,0 +1,691 @@
|
|||
// Format string literals.
|
||||
|
||||
use regex::Regex;
|
||||
use unicode_categories::UnicodeCategories;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::shape::Shape;
|
||||
use crate::utils::{unicode_str_width, wrap_str};
|
||||
|
||||
const MIN_STRING: usize = 10;
|
||||
|
||||
/// Describes the layout of a piece of text.
|
||||
pub(crate) struct StringFormat<'a> {
|
||||
/// The opening sequence of characters for the piece of text
|
||||
pub(crate) opener: &'a str,
|
||||
/// The closing sequence of characters for the piece of text
|
||||
pub(crate) closer: &'a str,
|
||||
/// The opening sequence of characters for a line
|
||||
pub(crate) line_start: &'a str,
|
||||
/// The closing sequence of characters for a line
|
||||
pub(crate) line_end: &'a str,
|
||||
/// The allocated box to fit the text into
|
||||
pub(crate) shape: Shape,
|
||||
/// Trim trailing whitespaces
|
||||
pub(crate) trim_end: bool,
|
||||
pub(crate) config: &'a Config,
|
||||
}
|
||||
|
||||
impl<'a> StringFormat<'a> {
|
||||
pub(crate) fn new(shape: Shape, config: &'a Config) -> StringFormat<'a> {
|
||||
StringFormat {
|
||||
opener: "\"",
|
||||
closer: "\"",
|
||||
line_start: " ",
|
||||
line_end: "\\",
|
||||
shape,
|
||||
trim_end: false,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the maximum number of graphemes that is possible on a line while taking the
|
||||
/// indentation into account.
|
||||
///
|
||||
/// If we cannot put at least a single character per line, the rewrite won't succeed.
|
||||
fn max_width_with_indent(&self) -> Option<usize> {
|
||||
Some(
|
||||
self.shape
|
||||
.width
|
||||
.checked_sub(self.opener.len() + self.line_end.len() + 1)?
|
||||
+ 1,
|
||||
)
|
||||
}
|
||||
|
||||
/// Like max_width_with_indent but the indentation is not subtracted.
|
||||
/// This allows to fit more graphemes from the string on a line when
|
||||
/// SnippetState::EndWithLineFeed.
|
||||
fn max_width_without_indent(&self) -> Option<usize> {
|
||||
Some(self.config.max_width().checked_sub(self.line_end.len())?)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rewrite_string<'a>(
|
||||
orig: &str,
|
||||
fmt: &StringFormat<'a>,
|
||||
newline_max_chars: usize,
|
||||
) -> Option<String> {
|
||||
let max_width_with_indent = fmt.max_width_with_indent()?;
|
||||
let max_width_without_indent = fmt.max_width_without_indent()?;
|
||||
let indent_with_newline = fmt.shape.indent.to_string_with_newline(fmt.config);
|
||||
let indent_without_newline = fmt.shape.indent.to_string(fmt.config);
|
||||
|
||||
// Strip line breaks.
|
||||
// With this regex applied, all remaining whitespaces are significant
|
||||
let strip_line_breaks_re = Regex::new(r"([^\\](\\\\)*)\\[\n\r][[:space:]]*").unwrap();
|
||||
let stripped_str = strip_line_breaks_re.replace_all(orig, "$1");
|
||||
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*stripped_str, false).collect::<Vec<&str>>();
|
||||
|
||||
// `cur_start` is the position in `orig` of the start of the current line.
|
||||
let mut cur_start = 0;
|
||||
let mut result = String::with_capacity(
|
||||
stripped_str
|
||||
.len()
|
||||
.checked_next_power_of_two()
|
||||
.unwrap_or(usize::max_value()),
|
||||
);
|
||||
result.push_str(fmt.opener);
|
||||
|
||||
// Snip a line at a time from `stripped_str` until it is used up. Push the snippet
|
||||
// onto result.
|
||||
let mut cur_max_width = max_width_with_indent;
|
||||
let is_bareline_ok = fmt.line_start.is_empty() || is_whitespace(fmt.line_start);
|
||||
loop {
|
||||
// All the input starting at cur_start fits on the current line
|
||||
if graphemes_width(&graphemes[cur_start..]) <= cur_max_width {
|
||||
for (i, grapheme) in graphemes[cur_start..].iter().enumerate() {
|
||||
if is_new_line(grapheme) {
|
||||
// take care of blank lines
|
||||
result = trim_end_but_line_feed(fmt.trim_end, result);
|
||||
result.push_str("\n");
|
||||
if !is_bareline_ok && cur_start + i + 1 < graphemes.len() {
|
||||
result.push_str(&indent_without_newline);
|
||||
result.push_str(fmt.line_start);
|
||||
}
|
||||
} else {
|
||||
result.push_str(grapheme);
|
||||
}
|
||||
}
|
||||
result = trim_end_but_line_feed(fmt.trim_end, result);
|
||||
break;
|
||||
}
|
||||
|
||||
// The input starting at cur_start needs to be broken
|
||||
match break_string(
|
||||
cur_max_width,
|
||||
fmt.trim_end,
|
||||
fmt.line_end,
|
||||
&graphemes[cur_start..],
|
||||
) {
|
||||
SnippetState::LineEnd(line, len) => {
|
||||
result.push_str(&line);
|
||||
result.push_str(fmt.line_end);
|
||||
result.push_str(&indent_with_newline);
|
||||
result.push_str(fmt.line_start);
|
||||
cur_max_width = newline_max_chars;
|
||||
cur_start += len;
|
||||
}
|
||||
SnippetState::EndWithLineFeed(line, len) => {
|
||||
if line == "\n" && fmt.trim_end {
|
||||
result = result.trim_end().to_string();
|
||||
}
|
||||
result.push_str(&line);
|
||||
if is_bareline_ok {
|
||||
// the next line can benefit from the full width
|
||||
cur_max_width = max_width_without_indent;
|
||||
} else {
|
||||
result.push_str(&indent_without_newline);
|
||||
result.push_str(fmt.line_start);
|
||||
cur_max_width = max_width_with_indent;
|
||||
}
|
||||
cur_start += len;
|
||||
}
|
||||
SnippetState::EndOfInput(line) => {
|
||||
result.push_str(&line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.push_str(fmt.closer);
|
||||
wrap_str(result, fmt.config.max_width(), fmt.shape)
|
||||
}
|
||||
|
||||
/// Returns the index to the end of the URL if the split at index of the given string includes an
|
||||
/// URL or alike. Otherwise, returns `None`.
|
||||
fn detect_url(s: &[&str], index: usize) -> Option<usize> {
|
||||
let start = match s[..=index].iter().rposition(|g| is_whitespace(g)) {
|
||||
Some(pos) => pos + 1,
|
||||
None => 0,
|
||||
};
|
||||
// 8 = minimum length for a string to contain a URL
|
||||
if s.len() < start + 8 {
|
||||
return None;
|
||||
}
|
||||
let split = s[start..].concat();
|
||||
if split.contains("https://")
|
||||
|| split.contains("http://")
|
||||
|| split.contains("ftp://")
|
||||
|| split.contains("file://")
|
||||
{
|
||||
match s[index..].iter().position(|g| is_whitespace(g)) {
|
||||
Some(pos) => Some(index + pos - 1),
|
||||
None => Some(s.len() - 1),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Trims whitespaces to the right except for the line feed character.
|
||||
fn trim_end_but_line_feed(trim_end: bool, result: String) -> String {
|
||||
let whitespace_except_line_feed = |c: char| c.is_whitespace() && c != '\n';
|
||||
if trim_end && result.ends_with(whitespace_except_line_feed) {
|
||||
result
|
||||
.trim_end_matches(whitespace_except_line_feed)
|
||||
.to_string()
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of breaking a string so it fits in a line and the state it ended in.
|
||||
/// The state informs about what to do with the snippet and how to continue the breaking process.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum SnippetState {
|
||||
/// The input could not be broken and so rewriting the string is finished.
|
||||
EndOfInput(String),
|
||||
/// The input could be broken and the returned snippet should be ended with a
|
||||
/// `[StringFormat::line_end]`. The next snippet needs to be indented.
|
||||
///
|
||||
/// The returned string is the line to print out and the number is the length that got read in
|
||||
/// the text being rewritten. That length may be greater than the returned string if trailing
|
||||
/// whitespaces got trimmed.
|
||||
LineEnd(String, usize),
|
||||
/// The input could be broken but a newline is present that cannot be trimmed. The next snippet
|
||||
/// to be rewritten *could* use more width than what is specified by the given shape. For
|
||||
/// example with a multiline string, the next snippet does not need to be indented, allowing
|
||||
/// more characters to be fit within a line.
|
||||
///
|
||||
/// The returned string is the line to print out and the number is the length that got read in
|
||||
/// the text being rewritten.
|
||||
EndWithLineFeed(String, usize),
|
||||
}
|
||||
|
||||
fn not_whitespace_except_line_feed(g: &str) -> bool {
|
||||
is_new_line(g) || !is_whitespace(g)
|
||||
}
|
||||
|
||||
/// Break the input string at a boundary character around the offset `max_width`. A boundary
|
||||
/// character is either a punctuation or a whitespace.
|
||||
/// FIXME(issue#3281): We must follow UAX#14 algorithm instead of this.
|
||||
fn break_string(max_width: usize, trim_end: bool, line_end: &str, input: &[&str]) -> SnippetState {
|
||||
let break_at = |index /* grapheme at index is included */| {
|
||||
// Take in any whitespaces to the left/right of `input[index]` while
|
||||
// preserving line feeds
|
||||
let index_minus_ws = input[0..=index]
|
||||
.iter()
|
||||
.rposition(|grapheme| not_whitespace_except_line_feed(grapheme))
|
||||
.unwrap_or(index);
|
||||
// Take into account newlines occurring in input[0..=index], i.e., the possible next new
|
||||
// line. If there is one, then text after it could be rewritten in a way that the available
|
||||
// space is fully used.
|
||||
for (i, grapheme) in input[0..=index].iter().enumerate() {
|
||||
if is_new_line(grapheme) {
|
||||
if i <= index_minus_ws {
|
||||
let mut line = &input[0..i].concat()[..];
|
||||
if trim_end {
|
||||
line = line.trim_end();
|
||||
}
|
||||
return SnippetState::EndWithLineFeed(format!("{}\n", line), i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut index_plus_ws = index;
|
||||
for (i, grapheme) in input[index + 1..].iter().enumerate() {
|
||||
if !trim_end && is_new_line(grapheme) {
|
||||
return SnippetState::EndWithLineFeed(
|
||||
input[0..=index + 1 + i].concat(),
|
||||
index + 2 + i,
|
||||
);
|
||||
} else if not_whitespace_except_line_feed(grapheme) {
|
||||
index_plus_ws = index + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if trim_end {
|
||||
SnippetState::LineEnd(input[0..=index_minus_ws].concat(), index_plus_ws + 1)
|
||||
} else {
|
||||
SnippetState::LineEnd(input[0..=index_plus_ws].concat(), index_plus_ws + 1)
|
||||
}
|
||||
};
|
||||
|
||||
// find a first index where the unicode width of input[0..x] become > max_width
|
||||
let max_width_index_in_input = {
|
||||
let mut cur_width = 0;
|
||||
let mut cur_index = 0;
|
||||
for (i, grapheme) in input.iter().enumerate() {
|
||||
cur_width += unicode_str_width(grapheme);
|
||||
cur_index = i;
|
||||
if cur_width > max_width {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cur_index
|
||||
};
|
||||
|
||||
// Find the position in input for breaking the string
|
||||
if line_end.is_empty()
|
||||
&& trim_end
|
||||
&& !is_whitespace(input[max_width_index_in_input - 1])
|
||||
&& is_whitespace(input[max_width_index_in_input])
|
||||
{
|
||||
// At a breaking point already
|
||||
// The line won't invalidate the rewriting because:
|
||||
// - no extra space needed for the line_end character
|
||||
// - extra whitespaces to the right can be trimmed
|
||||
return break_at(max_width_index_in_input - 1);
|
||||
}
|
||||
if let Some(url_index_end) = detect_url(input, max_width_index_in_input) {
|
||||
let index_plus_ws = url_index_end
|
||||
+ input[url_index_end..]
|
||||
.iter()
|
||||
.skip(1)
|
||||
.position(|grapheme| not_whitespace_except_line_feed(grapheme))
|
||||
.unwrap_or(0);
|
||||
return if trim_end {
|
||||
SnippetState::LineEnd(input[..=url_index_end].concat(), index_plus_ws + 1)
|
||||
} else {
|
||||
return SnippetState::LineEnd(input[..=index_plus_ws].concat(), index_plus_ws + 1);
|
||||
};
|
||||
}
|
||||
|
||||
match input[0..max_width_index_in_input]
|
||||
.iter()
|
||||
.rposition(|grapheme| is_whitespace(grapheme))
|
||||
{
|
||||
// Found a whitespace and what is on its left side is big enough.
|
||||
Some(index) if index >= MIN_STRING => break_at(index),
|
||||
// No whitespace found, try looking for a punctuation instead
|
||||
_ => match input[0..max_width_index_in_input]
|
||||
.iter()
|
||||
.rposition(|grapheme| is_punctuation(grapheme))
|
||||
{
|
||||
// Found a punctuation and what is on its left side is big enough.
|
||||
Some(index) if index >= MIN_STRING => break_at(index),
|
||||
// Either no boundary character was found to the left of `input[max_chars]`, or the line
|
||||
// got too small. We try searching for a boundary character to the right.
|
||||
_ => match input[max_width_index_in_input..]
|
||||
.iter()
|
||||
.position(|grapheme| is_whitespace(grapheme) || is_punctuation(grapheme))
|
||||
{
|
||||
// A boundary was found after the line limit
|
||||
Some(index) => break_at(max_width_index_in_input + index),
|
||||
// No boundary to the right, the input cannot be broken
|
||||
None => SnippetState::EndOfInput(input.concat()),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_new_line(grapheme: &str) -> bool {
|
||||
let bytes = grapheme.as_bytes();
|
||||
bytes.starts_with(b"\n") || bytes.starts_with(b"\r\n")
|
||||
}
|
||||
|
||||
fn is_whitespace(grapheme: &str) -> bool {
|
||||
grapheme.chars().all(char::is_whitespace)
|
||||
}
|
||||
|
||||
fn is_punctuation(grapheme: &str) -> bool {
|
||||
grapheme
|
||||
.chars()
|
||||
.all(UnicodeCategories::is_punctuation_other)
|
||||
}
|
||||
|
||||
fn graphemes_width(graphemes: &[&str]) -> usize {
|
||||
graphemes.iter().map(|s| unicode_str_width(s)).sum()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{break_string, detect_url, rewrite_string, SnippetState, StringFormat};
|
||||
use crate::config::Config;
|
||||
use crate::shape::{Indent, Shape};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
#[test]
|
||||
fn issue343() {
|
||||
let config = Default::default();
|
||||
let fmt = StringFormat::new(Shape::legacy(2, Indent::empty()), &config);
|
||||
rewrite_string("eq_", &fmt, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_break_on_whitespace() {
|
||||
let string = "Placerat felis. Mauris porta ante sagittis purus.";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
break_string(20, false, "", &graphemes[..]),
|
||||
SnippetState::LineEnd("Placerat felis. ".to_string(), 16)
|
||||
);
|
||||
assert_eq!(
|
||||
break_string(20, true, "", &graphemes[..]),
|
||||
SnippetState::LineEnd("Placerat felis.".to_string(), 16)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_break_on_punctuation() {
|
||||
let string = "Placerat_felis._Mauris_porta_ante_sagittis_purus.";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
break_string(20, false, "", &graphemes[..]),
|
||||
SnippetState::LineEnd("Placerat_felis.".to_string(), 15)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_break_forward() {
|
||||
let string = "Venenatis_tellus_vel_tellus. Aliquam aliquam dolor at justo.";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
break_string(20, false, "", &graphemes[..]),
|
||||
SnippetState::LineEnd("Venenatis_tellus_vel_tellus. ".to_string(), 29)
|
||||
);
|
||||
assert_eq!(
|
||||
break_string(20, true, "", &graphemes[..]),
|
||||
SnippetState::LineEnd("Venenatis_tellus_vel_tellus.".to_string(), 29)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nothing_to_break() {
|
||||
let string = "Venenatis_tellus_vel_tellus";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
break_string(20, false, "", &graphemes[..]),
|
||||
SnippetState::EndOfInput("Venenatis_tellus_vel_tellus".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn significant_whitespaces() {
|
||||
let string = "Neque in sem. \n Pellentesque tellus augue.";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
break_string(15, false, "", &graphemes[..]),
|
||||
SnippetState::EndWithLineFeed("Neque in sem. \n".to_string(), 20)
|
||||
);
|
||||
assert_eq!(
|
||||
break_string(25, false, "", &graphemes[..]),
|
||||
SnippetState::EndWithLineFeed("Neque in sem. \n".to_string(), 20)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
break_string(15, true, "", &graphemes[..]),
|
||||
SnippetState::LineEnd("Neque in sem.".to_string(), 19)
|
||||
);
|
||||
assert_eq!(
|
||||
break_string(25, true, "", &graphemes[..]),
|
||||
SnippetState::EndWithLineFeed("Neque in sem.\n".to_string(), 20)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn big_whitespace() {
|
||||
let string = "Neque in sem. Pellentesque tellus augue.";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
break_string(20, false, "", &graphemes[..]),
|
||||
SnippetState::LineEnd("Neque in sem. ".to_string(), 25)
|
||||
);
|
||||
assert_eq!(
|
||||
break_string(20, true, "", &graphemes[..]),
|
||||
SnippetState::LineEnd("Neque in sem.".to_string(), 25)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newline_in_candidate_line() {
|
||||
let string = "Nulla\nconsequat erat at massa. Vivamus id mi.";
|
||||
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(
|
||||
break_string(25, false, "", &graphemes[..]),
|
||||
SnippetState::EndWithLineFeed("Nulla\n".to_string(), 6)
|
||||
);
|
||||
assert_eq!(
|
||||
break_string(25, true, "", &graphemes[..]),
|
||||
SnippetState::EndWithLineFeed("Nulla\n".to_string(), 6)
|
||||
);
|
||||
|
||||
let mut config: Config = Default::default();
|
||||
config.set().max_width(27);
|
||||
let fmt = StringFormat::new(Shape::legacy(25, Indent::empty()), &config);
|
||||
let rewritten_string = rewrite_string(string, &fmt, 27);
|
||||
assert_eq!(
|
||||
rewritten_string,
|
||||
Some("\"Nulla\nconsequat erat at massa. \\\n Vivamus id mi.\"".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_line_fit_with_trailing_whitespaces() {
|
||||
let string = "Vivamus id mi. ";
|
||||
let config: Config = Default::default();
|
||||
let mut fmt = StringFormat::new(Shape::legacy(25, Indent::empty()), &config);
|
||||
|
||||
fmt.trim_end = true;
|
||||
let rewritten_string = rewrite_string(string, &fmt, 25);
|
||||
assert_eq!(rewritten_string, Some("\"Vivamus id mi.\"".to_string()));
|
||||
|
||||
fmt.trim_end = false; // default value of trim_end
|
||||
let rewritten_string = rewrite_string(string, &fmt, 25);
|
||||
assert_eq!(rewritten_string, Some("\"Vivamus id mi. \"".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_line_fit_with_newline() {
|
||||
let string = "Vivamus id mi.\nVivamus id mi.";
|
||||
let config: Config = Default::default();
|
||||
let fmt = StringFormat {
|
||||
opener: "",
|
||||
closer: "",
|
||||
line_start: "// ",
|
||||
line_end: "",
|
||||
shape: Shape::legacy(100, Indent::from_width(&config, 4)),
|
||||
trim_end: true,
|
||||
config: &config,
|
||||
};
|
||||
|
||||
let rewritten_string = rewrite_string(string, &fmt, 100);
|
||||
assert_eq!(
|
||||
rewritten_string,
|
||||
Some("Vivamus id mi.\n // Vivamus id mi.".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overflow_in_non_string_content() {
|
||||
let comment = "Aenean metus.\nVestibulum ac lacus. Vivamus porttitor";
|
||||
let config: Config = Default::default();
|
||||
let fmt = StringFormat {
|
||||
opener: "",
|
||||
closer: "",
|
||||
line_start: "// ",
|
||||
line_end: "",
|
||||
shape: Shape::legacy(30, Indent::from_width(&config, 8)),
|
||||
trim_end: true,
|
||||
config: &config,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 30),
|
||||
Some(
|
||||
"Aenean metus.\n // Vestibulum ac lacus. Vivamus\n // porttitor"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overflow_in_non_string_content_with_line_end() {
|
||||
let comment = "Aenean metus.\nVestibulum ac lacus. Vivamus porttitor";
|
||||
let config: Config = Default::default();
|
||||
let fmt = StringFormat {
|
||||
opener: "",
|
||||
closer: "",
|
||||
line_start: "// ",
|
||||
line_end: "@",
|
||||
shape: Shape::legacy(30, Indent::from_width(&config, 8)),
|
||||
trim_end: true,
|
||||
config: &config,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 30),
|
||||
Some(
|
||||
"Aenean metus.\n // Vestibulum ac lacus. Vivamus@\n // porttitor"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blank_line_with_non_empty_line_start() {
|
||||
let config: Config = Default::default();
|
||||
let mut fmt = StringFormat {
|
||||
opener: "",
|
||||
closer: "",
|
||||
line_start: "// ",
|
||||
line_end: "",
|
||||
shape: Shape::legacy(30, Indent::from_width(&config, 4)),
|
||||
trim_end: true,
|
||||
config: &config,
|
||||
};
|
||||
|
||||
let comment = "Aenean metus. Vestibulum\n\nac lacus. Vivamus porttitor";
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 30),
|
||||
Some(
|
||||
"Aenean metus. Vestibulum\n //\n // ac lacus. Vivamus porttitor".to_string()
|
||||
)
|
||||
);
|
||||
|
||||
fmt.shape = Shape::legacy(15, Indent::from_width(&config, 4));
|
||||
let comment = "Aenean\n\nmetus. Vestibulum ac lacus. Vivamus porttitor";
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 15),
|
||||
Some(
|
||||
r#"Aenean
|
||||
//
|
||||
// metus. Vestibulum
|
||||
// ac lacus. Vivamus
|
||||
// porttitor"#
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retain_blank_lines() {
|
||||
let config: Config = Default::default();
|
||||
let fmt = StringFormat {
|
||||
opener: "",
|
||||
closer: "",
|
||||
line_start: "// ",
|
||||
line_end: "",
|
||||
shape: Shape::legacy(20, Indent::from_width(&config, 4)),
|
||||
trim_end: true,
|
||||
config: &config,
|
||||
};
|
||||
|
||||
let comment = "Aenean\n\nmetus. Vestibulum ac lacus.\n\n";
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 20),
|
||||
Some(
|
||||
"Aenean\n //\n // metus. Vestibulum ac\n // lacus.\n //\n".to_string()
|
||||
)
|
||||
);
|
||||
|
||||
let comment = "Aenean\n\nmetus. Vestibulum ac lacus.\n";
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 20),
|
||||
Some("Aenean\n //\n // metus. Vestibulum ac\n // lacus.\n".to_string())
|
||||
);
|
||||
|
||||
let comment = "Aenean\n \nmetus. Vestibulum ac lacus.";
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 20),
|
||||
Some("Aenean\n //\n // metus. Vestibulum ac\n // lacus.".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boundary_on_edge() {
|
||||
let config: Config = Default::default();
|
||||
let mut fmt = StringFormat {
|
||||
opener: "",
|
||||
closer: "",
|
||||
line_start: "// ",
|
||||
line_end: "",
|
||||
shape: Shape::legacy(13, Indent::from_width(&config, 4)),
|
||||
trim_end: true,
|
||||
config: &config,
|
||||
};
|
||||
|
||||
let comment = "Aenean metus. Vestibulum ac lacus.";
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 13),
|
||||
Some("Aenean metus.\n // Vestibulum ac\n // lacus.".to_string())
|
||||
);
|
||||
|
||||
fmt.trim_end = false;
|
||||
let comment = "Vestibulum ac lacus.";
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 13),
|
||||
Some("Vestibulum \n // ac lacus.".to_string())
|
||||
);
|
||||
|
||||
fmt.trim_end = true;
|
||||
fmt.line_end = "\\";
|
||||
let comment = "Vestibulum ac lacus.";
|
||||
assert_eq!(
|
||||
rewrite_string(comment, &fmt, 13),
|
||||
Some("Vestibulum\\\n // ac lacus.".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detect_urls() {
|
||||
let string = "aaa http://example.org something";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(detect_url(&graphemes, 8), Some(21));
|
||||
|
||||
let string = "https://example.org something";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(detect_url(&graphemes, 0), Some(18));
|
||||
|
||||
let string = "aaa ftp://example.org something";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(detect_url(&graphemes, 8), Some(20));
|
||||
|
||||
let string = "aaa file://example.org something";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(detect_url(&graphemes, 8), Some(21));
|
||||
|
||||
let string = "aaa http not an url";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(detect_url(&graphemes, 6), None);
|
||||
|
||||
let string = "aaa file://example.org";
|
||||
let graphemes = UnicodeSegmentation::graphemes(&*string, false).collect::<Vec<&str>>();
|
||||
assert_eq!(detect_url(&graphemes, 8), Some(21));
|
||||
}
|
||||
}
|
||||
4
src/tools/rustfmt/src/syntux.rs
Normal file
4
src/tools/rustfmt/src/syntux.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
//! This module defines a thin abstract layer on top of the rustc's parser and syntax libraries.
|
||||
|
||||
pub(crate) mod parser;
|
||||
pub(crate) mod session;
|
||||
263
src/tools/rustfmt/src/syntux/parser.rs
Normal file
263
src/tools/rustfmt/src/syntux/parser.rs
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rustc_ast::token::{DelimToken, TokenKind};
|
||||
use rustc_ast::{ast, ptr};
|
||||
use rustc_errors::Diagnostic;
|
||||
use rustc_parse::{
|
||||
new_parser_from_file,
|
||||
parser::{ForceCollect, Parser as RawParser},
|
||||
};
|
||||
use rustc_span::{sym, symbol::kw, Span};
|
||||
|
||||
use crate::attr::first_attr_value_str_by_name;
|
||||
use crate::syntux::session::ParseSess;
|
||||
use crate::Input;
|
||||
|
||||
pub(crate) type DirectoryOwnership = rustc_expand::module::DirOwnership;
|
||||
pub(crate) type ModulePathSuccess = rustc_expand::module::ModulePathSuccess;
|
||||
pub(crate) type ModError<'a> = rustc_expand::module::ModError<'a>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Directory {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) ownership: DirectoryOwnership,
|
||||
}
|
||||
|
||||
/// A parser for Rust source code.
|
||||
pub(crate) struct Parser<'a> {
|
||||
parser: RawParser<'a>,
|
||||
}
|
||||
|
||||
/// A builder for the `Parser`.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ParserBuilder<'a> {
|
||||
sess: Option<&'a ParseSess>,
|
||||
input: Option<Input>,
|
||||
}
|
||||
|
||||
impl<'a> ParserBuilder<'a> {
|
||||
pub(crate) fn input(mut self, input: Input) -> ParserBuilder<'a> {
|
||||
self.input = Some(input);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn sess(mut self, sess: &'a ParseSess) -> ParserBuilder<'a> {
|
||||
self.sess = Some(sess);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Result<Parser<'a>, ParserError> {
|
||||
let sess = self.sess.ok_or(ParserError::NoParseSess)?;
|
||||
let input = self.input.ok_or(ParserError::NoInput)?;
|
||||
|
||||
let parser = match Self::parser(sess.inner(), input) {
|
||||
Ok(p) => p,
|
||||
Err(db) => {
|
||||
if let Some(diagnostics) = db {
|
||||
sess.emit_diagnostics(diagnostics);
|
||||
return Err(ParserError::ParserCreationError);
|
||||
}
|
||||
return Err(ParserError::ParsePanicError);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Parser { parser })
|
||||
}
|
||||
|
||||
fn parser(
|
||||
sess: &'a rustc_session::parse::ParseSess,
|
||||
input: Input,
|
||||
) -> Result<rustc_parse::parser::Parser<'a>, Option<Vec<Diagnostic>>> {
|
||||
match input {
|
||||
Input::File(ref file) => catch_unwind(AssertUnwindSafe(move || {
|
||||
new_parser_from_file(sess, file, None)
|
||||
}))
|
||||
.map_err(|_| None),
|
||||
Input::Text(text) => rustc_parse::maybe_new_parser_from_source_str(
|
||||
sess,
|
||||
rustc_span::FileName::Custom("stdin".to_owned()),
|
||||
text,
|
||||
)
|
||||
.map_err(|db| Some(db)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum ParserError {
|
||||
NoParseSess,
|
||||
NoInput,
|
||||
ParserCreationError,
|
||||
ParseError,
|
||||
ParsePanicError,
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub(crate) fn submod_path_from_attr(attrs: &[ast::Attribute], path: &Path) -> Option<PathBuf> {
|
||||
let path_string = first_attr_value_str_by_name(attrs, sym::path)?.as_str();
|
||||
// On windows, the base path might have the form
|
||||
// `\\?\foo\bar` in which case it does not tolerate
|
||||
// mixed `/` and `\` separators, so canonicalize
|
||||
// `/` to `\`.
|
||||
#[cfg(windows)]
|
||||
let path_string = path_string.replace("/", "\\");
|
||||
|
||||
Some(path.join(&*path_string))
|
||||
}
|
||||
|
||||
pub(crate) fn parse_file_as_module(
|
||||
sess: &'a ParseSess,
|
||||
path: &Path,
|
||||
span: Span,
|
||||
) -> Result<(Vec<ast::Attribute>, Vec<ptr::P<ast::Item>>, Span), ParserError> {
|
||||
let result = catch_unwind(AssertUnwindSafe(|| {
|
||||
let mut parser = new_parser_from_file(sess.inner(), &path, Some(span));
|
||||
match parser.parse_mod(&TokenKind::Eof) {
|
||||
Ok(result) => Some(result),
|
||||
Err(mut e) => {
|
||||
sess.emit_or_cancel_diagnostic(&mut e);
|
||||
if sess.can_reset_errors() {
|
||||
sess.reset_errors();
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}));
|
||||
match result {
|
||||
Ok(Some(m)) => {
|
||||
if !sess.has_errors() {
|
||||
return Ok(m);
|
||||
}
|
||||
|
||||
if sess.can_reset_errors() {
|
||||
sess.reset_errors();
|
||||
return Ok(m);
|
||||
}
|
||||
Err(ParserError::ParseError)
|
||||
}
|
||||
Ok(None) => Err(ParserError::ParseError),
|
||||
Err(..) if path.exists() => Err(ParserError::ParseError),
|
||||
Err(_) => Err(ParserError::ParsePanicError),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_crate(
|
||||
input: Input,
|
||||
sess: &'a ParseSess,
|
||||
) -> Result<ast::Crate, ParserError> {
|
||||
let krate = Parser::parse_crate_inner(input, sess)?;
|
||||
if !sess.has_errors() {
|
||||
return Ok(krate);
|
||||
}
|
||||
|
||||
if sess.can_reset_errors() {
|
||||
sess.reset_errors();
|
||||
return Ok(krate);
|
||||
}
|
||||
|
||||
Err(ParserError::ParseError)
|
||||
}
|
||||
|
||||
fn parse_crate_inner(input: Input, sess: &'a ParseSess) -> Result<ast::Crate, ParserError> {
|
||||
ParserBuilder::default()
|
||||
.input(input)
|
||||
.sess(sess)
|
||||
.build()?
|
||||
.parse_crate_mod()
|
||||
}
|
||||
|
||||
fn parse_crate_mod(&mut self) -> Result<ast::Crate, ParserError> {
|
||||
let mut parser = AssertUnwindSafe(&mut self.parser);
|
||||
|
||||
match catch_unwind(move || parser.parse_crate_mod()) {
|
||||
Ok(Ok(k)) => Ok(k),
|
||||
Ok(Err(mut db)) => {
|
||||
db.emit();
|
||||
Err(ParserError::ParseError)
|
||||
}
|
||||
Err(_) => Err(ParserError::ParsePanicError),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_cfg_if(
|
||||
sess: &'a ParseSess,
|
||||
mac: &'a ast::MacCall,
|
||||
) -> Result<Vec<ast::Item>, &'static str> {
|
||||
match catch_unwind(AssertUnwindSafe(|| Parser::parse_cfg_if_inner(sess, mac))) {
|
||||
Ok(Ok(items)) => Ok(items),
|
||||
Ok(err @ Err(_)) => err,
|
||||
Err(..) => Err("failed to parse cfg_if!"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cfg_if_inner(
|
||||
sess: &'a ParseSess,
|
||||
mac: &'a ast::MacCall,
|
||||
) -> Result<Vec<ast::Item>, &'static str> {
|
||||
let token_stream = mac.args.inner_tokens();
|
||||
let mut parser =
|
||||
rustc_parse::stream_to_parser(sess.inner(), token_stream.clone(), Some(""));
|
||||
|
||||
let mut items = vec![];
|
||||
let mut process_if_cfg = true;
|
||||
|
||||
while parser.token.kind != TokenKind::Eof {
|
||||
if process_if_cfg {
|
||||
if !parser.eat_keyword(kw::If) {
|
||||
return Err("Expected `if`");
|
||||
}
|
||||
// Inner attributes are not actually syntactically permitted here, but we don't
|
||||
// care about inner vs outer attributes in this position. Our purpose with this
|
||||
// special case parsing of cfg_if macros is to ensure we can correctly resolve
|
||||
// imported modules that may have a custom `path` defined.
|
||||
//
|
||||
// As such, we just need to advance the parser past the attribute and up to
|
||||
// to the opening brace.
|
||||
// See also https://github.com/rust-lang/rust/pull/79433
|
||||
parser
|
||||
.parse_attribute(rustc_parse::parser::attr::InnerAttrPolicy::Permitted)
|
||||
.map_err(|_| "Failed to parse attributes")?;
|
||||
}
|
||||
|
||||
if !parser.eat(&TokenKind::OpenDelim(DelimToken::Brace)) {
|
||||
return Err("Expected an opening brace");
|
||||
}
|
||||
|
||||
while parser.token != TokenKind::CloseDelim(DelimToken::Brace)
|
||||
&& parser.token.kind != TokenKind::Eof
|
||||
{
|
||||
let item = match parser.parse_item(ForceCollect::No) {
|
||||
Ok(Some(item_ptr)) => item_ptr.into_inner(),
|
||||
Ok(None) => continue,
|
||||
Err(mut err) => {
|
||||
err.cancel();
|
||||
parser.sess.span_diagnostic.reset_err_count();
|
||||
return Err(
|
||||
"Expected item inside cfg_if block, but failed to parse it as an item",
|
||||
);
|
||||
}
|
||||
};
|
||||
if let ast::ItemKind::Mod(..) = item.kind {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if !parser.eat(&TokenKind::CloseDelim(DelimToken::Brace)) {
|
||||
return Err("Expected a closing brace");
|
||||
}
|
||||
|
||||
if parser.eat(&TokenKind::Eof) {
|
||||
break;
|
||||
}
|
||||
|
||||
if !parser.eat_keyword(kw::Else) {
|
||||
return Err("Expected `else`");
|
||||
}
|
||||
|
||||
process_if_cfg = parser.token.is_keyword(kw::If);
|
||||
}
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
463
src/tools/rustfmt/src/syntux/session.rs
Normal file
463
src/tools/rustfmt/src/syntux/session.rs
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use rustc_data_structures::sync::{Lrc, Send};
|
||||
use rustc_errors::emitter::{Emitter, EmitterWriter};
|
||||
use rustc_errors::{ColorConfig, Diagnostic, Handler, Level as DiagnosticLevel};
|
||||
use rustc_session::parse::ParseSess as RawParseSess;
|
||||
use rustc_span::{
|
||||
source_map::{FilePathMapping, SourceMap},
|
||||
symbol, BytePos, Span,
|
||||
};
|
||||
|
||||
use crate::config::file_lines::LineRange;
|
||||
use crate::ignore_path::IgnorePathSet;
|
||||
use crate::source_map::LineRangeUtils;
|
||||
use crate::utils::starts_with_newline;
|
||||
use crate::visitor::SnippetProvider;
|
||||
use crate::{Config, ErrorKind, FileName};
|
||||
|
||||
/// ParseSess holds structs necessary for constructing a parser.
|
||||
pub(crate) struct ParseSess {
|
||||
parse_sess: RawParseSess,
|
||||
ignore_path_set: Lrc<IgnorePathSet>,
|
||||
can_reset_errors: Lrc<AtomicBool>,
|
||||
}
|
||||
|
||||
/// Emitter which discards every error.
|
||||
struct SilentEmitter;
|
||||
|
||||
impl Emitter for SilentEmitter {
|
||||
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
|
||||
None
|
||||
}
|
||||
fn emit_diagnostic(&mut self, _db: &Diagnostic) {}
|
||||
}
|
||||
|
||||
fn silent_emitter() -> Box<dyn Emitter + Send> {
|
||||
Box::new(SilentEmitter {})
|
||||
}
|
||||
|
||||
/// Emit errors against every files expect ones specified in the `ignore_path_set`.
|
||||
struct SilentOnIgnoredFilesEmitter {
|
||||
ignore_path_set: Lrc<IgnorePathSet>,
|
||||
source_map: Lrc<SourceMap>,
|
||||
emitter: Box<dyn Emitter + Send>,
|
||||
has_non_ignorable_parser_errors: bool,
|
||||
can_reset: Lrc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl SilentOnIgnoredFilesEmitter {
|
||||
fn handle_non_ignoreable_error(&mut self, db: &Diagnostic) {
|
||||
self.has_non_ignorable_parser_errors = true;
|
||||
self.can_reset.store(false, Ordering::Release);
|
||||
self.emitter.emit_diagnostic(db);
|
||||
}
|
||||
}
|
||||
|
||||
impl Emitter for SilentOnIgnoredFilesEmitter {
|
||||
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
|
||||
None
|
||||
}
|
||||
fn emit_diagnostic(&mut self, db: &Diagnostic) {
|
||||
if db.level == DiagnosticLevel::Fatal {
|
||||
return self.handle_non_ignoreable_error(db);
|
||||
}
|
||||
if let Some(primary_span) = &db.span.primary_span() {
|
||||
let file_name = self.source_map.span_to_filename(*primary_span);
|
||||
if let rustc_span::FileName::Real(rustc_span::RealFileName::LocalPath(ref path)) =
|
||||
file_name
|
||||
{
|
||||
if self
|
||||
.ignore_path_set
|
||||
.is_match(&FileName::Real(path.to_path_buf()))
|
||||
{
|
||||
if !self.has_non_ignorable_parser_errors {
|
||||
self.can_reset.store(true, Ordering::Release);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
self.handle_non_ignoreable_error(db);
|
||||
}
|
||||
}
|
||||
|
||||
fn default_handler(
|
||||
source_map: Lrc<SourceMap>,
|
||||
ignore_path_set: Lrc<IgnorePathSet>,
|
||||
can_reset: Lrc<AtomicBool>,
|
||||
hide_parse_errors: bool,
|
||||
) -> Handler {
|
||||
let supports_color = term::stderr().map_or(false, |term| term.supports_color());
|
||||
let color_cfg = if supports_color {
|
||||
ColorConfig::Auto
|
||||
} else {
|
||||
ColorConfig::Never
|
||||
};
|
||||
|
||||
let emitter = if hide_parse_errors {
|
||||
silent_emitter()
|
||||
} else {
|
||||
Box::new(EmitterWriter::stderr(
|
||||
color_cfg,
|
||||
Some(source_map.clone()),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
false,
|
||||
))
|
||||
};
|
||||
Handler::with_emitter(
|
||||
true,
|
||||
None,
|
||||
Box::new(SilentOnIgnoredFilesEmitter {
|
||||
has_non_ignorable_parser_errors: false,
|
||||
source_map,
|
||||
emitter,
|
||||
ignore_path_set,
|
||||
can_reset,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
impl ParseSess {
|
||||
pub(crate) fn new(config: &Config) -> Result<ParseSess, ErrorKind> {
|
||||
let ignore_path_set = match IgnorePathSet::from_ignore_list(&config.ignore()) {
|
||||
Ok(ignore_path_set) => Lrc::new(ignore_path_set),
|
||||
Err(e) => return Err(ErrorKind::InvalidGlobPattern(e)),
|
||||
};
|
||||
let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let can_reset_errors = Lrc::new(AtomicBool::new(false));
|
||||
|
||||
let handler = default_handler(
|
||||
Lrc::clone(&source_map),
|
||||
Lrc::clone(&ignore_path_set),
|
||||
Lrc::clone(&can_reset_errors),
|
||||
config.hide_parse_errors(),
|
||||
);
|
||||
let parse_sess = RawParseSess::with_span_handler(handler, source_map);
|
||||
|
||||
Ok(ParseSess {
|
||||
parse_sess,
|
||||
ignore_path_set,
|
||||
can_reset_errors,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn default_submod_path(
|
||||
&self,
|
||||
id: symbol::Ident,
|
||||
relative: Option<symbol::Ident>,
|
||||
dir_path: &Path,
|
||||
) -> Result<rustc_expand::module::ModulePathSuccess, rustc_expand::module::ModError<'_>> {
|
||||
rustc_expand::module::default_submod_path(&self.parse_sess, id, relative, dir_path)
|
||||
}
|
||||
|
||||
pub(crate) fn is_file_parsed(&self, path: &Path) -> bool {
|
||||
self.parse_sess
|
||||
.source_map()
|
||||
.get_source_file(&rustc_span::FileName::Real(
|
||||
rustc_span::RealFileName::LocalPath(path.to_path_buf()),
|
||||
))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn ignore_file(&self, path: &FileName) -> bool {
|
||||
self.ignore_path_set.as_ref().is_match(&path)
|
||||
}
|
||||
|
||||
pub(crate) fn set_silent_emitter(&mut self) {
|
||||
self.parse_sess.span_diagnostic = Handler::with_emitter(true, None, silent_emitter());
|
||||
}
|
||||
|
||||
pub(crate) fn span_to_filename(&self, span: Span) -> FileName {
|
||||
self.parse_sess.source_map().span_to_filename(span).into()
|
||||
}
|
||||
|
||||
pub(crate) fn span_to_first_line_string(&self, span: Span) -> String {
|
||||
let file_lines = self.parse_sess.source_map().span_to_lines(span).ok();
|
||||
|
||||
match file_lines {
|
||||
Some(fl) => fl
|
||||
.file
|
||||
.get_line(fl.lines[0].line_index)
|
||||
.map_or_else(String::new, |s| s.to_string()),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn line_of_byte_pos(&self, pos: BytePos) -> usize {
|
||||
self.parse_sess.source_map().lookup_char_pos(pos).line
|
||||
}
|
||||
|
||||
pub(crate) fn span_to_debug_info(&self, span: Span) -> String {
|
||||
self.parse_sess.source_map().span_to_diagnostic_string(span)
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &RawParseSess {
|
||||
&self.parse_sess
|
||||
}
|
||||
|
||||
pub(crate) fn snippet_provider(&self, span: Span) -> SnippetProvider {
|
||||
let source_file = self.parse_sess.source_map().lookup_char_pos(span.lo()).file;
|
||||
SnippetProvider::new(
|
||||
source_file.start_pos,
|
||||
source_file.end_pos,
|
||||
Lrc::clone(source_file.src.as_ref().unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn get_original_snippet(&self, file_name: &FileName) -> Option<Lrc<String>> {
|
||||
self.parse_sess
|
||||
.source_map()
|
||||
.get_source_file(&file_name.into())
|
||||
.and_then(|source_file| source_file.src.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// Methods that should be restricted within the syntux module.
|
||||
impl ParseSess {
|
||||
pub(super) fn emit_diagnostics(&self, diagnostics: Vec<Diagnostic>) {
|
||||
for diagnostic in diagnostics {
|
||||
self.parse_sess.span_diagnostic.emit_diagnostic(&diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn emit_or_cancel_diagnostic(&self, diagnostic: &mut Diagnostic) {
|
||||
self.parse_sess.span_diagnostic.emit_diagnostic(diagnostic);
|
||||
// The Handler will check whether the diagnostic should be emitted
|
||||
// based on the user's rustfmt configuration and the originating file
|
||||
// that caused the parser error. If the Handler determined it should skip
|
||||
// emission then we need to ensure the diagnostic is cancelled.
|
||||
if !diagnostic.cancelled() {
|
||||
diagnostic.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn can_reset_errors(&self) -> bool {
|
||||
self.can_reset_errors.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(super) fn has_errors(&self) -> bool {
|
||||
self.parse_sess.span_diagnostic.has_errors()
|
||||
}
|
||||
|
||||
pub(super) fn reset_errors(&self) {
|
||||
self.parse_sess.span_diagnostic.reset_err_count();
|
||||
}
|
||||
}
|
||||
|
||||
impl LineRangeUtils for ParseSess {
|
||||
fn lookup_line_range(&self, span: Span) -> LineRange {
|
||||
let snippet = self
|
||||
.parse_sess
|
||||
.source_map()
|
||||
.span_to_snippet(span)
|
||||
.unwrap_or_default();
|
||||
let lo = self.parse_sess.source_map().lookup_line(span.lo()).unwrap();
|
||||
let hi = self.parse_sess.source_map().lookup_line(span.hi()).unwrap();
|
||||
|
||||
debug_assert_eq!(
|
||||
lo.sf.name, hi.sf.name,
|
||||
"span crossed file boundary: lo: {:?}, hi: {:?}",
|
||||
lo, hi
|
||||
);
|
||||
|
||||
// in case the span starts with a newline, the line range is off by 1 without the
|
||||
// adjustment below
|
||||
let offset = 1 + if starts_with_newline(&snippet) { 1 } else { 0 };
|
||||
// Line numbers start at 1
|
||||
LineRange {
|
||||
file: lo.sf.clone(),
|
||||
lo: lo.line + offset,
|
||||
hi: hi.line + offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod emitter {
|
||||
use super::*;
|
||||
use crate::config::IgnoreList;
|
||||
use crate::is_nightly_channel;
|
||||
use crate::utils::mk_sp;
|
||||
use rustc_span::{FileName as SourceMapFileName, MultiSpan, RealFileName, DUMMY_SP};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
struct TestEmitter {
|
||||
num_emitted_errors: Lrc<AtomicU32>,
|
||||
}
|
||||
|
||||
impl Emitter for TestEmitter {
|
||||
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
|
||||
None
|
||||
}
|
||||
fn emit_diagnostic(&mut self, _db: &Diagnostic) {
|
||||
self.num_emitted_errors.fetch_add(1, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_diagnostic(level: DiagnosticLevel, span: Option<MultiSpan>) -> Diagnostic {
|
||||
Diagnostic {
|
||||
level,
|
||||
code: None,
|
||||
message: vec![],
|
||||
children: vec![],
|
||||
suggestions: vec![],
|
||||
span: span.unwrap_or_else(MultiSpan::new),
|
||||
sort_span: DUMMY_SP,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_emitter(
|
||||
num_emitted_errors: Lrc<AtomicU32>,
|
||||
can_reset: Lrc<AtomicBool>,
|
||||
source_map: Option<Lrc<SourceMap>>,
|
||||
ignore_list: Option<IgnoreList>,
|
||||
) -> SilentOnIgnoredFilesEmitter {
|
||||
let emitter_writer = TestEmitter { num_emitted_errors };
|
||||
let source_map =
|
||||
source_map.unwrap_or_else(|| Lrc::new(SourceMap::new(FilePathMapping::empty())));
|
||||
let ignore_path_set = Lrc::new(
|
||||
IgnorePathSet::from_ignore_list(&ignore_list.unwrap_or_default()).unwrap(),
|
||||
);
|
||||
SilentOnIgnoredFilesEmitter {
|
||||
has_non_ignorable_parser_errors: false,
|
||||
source_map,
|
||||
emitter: Box::new(emitter_writer),
|
||||
ignore_path_set,
|
||||
can_reset,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ignore_list(config: &str) -> IgnoreList {
|
||||
Config::from_toml(config, Path::new("")).unwrap().ignore()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_fatal_parse_error_in_ignored_file() {
|
||||
let num_emitted_errors = Lrc::new(AtomicU32::new(0));
|
||||
let can_reset_errors = Lrc::new(AtomicBool::new(false));
|
||||
let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
|
||||
let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let source =
|
||||
String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#);
|
||||
source_map.new_source_file(
|
||||
SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))),
|
||||
source,
|
||||
);
|
||||
let mut emitter = build_emitter(
|
||||
Lrc::clone(&num_emitted_errors),
|
||||
Lrc::clone(&can_reset_errors),
|
||||
Some(Lrc::clone(&source_map)),
|
||||
Some(ignore_list),
|
||||
);
|
||||
let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
|
||||
let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, Some(span));
|
||||
emitter.emit_diagnostic(&fatal_diagnostic);
|
||||
assert_eq!(num_emitted_errors.load(Ordering::Acquire), 1);
|
||||
assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_recoverable_parse_error_in_ignored_file() {
|
||||
if !is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let num_emitted_errors = Lrc::new(AtomicU32::new(0));
|
||||
let can_reset_errors = Lrc::new(AtomicBool::new(false));
|
||||
let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
|
||||
let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let source = String::from(r#"pub fn bar() { 1x; }"#);
|
||||
source_map.new_source_file(
|
||||
SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))),
|
||||
source,
|
||||
);
|
||||
let mut emitter = build_emitter(
|
||||
Lrc::clone(&num_emitted_errors),
|
||||
Lrc::clone(&can_reset_errors),
|
||||
Some(Lrc::clone(&source_map)),
|
||||
Some(ignore_list),
|
||||
);
|
||||
let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
|
||||
let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(span));
|
||||
emitter.emit_diagnostic(&non_fatal_diagnostic);
|
||||
assert_eq!(num_emitted_errors.load(Ordering::Acquire), 0);
|
||||
assert_eq!(can_reset_errors.load(Ordering::Acquire), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_recoverable_parse_error_in_non_ignored_file() {
|
||||
if !is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let num_emitted_errors = Lrc::new(AtomicU32::new(0));
|
||||
let can_reset_errors = Lrc::new(AtomicBool::new(false));
|
||||
let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let source = String::from(r#"pub fn bar() { 1x; }"#);
|
||||
source_map.new_source_file(
|
||||
SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))),
|
||||
source,
|
||||
);
|
||||
let mut emitter = build_emitter(
|
||||
Lrc::clone(&num_emitted_errors),
|
||||
Lrc::clone(&can_reset_errors),
|
||||
Some(Lrc::clone(&source_map)),
|
||||
None,
|
||||
);
|
||||
let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
|
||||
let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(span));
|
||||
emitter.emit_diagnostic(&non_fatal_diagnostic);
|
||||
assert_eq!(num_emitted_errors.load(Ordering::Acquire), 1);
|
||||
assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_mix_of_recoverable_parse_error() {
|
||||
if !is_nightly_channel!() {
|
||||
return;
|
||||
}
|
||||
let num_emitted_errors = Lrc::new(AtomicU32::new(0));
|
||||
let can_reset_errors = Lrc::new(AtomicBool::new(false));
|
||||
let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
|
||||
let bar_source = String::from(r#"pub fn bar() { 1x; }"#);
|
||||
let foo_source = String::from(r#"pub fn foo() { 1x; }"#);
|
||||
let fatal_source =
|
||||
String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#);
|
||||
source_map.new_source_file(
|
||||
SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("bar.rs"))),
|
||||
bar_source,
|
||||
);
|
||||
source_map.new_source_file(
|
||||
SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))),
|
||||
foo_source,
|
||||
);
|
||||
source_map.new_source_file(
|
||||
SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("fatal.rs"))),
|
||||
fatal_source,
|
||||
);
|
||||
let mut emitter = build_emitter(
|
||||
Lrc::clone(&num_emitted_errors),
|
||||
Lrc::clone(&can_reset_errors),
|
||||
Some(Lrc::clone(&source_map)),
|
||||
Some(ignore_list),
|
||||
);
|
||||
let bar_span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
|
||||
let foo_span = MultiSpan::from_span(mk_sp(BytePos(21), BytePos(22)));
|
||||
let bar_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(bar_span));
|
||||
let foo_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(foo_span));
|
||||
let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, None);
|
||||
emitter.emit_diagnostic(&bar_diagnostic);
|
||||
emitter.emit_diagnostic(&foo_diagnostic);
|
||||
emitter.emit_diagnostic(&fatal_diagnostic);
|
||||
assert_eq!(num_emitted_errors.load(Ordering::Acquire), 2);
|
||||
assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
287
src/tools/rustfmt/src/test/configuration_snippet.rs
Normal file
287
src/tools/rustfmt/src/test/configuration_snippet.rs
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::iter::Enumerate;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::{print_mismatches, write_message, DIFF_CONTEXT_SIZE};
|
||||
use crate::config::{Config, EmitMode, Verbosity};
|
||||
use crate::rustfmt_diff::{make_diff, Mismatch};
|
||||
use crate::{Input, Session};
|
||||
|
||||
const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md";
|
||||
|
||||
// This enum is used to represent one of three text features in Configurations.md: a block of code
|
||||
// with its starting line number, the name of a rustfmt configuration option, or the value of a
|
||||
// rustfmt configuration option.
|
||||
enum ConfigurationSection {
|
||||
CodeBlock((String, u32)), // (String: block of code, u32: line number of code block start)
|
||||
ConfigName(String),
|
||||
ConfigValue(String),
|
||||
}
|
||||
|
||||
impl ConfigurationSection {
|
||||
fn get_section<I: Iterator<Item = String>>(
|
||||
file: &mut Enumerate<I>,
|
||||
) -> Option<ConfigurationSection> {
|
||||
lazy_static! {
|
||||
static ref CONFIG_NAME_REGEX: regex::Regex =
|
||||
regex::Regex::new(r"^## `([^`]+)`").expect("failed creating configuration pattern");
|
||||
static ref CONFIG_VALUE_REGEX: regex::Regex =
|
||||
regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#)
|
||||
.expect("failed creating configuration value pattern");
|
||||
}
|
||||
|
||||
loop {
|
||||
match file.next() {
|
||||
Some((i, line)) => {
|
||||
if line.starts_with("```rust") {
|
||||
// Get the lines of the code block.
|
||||
let lines: Vec<String> = file
|
||||
.map(|(_i, l)| l)
|
||||
.take_while(|l| !l.starts_with("```"))
|
||||
.collect();
|
||||
let block = format!("{}\n", lines.join("\n"));
|
||||
|
||||
// +1 to translate to one-based indexing
|
||||
// +1 to get to first line of code (line after "```")
|
||||
let start_line = (i + 2) as u32;
|
||||
|
||||
return Some(ConfigurationSection::CodeBlock((block, start_line)));
|
||||
} else if let Some(c) = CONFIG_NAME_REGEX.captures(&line) {
|
||||
return Some(ConfigurationSection::ConfigName(String::from(&c[1])));
|
||||
} else if let Some(c) = CONFIG_VALUE_REGEX.captures(&line) {
|
||||
return Some(ConfigurationSection::ConfigValue(String::from(&c[1])));
|
||||
}
|
||||
}
|
||||
None => return None, // reached the end of the file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This struct stores the information about code blocks in the configurations
|
||||
// file, formats the code blocks, and prints formatting errors.
|
||||
struct ConfigCodeBlock {
|
||||
config_name: Option<String>,
|
||||
config_value: Option<String>,
|
||||
code_block: Option<String>,
|
||||
code_block_start: Option<u32>,
|
||||
}
|
||||
|
||||
impl ConfigCodeBlock {
|
||||
fn new() -> ConfigCodeBlock {
|
||||
ConfigCodeBlock {
|
||||
config_name: None,
|
||||
config_value: None,
|
||||
code_block: None,
|
||||
code_block_start: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_config_name(&mut self, name: Option<String>) {
|
||||
self.config_name = name;
|
||||
self.config_value = None;
|
||||
}
|
||||
|
||||
fn set_config_value(&mut self, value: Option<String>) {
|
||||
self.config_value = value;
|
||||
}
|
||||
|
||||
fn set_code_block(&mut self, code_block: String, code_block_start: u32) {
|
||||
self.code_block = Some(code_block);
|
||||
self.code_block_start = Some(code_block_start);
|
||||
}
|
||||
|
||||
fn get_block_config(&self) -> Config {
|
||||
let mut config = Config::default();
|
||||
config.set().verbose(Verbosity::Quiet);
|
||||
if self.config_name.is_some() && self.config_value.is_some() {
|
||||
config.override_value(
|
||||
self.config_name.as_ref().unwrap(),
|
||||
self.config_value.as_ref().unwrap(),
|
||||
);
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
fn code_block_valid(&self) -> bool {
|
||||
// We never expect to not have a code block.
|
||||
assert!(self.code_block.is_some() && self.code_block_start.is_some());
|
||||
|
||||
// See if code block begins with #![rustfmt::skip].
|
||||
let fmt_skip = self
|
||||
.code_block
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lines()
|
||||
.nth(0)
|
||||
.unwrap_or("")
|
||||
== "#![rustfmt::skip]";
|
||||
|
||||
if self.config_name.is_none() && !fmt_skip {
|
||||
write_message(&format!(
|
||||
"No configuration name for {}:{}",
|
||||
CONFIGURATIONS_FILE_NAME,
|
||||
self.code_block_start.unwrap()
|
||||
));
|
||||
return false;
|
||||
}
|
||||
if self.config_value.is_none() && !fmt_skip {
|
||||
write_message(&format!(
|
||||
"No configuration value for {}:{}",
|
||||
CONFIGURATIONS_FILE_NAME,
|
||||
self.code_block_start.unwrap()
|
||||
));
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn has_parsing_errors<T: Write>(&self, session: &Session<'_, T>) -> bool {
|
||||
if session.has_parsing_errors() {
|
||||
write_message(&format!(
|
||||
"\u{261d}\u{1f3fd} Cannot format {}:{}",
|
||||
CONFIGURATIONS_FILE_NAME,
|
||||
self.code_block_start.unwrap()
|
||||
));
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn print_diff(&self, compare: Vec<Mismatch>) {
|
||||
let mut mismatches = HashMap::new();
|
||||
mismatches.insert(PathBuf::from(CONFIGURATIONS_FILE_NAME), compare);
|
||||
print_mismatches(mismatches, |line_num| {
|
||||
format!(
|
||||
"\nMismatch at {}:{}:",
|
||||
CONFIGURATIONS_FILE_NAME,
|
||||
line_num + self.code_block_start.unwrap() - 1
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn formatted_has_diff(&self, text: &str) -> bool {
|
||||
let compare = make_diff(self.code_block.as_ref().unwrap(), text, DIFF_CONTEXT_SIZE);
|
||||
if !compare.is_empty() {
|
||||
self.print_diff(compare);
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// Return a bool indicating if formatting this code block is an idempotent
|
||||
// operation. This function also triggers printing any formatting failure
|
||||
// messages.
|
||||
fn formatted_is_idempotent(&self) -> bool {
|
||||
// Verify that we have all of the expected information.
|
||||
if !self.code_block_valid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let input = Input::Text(self.code_block.as_ref().unwrap().to_owned());
|
||||
let mut config = self.get_block_config();
|
||||
config.set().emit_mode(EmitMode::Stdout);
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
|
||||
{
|
||||
let mut session = Session::new(config, Some(&mut buf));
|
||||
session.format(input).unwrap();
|
||||
if self.has_parsing_errors(&session) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
!self.formatted_has_diff(&String::from_utf8(buf).unwrap())
|
||||
}
|
||||
|
||||
// Extract a code block from the iterator. Behavior:
|
||||
// - Rust code blocks are identifed by lines beginning with "```rust".
|
||||
// - One explicit configuration setting is supported per code block.
|
||||
// - Rust code blocks with no configuration setting are illegal and cause an
|
||||
// assertion failure, unless the snippet begins with #![rustfmt::skip].
|
||||
// - Configuration names in Configurations.md must be in the form of
|
||||
// "## `NAME`".
|
||||
// - Configuration values in Configurations.md must be in the form of
|
||||
// "#### `VALUE`".
|
||||
fn extract<I: Iterator<Item = String>>(
|
||||
file: &mut Enumerate<I>,
|
||||
prev: Option<&ConfigCodeBlock>,
|
||||
hash_set: &mut HashSet<String>,
|
||||
) -> Option<ConfigCodeBlock> {
|
||||
let mut code_block = ConfigCodeBlock::new();
|
||||
code_block.config_name = prev.and_then(|cb| cb.config_name.clone());
|
||||
|
||||
loop {
|
||||
match ConfigurationSection::get_section(file) {
|
||||
Some(ConfigurationSection::CodeBlock((block, start_line))) => {
|
||||
code_block.set_code_block(block, start_line);
|
||||
break;
|
||||
}
|
||||
Some(ConfigurationSection::ConfigName(name)) => {
|
||||
assert!(
|
||||
Config::is_valid_name(&name),
|
||||
"an unknown configuration option was found: {}",
|
||||
name
|
||||
);
|
||||
assert!(
|
||||
hash_set.remove(&name),
|
||||
"multiple configuration guides found for option {}",
|
||||
name
|
||||
);
|
||||
code_block.set_config_name(Some(name));
|
||||
}
|
||||
Some(ConfigurationSection::ConfigValue(value)) => {
|
||||
code_block.set_config_value(Some(value));
|
||||
}
|
||||
None => return None, // end of file was reached
|
||||
}
|
||||
}
|
||||
|
||||
Some(code_block)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configuration_snippet_tests() {
|
||||
super::init_log();
|
||||
let blocks = get_code_blocks();
|
||||
let failures = blocks
|
||||
.iter()
|
||||
.map(ConfigCodeBlock::formatted_is_idempotent)
|
||||
.fold(0, |acc, r| acc + (!r as u32));
|
||||
|
||||
// Display results.
|
||||
println!("Ran {} configurations tests.", blocks.len());
|
||||
assert_eq!(failures, 0, "{} configurations tests failed", failures);
|
||||
}
|
||||
|
||||
// Read Configurations.md and build a `Vec` of `ConfigCodeBlock` structs with one
|
||||
// entry for each Rust code block found.
|
||||
fn get_code_blocks() -> Vec<ConfigCodeBlock> {
|
||||
let mut file_iter = BufReader::new(
|
||||
fs::File::open(Path::new(CONFIGURATIONS_FILE_NAME))
|
||||
.unwrap_or_else(|_| panic!("couldn't read file {}", CONFIGURATIONS_FILE_NAME)),
|
||||
)
|
||||
.lines()
|
||||
.map(Result::unwrap)
|
||||
.enumerate();
|
||||
let mut code_blocks: Vec<ConfigCodeBlock> = Vec::new();
|
||||
let mut hash_set = Config::hash_set();
|
||||
|
||||
while let Some(cb) = ConfigCodeBlock::extract(&mut file_iter, code_blocks.last(), &mut hash_set)
|
||||
{
|
||||
code_blocks.push(cb);
|
||||
}
|
||||
|
||||
for name in hash_set {
|
||||
if !Config::is_hidden_option(&name) {
|
||||
panic!("{} does not have a configuration guide", name);
|
||||
}
|
||||
}
|
||||
|
||||
code_blocks
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue