Web playground with WASM (#1279)

This commit is contained in:
Reiner Gerecke 2022-12-26 18:09:17 +01:00 committed by GitHub
parent 8c018e8261
commit bdb1505262
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 3104 additions and 402 deletions

View file

@ -12,7 +12,7 @@ env:
RUSTUP_MAX_RETRIES: 10 RUSTUP_MAX_RETRIES: 10
jobs: jobs:
cargo_build: cargo-build:
name: "cargo build" name: "cargo build"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -45,8 +45,11 @@ jobs:
- run: ./target/release/ruff_dev generate-json-schema - run: ./target/release/ruff_dev generate-json-schema
- run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. You may have to rerun 'cargo dev generate-json-schema'." - run: git diff --quiet ruff.schema.json || echo "::error file=ruff.schema.json::This file is outdated. You may have to rerun 'cargo dev generate-json-schema'."
- run: git diff --exit-code -- ruff.schema.json - run: git diff --exit-code -- ruff.schema.json
- run: ./target/release/ruff_dev generate-playground-options
- run: git diff --quiet playground/src/ruff_options.rs || echo "::error file=playground/src/ruff_options.ts::This file is outdated. You may have to rerun 'cargo dev generate-playground-options'."
- run: git diff --exit-code -- README.md src/checks_gen.rs playground/src/ruff_options.ts
cargo_fmt: cargo-fmt:
name: "cargo fmt" name: "cargo fmt"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -82,6 +85,7 @@ jobs:
toolchain: nightly-2022-11-01 toolchain: nightly-2022-11-01
override: true override: true
components: clippy components: clippy
target: wasm32-unknown-unknown
- uses: actions/cache@v3 - uses: actions/cache@v3
env: env:
cache-name: cache-cargo cache-name: cache-cargo
@ -95,8 +99,9 @@ jobs:
${{ runner.os }}-build- ${{ runner.os }}-build-
${{ runner.os }}- ${{ runner.os }}-
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic - run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
- run: cargo clippy --workspace --target wasm32-unknown-unknown --all-features -- -D warnings -W clippy::pedantic
cargo_test: cargo-test:
name: "cargo test" name: "cargo test"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -122,7 +127,32 @@ jobs:
- run: cargo test --all - run: cargo test --all
- run: cargo test --package ruff --test black_compatibility_test -- --ignored - run: cargo test --package ruff --test black_compatibility_test -- --ignored
maturin_build: wasm-pack-test:
name: "wasm-pack test"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: actions/cache@v3
env:
cache-name: cache-cargo
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- run: wasm-pack test --node
maturin-build:
name: "maturin build" name: "maturin build"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

57
.github/workflows/playground.yaml vendored Normal file
View file

@ -0,0 +1,57 @@
name: Playground
on:
workflow_dispatch:
push:
branches: [main]
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
RUSTUP_MAX_RETRIES: 10
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
target: wasm32-unknown-unknown
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
cache-dependency-path: playground/package-lock.json
- uses: actions/cache@v3
env:
cache-name: cache-cargo
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: "Install wasm-pack"
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: "Run wasm-pack"
run: wasm-pack build --target web --out-dir playground/src/pkg
- name: "Install Node dependencies"
run: npm ci
working-directory: playground
- name: "Build JavaScript bundle"
run: npm run build
working-directory: playground
- name: "Deploy to Cloudflare Pages"
uses: cloudflare/wrangler-action@2.0.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
workingDirectory: playground
command: pages publish dist --project-name=ruff

236
Cargo.lock generated
View file

@ -61,9 +61,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.66" version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]] [[package]]
name = "ascii" name = "ascii"
@ -86,7 +86,7 @@ version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3" checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3"
dependencies = [ dependencies = [
"bstr 1.0.1", "bstr 1.1.0",
"doc-comment", "doc-comment",
"predicates", "predicates",
"predicates-core", "predicates-core",
@ -160,9 +160,9 @@ dependencies = [
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
dependencies = [ dependencies = [
"memchr", "memchr",
"once_cell", "once_cell",
@ -193,9 +193,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.77" version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -280,9 +280,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.0.29" version = "4.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"clap_derive", "clap_derive",
@ -295,11 +295,11 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.0.6" version = "4.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da" checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b"
dependencies = [ dependencies = [
"clap 4.0.29", "clap 4.0.32",
] ]
[[package]] [[package]]
@ -308,7 +308,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31" checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
dependencies = [ dependencies = [
"clap 4.0.29", "clap 4.0.32",
"clap_complete", "clap_complete",
"clap_complete_fig", "clap_complete_fig",
] ]
@ -319,7 +319,7 @@ version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3" checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3"
dependencies = [ dependencies = [
"clap 4.0.29", "clap 4.0.32",
"clap_complete", "clap_complete",
] ]
@ -422,6 +422,26 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen",
]
[[package]]
name = "console_log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494"
dependencies = [
"log",
"web-sys",
]
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.3" version = "0.8.3"
@ -524,9 +544,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "cxx" name = "cxx"
version = "1.0.83" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
dependencies = [ dependencies = [
"cc", "cc",
"cxxbridge-flags", "cxxbridge-flags",
@ -536,9 +556,9 @@ dependencies = [
[[package]] [[package]]
name = "cxx-build" name = "cxx-build"
version = "1.0.83" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
dependencies = [ dependencies = [
"cc", "cc",
"codespan-reporting", "codespan-reporting",
@ -551,15 +571,15 @@ dependencies = [
[[package]] [[package]]
name = "cxxbridge-flags" name = "cxxbridge-flags"
version = "1.0.83" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
[[package]] [[package]]
name = "cxxbridge-macro" name = "cxxbridge-macro"
version = "1.0.83" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -712,9 +732,9 @@ dependencies = [
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.18" version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
@ -733,7 +753,7 @@ name = "flake8-to-ruff"
version = "0.0.194-dev.0" version = "0.0.194-dev.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap 4.0.29", "clap 4.0.32",
"configparser", "configparser",
"once_cell", "once_cell",
"regex", "regex",
@ -952,9 +972,9 @@ dependencies = [
[[package]] [[package]]
name = "insta" name = "insta"
version = "1.22.0" version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "197f4e300af8b23664d4077bf5c40e0afa9ba66a567bb5a51d3def3c7b287d1c" checksum = "e48b08a091dfe5b09a6a9688c468fdd5b4396e92ce09e2eb932f0884b02788a4"
dependencies = [ dependencies = [
"console", "console",
"lazy_static", "lazy_static",
@ -985,9 +1005,9 @@ dependencies = [
[[package]] [[package]]
name = "is-terminal" name = "is-terminal"
version = "0.4.1" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
dependencies = [ dependencies = [
"hermit-abi 0.2.6", "hermit-abi 0.2.6",
"io-lifetimes", "io-lifetimes",
@ -1006,9 +1026,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]] [[package]]
name = "joinery" name = "joinery"
@ -1115,9 +1135,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.138" version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]] [[package]]
name = "libcst" name = "libcst"
@ -1145,9 +1165,9 @@ dependencies = [
[[package]] [[package]]
name = "link-cplusplus" name = "link-cplusplus"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@ -1160,9 +1180,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.1.3" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -1340,11 +1360,11 @@ dependencies = [
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.14.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [ dependencies = [
"hermit-abi 0.1.19", "hermit-abi 0.2.6",
"libc", "libc",
] ]
@ -1391,9 +1411,9 @@ dependencies = [
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.9" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
[[package]] [[package]]
name = "path-absolutize" name = "path-absolutize"
@ -1631,9 +1651,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.47" version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1663,9 +1683,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.21" version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -1759,11 +1779,10 @@ dependencies = [
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [ dependencies = [
"crossbeam-deque",
"either", "either",
"rayon-core", "rayon-core",
] ]
@ -1868,12 +1887,15 @@ dependencies = [
"bincode", "bincode",
"bitflags", "bitflags",
"cachedir", "cachedir",
"cfg-if 1.0.0",
"chrono", "chrono",
"clap 4.0.29", "clap 4.0.32",
"clap_complete_command", "clap_complete_command",
"clearscreen", "clearscreen",
"colored", "colored",
"common-path", "common-path",
"console_error_panic_hook",
"console_log",
"criterion", "criterion",
"dirs 4.0.0", "dirs 4.0.0",
"fern", "fern",
@ -1884,6 +1906,7 @@ dependencies = [
"ignore", "ignore",
"insta", "insta",
"itertools", "itertools",
"js-sys",
"libcst", "libcst",
"log", "log",
"natord", "natord",
@ -1904,6 +1927,7 @@ dependencies = [
"schemars", "schemars",
"semver", "semver",
"serde", "serde",
"serde-wasm-bindgen",
"serde_json", "serde_json",
"shellexpand", "shellexpand",
"strum", "strum",
@ -1915,6 +1939,8 @@ dependencies = [
"update-informer", "update-informer",
"ureq", "ureq",
"walkdir", "walkdir",
"wasm-bindgen",
"wasm-bindgen-test",
] ]
[[package]] [[package]]
@ -1922,7 +1948,7 @@ name = "ruff_dev"
version = "0.0.194" version = "0.0.194"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap 4.0.29", "clap 4.0.32",
"codegen", "codegen",
"itertools", "itertools",
"libcst", "libcst",
@ -1935,6 +1961,7 @@ dependencies = [
"serde_json", "serde_json",
"strum", "strum",
"strum_macros", "strum_macros",
"textwrap",
] ]
[[package]] [[package]]
@ -1955,9 +1982,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.4" version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23" checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -2056,15 +2083,15 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.9" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.11" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -2099,6 +2126,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@ -2107,9 +2140,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "scratch" name = "scratch"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]] [[package]]
name = "sct" name = "sct"
@ -2129,18 +2162,29 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.148" version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde-wasm-bindgen"
version = "1.0.148" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2216,9 +2260,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "str_indices" name = "str_indices"
version = "0.4.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0" checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd"
[[package]] [[package]]
name = "string_cache" name = "string_cache"
@ -2263,9 +2307,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.105" version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2370,18 +2414,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2455,9 +2499,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.9" version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -2544,9 +2588,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.5" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]] [[package]]
name = "unicode-linebreak" name = "unicode-linebreak"
@ -2715,6 +2759,18 @@ dependencies = [
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.83" version = "0.2.83"
@ -2744,6 +2800,30 @@ version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d2fff962180c3fadf677438054b1db62bee4aa32af26a45388af07d1287e1d"
dependencies = [
"console_error_panic_hook",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4683da3dfc016f704c9f82cf401520c4f1cb3ee440f7f52b3d6ac29506a49ca7"
dependencies = [
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.60" version = "0.3.60"
@ -2766,9 +2846,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.22.5" version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
dependencies = [ dependencies = [
"webpki", "webpki",
] ]

View file

@ -12,6 +12,7 @@ rust-version = "1.65.0"
[lib] [lib]
name = "ruff" name = "ruff"
crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
annotate-snippets = { version = "0.9.1", features = ["color"] } annotate-snippets = { version = "0.9.1", features = ["color"] }
@ -20,9 +21,10 @@ atty = { version = "0.2.14" }
bincode = { version = "1.3.3" } bincode = { version = "1.3.3" }
bitflags = { version = "1.3.2" } bitflags = { version = "1.3.2" }
cachedir = { version = "0.3.0" } cachedir = { version = "0.3.0" }
cfg-if = { version = "1.0.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] } chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { version = "4.0.1", features = ["derive"] } clap = { version = "4.0.1", features = ["derive"] }
clap_complete_command = "0.4.0" clap_complete_command = { version = "0.4.0" }
colored = { version = "2.0.0" } colored = { version = "2.0.0" }
common-path = { version = "1.0.0" } common-path = { version = "1.0.0" }
dirs = { version = "4.0.0" } dirs = { version = "4.0.0" }
@ -41,7 +43,6 @@ num-bigint = { version = "0.4.3" }
once_cell = { version = "1.16.0" } once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] } path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
quick-junit = { version = "0.3.2" } quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" } regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false } ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.194", path = "ruff_macros" } ruff_macros = { version = "0.0.194", path = "ruff_macros" }
@ -59,23 +60,32 @@ strum_macros = { version = "0.24.3" }
textwrap = { version = "0.16.0" } textwrap = { version = "0.16.0" }
titlecase = { version = "2.2.1" } titlecase = { version = "2.2.1" }
toml = { version = "0.5.9" } toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default-features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" } walkdir = { version = "2.3.2" }
[target.'cfg(not(target_family = "wasm"))'.dependencies] [target.'cfg(not(target_family = "wasm"))'.dependencies]
clearscreen = { version = "1.0.10" } # uses which clearscreen = { version = "1.0.10" }
rayon = { version = "1.5.3" }
update-informer = { version = "0.5.0", default-features = false, features = ["pypi"], optional = true }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support # https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support # For (future) wasm-pack support
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2.7", features = ["js"] } getrandom = { version = "0.2.7", features = ["js"] }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "0.2.0" }
serde-wasm-bindgen = { version = "0.4" }
js-sys = { version = "0.3.60" }
wasm-bindgen = { version = "0.2.83" }
[dev-dependencies] [dev-dependencies]
assert_cmd = { version = "2.0.4" }
criterion = { version = "0.4.0" }
insta = { version = "1.19.1", features = ["yaml"] } insta = { version = "1.19.1", features = ["yaml"] }
test-case = { version = "2.2.2" } test-case = { version = "2.2.2" }
ureq = { version = "2.5.0", features = [] } ureq = { version = "2.5.0", features = [] }
wasm-bindgen-test = { version = "0.3.33" }
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
assert_cmd = { version = "2.0.4" }
criterion = { version = "0.4.0" }
[features] [features]
default = ["update-informer"] default = ["update-informer"]
@ -93,6 +103,11 @@ opt-level = 3
[profile.dev.package.similar] [profile.dev.package.similar]
opt-level = 3 opt-level = 3
# Reduce complexity of a parser function that would trigger a locals limit in a wasm tool.
# https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29
[profile.dev.package.rustpython-parser]
opt-level = 1
[[bench]] [[bench]]
name = "source_code_locator" name = "source_code_locator"
harness = false harness = false

24
playground/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

12
playground/index.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ruff Playground</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

1517
playground/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

27
playground/package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "playground",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"push": "wrangler pages publish dist --project-name=ruff"
},
"dependencies": {
"@monaco-editor/react": "^4.4.6",
"lz-string": "^1.4.4",
"monaco-editor": "^0.34.1",
"prettier": "^2.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react-swc": "^3.0.0",
"typescript": "^4.9.3",
"vite": "^4.0.0"
}
}

141
playground/src/App.tsx Normal file
View file

@ -0,0 +1,141 @@
import lzstring from "lz-string";
import Editor, { useMonaco } from "@monaco-editor/react";
import { MarkerSeverity } from "monaco-editor/esm/vs/editor/editor.api";
import { useEffect, useState, useCallback } from "react";
// @ts-ignore
import init, { Check, check } from "./pkg/ruff.js";
import { AVAILABLE_OPTIONS } from "./ruff_options";
import { Config, getDefaultConfig, toRuffConfig } from "./config";
import { Options } from "./Options";
const DEFAULT_SOURCE = "print(1 + 2)";
function restoreConfigAndSource(): [Config, string] {
let value = lzstring.decompressFromEncodedURIComponent(
window.location.hash.slice(1)
);
let config = {};
let source = DEFAULT_SOURCE;
if (value) {
let parts = value.split("$$$");
config = JSON.parse(parts[0]);
source = parts[1];
}
return [config, source];
}
function persistConfigAndSource(config: Config, source: string) {
window.location.hash = lzstring.compressToEncodedURIComponent(
JSON.stringify(config) + "$$$" + source
);
}
const defaultConfig = getDefaultConfig(AVAILABLE_OPTIONS);
export default function App() {
const monaco = useMonaco();
const [ruffInitialized, setRuffInitialized] = useState<boolean>(false);
const [config, setConfig] = useState<Config | null>(null);
const [source, setSource] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
init().then(() => setRuffInitialized(true));
}, []);
useEffect(() => {
if (source === null && config === null && monaco) {
let [config, source] = restoreConfigAndSource();
setConfig(config);
setSource(source);
}
}, [monaco, source, config]);
useEffect(() => {
if (config != null && source != null) {
persistConfigAndSource(config, source);
}
}, [config, source]);
useEffect(() => {
let editor = monaco?.editor;
let model = editor?.getModels()[0];
if (
!editor ||
!model ||
!ruffInitialized ||
source === null ||
config === null
) {
return;
}
let checks: Check[];
try {
checks = check(source, toRuffConfig(config));
setError(null);
} catch (e) {
setError(String(e));
return;
}
editor.setModelMarkers(
model,
"owner",
checks.map((check) => ({
startLineNumber: check.location.row,
startColumn: check.location.column + 1,
endLineNumber: check.end_location.row,
endColumn: check.end_location.column + 1,
message: `${check.code}: ${check.message}`,
severity: MarkerSeverity.Error,
}))
);
}, [config, source, monaco, ruffInitialized]);
const handleEditorChange = useCallback(
(value: string | undefined) => {
value && setSource(value);
},
[setSource]
);
const handleOptionChange = useCallback(
(groupName: string, fieldName: string, value: string) => {
let group = Object.assign({}, (config || {})[groupName]);
if (value === defaultConfig[groupName][fieldName] || value === "") {
delete group[fieldName];
} else {
group[fieldName] = value;
}
setConfig({
...config,
[groupName]: group,
});
},
[config]
);
return (
<div id="app">
<Options
config={config}
defaultConfig={defaultConfig}
onChange={handleOptionChange}
/>
<Editor
options={{ readOnly: false, minimap: { enabled: false } }}
wrapperProps={{ className: "editor" }}
defaultLanguage="python"
value={source || ""}
theme={"light"}
onChange={handleEditorChange}
/>
{error && <div id="error">{error}</div>}
</div>
);
}

View file

@ -0,0 +1,72 @@
import { Config } from "./config";
import { AVAILABLE_OPTIONS } from "./ruff_options";
function OptionEntry({
config,
defaultConfig,
groupName,
fieldName,
onChange,
}: {
config: Config | null;
defaultConfig: Config;
groupName: string;
fieldName: string;
onChange: (groupName: string, fieldName: string, value: string) => void;
}) {
const value =
config && config[groupName] && config[groupName][fieldName]
? config[groupName][fieldName]
: "";
return (
<span>
<label>
{fieldName}
<input
value={value}
placeholder={defaultConfig[groupName][fieldName]}
type="text"
onChange={(event) => {
onChange(groupName, fieldName, event.target.value);
}}
/>
</label>
</span>
);
}
export function Options({
config,
defaultConfig,
onChange,
}: {
config: Config | null;
defaultConfig: Config;
onChange: (groupName: string, fieldName: string, value: string) => void;
}) {
return (
<div className="options">
{AVAILABLE_OPTIONS.map((group) => (
<details key={group.name}>
<summary>{group.name}</summary>
<div>
<ul>
{group.fields.map((field) => (
<li key={field.name}>
<OptionEntry
config={config}
defaultConfig={defaultConfig}
groupName={group.name}
fieldName={field.name}
onChange={onChange}
/>
</li>
))}
</ul>
</div>
</details>
))}
</div>
);
}

52
playground/src/config.ts Normal file
View file

@ -0,0 +1,52 @@
import { OptionGroup } from "./ruff_options";
export type Config = { [key: string]: { [key: string]: string } };
export function getDefaultConfig(availableOptions: OptionGroup[]): Config {
const config: Config = {};
availableOptions.forEach((group) => {
config[group.name] = {};
group.fields.forEach((f) => {
config[group.name][f.name] = f.default;
});
});
return config;
}
/**
* Convert the config in the application to something Ruff accepts.
*
* Application config is always nested one level. Ruff allows for some
* top-level options.
*
* Any option value is parsed as JSON to convert it to a native JS object.
* If that fails, e.g. while a user is typing, we let the application handle that
* and show an error.
*/
export function toRuffConfig(config: Config): any {
const convertValue = (value: string): any => {
return value === "None" ? null : JSON.parse(value);
};
let result: any = {};
Object.keys(config).forEach((group_name) => {
let fields = config[group_name];
if (!fields || Object.keys(fields).length === 0) {
return;
}
if (group_name === "globals") {
Object.keys(fields).forEach((field_name) => {
result[field_name] = convertValue(fields[field_name]);
});
} else {
result[group_name] = {};
Object.keys(fields).forEach((field_name) => {
result[group_name][field_name] = convertValue(fields[field_name]);
});
}
});
return result;
}

6
playground/src/custom.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
declare module "lz-string" {
function decompressFromEncodedURIComponent(
input: string | null
): string | null;
function compressToEncodedURIComponent(input: string | null): string;
}

10
playground/src/main.tsx Normal file
View file

@ -0,0 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./style.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View file

@ -0,0 +1,222 @@
// This file is auto-generated by `cargo dev generate-playground-options`.
export interface OptionGroup {
name: string;
fields: {
name: string;
default: string;
type: string;
}[];
};
export const AVAILABLE_OPTIONS: OptionGroup[] = [
{"name": "globals", "fields": [
{
"name": "allowed-confusables",
"default": '[]',
"type": 'Vec<char>',
},
{
"name": "dummy-variable-rgx",
"default": '"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"',
"type": 'Regex',
},
{
"name": "extend-ignore",
"default": '[]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "extend-select",
"default": '[]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "external",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "fix-only",
"default": 'false',
"type": 'bool',
},
{
"name": "ignore",
"default": '[]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "line-length",
"default": '88',
"type": 'usize',
},
{
"name": "required-version",
"default": 'None',
"type": 'String',
},
{
"name": "select",
"default": '["E", "F"]',
"type": 'Vec<CheckCodePrefix>',
},
{
"name": "target-version",
"default": '"py310"',
"type": 'PythonVersion',
},
{
"name": "unfixable",
"default": '[]',
"type": 'Vec<CheckCodePrefix>',
},
]},
{"name": "flake8-annotations", "fields": [
{
"name": "allow-star-arg-any",
"default": 'false',
"type": 'bool',
},
{
"name": "mypy-init-return",
"default": 'false',
"type": 'bool',
},
{
"name": "suppress-dummy-args",
"default": 'false',
"type": 'bool',
},
{
"name": "suppress-none-returning",
"default": 'false',
"type": 'bool',
},
]},
{"name": "flake8-bugbear", "fields": [
{
"name": "extend-immutable-calls",
"default": '[]',
"type": 'Vec<String>',
},
]},
{"name": "flake8-errmsg", "fields": [
{
"name": "max-string-length",
"default": '0',
"type": 'usize',
},
]},
{"name": "flake8-import-conventions", "fields": [
{
"name": "aliases",
"default": '{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}',
"type": 'FxHashMap<String, String>',
},
{
"name": "extend-aliases",
"default": '{}',
"type": 'FxHashMap<String, String>',
},
]},
{"name": "flake8-quotes", "fields": [
{
"name": "avoid-escape",
"default": 'true',
"type": 'bool',
},
{
"name": "docstring-quotes",
"default": '"double"',
"type": 'Quote',
},
{
"name": "inline-quotes",
"default": '"double"',
"type": 'Quote',
},
{
"name": "multiline-quotes",
"default": '"double"',
"type": 'Quote',
},
]},
{"name": "flake8-tidy-imports", "fields": [
{
"name": "ban-relative-imports",
"default": '"parents"',
"type": 'Strictness',
},
]},
{"name": "flake8-unused-arguments", "fields": [
{
"name": "ignore-variadic-names",
"default": 'false',
"type": 'bool',
},
]},
{"name": "isort", "fields": [
{
"name": "combine-as-imports",
"default": 'false',
"type": 'bool',
},
{
"name": "extra-standard-library",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "force-wrap-aliases",
"default": 'false',
"type": 'bool',
},
{
"name": "known-first-party",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "known-third-party",
"default": '[]',
"type": 'Vec<String>',
},
{
"name": "split-on-trailing-comma",
"default": 'true',
"type": 'bool',
},
]},
{"name": "mccabe", "fields": [
{
"name": "max-complexity",
"default": '10',
"type": 'usize',
},
]},
{"name": "pep8-naming", "fields": [
{
"name": "classmethod-decorators",
"default": '["classmethod"]',
"type": 'Vec<String>',
},
{
"name": "ignore-names",
"default": '["setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule", "asyncSetUp", "asyncTearDown", "setUpTestData", "failureException", "longMessage", "maxDiff"]',
"type": 'Vec<String>',
},
{
"name": "staticmethod-decorators",
"default": '["staticmethod"]',
"type": 'Vec<String>',
},
]},
{"name": "pyupgrade", "fields": [
{
"name": "keep-runtime-typing",
"default": 'false',
"type": 'bool',
},
]},
];

60
playground/src/style.css Normal file
View file

@ -0,0 +1,60 @@
* {
box-sizing: border-box;
}
body,
html,
#root,
#app {
margin: 0;
height: 100%;
width: 100%;
}
#app {
display: flex;
}
.options {
height: 100vh;
overflow-y: scroll;
padding: 1em;
min-width: 300px;
border-right: 1px solid lightgray;
}
.options ul {
padding-left: 1em;
list-style-type: none;
}
.options li {
margin-bottom: 0.3em;
}
.options details {
margin-bottom: 1em;
}
.options summary {
font-size: 1.3rem;
}
.options input {
display: block;
width: 100%;
}
.editor {
padding: 1em;
}
#error {
position: fixed;
bottom: 0;
width: 100%;
min-height: 1em;
padding: 1em;
background: darkred;
color: white;
}

1
playground/src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

21
playground/tsconfig.json Normal file
View file

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View file

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View file

@ -0,0 +1,7 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});

View file

@ -18,3 +18,4 @@ schemars = { version = "0.8.11" }
serde_json = {version="1.0.91"} serde_json = {version="1.0.91"}
strum = { version = "0.24.1", features = ["strum_macros"] } strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" } strum_macros = { version = "0.24.3" }
textwrap = { version = "0.16.0" }

View file

@ -0,0 +1,142 @@
//! Generate typescript file defining options to be used by the web playground.
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use itertools::Itertools;
use ruff::settings::options::Options;
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
#[derive(Args)]
pub struct Cli {
/// Write the generated table to stdout (rather than to `TODO`).
#[arg(long)]
dry_run: bool,
}
fn emit_field(output: &mut String, field: &OptionField) {
output.push_str(&textwrap::indent(
&textwrap::dedent(&format!(
"
{{
\"name\": \"{}\",
\"default\": '{}',
\"type\": '{}',
}},",
field.name, field.default, field.value_type
)),
" ",
));
}
pub fn main(cli: &Cli) -> Result<()> {
let mut output = String::new();
// Generate all the top-level fields.
output.push_str(&format!("{{\"name\": \"{}\", \"fields\": [", "globals"));
for field in Options::get_available_options()
.into_iter()
.filter_map(|entry| {
if let OptionEntry::Field(field) = entry {
Some(field)
} else {
None
}
})
// Filter out options that don't make sense in the playground.
.filter(|field| {
!matches!(
field.name,
"src"
| "fix"
| "format"
| "exclude"
| "extend"
| "extend-exclude"
| "fixable"
| "force-exclude"
| "ignore-init-module-imports"
| "respect-gitignore"
| "show-source"
| "cache-dir"
| "per-file-ignores"
)
})
.sorted_by_key(|field| field.name)
{
emit_field(&mut output, &field);
}
output.push_str("\n]},\n");
// Generate all the sub-groups.
for group in Options::get_available_options()
.into_iter()
.filter_map(|entry| {
if let OptionEntry::Group(group) = entry {
Some(group)
} else {
None
}
})
.sorted_by_key(|group| group.name)
{
output.push_str(&format!("{{\"name\": \"{}\", \"fields\": [", group.name));
for field in group
.fields
.iter()
.filter_map(|entry| {
if let OptionEntry::Field(field) = entry {
Some(field)
} else {
None
}
})
.sorted_by_key(|field| field.name)
{
emit_field(&mut output, field);
}
output.push_str("\n]},\n");
}
let prefix = textwrap::dedent(
r"
// This file is auto-generated by `cargo dev generate-playground-options`.
export interface OptionGroup {
name: string;
fields: {
name: string;
default: string;
type: string;
}[];
};
export const AVAILABLE_OPTIONS: OptionGroup[] = [
",
);
let postfix = "];";
if cli.dry_run {
print!("{output}");
} else {
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("playground")
.join("src")
.join("ruff_options.ts");
let mut f = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(file)?;
write!(f, "{prefix}")?;
write!(f, "{}", textwrap::indent(&output, " "))?;
write!(f, "{postfix}")?;
}
Ok(())
}

View file

@ -14,6 +14,7 @@
pub mod generate_check_code_prefix; pub mod generate_check_code_prefix;
pub mod generate_json_schema; pub mod generate_json_schema;
pub mod generate_options; pub mod generate_options;
pub mod generate_playground_options;
pub mod generate_rules_table; pub mod generate_rules_table;
pub mod generate_source_code; pub mod generate_source_code;
pub mod print_ast; pub mod print_ast;

View file

@ -14,8 +14,9 @@
use anyhow::Result; use anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use ruff_dev::{ use ruff_dev::{
generate_check_code_prefix, generate_json_schema, generate_options, generate_rules_table, generate_check_code_prefix, generate_json_schema, generate_options,
generate_source_code, print_ast, print_cst, print_tokens, generate_playground_options, generate_rules_table, generate_source_code, print_ast, print_cst,
print_tokens,
}; };
#[derive(Parser)] #[derive(Parser)]
@ -36,6 +37,9 @@ enum Commands {
GenerateRulesTable(generate_rules_table::Cli), GenerateRulesTable(generate_rules_table::Cli),
/// Generate a Markdown-compatible listing of configuration options. /// Generate a Markdown-compatible listing of configuration options.
GenerateOptions(generate_options::Cli), GenerateOptions(generate_options::Cli),
/// Generate typescript file defining options to be used by the web
/// playground.
GeneratePlaygroundOptions(generate_playground_options::Cli),
/// Run round-trip source code generation on a given Python file. /// Run round-trip source code generation on a given Python file.
GenerateSourceCode(generate_source_code::Cli), GenerateSourceCode(generate_source_code::Cli),
/// Print the AST for a given Python file. /// Print the AST for a given Python file.
@ -54,6 +58,7 @@ fn main() -> Result<()> {
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?, Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Commands::GenerateSourceCode(args) => generate_source_code::main(args)?, Commands::GenerateSourceCode(args) => generate_source_code::main(args)?,
Commands::GenerateOptions(args) => generate_options::main(args)?, Commands::GenerateOptions(args) => generate_options::main(args)?,
Commands::GeneratePlaygroundOptions(args) => generate_playground_options::main(args)?,
Commands::PrintAST(args) => print_ast::main(args)?, Commands::PrintAST(args) => print_ast::main(args)?,
Commands::PrintCST(args) => print_cst::main(args)?, Commands::PrintCST(args) => print_cst::main(args)?,
Commands::PrintTokens(args) => print_tokens::main(args)?, Commands::PrintTokens(args) => print_tokens::main(args)?,

View file

@ -11,19 +11,10 @@
clippy::too_many_lines clippy::too_many_lines
)] )]
use std::path::Path; use cfg_if::cfg_if;
use anyhow::Result;
use path_absolutize::path_dedot;
use rustpython_helpers::tokenize;
use rustpython_parser::lexer::LexResult;
use settings::{pyproject, Settings};
use crate::checks::Check; use crate::checks::Check;
use crate::linter::check_path; use crate::settings::Settings;
use crate::resolver::Relativity;
use crate::settings::configuration::Configuration;
use crate::settings::flags;
use crate::source_code_locator::SourceCodeLocator; use crate::source_code_locator::SourceCodeLocator;
mod ast; mod ast;
@ -34,7 +25,6 @@ pub mod checks;
pub mod checks_gen; pub mod checks_gen;
pub mod cli; pub mod cli;
pub mod code_gen; pub mod code_gen;
pub mod commands;
mod cst; mod cst;
mod directives; mod directives;
mod docstrings; mod docstrings;
@ -66,7 +56,6 @@ pub mod logging;
pub mod mccabe; pub mod mccabe;
pub mod message; pub mod message;
mod noqa; mod noqa;
mod packages;
mod pandas_vet; mod pandas_vet;
pub mod pep8_naming; pub mod pep8_naming;
pub mod printer; pub mod printer;
@ -82,58 +71,20 @@ mod ruff;
mod rustpython_helpers; mod rustpython_helpers;
pub mod settings; pub mod settings;
pub mod source_code_locator; pub mod source_code_locator;
#[cfg(feature = "update-informer")]
pub mod updates;
mod vendored; mod vendored;
pub mod visibility; pub mod visibility;
/// Load the relevant `Settings` for a given `Path`. cfg_if! {
fn resolve(path: &Path) -> Result<Settings> { if #[cfg(not(target_family = "wasm"))] {
if let Some(pyproject) = pyproject::find_settings_toml(path)? { pub mod commands;
// First priority: `pyproject.toml` in the current `Path`. mod packages;
resolver::resolve_settings(&pyproject, &Relativity::Parent, None) #[cfg(all(feature = "update-informer"))]
} else if let Some(pyproject) = pyproject::find_user_settings_toml() { pub mod updates;
// Second priority: user-specific `pyproject.toml`.
resolver::resolve_settings(&pyproject, &Relativity::Cwd, None) mod lib_native;
pub use lib_native::check;
} else { } else {
// Fallback: default settings. mod lib_wasm;
Settings::from_configuration(Configuration::default(), &path_dedot::CWD) pub use lib_wasm::check;
} }
} }
/// Run Ruff over Python source code directly.
pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
// Load the relevant `Settings` for the given `Path`.
let settings = resolve(path)?;
// Validate the `Settings` and return any errors.
settings.validate()?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);
// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(contents);
// Extract the `# noqa` and `# isort: skip` directives from the source.
let directives = directives::extract_directives(
&tokens,
&locator,
directives::Flags::from_settings(&settings),
);
// Generate checks.
let checks = check_path(
path,
packages::detect_package_root(path),
contents,
tokens,
&locator,
&directives,
&settings,
autofix.into(),
flags::Noqa::Enabled,
)?;
Ok(checks)
}

65
src/lib_native.rs Normal file
View file

@ -0,0 +1,65 @@
use std::path::Path;
use anyhow::Result;
use path_absolutize::path_dedot;
use rustpython_parser::lexer::LexResult;
use crate::checks::Check;
use crate::linter::check_path;
use crate::resolver::Relativity;
use crate::rustpython_helpers::tokenize;
use crate::settings::configuration::Configuration;
use crate::settings::{flags, pyproject, Settings};
use crate::source_code_locator::SourceCodeLocator;
use crate::{directives, packages, resolver};
/// Load the relevant `Settings` for a given `Path`.
fn resolve(path: &Path) -> Result<Settings> {
if let Some(pyproject) = pyproject::find_settings_toml(path)? {
// First priority: `pyproject.toml` in the current `Path`.
resolver::resolve_settings(&pyproject, &Relativity::Parent, None)
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
// Second priority: user-specific `pyproject.toml`.
resolver::resolve_settings(&pyproject, &Relativity::Cwd, None)
} else {
// Fallback: default settings.
Settings::from_configuration(Configuration::default(), &path_dedot::CWD)
}
}
/// Run Ruff over Python source code directly.
pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
// Load the relevant `Settings` for the given `Path`.
let settings = resolve(path)?;
// Validate the `Settings` and return any errors.
settings.validate()?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);
// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(contents);
// Extract the `# noqa` and `# isort: skip` directives from the source.
let directives = directives::extract_directives(
&tokens,
&locator,
directives::Flags::from_settings(&settings),
);
// Generate checks.
let checks = check_path(
path,
packages::detect_package_root(path),
contents,
tokens,
&locator,
&directives,
&settings,
autofix.into(),
flags::Noqa::Enabled,
)?;
Ok(checks)
}

141
src/lib_wasm.rs Normal file
View file

@ -0,0 +1,141 @@
use std::path::Path;
use rustpython_ast::Location;
use rustpython_parser::lexer::LexResult;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use crate::checks::CheckCode;
use crate::directives;
use crate::linter::check_path;
use crate::rustpython_helpers::tokenize;
use crate::settings::configuration::Configuration;
use crate::settings::options::Options;
use crate::settings::{flags, Settings};
use crate::source_code_locator::SourceCodeLocator;
#[wasm_bindgen(typescript_custom_section)]
const TYPES: &'static str = r#"
export interface Check {
code: string;
message: string;
location: {
row: number;
column: number;
};
end_location: {
row: number;
column: number;
};
};
"#;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Message {
code: CheckCode,
message: String,
location: Location,
end_location: Location,
}
#[wasm_bindgen(start)]
pub fn run() {
use log::Level;
console_error_panic_hook::set_once();
console_log::init_with_level(Level::Debug).expect("Initializing logger went wrong.");
}
#[wasm_bindgen]
pub fn check(contents: &str, options: JsValue) -> Result<JsValue, JsValue> {
let options: Options = serde_wasm_bindgen::from_value(options).map_err(|e| e.to_string())?;
let configuration =
Configuration::from_options(options, Path::new(".")).map_err(|e| e.to_string())?;
let settings =
Settings::from_configuration(configuration, Path::new(".")).map_err(|e| e.to_string())?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);
// Initialize the SourceCodeLocator (which computes offsets lazily).
let locator = SourceCodeLocator::new(contents);
// Extract the `# noqa` and `# isort: skip` directives from the source.
let directives = directives::extract_directives(&tokens, &locator, directives::Flags::empty());
// Generate checks.
let checks = check_path(
Path::new("<filename>"),
None,
contents,
tokens,
&locator,
&directives,
&settings,
false.into(),
flags::Noqa::Enabled,
)
.map_err(|e| e.to_string())?;
let messages: Vec<Message> = checks
.into_iter()
.map(|check| Message {
code: check.kind.code().clone(),
message: check.kind.body(),
location: check.location,
end_location: check.end_location,
})
.collect();
Ok(serde_wasm_bindgen::to_value(&messages)?)
}
#[cfg(test)]
mod test {
use js_sys;
use wasm_bindgen_test::*;
use super::*;
macro_rules! check {
($source:expr, $config:expr, $expected:expr) => {{
let foo = js_sys::JSON::parse($config).unwrap();
match check($source, foo) {
Ok(output) => {
let result: Vec<Message> = serde_wasm_bindgen::from_value(output).unwrap();
assert_eq!(result, $expected);
}
Err(e) => assert!(false, "{:#?}", e),
}
}};
}
#[wasm_bindgen_test]
fn empty_config() {
check!(
"if (1, 2): pass",
r#"{}"#,
[Message {
code: CheckCode::F634,
message: "If test is a tuple, which is always `True`".to_string(),
location: Location::new(1, 0),
end_location: Location::new(1, 15)
}]
);
}
#[wasm_bindgen_test]
fn partial_config() {
check!("if (1, 2): pass", r#"{"ignore": ["F"]}"#, []);
}
#[wasm_bindgen_test]
fn partial_nested_config() {
let config = r#"{
"select": ["Q"],
"flake8-quotes": {
"inline-quotes": "single"
}
}"#;
check!(r#"print('hello world')"#, config, []);
}
}

View file

@ -5,7 +5,6 @@ use std::ops::AddAssign;
use std::path::Path; use std::path::Path;
use anyhow::Result; use anyhow::Result;
#[cfg(not(target_family = "wasm"))]
use log::debug; use log::debug;
use rustpython_parser::lexer::LexResult; use rustpython_parser::lexer::LexResult;

View file

@ -11,263 +11,23 @@
clippy::too_many_lines clippy::too_many_lines
)] )]
use std::io::{self};
use std::path::{Path, PathBuf};
use std::process::ExitCode; use std::process::ExitCode;
use std::sync::mpsc::channel;
use ::ruff::autofix::fixer; use cfg_if::cfg_if;
use ::ruff::cli::{extract_log_level, Cli, Overrides};
use ::ruff::commands;
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::printer::{Printer, Violations};
use ::ruff::resolver::{resolve_settings, FileDiscovery, PyprojectDiscovery, Relativity};
use ::ruff::settings::configuration::Configuration;
use ::ruff::settings::types::SerializationFormat;
use ::ruff::settings::{pyproject, Settings};
#[cfg(feature = "update-informer")]
use ::ruff::updates;
use anyhow::Result;
use clap::{CommandFactory, Parser};
use colored::Colorize; use colored::Colorize;
use notify::{recommended_watcher, RecursiveMode, Watcher};
use path_absolutize::path_dedot;
/// Resolve the relevant settings strategy and defaults for the current cfg_if! {
/// invocation. if #[cfg(not(target_family = "wasm"))] {
fn resolve( mod main_native;
config: Option<&Path>, use main_native::inner_main;
overrides: &Overrides,
stdin_filename: Option<&Path>,
) -> Result<PyprojectDiscovery> {
if let Some(pyproject) = config {
// First priority: the user specified a `pyproject.toml` file. Use that
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
// current working directory. (This matches ESLint's behavior.)
let settings = resolve_settings(pyproject, &Relativity::Cwd, Some(overrides))?;
Ok(PyprojectDiscovery::Fixed(settings))
} else if let Some(pyproject) = pyproject::find_settings_toml(
stdin_filename
.as_ref()
.unwrap_or(&path_dedot::CWD.as_path()),
)? {
// Second priority: find a `pyproject.toml` file in either an ancestor of
// `stdin_filename` (if set) or the current working path all paths relative to
// that directory. (With `Strategy::Hierarchical`, we'll end up finding
// the "closest" `pyproject.toml` file for every Python file later on,
// so these act as the "default" settings.)
let settings = resolve_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
Ok(PyprojectDiscovery::Hierarchical(settings))
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
// Third priority: find a user-specific `pyproject.toml`, but resolve all paths
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
// end up the "closest" `pyproject.toml` file for every Python file later on, so
// these act as the "default" settings.)
let settings = resolve_settings(&pyproject, &Relativity::Cwd, Some(overrides))?;
Ok(PyprojectDiscovery::Hierarchical(settings))
} else { } else {
// Fallback: load Ruff's default settings, and resolve all paths relative to the use anyhow::Result;
// current working directory. (With `Strategy::Hierarchical`, we'll end up the
// "closest" `pyproject.toml` file for every Python file later on, so these act
// as the "default" settings.)
let mut config = Configuration::default();
// Apply command-line options that override defaults.
config.apply(overrides.clone());
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
Ok(PyprojectDiscovery::Hierarchical(settings))
}
}
fn inner_main() -> Result<ExitCode> { #[allow(clippy::unnecessary_wraps)]
// Extract command-line arguments. fn inner_main() -> Result<ExitCode> {
let (cli, overrides) = Cli::parse().partition(); Ok(ExitCode::FAILURE)
let log_level = extract_log_level(&cli);
set_up_logging(&log_level)?;
if cli.show_settings && cli.show_files {
anyhow::bail!("specify --show-settings or show-files (not both)")
}
if let Some(shell) = cli.generate_shell_completion {
shell.generate(&mut Cli::command(), &mut io::stdout());
return Ok(ExitCode::SUCCESS);
}
// Construct the "default" settings. These are used when no `pyproject.toml`
// files are present, or files are injected from outside of the hierarchy.
let pyproject_strategy = resolve(
cli.config.as_deref(),
&overrides,
cli.stdin_filename.as_deref(),
)?;
// Validate the `Settings` and return any errors.
match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.validate()?,
PyprojectDiscovery::Hierarchical(settings) => settings.validate()?,
};
// Extract options that are included in `Settings`, but only apply at the top
// level.
let file_strategy = FileDiscovery {
force_exclude: match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.force_exclude,
PyprojectDiscovery::Hierarchical(settings) => settings.force_exclude,
},
respect_gitignore: match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.respect_gitignore,
PyprojectDiscovery::Hierarchical(settings) => settings.respect_gitignore,
},
};
let (fix, fix_only, format) = match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => (settings.fix, settings.fix_only, settings.format),
PyprojectDiscovery::Hierarchical(settings) => {
(settings.fix, settings.fix_only, settings.format)
}
};
let autofix = if fix || fix_only {
fixer::Mode::Apply
} else if matches!(format, SerializationFormat::Json) {
fixer::Mode::Generate
} else {
fixer::Mode::None
};
let violations = if fix_only {
Violations::Hide
} else {
Violations::Show
};
let cache = !cli.no_cache;
if let Some(code) = cli.explain {
commands::explain(&code, &format)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_settings {
commands::show_settings(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_files {
commands::show_files(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
return Ok(ExitCode::SUCCESS);
}
let printer = Printer::new(&format, &log_level, &autofix, &violations);
if cli.watch {
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
eprintln!("Warning: --fix is not enabled in watch mode.");
}
if cli.add_noqa {
eprintln!("Warning: --add-noqa is not enabled in watch mode.");
}
if cli.autoformat {
eprintln!("Warning: --autoformat is not enabled in watch mode.");
}
if format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
// Perform an initial run instantly.
printer.clear_screen()?;
printer.write_to_user("Starting linter in watch mode...\n");
let messages = commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
fixer::Mode::None,
)?;
printer.write_continuously(&messages)?;
// Configure the file watcher.
let (tx, rx) = channel();
let mut watcher = recommended_watcher(tx)?;
for file in &cli.files {
watcher.watch(file, RecursiveMode::Recursive)?;
}
loop {
match rx.recv() {
Ok(event) => {
let paths = event?.paths;
let py_changed = paths.iter().any(|path| {
path.extension()
.map(|ext| ext == "py" || ext == "pyi")
.unwrap_or_default()
});
if py_changed {
printer.clear_screen()?;
printer.write_to_user("File change detected...\n");
let messages = commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
fixer::Mode::None,
)?;
printer.write_continuously(&messages)?;
}
}
Err(err) => return Err(err.into()),
}
}
} else if cli.add_noqa {
let modifications =
commands::add_noqa(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
if modifications > 0 && log_level >= LogLevel::Default {
println!("Added {modifications} noqa directives.");
}
} else if cli.autoformat {
let modifications =
commands::autoformat(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
if modifications > 0 && log_level >= LogLevel::Default {
println!("Formatted {modifications} files.");
}
} else {
let is_stdin = cli.files == vec![PathBuf::from("-")];
// Generate lint violations.
let diagnostics = if is_stdin {
commands::run_stdin(
cli.stdin_filename.as_deref(),
&pyproject_strategy,
&file_strategy,
&overrides,
autofix,
)?
} else {
commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
autofix,
)?
};
// Always try to print violations (the printer itself may suppress output),
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) {
printer.write_once(&diagnostics)?;
}
// Check for updates if we're in a non-silent log level.
#[cfg(feature = "update-informer")]
if !is_stdin && log_level >= LogLevel::Default && atty::is(atty::Stream::Stdout) {
drop(updates::check_for_updates());
}
if !diagnostics.messages.is_empty() && !cli.exit_zero && !fix_only {
return Ok(ExitCode::FAILURE);
} }
} }
Ok(ExitCode::SUCCESS)
} }
fn main() -> ExitCode { fn main() -> ExitCode {

270
src/main_native.rs Normal file
View file

@ -0,0 +1,270 @@
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use std::io::{self};
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use std::sync::mpsc::channel;
use ::ruff::autofix::fixer;
use ::ruff::cli::{extract_log_level, Cli, Overrides};
use ::ruff::commands;
use ::ruff::logging::{set_up_logging, LogLevel};
use ::ruff::printer::{Printer, Violations};
use ::ruff::resolver::{resolve_settings, FileDiscovery, PyprojectDiscovery, Relativity};
use ::ruff::settings::configuration::Configuration;
use ::ruff::settings::types::SerializationFormat;
use ::ruff::settings::{pyproject, Settings};
#[cfg(feature = "update-informer")]
use ::ruff::updates;
use anyhow::Result;
use clap::{CommandFactory, Parser};
use notify::{recommended_watcher, RecursiveMode, Watcher};
use path_absolutize::path_dedot;
/// Resolve the relevant settings strategy and defaults for the current
/// invocation.
fn resolve(
config: Option<&Path>,
overrides: &Overrides,
stdin_filename: Option<&Path>,
) -> Result<PyprojectDiscovery> {
if let Some(pyproject) = config {
// First priority: the user specified a `pyproject.toml` file. Use that
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
// current working directory. (This matches ESLint's behavior.)
let settings = resolve_settings(pyproject, &Relativity::Cwd, Some(overrides))?;
Ok(PyprojectDiscovery::Fixed(settings))
} else if let Some(pyproject) = pyproject::find_settings_toml(
stdin_filename
.as_ref()
.unwrap_or(&path_dedot::CWD.as_path()),
)? {
// Second priority: find a `pyproject.toml` file in either an ancestor of
// `stdin_filename` (if set) or the current working path all paths relative to
// that directory. (With `Strategy::Hierarchical`, we'll end up finding
// the "closest" `pyproject.toml` file for every Python file later on,
// so these act as the "default" settings.)
let settings = resolve_settings(&pyproject, &Relativity::Parent, Some(overrides))?;
Ok(PyprojectDiscovery::Hierarchical(settings))
} else if let Some(pyproject) = pyproject::find_user_settings_toml() {
// Third priority: find a user-specific `pyproject.toml`, but resolve all paths
// relative the current working directory. (With `Strategy::Hierarchical`, we'll
// end up the "closest" `pyproject.toml` file for every Python file later on, so
// these act as the "default" settings.)
let settings = resolve_settings(&pyproject, &Relativity::Cwd, Some(overrides))?;
Ok(PyprojectDiscovery::Hierarchical(settings))
} else {
// Fallback: load Ruff's default settings, and resolve all paths relative to the
// current working directory. (With `Strategy::Hierarchical`, we'll end up the
// "closest" `pyproject.toml` file for every Python file later on, so these act
// as the "default" settings.)
let mut config = Configuration::default();
// Apply command-line options that override defaults.
config.apply(overrides.clone());
let settings = Settings::from_configuration(config, &path_dedot::CWD)?;
Ok(PyprojectDiscovery::Hierarchical(settings))
}
}
pub(crate) fn inner_main() -> Result<ExitCode> {
// Extract command-line arguments.
let (cli, overrides) = Cli::parse().partition();
let log_level = extract_log_level(&cli);
set_up_logging(&log_level)?;
if cli.show_settings && cli.show_files {
anyhow::bail!("specify --show-settings or show-files (not both)")
}
if let Some(shell) = cli.generate_shell_completion {
shell.generate(&mut Cli::command(), &mut io::stdout());
return Ok(ExitCode::SUCCESS);
}
// Construct the "default" settings. These are used when no `pyproject.toml`
// files are present, or files are injected from outside of the hierarchy.
let pyproject_strategy = resolve(
cli.config.as_deref(),
&overrides,
cli.stdin_filename.as_deref(),
)?;
// Validate the `Settings` and return any errors.
match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.validate()?,
PyprojectDiscovery::Hierarchical(settings) => settings.validate()?,
};
// Extract options that are included in `Settings`, but only apply at the top
// level.
let file_strategy = FileDiscovery {
force_exclude: match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.force_exclude,
PyprojectDiscovery::Hierarchical(settings) => settings.force_exclude,
},
respect_gitignore: match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.respect_gitignore,
PyprojectDiscovery::Hierarchical(settings) => settings.respect_gitignore,
},
};
let (fix, fix_only, format) = match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => (settings.fix, settings.fix_only, settings.format),
PyprojectDiscovery::Hierarchical(settings) => {
(settings.fix, settings.fix_only, settings.format)
}
};
let autofix = if fix || fix_only {
fixer::Mode::Apply
} else if matches!(format, SerializationFormat::Json) {
fixer::Mode::Generate
} else {
fixer::Mode::None
};
let violations = if fix_only {
Violations::Hide
} else {
Violations::Show
};
let cache = !cli.no_cache;
if let Some(code) = cli.explain {
commands::explain(&code, &format)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_settings {
commands::show_settings(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
return Ok(ExitCode::SUCCESS);
}
if cli.show_files {
commands::show_files(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
return Ok(ExitCode::SUCCESS);
}
let printer = Printer::new(&format, &log_level, &autofix, &violations);
if cli.watch {
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
eprintln!("Warning: --fix is not enabled in watch mode.");
}
if cli.add_noqa {
eprintln!("Warning: --add-noqa is not enabled in watch mode.");
}
if cli.autoformat {
eprintln!("Warning: --autoformat is not enabled in watch mode.");
}
if format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
// Perform an initial run instantly.
printer.clear_screen()?;
printer.write_to_user("Starting linter in watch mode...\n");
let messages = commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
fixer::Mode::None,
)?;
printer.write_continuously(&messages)?;
// Configure the file watcher.
let (tx, rx) = channel();
let mut watcher = recommended_watcher(tx)?;
for file in &cli.files {
watcher.watch(file, RecursiveMode::Recursive)?;
}
loop {
match rx.recv() {
Ok(event) => {
let paths = event?.paths;
let py_changed = paths.iter().any(|path| {
path.extension()
.map(|ext| ext == "py" || ext == "pyi")
.unwrap_or_default()
});
if py_changed {
printer.clear_screen()?;
printer.write_to_user("File change detected...\n");
let messages = commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
fixer::Mode::None,
)?;
printer.write_continuously(&messages)?;
}
}
Err(err) => return Err(err.into()),
}
}
} else if cli.add_noqa {
let modifications =
commands::add_noqa(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
if modifications > 0 && log_level >= LogLevel::Default {
println!("Added {modifications} noqa directives.");
}
} else if cli.autoformat {
let modifications =
commands::autoformat(&cli.files, &pyproject_strategy, &file_strategy, &overrides)?;
if modifications > 0 && log_level >= LogLevel::Default {
println!("Formatted {modifications} files.");
}
} else {
let is_stdin = cli.files == vec![PathBuf::from("-")];
// Generate lint violations.
let diagnostics = if is_stdin {
commands::run_stdin(
cli.stdin_filename.as_deref(),
&pyproject_strategy,
&file_strategy,
&overrides,
autofix,
)?
} else {
commands::run(
&cli.files,
&pyproject_strategy,
&file_strategy,
&overrides,
cache.into(),
autofix,
)?
};
// Always try to print violations (the printer itself may suppress output),
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) {
printer.write_once(&diagnostics)?;
}
// Check for updates if we're in a non-silent log level.
#[cfg(feature = "update-informer")]
if !is_stdin && log_level >= LogLevel::Default && atty::is(atty::Stream::Stdout) {
drop(updates::check_for_updates());
}
if !diagnostics.messages.is_empty() && !cli.exit_zero && !fix_only {
return Ok(ExitCode::FAILURE);
}
}
Ok(ExitCode::SUCCESS)
}

View file

@ -1,3 +1,5 @@
#![cfg(not(target_family = "wasm"))]
use std::io::{ErrorKind, Read}; use std::io::{ErrorKind, Read};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream}; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};

View file

@ -1,3 +1,5 @@
#![cfg(not(target_family = "wasm"))]
use std::str; use std::str;
use anyhow::Result; use anyhow::Result;