mirror of https://github.com/mtshiba/pylyzer
Compare commits
155 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
b7e78b310c | |
|
|
83bd0ffc61 | |
|
|
f0a44bf272 | |
|
|
7871d31c93 | |
|
|
7248c00448 | |
|
|
5fb0c71a63 | |
|
|
fd4b5895f3 | |
|
|
71f5da0963 | |
|
|
3003076cef | |
|
|
f653c3fe16 | |
|
|
8b9437ef7a | |
|
|
b9b757d896 | |
|
|
5882f18c46 | |
|
|
001183db79 | |
|
|
acd50996ae | |
|
|
640f654b1f | |
|
|
4caac7c325 | |
|
|
814c46996d | |
|
|
b52b017c37 | |
|
|
1aa3f53d5d | |
|
|
50c7b7a252 | |
|
|
1083e2f140 | |
|
|
dedfa8443b | |
|
|
accd453a12 | |
|
|
9cd1846216 | |
|
|
faec281fa9 | |
|
|
0da66dceca | |
|
|
deb6736f9f | |
|
|
2a98535d4c | |
|
|
233b7ee382 | |
|
|
c985e798f0 | |
|
|
088c22a84c | |
|
|
18386546b4 | |
|
|
365530a220 | |
|
|
12844b19f4 | |
|
|
25c37cbc28 | |
|
|
a24164bbee | |
|
|
4c57e6ead7 | |
|
|
fa4cefd327 | |
|
|
f06940bb4c | |
|
|
a0d713b2d2 | |
|
|
9a9215ef58 | |
|
|
5bc13dc463 | |
|
|
6e644dacd9 | |
|
|
16fef0b04a | |
|
|
4afbd2922d | |
|
|
5a81c4c517 | |
|
|
f2153372d0 | |
|
|
a710224ccc | |
|
|
d4c2a27356 | |
|
|
e01c99d2c0 | |
|
|
a7801eb145 | |
|
|
64169a3962 | |
|
|
96a1a03d9f | |
|
|
4b5d10b029 | |
|
|
bb6b731fbf | |
|
|
97020008b5 | |
|
|
0157455816 | |
|
|
460ca54cf8 | |
|
|
00e0ae46f2 | |
|
|
c1f37108a0 | |
|
|
8621139e64 | |
|
|
a0747938dd | |
|
|
c4b7aa7faa | |
|
|
fddc571eea | |
|
|
74163c48b8 | |
|
|
d3d3bdb58a | |
|
|
02c217e001 | |
|
|
d650569de6 | |
|
|
ec539b013f | |
|
|
1bfb52f801 | |
|
|
b854eb7126 | |
|
|
a1ffcf0a35 | |
|
|
2fd2b37411 | |
|
|
4e335ac3c1 | |
|
|
fa15f92276 | |
|
|
56e016e915 | |
|
|
31d7ad451d | |
|
|
a209a67e55 | |
|
|
e899546687 | |
|
|
18af09b5e4 | |
|
|
42c11642fd | |
|
|
e3a9720159 | |
|
|
b6a368257f | |
|
|
6828ddcf56 | |
|
|
f5503d6f9e | |
|
|
d448aaf974 | |
|
|
94221a6419 | |
|
|
6e88efebe8 | |
|
|
dcae47070a | |
|
|
96b6db2904 | |
|
|
d9d074e088 | |
|
|
745d62cf77 | |
|
|
67a65b5c52 | |
|
|
ce12285143 | |
|
|
582906ed92 | |
|
|
172cd5257b | |
|
|
bbe828db94 | |
|
|
11527b305f | |
|
|
513f112d6a | |
|
|
4c471b8261 | |
|
|
8cf472f469 | |
|
|
55d44bc5a4 | |
|
|
14d7fd3c33 | |
|
|
7440f2fa46 | |
|
|
bff700f17c | |
|
|
3c46a0340d | |
|
|
11b3940f32 | |
|
|
974269a2ba | |
|
|
e64baf4107 | |
|
|
0717723ca7 | |
|
|
5e9a7f9215 | |
|
|
f1f13285af | |
|
|
679a2fdadd | |
|
|
a088185612 | |
|
|
91427c607d | |
|
|
0d155ee0cd | |
|
|
c8863ca77c | |
|
|
a59528ba7c | |
|
|
fc26c8578c | |
|
|
f24a10f30d | |
|
|
b35a26bba4 | |
|
|
70c23905ae | |
|
|
9fd1bf3373 | |
|
|
a988e3e52c | |
|
|
6d6aea5ce5 | |
|
|
9c335837e9 | |
|
|
38cd37b75d | |
|
|
021f33b31a | |
|
|
84c72e6ef6 | |
|
|
7e0d3a7f4a | |
|
|
f396fcb2c5 | |
|
|
be21322cef | |
|
|
12a4c4c45e | |
|
|
2f919b2e6f | |
|
|
e44f7f4a48 | |
|
|
058e4b97d6 | |
|
|
b9fa9982bc | |
|
|
bb9274ed8f | |
|
|
9e3df04786 | |
|
|
bfdafa9965 | |
|
|
71e69d6342 | |
|
|
1a4cb8db60 | |
|
|
a08003d5b8 | |
|
|
965418be80 | |
|
|
a5435180c0 | |
|
|
3b6d46e3de | |
|
|
b69ce84587 | |
|
|
80ab984b6d | |
|
|
75be1077db | |
|
|
6b807d17e1 | |
|
|
95510f6227 | |
|
|
a369950784 | |
|
|
700937e6f0 | |
|
|
ca262b4046 |
|
|
@ -0,0 +1,53 @@
|
||||||
|
name: Bug report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Describe the bug"
|
||||||
|
description: "A clear and concise description of what the bug is."
|
||||||
|
placeholder: "Enter a detailed description of the bug"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Reproducible Code"
|
||||||
|
description: "Provide code or steps needed to reproduce the bug."
|
||||||
|
placeholder: "Enter code snippet or reproduction steps"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Environment"
|
||||||
|
description: "Provide details such as OS, version, etc."
|
||||||
|
placeholder: "OS: \nVersion: "
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Expected behavior"
|
||||||
|
description: "A clear and concise description of what you expected to happen."
|
||||||
|
placeholder: "Enter what you expected to happen"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Error log"
|
||||||
|
description: "Add error logs here. Language server errors may be logged in $ERG_PATH/els.log."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Screenshots"
|
||||||
|
description: "Add screenshots to help explain your problem."
|
||||||
|
placeholder: "Provide screenshot links or instructions to attach images"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Additional context"
|
||||||
|
description: "Add any other context about the problem here."
|
||||||
|
placeholder: "Enter any additional context"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# This file cannot use the extension `.yaml`.
|
||||||
|
blank_issues_enabled: false
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Describe the feature"
|
||||||
|
description: "A clear and concise description of what the feature is."
|
||||||
|
placeholder: "Enter a detailed description of the feature"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Additional context"
|
||||||
|
description: "Add any other context about the feature here."
|
||||||
|
placeholder: "Enter any additional context"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
@ -26,35 +26,71 @@ jobs:
|
||||||
cargo login ${{ secrets.CARGO_TOKEN }}
|
cargo login ${{ secrets.CARGO_TOKEN }}
|
||||||
chmod +x cargo_publish.sh
|
chmod +x cargo_publish.sh
|
||||||
./cargo_publish.sh --cargo-only
|
./cargo_publish.sh --cargo-only
|
||||||
pypi:
|
make-pypi-artifacts:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# - target: armv7-unknown-linux-gnueabihf
|
|
||||||
# os: ubuntu-latest
|
|
||||||
# - target: aarch64-unknown-linux-gnu
|
|
||||||
# os: ubuntu-latest
|
|
||||||
# - target: aarch64-apple-darwin
|
|
||||||
# os: macos-latest
|
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
platform: linux
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
|
platform: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
|
platform: windows
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
# - target: i686-pc-windows-msvc
|
|
||||||
# os: windows-latest
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
- name: setup-tools
|
- name: setup-tools
|
||||||
run: |
|
run: |
|
||||||
rustup update stable
|
rustup update stable
|
||||||
|
pip3 install twine build cibuildwheel setuptools-rust tomli
|
||||||
rustup target add ${{ matrix.target }}
|
rustup target add ${{ matrix.target }}
|
||||||
pip3 install maturin
|
- name: build
|
||||||
- name: upload
|
run: cibuildwheel --output-dir dist --platform ${{ matrix.platform }}
|
||||||
run: |
|
env:
|
||||||
maturin publish -u mtshiba -p ${{ secrets.PYPI_PASSWORD }} --target ${{ matrix.target }} --skip-existing
|
# rust doesn't seem to be available for musl linux on i686
|
||||||
|
CIBW_SKIP: '*-musllinux_i686'
|
||||||
|
CIBW_ENVIRONMENT: 'PATH="$HOME/.cargo/bin:$PATH" CARGO_TERM_COLOR="always"'
|
||||||
|
CIBW_ENVIRONMENT_WINDOWS: 'PATH="$UserProfile\.cargo\bin;$PATH"'
|
||||||
|
CIBW_BEFORE_BUILD: rustup show
|
||||||
|
CIBW_BEFORE_BUILD_LINUX: >
|
||||||
|
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain=stable --profile=minimal -y &&
|
||||||
|
rustup show
|
||||||
|
# CIBW_BUILD_VERBOSITY: 1
|
||||||
|
# - name: upload
|
||||||
|
# run: |
|
||||||
|
# twine upload -u mtshiba -p ${{ secrets.PYPI_PASSWORD }} --skip-existing dist/*
|
||||||
|
# cargo build --release --target ${{ matrix.target }}
|
||||||
|
# python3 -m build --wheel
|
||||||
|
# maturin publish -u mtshiba -p ${{ secrets.PYPI_PASSWORD }} --target ${{ matrix.target }} --skip-existing
|
||||||
|
- name: upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-${{ matrix.platform }}
|
||||||
|
path: dist
|
||||||
|
publish-pypi-artifacts:
|
||||||
|
needs: make-pypi-artifacts
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
# For pypi trusted publishing
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: download-artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: dist-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Publish to PyPi
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
skip-existing: true
|
||||||
|
verbose: true
|
||||||
upload-assets:
|
upload-assets:
|
||||||
needs: create-release
|
needs: create-release
|
||||||
strategy:
|
strategy:
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
# installing pylyzer itself is required for the tests
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
rustup update stable
|
rustup update stable
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
name: Close inactive issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v9
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity."
|
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
exempt-issue-labels: "bug"
|
||||||
|
days-before-issue-stale: 60
|
||||||
|
days-before-issue-close: 14
|
||||||
|
days-before-pr-stale: -1
|
||||||
|
days-before-pr-close: -1
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/**"
|
||||||
|
- "images/**"
|
||||||
|
- "**.md"
|
||||||
|
- "**.yml"
|
||||||
|
- "LICENSE-**"
|
||||||
|
- ".gitmessage"
|
||||||
|
- ".pre-commit-config.yaml"
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/**"
|
||||||
|
- "images/**"
|
||||||
|
- "**.md"
|
||||||
|
- "**.yml"
|
||||||
|
- "LICENSE-**"
|
||||||
|
- ".pre-commit-config.yaml"
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package-test:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
- name: Install
|
||||||
|
run: |
|
||||||
|
rustup update stable
|
||||||
|
cargo install --path .
|
||||||
|
- name: boto3
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
pip3 install boto3
|
||||||
|
pylyzer -c "import boto3" || true
|
||||||
|
- name: urllib3
|
||||||
|
run: |
|
||||||
|
pip3 install urllib3
|
||||||
|
pylyzer -c "import urllib3" || true
|
||||||
|
- name: setuptools
|
||||||
|
run: |
|
||||||
|
pip3 install setuptools
|
||||||
|
pylyzer -c "import setuptools" || true
|
||||||
|
- name: requests
|
||||||
|
run: |
|
||||||
|
pip3 install requests
|
||||||
|
pylyzer -c "import requests" || true
|
||||||
|
- name: certifi
|
||||||
|
run: |
|
||||||
|
pip3 install certifi
|
||||||
|
pylyzer -c "import certifi" || true
|
||||||
|
- name: charset-normalizer
|
||||||
|
run: |
|
||||||
|
pip3 install charset-normalizer
|
||||||
|
pylyzer -c "import charset_normalizer" || true
|
||||||
|
- name: idna
|
||||||
|
run: |
|
||||||
|
pip3 install idna
|
||||||
|
pylyzer -c "import idna" || true
|
||||||
|
- name: typing-extensions
|
||||||
|
run: |
|
||||||
|
pip3 install typing-extensions
|
||||||
|
pylyzer -c "import typing_extensions" || true
|
||||||
|
- name: python-dateutil
|
||||||
|
run: |
|
||||||
|
pip3 install python-dateutil
|
||||||
|
pylyzer -c "import dateutil" || true
|
||||||
|
- name: packaging
|
||||||
|
run: |
|
||||||
|
pip3 install packaging
|
||||||
|
pylyzer -c "import packaging" || true
|
||||||
|
- name: six
|
||||||
|
run: |
|
||||||
|
pip3 install six
|
||||||
|
pylyzer -c "import six" || true
|
||||||
|
- name: s3transfer
|
||||||
|
run: |
|
||||||
|
pip3 install s3transfer
|
||||||
|
pylyzer -c "import s3transfer" || true
|
||||||
|
- name: pyyaml
|
||||||
|
run: |
|
||||||
|
pip3 install pyyaml
|
||||||
|
pylyzer -c "import yaml" || true
|
||||||
|
- name: cryptography
|
||||||
|
run: |
|
||||||
|
pip3 install cryptography
|
||||||
|
pylyzer -c "import cryptography" || true
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
/target
|
/target
|
||||||
__pycache__/
|
__pycache__/
|
||||||
test*.py
|
test*.py
|
||||||
|
/site
|
||||||
|
.venv
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,12 @@
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||||
},
|
},
|
||||||
"python.formatting.provider": "none"
|
"python.formatting.provider": "none",
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"cSpell.words": [
|
||||||
|
"indexmap"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
51
Cargo.toml
51
Cargo.toml
|
|
@ -12,42 +12,53 @@ repository.workspace = true
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/py2erg",
|
"crates/py2erg",
|
||||||
|
"crates/pylyzer_core",
|
||||||
|
"crates/pylyzer_wasm",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.33"
|
version = "0.0.82"
|
||||||
authors = ["Shunsuke Shibayama <sbym1346@gmail.com>"]
|
authors = ["Shunsuke Shibayama <sbym1346@gmail.com>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://github.com/mtshiba/pylyzer"
|
repository = "https://github.com/mtshiba/pylyzer"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# erg_common = { version = "0.6.15", features = ["py_compat", "els"] }
|
erg_common = { version = "0.6.53-nightly.5", features = ["py_compat", "els"] }
|
||||||
# erg_compiler = { version = "0.6.15", features = ["py_compat", "els"] }
|
erg_compiler = { version = "0.6.53-nightly.5", features = ["py_compat", "els"] }
|
||||||
# els = { version = "0.1.27", features = ["py_compat"] }
|
els = { version = "0.1.65-nightly.5", features = ["py_compat"] }
|
||||||
rustpython-parser = { git = "https://github.com/RustPython/Parser", version = "0.2.0", features = ["all-nodes-with-ranges", "location"] }
|
# rustpython-parser = { version = "0.3.0", features = ["all-nodes-with-ranges", "location"] }
|
||||||
rustpython-ast = { git = "https://github.com/RustPython/Parser", version = "0.2.0", features = ["all-nodes-with-ranges", "location"] }
|
# rustpython-ast = { version = "0.3.0", features = ["all-nodes-with-ranges", "location"] }
|
||||||
erg_compiler = { git = "https://github.com/erg-lang/erg", branch = "main", features = ["py_compat", "els"] }
|
rustpython-parser = { git = "https://github.com/RustPython/Parser", version = "0.4.0", features = ["all-nodes-with-ranges", "location"] }
|
||||||
erg_common = { git = "https://github.com/erg-lang/erg", branch = "main", features = ["py_compat", "els"] }
|
rustpython-ast = { git = "https://github.com/RustPython/Parser", version = "0.4.0", features = ["all-nodes-with-ranges", "location"] }
|
||||||
els = { git = "https://github.com/erg-lang/erg", branch = "main", features = ["py_compat"] }
|
# erg_compiler = { git = "https://github.com/erg-lang/erg", branch = "main", features = ["py_compat", "els"] }
|
||||||
|
# erg_common = { git = "https://github.com/erg-lang/erg", branch = "main", features = ["py_compat", "els"] }
|
||||||
|
# els = { git = "https://github.com/erg-lang/erg", branch = "main", features = ["py_compat"] }
|
||||||
# erg_compiler = { path = "../erg/crates/erg_compiler", features = ["py_compat", "els"] }
|
# erg_compiler = { path = "../erg/crates/erg_compiler", features = ["py_compat", "els"] }
|
||||||
# erg_common = { path = "../erg/crates/erg_common", features = ["py_compat", "els"] }
|
# erg_common = { path = "../erg/crates/erg_common", features = ["py_compat", "els"] }
|
||||||
# els = { path = "../erg/crates/els", features = ["py_compat"] }
|
# els = { path = "../erg/crates/els", features = ["py_compat"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug = ["erg_compiler/debug", "erg_common/debug", "py2erg/debug"]
|
debug = ["erg_common/debug", "pylyzer_core/debug"]
|
||||||
large_thread = ["erg_compiler/large_thread", "erg_common/large_thread", "els/large_thread"]
|
large_thread = ["erg_common/large_thread", "els/large_thread", "pylyzer_core/large_thread"]
|
||||||
pretty = ["erg_compiler/pretty", "erg_common/pretty"]
|
pretty = ["erg_common/pretty", "pylyzer_core/pretty"]
|
||||||
backtrace = ["erg_common/backtrace"]
|
backtrace = ["erg_common/backtrace", "els/backtrace", "pylyzer_core/backtrace"]
|
||||||
experimental = ["els/experimental", "erg_compiler/experimental", "erg_common/experimental"]
|
experimental = ["erg_common/experimental", "els/experimental", "pylyzer_core/experimental", "parallel"]
|
||||||
|
parallel = ["erg_common/parallel", "pylyzer_core/parallel"]
|
||||||
|
japanese = ["erg_common/japanese", "els/japanese"]
|
||||||
|
simplified_chinese = ["erg_common/simplified_chinese", "els/simplified_chinese"]
|
||||||
|
traditional_chinese = ["erg_common/traditional_chinese", "els/traditional_chinese"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
erg_compiler = { workspace = true }
|
pylyzer_core = { version = "0.0.82", path = "./crates/pylyzer_core" }
|
||||||
erg_common = { workspace = true }
|
erg_common = { workspace = true }
|
||||||
els = { workspace = true }
|
els = { workspace = true }
|
||||||
rustpython-parser = { workspace = true }
|
glob = "0.3.2"
|
||||||
rustpython-ast = { workspace = true }
|
indexmap = "2.7.1"
|
||||||
py2erg = { version = "0.0.33", path = "./crates/py2erg" }
|
|
||||||
|
|
||||||
[lib]
|
[dev-dependencies]
|
||||||
path = "src/lib.rs"
|
erg_compiler = { workspace = true }
|
||||||
|
|
||||||
|
[profile.opt-with-dbg]
|
||||||
|
inherits = "release"
|
||||||
|
debug = true
|
||||||
|
|
|
||||||
138
README.md
138
README.md
|
|
@ -1,5 +1,9 @@
|
||||||
# pylyzer ⚡
|
# pylyzer ⚡
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> pylyzer is now under the maintenance phase, which means that only bug fixes will be made and no new features will be added.
|
||||||
|
The author is currently cooperating with the development of [astral-sh/ty](https://github.com/astral-sh/ty). Please try that instead!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<a href="https://marketplace.visualstudio.com/items?itemName=pylyzer.pylyzer" target="_blank" rel="noreferrer noopener nofollow"><img src="https://img.shields.io/visual-studio-marketplace/v/pylyzer.pylyzer?style=flat&label=VS%20Marketplace&logo=visual-studio-code" alt="vsm-version"></a>
|
<a href="https://marketplace.visualstudio.com/items?itemName=pylyzer.pylyzer" target="_blank" rel="noreferrer noopener nofollow"><img src="https://img.shields.io/visual-studio-marketplace/v/pylyzer.pylyzer?style=flat&label=VS%20Marketplace&logo=visual-studio-code" alt="vsm-version"></a>
|
||||||
|
|
@ -10,35 +14,62 @@
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### cargo (rust package manager)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install pylyzer
|
|
||||||
```
|
|
||||||
|
|
||||||
### build from source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/mtshiba/pylyzer.git
|
|
||||||
cargo install --path .
|
|
||||||
```
|
|
||||||
|
|
||||||
Make sure that `cargo/rustc` is up-to-date, as pylyzer may be written with the latest language features.
|
|
||||||
|
|
||||||
### pip
|
### pip
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install pylyzer
|
pip install pylyzer
|
||||||
```
|
```
|
||||||
|
|
||||||
__If installed this way, you also need to [install Erg](https://github.com/mtshiba/ergup).__
|
### cargo (Rust package manager)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -L https://github.com/mtshiba/ergup/raw/main/ergup.py | python3
|
cargo install pylyzer --locked
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### build from source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/mtshiba/pylyzer.git
|
||||||
|
cargo install --path . --locked
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure that `cargo`/`rustc` is up-to-date, as pylyzer may be written with the latest (stable) language features.
|
||||||
|
|
||||||
### [GitHub Releases](https://github.com/mtshiba/pylyzer/releases/latest)
|
### [GitHub Releases](https://github.com/mtshiba/pylyzer/releases/latest)
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Check a single file
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pylyzer file.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check multiple files
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# glob patterns are supported
|
||||||
|
pylyzer file1.py file2.py dir/file*.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check an entire package
|
||||||
|
|
||||||
|
If you don't specify a file path, pylyzer will automatically search for the entry point.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pylyzer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start the language server
|
||||||
|
|
||||||
|
This option is used when an LSP-aware editor requires arguments to start pylyzer.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pylyzer --server
|
||||||
|
```
|
||||||
|
|
||||||
|
For other options, check [the manual](https://mtshiba.github.io/pylyzer/options/options/).
|
||||||
|
|
||||||
## What is the advantage over pylint, pyright, pytype, etc.?
|
## What is the advantage over pylint, pyright, pytype, etc.?
|
||||||
|
|
||||||
* Performance 🌟
|
* Performance 🌟
|
||||||
|
|
@ -47,24 +78,18 @@ On average, pylyzer can inspect Python scripts more than __100 times faster__ th
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
* Detailed analysis 🩺
|
|
||||||
|
|
||||||
pylyzer can do more than the type checking. For example, it can detect out-of-bounds accesses to lists and accesses to nonexistent keys in dicts.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
* Reports readability 📖
|
* Reports readability 📖
|
||||||
|
|
||||||
While pytype/pyright's error reports are illegible, pylyzer shows where the error occurred and provides clear error messages.
|
While pytype/pyright's error reports are illegible, pylyzer shows where the error occurred and provides clear error messages.
|
||||||
|
|
||||||
|
### pyright
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### pylyzer 😃
|
### pylyzer 😃
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### pyright 🙃
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
* Rich LSP support 📝
|
* Rich LSP support 📝
|
||||||
|
|
||||||
pylyzer as a language server supports various features, such as completion and renaming (The language server is an adaptation of the Erg Language Server (ELS). For more information on the implemented features, please see [here](https://github.com/erg-lang/erg/tree/main/crates/els#readme)).
|
pylyzer as a language server supports various features, such as completion and renaming (The language server is an adaptation of the Erg Language Server (ELS). For more information on the implemented features, please see [here](https://github.com/erg-lang/erg/tree/main/crates/els#readme)).
|
||||||
|
|
@ -81,10 +106,10 @@ You can install the VSCode extension from the [Marketplace](https://marketplace.
|
||||||
code --install-extension pylyzer.pylyzer
|
code --install-extension pylyzer.pylyzer
|
||||||
```
|
```
|
||||||
|
|
||||||
## What is the difference from [Ruff](https://github.com/charliermarsh/ruff)?
|
## What is the difference from [Ruff](https://github.com/astral-sh/ruff)?
|
||||||
|
|
||||||
[Ruff](https://github.com/charliermarsh/ruff), like pylyzer, is a static code analysis tool for Python written in Rust, but Ruff is a linter and pylyzer is a type checker & language server.
|
[Ruff](https://github.com/astral-sh/ruff), like pylyzer, is a static code analysis tool for Python written in Rust, but Ruff is a linter and pylyzer is a type checker & language server.
|
||||||
pylyzer does not perform linting, and Ruff does not perform type checking.
|
pylyzer does not perform linting & formatting, and Ruff does not perform type checking.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
|
|
@ -108,39 +133,76 @@ pylyzer converts Python ASTs to Erg ASTs and passes them to Erg's type checker.
|
||||||
* [x] operator
|
* [x] operator
|
||||||
* [x] function/method
|
* [x] function/method
|
||||||
* [x] class
|
* [x] class
|
||||||
|
* [ ] `async/await`
|
||||||
|
* [ ] user-defined abstract class
|
||||||
* [x] type inference
|
* [x] type inference
|
||||||
* [x] variable
|
* [x] variable
|
||||||
* [x] operator
|
* [x] operator
|
||||||
* [x] function/method
|
* [x] function/method
|
||||||
* [x] class
|
* [x] class
|
||||||
* [x] builtin modules resolving (partially)
|
* [x] builtin modules analysis
|
||||||
* [x] local scripts resolving
|
* [x] local scripts analysis
|
||||||
* [ ] local packages resolving
|
* [x] local packages analysis
|
||||||
|
* [x] LSP features
|
||||||
|
* [x] diagnostics
|
||||||
|
* [x] completion
|
||||||
|
* [x] rename
|
||||||
|
* [x] hover
|
||||||
|
* [x] goto definition
|
||||||
|
* [x] signature help
|
||||||
|
* [x] find references
|
||||||
|
* [x] document symbol
|
||||||
|
* [x] call hierarchy
|
||||||
* [x] collection types
|
* [x] collection types
|
||||||
* [x] `list`
|
* [x] `list`
|
||||||
* [x] `dict`
|
* [x] `dict`
|
||||||
* [x] `tuple`
|
* [x] `tuple`
|
||||||
|
* [x] `set`
|
||||||
* [ ] `typing`
|
* [ ] `typing`
|
||||||
* [x] `Union`
|
* [x] `Union`
|
||||||
* [x] `Optional`
|
* [x] `Optional`
|
||||||
* [x] `Literal`
|
* [x] `Literal`
|
||||||
* [x] `Callable`
|
* [x] `Callable`
|
||||||
|
* [x] `Any`
|
||||||
|
* [x] `TypeVar`
|
||||||
* [ ] `TypedDict`
|
* [ ] `TypedDict`
|
||||||
* [ ] type variable (`TypeVar`, `Generic`)
|
* [ ] `ClassVar`
|
||||||
|
* [ ] `Generic`
|
||||||
* [ ] `Protocol`
|
* [ ] `Protocol`
|
||||||
* [ ] `Final`
|
* [ ] `Final`
|
||||||
* [ ] `Annotated`
|
* [ ] `Annotated`
|
||||||
* [ ] `TypeAlias`
|
* [ ] `TypeAlias`
|
||||||
* [ ] type guard (`TypeGuard`)
|
* [ ] `TypeGuard`
|
||||||
|
* [x] type parameter syntax
|
||||||
|
* [x] type narrowing
|
||||||
* [ ] others
|
* [ ] others
|
||||||
* `collections.abc`
|
* [ ] `collections.abc`
|
||||||
|
* [x] `Collection`
|
||||||
|
* [x] `Container`
|
||||||
|
* [x] `Generator`
|
||||||
* [x] `Iterable`
|
* [x] `Iterable`
|
||||||
* [x] `Iterator`
|
* [x] `Iterator`
|
||||||
* [x] `Mapping`
|
* [x] `Mapping`, `MutableMapping`
|
||||||
* [x] `Sequence`
|
* [x] `Sequence`, `MutableSequence`
|
||||||
* [ ] others
|
* [ ] others
|
||||||
* [x] type assertion (`typing.cast`)
|
* [x] type assertion (`typing.cast`)
|
||||||
* [x] type narrowing (`is`, `isinstance`)
|
* [x] type narrowing (`is`, `isinstance`)
|
||||||
|
* [x] `pyi` (stub) files support
|
||||||
|
* [x] glob pattern file check
|
||||||
|
* [x] type comment (`# type: ...`)
|
||||||
|
* [x] virtual environment support
|
||||||
|
* [x] package manager support
|
||||||
|
* [x] `pip`
|
||||||
|
* [x] `poetry`
|
||||||
|
* [x] `uv`
|
||||||
|
|
||||||
|
## Join us!
|
||||||
|
|
||||||
|
We are looking for contributors to help us improve pylyzer. If you are interested in contributing and have any questions, please feel free to contact us.
|
||||||
|
|
||||||
|
* [Discord (Erg language)](https://discord.gg/kQBuaSUS46)
|
||||||
|
* [#pylyzer](https://discord.com/channels/1006946336433774742/1056815981168697354)
|
||||||
|
* [GitHub discussions](https://github.com/mtshiba/pylyzer/discussions)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ if [[ "$PWD" == */pylyzer ]]; then
|
||||||
cd crates/py2erg
|
cd crates/py2erg
|
||||||
echo "publish py2erg ..."
|
echo "publish py2erg ..."
|
||||||
cargo publish
|
cargo publish
|
||||||
|
cd ../pylyzer_core
|
||||||
|
echo "publish pylyzer_core ..."
|
||||||
|
cargo publish
|
||||||
cd ../../
|
cd ../../
|
||||||
cargo publish
|
cargo publish
|
||||||
if [ "$1" = "--cargo-only" ]; then
|
if [ "$1" = "--cargo-only" ]; then
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ repository.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug = ["erg_compiler/debug", "erg_common/debug"]
|
debug = ["erg_compiler/debug", "erg_common/debug"]
|
||||||
|
japanese = ["erg_compiler/japanese", "erg_common/japanese"]
|
||||||
|
simplified_chinese = ["erg_compiler/simplified_chinese", "erg_common/simplified_chinese"]
|
||||||
|
traditional_chinese = ["erg_compiler/traditional_chinese", "erg_common/traditional_chinese"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustpython-parser = { workspace = true }
|
rustpython-parser = { workspace = true }
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,11 +1,14 @@
|
||||||
use std::fs::File;
|
use std::fs::{create_dir_all, File};
|
||||||
use std::io::{BufWriter, Write};
|
use std::io::{BufWriter, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use erg_common::io::Input;
|
use erg_common::pathutil::{mod_name, NormalizedPathBuf};
|
||||||
use erg_common::log;
|
use erg_common::set::Set;
|
||||||
use erg_common::traits::LimitedDisplay;
|
use erg_common::traits::LimitedDisplay;
|
||||||
use erg_compiler::context::register::{CheckStatus, PylyzerStatus};
|
use erg_common::{log, Str};
|
||||||
use erg_compiler::hir::{Expr, HIR};
|
use erg_compiler::build_package::{CheckStatus, PylyzerStatus};
|
||||||
|
use erg_compiler::hir::{ClassDef, Expr, HIR};
|
||||||
|
use erg_compiler::module::SharedModuleCache;
|
||||||
use erg_compiler::ty::value::{GenTypeObj, TypeObj};
|
use erg_compiler::ty::value::{GenTypeObj, TypeObj};
|
||||||
use erg_compiler::ty::{HasType, Type};
|
use erg_compiler::ty::{HasType, Type};
|
||||||
|
|
||||||
|
|
@ -13,41 +16,41 @@ pub struct DeclFile {
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub code: String,
|
pub code: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn escape_type(typ: String) -> String {
|
|
||||||
typ.replace('%', "Type_").replace("<module>", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DeclFileGenerator {
|
pub struct DeclFileGenerator {
|
||||||
filename: String,
|
filename: String,
|
||||||
namespace: String,
|
namespace: String,
|
||||||
|
imported: Set<Str>,
|
||||||
code: String,
|
code: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeclFileGenerator {
|
impl DeclFileGenerator {
|
||||||
pub fn new(input: &Input, status: CheckStatus) -> Self {
|
pub fn new(path: &NormalizedPathBuf, status: CheckStatus) -> std::io::Result<Self> {
|
||||||
let (timestamp, hash) = {
|
let (timestamp, hash) = {
|
||||||
let py_file_path = input.path();
|
let metadata = std::fs::metadata(path)?;
|
||||||
let metadata = std::fs::metadata(py_file_path).unwrap();
|
|
||||||
let dummy_hash = metadata.len();
|
let dummy_hash = metadata.len();
|
||||||
(metadata.modified().unwrap(), dummy_hash)
|
(metadata.modified()?, dummy_hash)
|
||||||
};
|
};
|
||||||
let status = PylyzerStatus {
|
let status = PylyzerStatus {
|
||||||
status,
|
status,
|
||||||
file: input.path().into(),
|
file: path.to_path_buf(),
|
||||||
timestamp,
|
timestamp,
|
||||||
hash,
|
hash,
|
||||||
};
|
};
|
||||||
let code = format!("{status}\n");
|
let code = format!("{status}\n");
|
||||||
Self {
|
Ok(Self {
|
||||||
filename: input.filename().replace(".py", ".d.er"),
|
filename: path
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string_lossy()
|
||||||
|
.replace(".py", ".d.er"),
|
||||||
namespace: "".to_string(),
|
namespace: "".to_string(),
|
||||||
|
imported: Set::new(),
|
||||||
code,
|
code,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_decl_er(mut self, hir: HIR) -> DeclFile {
|
pub fn gen_decl_er(mut self, hir: &HIR) -> DeclFile {
|
||||||
for chunk in hir.module.into_iter() {
|
for chunk in hir.module.iter() {
|
||||||
self.gen_chunk_decl(chunk);
|
self.gen_chunk_decl(chunk);
|
||||||
}
|
}
|
||||||
log!("code:\n{}", self.code);
|
log!("code:\n{}", self.code);
|
||||||
|
|
@ -57,7 +60,35 @@ impl DeclFileGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_chunk_decl(&mut self, chunk: Expr) {
|
fn escape_type(&self, typ: String) -> String {
|
||||||
|
typ.replace('%', "Type_")
|
||||||
|
.replace("<module>", "")
|
||||||
|
.replace('/', ".")
|
||||||
|
.trim_start_matches(self.filename.trim_end_matches(".d.er"))
|
||||||
|
.trim_start_matches(&self.namespace)
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
// e.g. `x: foo.Bar` => `foo = pyimport "foo"; x: foo.Bar`
|
||||||
|
fn prepare_using_type(&mut self, typ: &Type) {
|
||||||
|
let namespace = Str::rc(
|
||||||
|
typ.namespace()
|
||||||
|
.split('/')
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.split('.')
|
||||||
|
.next()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
if namespace != self.namespace
|
||||||
|
&& !namespace.is_empty()
|
||||||
|
&& self.imported.insert(namespace.clone())
|
||||||
|
{
|
||||||
|
self.code += &format!("{namespace} = pyimport \"{namespace}\"\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_chunk_decl(&mut self, chunk: &Expr) {
|
||||||
match chunk {
|
match chunk {
|
||||||
Expr::Def(def) => {
|
Expr::Def(def) => {
|
||||||
let mut name = def
|
let mut name = def
|
||||||
|
|
@ -65,19 +96,29 @@ impl DeclFileGenerator {
|
||||||
.ident()
|
.ident()
|
||||||
.inspect()
|
.inspect()
|
||||||
.replace('\0', "")
|
.replace('\0', "")
|
||||||
.replace('%', "___");
|
.replace(['%', '*'], "___");
|
||||||
let ref_t = def.sig.ident().ref_t();
|
let ref_t = def.sig.ident().ref_t();
|
||||||
let typ = ref_t.replace_failure().to_string_unabbreviated();
|
self.prepare_using_type(ref_t);
|
||||||
let typ = escape_type(typ);
|
let typ = self.escape_type(ref_t.replace_failure().to_string_unabbreviated());
|
||||||
// Erg can automatically import nested modules
|
// Erg can automatically import nested modules
|
||||||
// `import http.client` => `http = pyimport "http"`
|
// `import http.client` => `http = pyimport "http"`
|
||||||
let decl = if ref_t.is_py_module() {
|
let decl = if ref_t.is_py_module() && ref_t.typarams()[0].is_str_value() {
|
||||||
name = name.split('.').next().unwrap().to_string();
|
name = name.split('.').next().unwrap().to_string();
|
||||||
|
let full_path_str = ref_t.typarams()[0].to_string_unabbreviated();
|
||||||
|
let mod_name = mod_name(Path::new(full_path_str.trim_matches('"')));
|
||||||
|
let imported = if self.imported.insert(mod_name.clone()) {
|
||||||
|
format!("{}.{mod_name} = pyimport \"{mod_name}\"", self.namespace)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
if self.imported.insert(name.clone().into()) {
|
||||||
format!(
|
format!(
|
||||||
"{}.{name} = pyimport {}",
|
"{}.{name} = pyimport \"{mod_name}\"\n{imported}",
|
||||||
self.namespace,
|
self.namespace,
|
||||||
ref_t.typarams()[0]
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
imported
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
format!("{}.{name}: {typ}", self.namespace)
|
format!("{}.{name}: {typ}", self.namespace)
|
||||||
};
|
};
|
||||||
|
|
@ -89,15 +130,21 @@ impl DeclFileGenerator {
|
||||||
.ident()
|
.ident()
|
||||||
.inspect()
|
.inspect()
|
||||||
.replace('\0', "")
|
.replace('\0', "")
|
||||||
.replace('%', "___");
|
.replace(['%', '*'], "___");
|
||||||
let src = format!("{}.{class_name}", self.namespace);
|
let src = format!("{}.{class_name}", self.namespace);
|
||||||
let stash = std::mem::replace(&mut self.namespace, src);
|
let stash = std::mem::replace(&mut self.namespace, src);
|
||||||
let decl = format!(".{class_name}: ClassType");
|
let decl = format!(".{class_name}: ClassType");
|
||||||
self.code += &decl;
|
self.code += &decl;
|
||||||
self.code.push('\n');
|
self.code.push('\n');
|
||||||
if let GenTypeObj::Subclass(class) = &def.obj {
|
if let GenTypeObj::Subclass(class) = def.obj.as_ref() {
|
||||||
let sup = class.sup.as_ref().typ().to_string_unabbreviated();
|
let sup = class
|
||||||
let sup = escape_type(sup);
|
.sup
|
||||||
|
.as_ref()
|
||||||
|
.typ()
|
||||||
|
.replace_failure()
|
||||||
|
.to_string_unabbreviated();
|
||||||
|
self.prepare_using_type(class.sup.typ());
|
||||||
|
let sup = self.escape_type(sup);
|
||||||
let decl = format!(".{class_name} <: {sup}\n");
|
let decl = format!(".{class_name} <: {sup}\n");
|
||||||
self.code += &decl;
|
self.code += &decl;
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +154,8 @@ impl DeclFileGenerator {
|
||||||
}) = def.obj.base_or_sup()
|
}) = def.obj.base_or_sup()
|
||||||
{
|
{
|
||||||
for (attr, t) in rec.iter() {
|
for (attr, t) in rec.iter() {
|
||||||
let typ = escape_type(t.to_string_unabbreviated());
|
self.prepare_using_type(t);
|
||||||
|
let typ = self.escape_type(t.replace_failure().to_string_unabbreviated());
|
||||||
let decl = format!("{}.{}: {typ}\n", self.namespace, attr.symbol);
|
let decl = format!("{}.{}: {typ}\n", self.namespace, attr.symbol);
|
||||||
self.code += &decl;
|
self.code += &decl;
|
||||||
}
|
}
|
||||||
|
|
@ -118,52 +166,66 @@ impl DeclFileGenerator {
|
||||||
}) = def.obj.additional()
|
}) = def.obj.additional()
|
||||||
{
|
{
|
||||||
for (attr, t) in rec.iter() {
|
for (attr, t) in rec.iter() {
|
||||||
let typ = escape_type(t.to_string_unabbreviated());
|
self.prepare_using_type(t);
|
||||||
|
let typ = self.escape_type(t.replace_failure().to_string_unabbreviated());
|
||||||
let decl = format!("{}.{}: {typ}\n", self.namespace, attr.symbol);
|
let decl = format!("{}.{}: {typ}\n", self.namespace, attr.symbol);
|
||||||
self.code += &decl;
|
self.code += &decl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for attr in def.methods.into_iter() {
|
for attr in ClassDef::get_all_methods(&def.methods_list) {
|
||||||
self.gen_chunk_decl(attr);
|
self.gen_chunk_decl(attr);
|
||||||
}
|
}
|
||||||
self.namespace = stash;
|
self.namespace = stash;
|
||||||
}
|
}
|
||||||
Expr::Dummy(dummy) => {
|
Expr::Dummy(dummy) => {
|
||||||
for chunk in dummy.into_iter() {
|
for chunk in dummy.iter() {
|
||||||
self.gen_chunk_decl(chunk);
|
self.gen_chunk_decl(chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Compound(compound) => {
|
||||||
|
for chunk in compound.iter() {
|
||||||
|
self.gen_chunk_decl(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Call(call) if call.control_kind().is_some() => {
|
||||||
|
for arg in call.args.iter() {
|
||||||
|
self.gen_chunk_decl(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Lambda(lambda) => {
|
||||||
|
for arg in lambda.body.iter() {
|
||||||
|
self.gen_chunk_decl(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
self.code.push('\n');
|
self.code.push('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reserve_decl_er(input: Input) {
|
fn dump_decl_er(path: &NormalizedPathBuf, hir: &HIR, status: CheckStatus) -> std::io::Result<()> {
|
||||||
let mut dir = input.dir();
|
let decl_gen = DeclFileGenerator::new(path, status)?;
|
||||||
dir.push("__pycache__");
|
let file = decl_gen.gen_decl_er(hir);
|
||||||
let pycache_dir = dir.as_path();
|
let Some(dir) = path.parent().and_then(|p| p.canonicalize().ok()) else {
|
||||||
if !pycache_dir.exists() {
|
return Ok(());
|
||||||
std::fs::create_dir(pycache_dir).unwrap();
|
};
|
||||||
|
let cache_dir = dir.join("__pycache__");
|
||||||
|
if !cache_dir.exists() {
|
||||||
|
let _ = create_dir_all(&cache_dir);
|
||||||
}
|
}
|
||||||
let filename = input.filename();
|
let path = cache_dir.join(file.filename);
|
||||||
let mut path = pycache_dir.join(filename);
|
|
||||||
path.set_extension("d.er");
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
let _f = File::create(path).unwrap();
|
File::create(&path)?;
|
||||||
}
|
}
|
||||||
|
let f = File::options().write(true).open(path)?;
|
||||||
|
let mut f = BufWriter::new(f);
|
||||||
|
f.write_all(file.code.as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dump_decl_er(input: Input, hir: HIR, status: CheckStatus) {
|
pub fn dump_decl_package(modules: &SharedModuleCache) {
|
||||||
let decl_gen = DeclFileGenerator::new(&input, status);
|
for (path, module) in modules.raw_iter() {
|
||||||
let file = decl_gen.gen_decl_er(hir);
|
if let Some(hir) = module.hir.as_ref() {
|
||||||
let mut dir = input.dir();
|
let _ = dump_decl_er(path, hir, module.status);
|
||||||
dir.push("__pycache__");
|
}
|
||||||
let pycache_dir = dir.as_path();
|
}
|
||||||
let f = File::options()
|
|
||||||
.write(true)
|
|
||||||
.open(pycache_dir.join(file.filename))
|
|
||||||
.unwrap();
|
|
||||||
let mut f = BufWriter::new(f);
|
|
||||||
f.write_all(file.code.as_bytes()).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "pylyzer_core"
|
||||||
|
description = "pylyzer core"
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
debug = ["erg_compiler/debug", "erg_common/debug", "py2erg/debug"]
|
||||||
|
large_thread = ["erg_compiler/large_thread", "erg_common/large_thread"]
|
||||||
|
pretty = ["erg_compiler/pretty", "erg_common/pretty"]
|
||||||
|
backtrace = ["erg_common/backtrace"]
|
||||||
|
experimental = ["erg_compiler/experimental", "erg_common/experimental", "parallel"]
|
||||||
|
parallel = ["erg_compiler/parallel", "erg_common/parallel"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
erg_common = { workspace = true }
|
||||||
|
erg_compiler = { workspace = true }
|
||||||
|
rustpython-parser = { workspace = true }
|
||||||
|
rustpython-ast = { workspace = true }
|
||||||
|
py2erg = { version = "0.0.82", path = "../py2erg" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "lib.rs"
|
||||||
|
|
@ -2,69 +2,163 @@ use erg_common::config::ErgConfig;
|
||||||
use erg_common::error::{ErrorCore, ErrorKind, MultiErrorDisplay};
|
use erg_common::error::{ErrorCore, ErrorKind, MultiErrorDisplay};
|
||||||
use erg_common::style::colors::{BLUE, GREEN, RED, YELLOW};
|
use erg_common::style::colors::{BLUE, GREEN, RED, YELLOW};
|
||||||
use erg_common::style::RESET;
|
use erg_common::style::RESET;
|
||||||
use erg_common::traits::{ExitStatus, Runnable, Stream};
|
use erg_common::traits::{ExitStatus, New, Runnable, Stream};
|
||||||
use erg_common::Str;
|
use erg_common::Str;
|
||||||
use erg_compiler::artifact::{BuildRunnable, Buildable, CompleteArtifact, IncompleteArtifact};
|
use erg_compiler::artifact::{BuildRunnable, Buildable, CompleteArtifact, IncompleteArtifact};
|
||||||
use erg_compiler::context::register::CheckStatus;
|
use erg_compiler::build_package::GenericPackageBuilder;
|
||||||
use erg_compiler::context::ModuleContext;
|
use erg_compiler::context::{Context, ContextProvider, ModuleContext};
|
||||||
use erg_compiler::erg_parser::ast::{Module, AST};
|
use erg_compiler::erg_parser::ast::{Module, VarName, AST};
|
||||||
|
use erg_compiler::erg_parser::build_ast::ASTBuildable;
|
||||||
use erg_compiler::erg_parser::error::{
|
use erg_compiler::erg_parser::error::{
|
||||||
CompleteArtifact as ParseArtifact, IncompleteArtifact as IncompleteParseArtifact, ParseErrors,
|
CompleteArtifact as ParseArtifact, IncompleteArtifact as IncompleteParseArtifact, ParseErrors,
|
||||||
|
ParserRunnerErrors,
|
||||||
};
|
};
|
||||||
use erg_compiler::erg_parser::parse::Parsable;
|
use erg_compiler::erg_parser::parse::Parsable;
|
||||||
use erg_compiler::error::{CompileError, CompileErrors};
|
use erg_compiler::error::{CompileError, CompileErrors};
|
||||||
use erg_compiler::lower::ASTLowerer;
|
|
||||||
use erg_compiler::module::SharedCompilerResource;
|
use erg_compiler::module::SharedCompilerResource;
|
||||||
use py2erg::{dump_decl_er, reserve_decl_er, ShadowingMode};
|
use erg_compiler::varinfo::VarInfo;
|
||||||
use rustpython_ast::source_code::RandomLocator;
|
use erg_compiler::GenericHIRBuilder;
|
||||||
|
use py2erg::{dump_decl_package, CommentStorage, ShadowingMode};
|
||||||
|
use rustpython_ast::source_code::{RandomLocator, SourceRange};
|
||||||
use rustpython_ast::{Fold, ModModule};
|
use rustpython_ast::{Fold, ModModule};
|
||||||
use rustpython_parser::{Parse, ParseErrorType};
|
use rustpython_parser::{Parse, ParseErrorType};
|
||||||
|
|
||||||
use crate::handle_err;
|
use crate::handle_err;
|
||||||
|
|
||||||
pub struct SimplePythonParser {}
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SimplePythonParser {
|
||||||
|
cfg: ErgConfig,
|
||||||
|
}
|
||||||
|
|
||||||
impl Parsable for SimplePythonParser {
|
impl Parsable for SimplePythonParser {
|
||||||
fn parse(code: String) -> Result<ParseArtifact, IncompleteParseArtifact<Module, ParseErrors>> {
|
fn parse(code: String) -> Result<ParseArtifact, IncompleteParseArtifact<Module, ParseErrors>> {
|
||||||
let py_program = ModModule::parse(&code, "<stdin>").map_err(|_err| ParseErrors::empty())?;
|
let mut slf = Self::new(ErgConfig::string(code.clone()));
|
||||||
let mut locator = RandomLocator::new(&code);
|
slf.build_ast(code)
|
||||||
// let mut locator = LinearLocator::new(&code);
|
.map(|art| ParseArtifact::new(art.ast.module, art.warns.into()))
|
||||||
let py_program = locator
|
.map_err(|iart| {
|
||||||
.fold(py_program)
|
IncompleteParseArtifact::new(
|
||||||
.map_err(|_err| ParseErrors::empty())?;
|
iart.ast.map(|art| art.module),
|
||||||
|
iart.errors.into(),
|
||||||
|
iart.warns.into(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl New for SimplePythonParser {
|
||||||
|
fn new(cfg: ErgConfig) -> Self {
|
||||||
|
Self { cfg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ASTBuildable for SimplePythonParser {
|
||||||
|
fn build_ast(
|
||||||
|
&mut self,
|
||||||
|
code: String,
|
||||||
|
) -> Result<
|
||||||
|
ParseArtifact<AST, ParserRunnerErrors>,
|
||||||
|
IncompleteParseArtifact<AST, ParserRunnerErrors>,
|
||||||
|
> {
|
||||||
|
let filename = self.cfg.input.filename();
|
||||||
|
let mut comments = CommentStorage::new();
|
||||||
|
comments.read(&code);
|
||||||
|
let py_program = self.parse_py_code(code)?;
|
||||||
let shadowing = if cfg!(feature = "debug") {
|
let shadowing = if cfg!(feature = "debug") {
|
||||||
ShadowingMode::Visible
|
ShadowingMode::Visible
|
||||||
} else {
|
} else {
|
||||||
ShadowingMode::Invisible
|
ShadowingMode::Invisible
|
||||||
};
|
};
|
||||||
let converter = py2erg::ASTConverter::new(ErgConfig::default(), shadowing);
|
let converter = py2erg::ASTConverter::new(ErgConfig::default(), shadowing, comments);
|
||||||
let IncompleteArtifact{ object: Some(erg_module), errors, warns } = converter.convert_program(py_program) else { unreachable!() };
|
let IncompleteArtifact {
|
||||||
|
object: Some(erg_module),
|
||||||
|
errors,
|
||||||
|
warns,
|
||||||
|
} = converter.convert_program(py_program)
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let erg_ast = AST::new(erg_common::Str::rc(&filename), erg_module);
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
Ok(ParseArtifact::new(erg_module, warns.into()))
|
Ok(ParseArtifact::new(erg_ast, warns.into()))
|
||||||
} else {
|
} else {
|
||||||
Err(IncompleteParseArtifact::new(
|
Err(IncompleteParseArtifact::new(
|
||||||
Some(erg_module),
|
Some(erg_ast),
|
||||||
warns.into(),
|
|
||||||
errors.into(),
|
errors.into(),
|
||||||
|
warns.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SimplePythonParser {
|
||||||
|
pub fn parse_py_code(
|
||||||
|
&self,
|
||||||
|
code: String,
|
||||||
|
) -> Result<ModModule<SourceRange>, IncompleteParseArtifact<AST, ParserRunnerErrors>> {
|
||||||
|
let py_program = ModModule::parse(&code, "<stdin>").map_err(|err| {
|
||||||
|
let mut locator = RandomLocator::new(&code);
|
||||||
|
// let mut locator = LinearLocator::new(&py_code);
|
||||||
|
let err = locator.locate_error::<_, ParseErrorType>(err);
|
||||||
|
let msg = err.to_string();
|
||||||
|
let loc = err.location.unwrap_or_default();
|
||||||
|
let core = ErrorCore::new(
|
||||||
|
vec![],
|
||||||
|
msg,
|
||||||
|
0,
|
||||||
|
ErrorKind::SyntaxError,
|
||||||
|
erg_common::error::Location::range(
|
||||||
|
loc.row.get(),
|
||||||
|
loc.column.to_zero_indexed(),
|
||||||
|
loc.row.get(),
|
||||||
|
loc.column.to_zero_indexed(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let err = CompileError::new(core, self.cfg.input.clone(), "".into());
|
||||||
|
IncompleteParseArtifact::new(
|
||||||
|
None,
|
||||||
|
ParserRunnerErrors::from(err),
|
||||||
|
ParserRunnerErrors::empty(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut locator = RandomLocator::new(&code);
|
||||||
|
// let mut locator = LinearLocator::new(&code);
|
||||||
|
let module = locator
|
||||||
|
.fold(py_program)
|
||||||
|
.map_err(|_err| ParserRunnerErrors::empty())?;
|
||||||
|
Ok(module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct PythonAnalyzer {
|
pub struct PythonAnalyzer {
|
||||||
pub cfg: ErgConfig,
|
pub cfg: ErgConfig,
|
||||||
checker: ASTLowerer,
|
checker: GenericPackageBuilder<SimplePythonParser, GenericHIRBuilder<SimplePythonParser>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl New for PythonAnalyzer {
|
||||||
|
fn new(cfg: ErgConfig) -> Self {
|
||||||
|
let checker =
|
||||||
|
GenericPackageBuilder::new(cfg.clone(), SharedCompilerResource::new(cfg.clone()));
|
||||||
|
Self { checker, cfg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextProvider for PythonAnalyzer {
|
||||||
|
fn dir(&self) -> erg_common::dict::Dict<&VarName, &VarInfo> {
|
||||||
|
self.checker.dir()
|
||||||
|
}
|
||||||
|
fn get_receiver_ctx(&self, receiver_name: &str) -> Option<&Context> {
|
||||||
|
self.checker.get_receiver_ctx(receiver_name)
|
||||||
|
}
|
||||||
|
fn get_var_info(&self, name: &str) -> Option<(&VarName, &VarInfo)> {
|
||||||
|
self.checker.get_var_info(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runnable for PythonAnalyzer {
|
impl Runnable for PythonAnalyzer {
|
||||||
type Err = CompileError;
|
type Err = CompileError;
|
||||||
type Errs = CompileErrors;
|
type Errs = CompileErrors;
|
||||||
const NAME: &'static str = "Python Analyzer";
|
const NAME: &'static str = "Python Analyzer";
|
||||||
fn new(cfg: ErgConfig) -> Self {
|
|
||||||
let checker = ASTLowerer::new(cfg.clone());
|
|
||||||
Self { checker, cfg }
|
|
||||||
}
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn cfg(&self) -> &ErgConfig {
|
fn cfg(&self) -> &ErgConfig {
|
||||||
&self.cfg
|
&self.cfg
|
||||||
|
|
@ -95,17 +189,31 @@ impl Buildable for PythonAnalyzer {
|
||||||
let mod_name = Str::rc(&cfg.input.file_stem());
|
let mod_name = Str::rc(&cfg.input.file_stem());
|
||||||
Self {
|
Self {
|
||||||
cfg: cfg.copy(),
|
cfg: cfg.copy(),
|
||||||
checker: ASTLowerer::new_with_cache(cfg, mod_name, shared),
|
checker: GenericPackageBuilder::new_with_cache(cfg, mod_name, shared),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn inherit_with_name(cfg: ErgConfig, mod_name: Str, shared: SharedCompilerResource) -> Self {
|
||||||
|
Self {
|
||||||
|
cfg: cfg.copy(),
|
||||||
|
checker: GenericPackageBuilder::new_with_cache(cfg, mod_name, shared),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn build(&mut self, code: String, mode: &str) -> Result<CompleteArtifact, IncompleteArtifact> {
|
fn build(&mut self, code: String, mode: &str) -> Result<CompleteArtifact, IncompleteArtifact> {
|
||||||
self.analyze(code, mode)
|
self.analyze(code, mode)
|
||||||
}
|
}
|
||||||
|
fn build_from_ast(
|
||||||
|
&mut self,
|
||||||
|
ast: AST,
|
||||||
|
mode: &str,
|
||||||
|
) -> Result<CompleteArtifact<erg_compiler::hir::HIR>, IncompleteArtifact<erg_compiler::hir::HIR>>
|
||||||
|
{
|
||||||
|
self.check(ast, CompileErrors::empty(), CompileErrors::empty(), mode)
|
||||||
|
}
|
||||||
fn pop_context(&mut self) -> Option<ModuleContext> {
|
fn pop_context(&mut self) -> Option<ModuleContext> {
|
||||||
self.checker.pop_mod_ctx()
|
self.checker.pop_context()
|
||||||
}
|
}
|
||||||
fn get_context(&self) -> Option<&ModuleContext> {
|
fn get_context(&self) -> Option<&ModuleContext> {
|
||||||
Some(self.checker.get_mod_ctx())
|
self.checker.get_context()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,53 +221,22 @@ impl BuildRunnable for PythonAnalyzer {}
|
||||||
|
|
||||||
impl PythonAnalyzer {
|
impl PythonAnalyzer {
|
||||||
pub fn new(cfg: ErgConfig) -> Self {
|
pub fn new(cfg: ErgConfig) -> Self {
|
||||||
Runnable::new(cfg)
|
New::new(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn analyze(
|
#[allow(clippy::result_large_err)]
|
||||||
|
fn check(
|
||||||
&mut self,
|
&mut self,
|
||||||
py_code: String,
|
erg_ast: AST,
|
||||||
|
mut errors: CompileErrors,
|
||||||
|
mut warns: CompileErrors,
|
||||||
mode: &str,
|
mode: &str,
|
||||||
) -> Result<CompleteArtifact, IncompleteArtifact> {
|
) -> Result<CompleteArtifact, IncompleteArtifact> {
|
||||||
let filename = self.cfg.input.filename();
|
match self.checker.build_from_ast(erg_ast, mode) {
|
||||||
let py_program = ModModule::parse(&py_code, &filename).map_err(|err| {
|
|
||||||
let mut locator = RandomLocator::new(&py_code);
|
|
||||||
// let mut locator = LinearLocator::new(&py_code);
|
|
||||||
let err = locator.locate_error::<_, ParseErrorType>(err);
|
|
||||||
let msg = err.to_string();
|
|
||||||
let loc = err.location.unwrap();
|
|
||||||
let core = ErrorCore::new(
|
|
||||||
vec![],
|
|
||||||
msg,
|
|
||||||
0,
|
|
||||||
ErrorKind::SyntaxError,
|
|
||||||
erg_common::error::Location::range(
|
|
||||||
loc.row.get(),
|
|
||||||
loc.column.to_zero_indexed(),
|
|
||||||
loc.row.get(),
|
|
||||||
loc.column.to_zero_indexed(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
let err = CompileError::new(core, self.cfg.input.clone(), "".into());
|
|
||||||
IncompleteArtifact::new(None, CompileErrors::from(err), CompileErrors::empty())
|
|
||||||
})?;
|
|
||||||
let mut locator = RandomLocator::new(&py_code);
|
|
||||||
// let mut locator = LinearLocator::new(&py_code);
|
|
||||||
let py_program = locator.fold(py_program).unwrap();
|
|
||||||
let shadowing = if cfg!(feature = "debug") {
|
|
||||||
ShadowingMode::Visible
|
|
||||||
} else {
|
|
||||||
ShadowingMode::Invisible
|
|
||||||
};
|
|
||||||
let converter = py2erg::ASTConverter::new(self.cfg.copy(), shadowing);
|
|
||||||
let IncompleteArtifact{ object: Some(erg_module), mut errors, mut warns } = converter.convert_program(py_program) else { unreachable!() };
|
|
||||||
let erg_ast = AST::new(erg_common::Str::rc(&filename), erg_module);
|
|
||||||
erg_common::log!("AST:\n{erg_ast}");
|
|
||||||
match self.checker.lower(erg_ast, mode) {
|
|
||||||
Ok(mut artifact) => {
|
Ok(mut artifact) => {
|
||||||
artifact.warns.extend(warns);
|
artifact.warns.extend(warns);
|
||||||
artifact.warns =
|
artifact.warns =
|
||||||
handle_err::filter_errors(self.checker.get_mod_ctx(), artifact.warns);
|
handle_err::filter_errors(self.get_context().unwrap(), artifact.warns);
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
Ok(artifact)
|
Ok(artifact)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -172,18 +249,56 @@ impl PythonAnalyzer {
|
||||||
}
|
}
|
||||||
Err(iart) => {
|
Err(iart) => {
|
||||||
errors.extend(iart.errors);
|
errors.extend(iart.errors);
|
||||||
let errors = handle_err::filter_errors(self.checker.get_mod_ctx(), errors);
|
let errors = handle_err::filter_errors(self.get_context().unwrap(), errors);
|
||||||
warns.extend(iart.warns);
|
warns.extend(iart.warns);
|
||||||
let warns = handle_err::filter_errors(self.checker.get_mod_ctx(), warns);
|
let warns = handle_err::filter_errors(self.get_context().unwrap(), warns);
|
||||||
Err(IncompleteArtifact::new(iart.object, errors, warns))
|
Err(IncompleteArtifact::new(iart.object, errors, warns))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
#[allow(clippy::result_large_err)]
|
||||||
if self.cfg.dist_dir.is_some() {
|
pub fn analyze(
|
||||||
reserve_decl_er(self.cfg.input.clone());
|
&mut self,
|
||||||
|
py_code: String,
|
||||||
|
mode: &str,
|
||||||
|
) -> Result<CompleteArtifact, IncompleteArtifact> {
|
||||||
|
let filename = self.cfg.input.filename();
|
||||||
|
let parser = SimplePythonParser::new(self.cfg.copy());
|
||||||
|
let mut comments = CommentStorage::new();
|
||||||
|
comments.read(&py_code);
|
||||||
|
let py_program = parser
|
||||||
|
.parse_py_code(py_code)
|
||||||
|
.map_err(|iart| IncompleteArtifact::new(None, iart.errors.into(), iart.warns.into()))?;
|
||||||
|
let shadowing = if cfg!(feature = "debug") {
|
||||||
|
ShadowingMode::Visible
|
||||||
|
} else {
|
||||||
|
ShadowingMode::Invisible
|
||||||
|
};
|
||||||
|
let converter = py2erg::ASTConverter::new(self.cfg.copy(), shadowing, comments);
|
||||||
|
let IncompleteArtifact {
|
||||||
|
object: Some(erg_module),
|
||||||
|
errors,
|
||||||
|
warns,
|
||||||
|
} = converter.convert_program(py_program)
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let erg_ast = AST::new(erg_common::Str::rc(&filename), erg_module);
|
||||||
|
erg_common::log!("AST:\n{erg_ast}");
|
||||||
|
let res = self.check(erg_ast, errors, warns, mode);
|
||||||
|
if self.cfg.mode.is_language_server() {
|
||||||
|
// mod_cache doesn't contains the current module
|
||||||
|
// we don't cache the current module's result for now
|
||||||
|
dump_decl_package(&self.checker.shared().mod_cache);
|
||||||
}
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> i32 {
|
||||||
|
/*if self.cfg.dist_dir.is_some() {
|
||||||
|
reserve_decl_er(self.cfg.input.clone());
|
||||||
|
}*/
|
||||||
let py_code = self.cfg.input.read();
|
let py_code = self.cfg.input.read();
|
||||||
let filename = self.cfg.input.filename();
|
let filename = self.cfg.input.filename();
|
||||||
println!("{BLUE}Start checking{RESET}: {filename}");
|
println!("{BLUE}Start checking{RESET}: {filename}");
|
||||||
|
|
@ -199,14 +314,10 @@ impl PythonAnalyzer {
|
||||||
}
|
}
|
||||||
println!("{GREEN}All checks OK{RESET}: {}", self.cfg.input.filename());
|
println!("{GREEN}All checks OK{RESET}: {}", self.cfg.input.filename());
|
||||||
if self.cfg.dist_dir.is_some() {
|
if self.cfg.dist_dir.is_some() {
|
||||||
dump_decl_er(
|
dump_decl_package(&self.checker.shared().mod_cache);
|
||||||
self.cfg.input.clone(),
|
|
||||||
artifact.object,
|
|
||||||
CheckStatus::Succeed,
|
|
||||||
);
|
|
||||||
println!("A declaration file has been generated to __pycache__ directory.");
|
println!("A declaration file has been generated to __pycache__ directory.");
|
||||||
}
|
}
|
||||||
std::process::exit(0);
|
0
|
||||||
}
|
}
|
||||||
Err(artifact) => {
|
Err(artifact) => {
|
||||||
if !artifact.warns.is_empty() {
|
if !artifact.warns.is_empty() {
|
||||||
|
|
@ -231,14 +342,10 @@ impl PythonAnalyzer {
|
||||||
};
|
};
|
||||||
// Even if type checking fails, some APIs are still valid, so generate a file
|
// Even if type checking fails, some APIs are still valid, so generate a file
|
||||||
if self.cfg.dist_dir.is_some() {
|
if self.cfg.dist_dir.is_some() {
|
||||||
dump_decl_er(
|
dump_decl_package(&self.checker.shared().mod_cache);
|
||||||
self.cfg.input.clone(),
|
|
||||||
artifact.object.unwrap(),
|
|
||||||
CheckStatus::Failed,
|
|
||||||
);
|
|
||||||
println!("A declaration file has been generated to __pycache__ directory.");
|
println!("A declaration file has been generated to __pycache__ directory.");
|
||||||
}
|
}
|
||||||
std::process::exit(code);
|
code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use erg_common::error::ErrorKind;
|
||||||
|
use erg_common::log;
|
||||||
|
use erg_common::style::{remove_style, StyledStr};
|
||||||
|
// use erg_common::style::{remove_style, StyledString, Color};
|
||||||
|
use erg_compiler::context::ModuleContext;
|
||||||
|
use erg_compiler::error::{CompileError, CompileErrors};
|
||||||
|
|
||||||
|
fn project_root(path: &Path) -> Option<PathBuf> {
|
||||||
|
let mut parent = path.to_path_buf();
|
||||||
|
while parent.pop() {
|
||||||
|
if parent.join("pyproject.toml").exists() || parent.join(".git").exists() {
|
||||||
|
let path = if parent == Path::new("") {
|
||||||
|
PathBuf::from(".")
|
||||||
|
} else {
|
||||||
|
parent
|
||||||
|
};
|
||||||
|
return path.canonicalize().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn filter_errors(ctx: &ModuleContext, errors: CompileErrors) -> CompileErrors {
|
||||||
|
let root = project_root(ctx.get_top_cfg().input.path());
|
||||||
|
errors
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|error| filter_error(root.as_deref(), ctx, error))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_name_error(error: CompileError) -> Option<CompileError> {
|
||||||
|
let main = &error.core.main_message;
|
||||||
|
if main.contains("is already declared")
|
||||||
|
|| main.contains("cannot be assigned more than once")
|
||||||
|
|| {
|
||||||
|
main.contains(" is not defined") && {
|
||||||
|
let name = StyledStr::destyle(main.trim_end_matches(" is not defined"));
|
||||||
|
name == "Any"
|
||||||
|
|| error
|
||||||
|
.core
|
||||||
|
.get_hint()
|
||||||
|
.is_some_and(|hint| hint.contains(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_error(
|
||||||
|
root: Option<&Path>,
|
||||||
|
ctx: &ModuleContext,
|
||||||
|
mut error: CompileError,
|
||||||
|
) -> Option<CompileError> {
|
||||||
|
if ctx.get_top_cfg().do_not_show_ext_errors
|
||||||
|
&& error.input.path() != Path::new("<string>")
|
||||||
|
&& root.is_some_and(|root| {
|
||||||
|
error
|
||||||
|
.input
|
||||||
|
.path()
|
||||||
|
.canonicalize()
|
||||||
|
.is_ok_and(|path| path.starts_with(root.join(".venv")) || !path.starts_with(root))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match error.core.kind {
|
||||||
|
ErrorKind::FeatureError => {
|
||||||
|
log!(err "this error is ignored:");
|
||||||
|
log!(err "{error}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
ErrorKind::InheritanceError => None,
|
||||||
|
ErrorKind::VisibilityError => None,
|
||||||
|
// exclude doc strings
|
||||||
|
ErrorKind::UnusedWarning => {
|
||||||
|
let code = error.input.reread_lines(
|
||||||
|
error.core.loc.ln_begin().unwrap_or(1) as usize,
|
||||||
|
error.core.loc.ln_end().unwrap_or(1) as usize,
|
||||||
|
);
|
||||||
|
if code[0].trim().starts_with("\"\"\"") {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
for sub in error.core.sub_messages.iter_mut() {
|
||||||
|
if let Some(hint) = &mut sub.hint {
|
||||||
|
*hint = remove_style(hint);
|
||||||
|
*hint = hint.replace("use discard function", "bind to `_` (`_ = ...`)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ErrorKind::NameError | ErrorKind::AssignError => handle_name_error(error),
|
||||||
|
_ => Some(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
mod analyze;
|
||||||
|
mod handle_err;
|
||||||
|
|
||||||
|
pub use analyze::{PythonAnalyzer, SimplePythonParser};
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "pylyzer_wasm"
|
||||||
|
description = "Wasm wrapper for pylyzer"
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
erg_common = { workspace = true }
|
||||||
|
erg_compiler = { workspace = true }
|
||||||
|
pylyzer_core = { version = "*", path = "../pylyzer_core" }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
path = "lib.rs"
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
# pylyzer_wasm
|
||||||
|
|
||||||
|
Wasm wrapper for pylyzer.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Analyzer } from 'pylyzer_wasm';
|
||||||
|
|
||||||
|
const analyzer = new Analyzer();
|
||||||
|
const errors = analyzer.check('print("Hello, World!")');
|
||||||
|
const locals = analyzer.dir();
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use erg_common::error::ErrorCore;
|
||||||
|
use erg_common::error::Location as Loc;
|
||||||
|
use erg_common::traits::{Runnable, Stream};
|
||||||
|
use erg_compiler::context::ContextProvider;
|
||||||
|
use erg_compiler::erg_parser::ast::VarName;
|
||||||
|
use erg_compiler::error::CompileError;
|
||||||
|
use erg_compiler::ty::Type as Ty;
|
||||||
|
use erg_compiler::varinfo::VarInfo;
|
||||||
|
use pylyzer_core::PythonAnalyzer;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub enum CompItemKind {
|
||||||
|
Method = 0,
|
||||||
|
Function = 1,
|
||||||
|
Constructor = 2,
|
||||||
|
Field = 3,
|
||||||
|
Variable = 4,
|
||||||
|
Class = 5,
|
||||||
|
Struct = 6,
|
||||||
|
Interface = 7,
|
||||||
|
Module = 8,
|
||||||
|
Property = 9,
|
||||||
|
Event = 10,
|
||||||
|
Operator = 11,
|
||||||
|
Unit = 12,
|
||||||
|
Value = 13,
|
||||||
|
Constant = 14,
|
||||||
|
Enum = 15,
|
||||||
|
EnumMember = 16,
|
||||||
|
Keyword = 17,
|
||||||
|
Text = 18,
|
||||||
|
Color = 19,
|
||||||
|
File = 20,
|
||||||
|
Reference = 21,
|
||||||
|
Customcolor = 22,
|
||||||
|
Folder = 23,
|
||||||
|
TypeParameter = 24,
|
||||||
|
User = 25,
|
||||||
|
Issue = 26,
|
||||||
|
Snippet = 27,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Location(Loc);
|
||||||
|
|
||||||
|
impl From<Loc> for Location {
|
||||||
|
fn from(loc: Loc) -> Self {
|
||||||
|
Self(loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
pub const UNKNOWN: Location = Location(Loc::Unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Type(Ty);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct VarEntry {
|
||||||
|
name: VarName,
|
||||||
|
vi: VarInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VarEntry {
|
||||||
|
pub fn new(name: VarName, vi: VarInfo) -> Self {
|
||||||
|
Self { name, vi }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl VarEntry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
self.name.to_string()
|
||||||
|
}
|
||||||
|
pub fn item_kind(&self) -> CompItemKind {
|
||||||
|
match &self.vi.t {
|
||||||
|
Ty::Callable { .. } => CompItemKind::Function,
|
||||||
|
Ty::Subr(subr) => {
|
||||||
|
if subr.self_t().is_some() {
|
||||||
|
CompItemKind::Method
|
||||||
|
} else {
|
||||||
|
CompItemKind::Function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ty::Quantified(quant) => match quant.as_ref() {
|
||||||
|
Ty::Callable { .. } => CompItemKind::Function,
|
||||||
|
Ty::Subr(subr) => {
|
||||||
|
if subr.self_t().is_some() {
|
||||||
|
CompItemKind::Method
|
||||||
|
} else {
|
||||||
|
CompItemKind::Function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Ty::ClassType => CompItemKind::Class,
|
||||||
|
Ty::TraitType => CompItemKind::Interface,
|
||||||
|
Ty::Poly { name, .. } if &name[..] == "Module" => CompItemKind::Module,
|
||||||
|
_ if self.vi.muty.is_const() => CompItemKind::Constant,
|
||||||
|
_ => CompItemKind::Variable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn typ(&self) -> String {
|
||||||
|
self.vi.t.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Location {
|
||||||
|
pub fn ln_begin(&self) -> Option<u32> {
|
||||||
|
self.0.ln_begin()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ln_end(&self) -> Option<u32> {
|
||||||
|
self.0.ln_end()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col_begin(&self) -> Option<u32> {
|
||||||
|
self.0.col_begin()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col_end(&self) -> Option<u32> {
|
||||||
|
self.0.col_end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
|
pub struct Error {
|
||||||
|
pub errno: usize,
|
||||||
|
pub is_warning: bool,
|
||||||
|
// pub kind: ErrorKind,
|
||||||
|
pub loc: Location,
|
||||||
|
pub desc: String,
|
||||||
|
pub hint: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_fallback_loc(err: &ErrorCore) -> Loc {
|
||||||
|
if err.loc == Loc::Unknown {
|
||||||
|
for sub in &err.sub_messages {
|
||||||
|
if sub.loc != Loc::Unknown {
|
||||||
|
return sub.loc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loc::Unknown
|
||||||
|
} else {
|
||||||
|
err.loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CompileError> for Error {
|
||||||
|
fn from(err: CompileError) -> Self {
|
||||||
|
let loc = Location(find_fallback_loc(&err.core));
|
||||||
|
let sub_msg = err
|
||||||
|
.core
|
||||||
|
.sub_messages
|
||||||
|
.first()
|
||||||
|
.map(|sub| {
|
||||||
|
sub.msg
|
||||||
|
.iter()
|
||||||
|
.fold("\n".to_string(), |acc, s| acc + s + "\n")
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let desc = err.core.main_message + &sub_msg;
|
||||||
|
Self {
|
||||||
|
errno: err.core.errno,
|
||||||
|
is_warning: err.core.kind.is_warning(),
|
||||||
|
// kind: err.kind(),
|
||||||
|
loc,
|
||||||
|
desc,
|
||||||
|
hint: err
|
||||||
|
.core
|
||||||
|
.sub_messages
|
||||||
|
.first()
|
||||||
|
.and_then(|sub| sub.hint.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub const fn new(
|
||||||
|
errno: usize,
|
||||||
|
is_warning: bool,
|
||||||
|
loc: Location,
|
||||||
|
desc: String,
|
||||||
|
hint: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
errno,
|
||||||
|
is_warning,
|
||||||
|
loc,
|
||||||
|
desc,
|
||||||
|
hint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
// #[derive()]
|
||||||
|
pub struct Analyzer {
|
||||||
|
analyzer: PythonAnalyzer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Analyzer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Analyzer {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Analyzer {
|
||||||
|
analyzer: PythonAnalyzer::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.analyzer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_message(&self) -> String {
|
||||||
|
self.analyzer.start_message()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dir(&mut self) -> Box<[VarEntry]> {
|
||||||
|
self.analyzer
|
||||||
|
.dir()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(n, vi)| VarEntry::new(n.clone(), vi.clone()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_boxed_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(&mut self, input: &str) -> Box<[Error]> {
|
||||||
|
match self.analyzer.analyze(input.to_string(), "exec") {
|
||||||
|
Ok(artifact) => artifact
|
||||||
|
.warns
|
||||||
|
.into_iter()
|
||||||
|
.map(Error::from)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_boxed_slice(),
|
||||||
|
Err(mut err_artifact) => {
|
||||||
|
err_artifact.errors.extend(err_artifact.warns);
|
||||||
|
let errs = err_artifact
|
||||||
|
.errors
|
||||||
|
.into_iter()
|
||||||
|
.map(Error::from)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
errs.into_boxed_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
# command line options
|
||||||
|
|
||||||
|
## --server
|
||||||
|
|
||||||
|
Launch as a language server.
|
||||||
|
|
||||||
|
## --clear-cache
|
||||||
|
|
||||||
|
Clear the cache files.
|
||||||
|
|
||||||
|
## --dump-decl
|
||||||
|
|
||||||
|
Dump a type declarations file (d.er) after type checking.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pylyzer --dump-decl test.py
|
||||||
|
Start checking: test.py
|
||||||
|
All checks OK: test.py
|
||||||
|
|
||||||
|
$ ls
|
||||||
|
test.py test.d.er
|
||||||
|
```
|
||||||
|
|
||||||
|
## -c/--code
|
||||||
|
|
||||||
|
Check code from the command line.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pylyzer -c "print('hello world')"
|
||||||
|
Start checking: string
|
||||||
|
All checks OK: string
|
||||||
|
```
|
||||||
|
|
||||||
|
## --disable
|
||||||
|
|
||||||
|
Disable a default LSP feature.
|
||||||
|
Default (disableable) features are:
|
||||||
|
|
||||||
|
* codeAction
|
||||||
|
* codeLens
|
||||||
|
* completion
|
||||||
|
* diagnostics
|
||||||
|
* findReferences
|
||||||
|
* gotoDefinition
|
||||||
|
* hover
|
||||||
|
* inlayHint
|
||||||
|
* rename
|
||||||
|
* semanticTokens
|
||||||
|
* signatureHelp
|
||||||
|
* documentLink
|
||||||
|
|
||||||
|
## --verbose
|
||||||
|
|
||||||
|
Print process information verbosely.
|
||||||
|
|
||||||
|
## --no-infer-fn-type
|
||||||
|
|
||||||
|
When a function type is not specified, no type inference is performed and the function type is assumed to be `Any`.
|
||||||
|
|
||||||
|
## --fast-error-report
|
||||||
|
|
||||||
|
Simplify error reporting by eliminating to search for similar variables when a variable does not exist.
|
||||||
|
|
||||||
|
## --hurry
|
||||||
|
|
||||||
|
Enable `--no-infer-fn-type` and `--fast-error-report`.
|
||||||
|
|
||||||
|
## --do-not-show-ext-errors
|
||||||
|
|
||||||
|
Do not show errors from external libraries.
|
||||||
|
|
||||||
|
## --do-not-respect-pyi
|
||||||
|
|
||||||
|
If specified, the actual `.py` types will be respected over the `.pyi` types.
|
||||||
|
Applying this option may slow down the analysis.
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# `pyproject.toml` options
|
||||||
|
|
||||||
|
## `tool.pylyzer.python.path`
|
||||||
|
|
||||||
|
Path to the Python interpreter to use. If not set, the default Python interpreter will be used.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[tool.pylyzer.python]
|
||||||
|
path = "path/to/python"
|
||||||
|
```
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# command line options
|
|
||||||
|
|
||||||
## --server
|
|
||||||
|
|
||||||
Launch as a language server.
|
|
||||||
|
|
||||||
## --dump-decl
|
|
||||||
|
|
||||||
Dump a type declarations file (d.er) after type checking.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pylyzer --dump-decl test.py
|
|
||||||
Start checking: test.py
|
|
||||||
All checks OK: test.py
|
|
||||||
|
|
||||||
$ ls
|
|
||||||
test.py test.d.er
|
|
||||||
```
|
|
||||||
|
|
||||||
## -c/--code
|
|
||||||
|
|
||||||
Check code from the command line.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ pylyzer -c "print('hello world')"
|
|
||||||
Start checking: string
|
|
||||||
All checks OK: string
|
|
||||||
```
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"recommendations": ["amodio.tsl-problem-matcher", "rome.rome"]
|
"recommendations": ["amodio.tsl-problem-matcher", "biomejs.biome"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ src/**
|
||||||
**/*.ts
|
**/*.ts
|
||||||
.gitignore
|
.gitignore
|
||||||
webpack.config.js
|
webpack.config.js
|
||||||
rome.json
|
biome.json
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,12 @@ cargo install pylyzer
|
||||||
| - | - | - |
|
| - | - | - |
|
||||||
| pylyzer.diagnostics | Enable diagnostics | true |
|
| pylyzer.diagnostics | Enable diagnostics | true |
|
||||||
| pylyzer.inlayHints | Enable inlay hints (this feature is unstable) | false |
|
| pylyzer.inlayHints | Enable inlay hints (this feature is unstable) | false |
|
||||||
|
| pylyzer.semanticTokens | Enable semantic tokens | false |
|
||||||
|
| pylyzer.hover | Enable hover | true |
|
||||||
|
| pylyzer.completion | Enable completion | true |
|
||||||
| pylyzer.smartCompletion | Enable smart completion (see [ELS features](https://github.com/erg-lang/erg/blob/main/crates/els/doc/features.md))| true |
|
| pylyzer.smartCompletion | Enable smart completion (see [ELS features](https://github.com/erg-lang/erg/blob/main/crates/els/doc/features.md))| true |
|
||||||
|
| pylyzer.deepCompletion | Enable deep completion | true |
|
||||||
|
| pylyzer.signatureHelp | Enable signature help | true |
|
||||||
|
| pylyzer.documentLink | Enable document link | true |
|
||||||
|
| pylyzer.codeAction | Enable code action | true |
|
||||||
|
| pylyzer.codeLens | Enable code lens | true |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.8.2/schema.json",
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "tab",
|
||||||
|
"lineWidth": 120,
|
||||||
|
"ignore": ["dist/**", "out/**", ".vscode-test/**", ".vscode/**"]
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
},
|
||||||
|
"ignore": ["dist/**", "out/**", ".vscode-test/**", ".vscode/**"]
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"indentStyle": "tab",
|
||||||
|
"lineWidth": 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"json": {
|
||||||
|
"formatter": {
|
||||||
|
"indentStyle": "tab",
|
||||||
|
"lineWidth": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,14 +3,11 @@
|
||||||
"displayName": "pylyzer",
|
"displayName": "pylyzer",
|
||||||
"description": "A fast Python static code analyzer & language server for VSCode",
|
"description": "A fast Python static code analyzer & language server for VSCode",
|
||||||
"publisher": "pylyzer",
|
"publisher": "pylyzer",
|
||||||
"version": "0.1.7",
|
"version": "0.1.11",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.70.0"
|
"vscode": "^1.70.0"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": ["Programming Languages", "Linters"],
|
||||||
"Programming Languages",
|
|
||||||
"Linters"
|
|
||||||
],
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/mtshiba/pylyzer.git"
|
"url": "https://github.com/mtshiba/pylyzer.git"
|
||||||
|
|
@ -18,6 +15,8 @@
|
||||||
"icon": "images/pylyzer-logo.png",
|
"icon": "images/pylyzer-logo.png",
|
||||||
"main": "./dist/extension.js",
|
"main": "./dist/extension.js",
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
|
"workspaceContains:pyproject.toml",
|
||||||
|
"workspaceContains:*/pyproject.toml",
|
||||||
"onLanguage:python"
|
"onLanguage:python"
|
||||||
],
|
],
|
||||||
"contributes": {
|
"contributes": {
|
||||||
|
|
@ -31,13 +30,8 @@
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"id": "python",
|
"id": "python",
|
||||||
"aliases": [
|
"aliases": ["Python", "python"],
|
||||||
"Python",
|
"extensions": [".py", ".pyi"]
|
||||||
"python"
|
|
||||||
],
|
|
||||||
"extensions": [
|
|
||||||
".py"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration": {
|
"configuration": {
|
||||||
|
|
@ -54,10 +48,50 @@
|
||||||
"default": false,
|
"default": false,
|
||||||
"markdownDescription": "Enable inlay hints (this feature is unstable)"
|
"markdownDescription": "Enable inlay hints (this feature is unstable)"
|
||||||
},
|
},
|
||||||
|
"pylyzer.semanticTokens": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"markdownDescription": "Enable semantic tokens (this feature is unstable)"
|
||||||
|
},
|
||||||
|
"pylyzer.hover": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"markdownDescription": "Enable hover"
|
||||||
|
},
|
||||||
|
"pylyzer.completion": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"markdownDescription": "Enable completion"
|
||||||
|
},
|
||||||
"pylyzer.smartCompletion": {
|
"pylyzer.smartCompletion": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": true,
|
||||||
"markdownDescription": "Enable smart completion (see [ELS features](https://github.com/erg-lang/erg/blob/main/crates/els/doc/features.md))"
|
"markdownDescription": "Enable smart completion (see [ELS features](https://github.com/erg-lang/erg/blob/main/crates/els/doc/features.md))"
|
||||||
|
},
|
||||||
|
"pylyzer.deepCompletion": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"markdownDescription": "Enable deep completion (see [ELS features](https://github.com/erg-lang/erg/blob/main/crates/els/doc/features.md))"
|
||||||
|
},
|
||||||
|
"pylyzer.signatureHelp": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"markdownDescription": "Enable signature help"
|
||||||
|
},
|
||||||
|
"pylyzer.documentLink": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"markdownDescription": "Enable document link"
|
||||||
|
},
|
||||||
|
"pylyzer.codeAction": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"markdownDescription": "Enable code action"
|
||||||
|
},
|
||||||
|
"pylyzer.codeLens": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"markdownDescription": "Enable code lens"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -74,16 +108,16 @@
|
||||||
"pretest": "npm run compile-tests && npm run compile && npm run lint",
|
"pretest": "npm run compile-tests && npm run compile && npm run lint",
|
||||||
"test": "node ./out/test/runTest.js",
|
"test": "node ./out/test/runTest.js",
|
||||||
"type-check": "tsc --noEmit",
|
"type-check": "tsc --noEmit",
|
||||||
"lint": "rome check .",
|
"lint": "biome lint .",
|
||||||
"format": "rome format .",
|
"format": "biome format .",
|
||||||
"lint:fix": "rome check --apply .",
|
"lint:fix-suggested": "biome check --write .",
|
||||||
"lint:fix-suggested": "rome check --apply-suggested .",
|
"format:fix": "biome format --write ."
|
||||||
"format:fix": "rome format --write ."
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vscode-languageclient": "^8.0.2"
|
"vscode-languageclient": "^8.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@biomejs/biome": "^1.8.2",
|
||||||
"@types/glob": "^8.0.0",
|
"@types/glob": "^8.0.0",
|
||||||
"@types/mocha": "^10.0.1",
|
"@types/mocha": "^10.0.1",
|
||||||
"@types/node": "18.x",
|
"@types/node": "18.x",
|
||||||
|
|
@ -91,13 +125,12 @@
|
||||||
"@vscode/test-electron": "^2.2.1",
|
"@vscode/test-electron": "^2.2.1",
|
||||||
"glob": "^8.0.3",
|
"glob": "^8.0.3",
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^10.2.0",
|
||||||
"rome": "^10.0.1",
|
|
||||||
"ts-loader": "^9.4.2",
|
"ts-loader": "^9.4.2",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"webpack": "^5.75.0",
|
"webpack": "^5.75.0",
|
||||||
"webpack-cli": "^5.0.1"
|
"webpack-cli": "^5.0.1"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*": "rome format --write"
|
"*": "biome format --write"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"linter": {
|
|
||||||
"rules": {
|
|
||||||
"recommended": true
|
|
||||||
},
|
|
||||||
"ignore": ["/dist/", "/out/", "/.vscode-test/"]
|
|
||||||
},
|
|
||||||
"formatter": {
|
|
||||||
"ignore": ["/dist/", "/out/", "/.vscode-test/"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
|
import { Uri, commands } from "vscode";
|
||||||
// copied and modified from https://github.com/rust-lang/rust-analyzer/blob/27239fbb58a115915ffc1ce65ededc951eb00fd2/editors/code/src/commands.ts
|
// copied and modified from https://github.com/rust-lang/rust-analyzer/blob/27239fbb58a115915ffc1ce65ededc951eb00fd2/editors/code/src/commands.ts
|
||||||
import { LanguageClient, Location, Position } from 'vscode-languageclient/node';
|
import type { LanguageClient, Location, Position } from "vscode-languageclient/node";
|
||||||
import { Uri, commands } from 'vscode';
|
|
||||||
|
|
||||||
export async function showReferences(
|
export async function showReferences(
|
||||||
client: LanguageClient | undefined,
|
client: LanguageClient | undefined,
|
||||||
uri: string,
|
uri: string,
|
||||||
position: Position,
|
position: Position,
|
||||||
locations: Location[]
|
locations: Location[],
|
||||||
) {
|
) {
|
||||||
if (client) {
|
if (client) {
|
||||||
await commands.executeCommand(
|
await commands.executeCommand(
|
||||||
"editor.action.showReferences",
|
"editor.action.showReferences",
|
||||||
Uri.parse(uri),
|
Uri.parse(uri),
|
||||||
client.protocol2CodeConverter.asPosition(position),
|
client.protocol2CodeConverter.asPosition(position),
|
||||||
locations.map(client.protocol2CodeConverter.asLocation)
|
locations.map(client.protocol2CodeConverter.asLocation),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { ExtensionContext, commands, window, workspace } from "vscode";
|
import { type ExtensionContext, commands, window, workspace } from "vscode";
|
||||||
import {
|
import { LanguageClient, type LanguageClientOptions, type ServerOptions } from "vscode-languageclient/node";
|
||||||
LanguageClient,
|
import fs from "node:fs";
|
||||||
LanguageClientOptions,
|
|
||||||
ServerOptions,
|
|
||||||
} from "vscode-languageclient/node";
|
|
||||||
import { showReferences } from "./commands";
|
import { showReferences } from "./commands";
|
||||||
|
|
||||||
let client: LanguageClient | undefined;
|
let client: LanguageClient | undefined;
|
||||||
|
|
@ -11,16 +8,25 @@ let client: LanguageClient | undefined;
|
||||||
async function startLanguageClient(context: ExtensionContext) {
|
async function startLanguageClient(context: ExtensionContext) {
|
||||||
try {
|
try {
|
||||||
const executablePath = (() => {
|
const executablePath = (() => {
|
||||||
const executablePath = workspace
|
const fp = workspace.workspaceFolders?.at(0)?.uri.fsPath;
|
||||||
.getConfiguration("pylyzer")
|
const venvExecutablePath = `${fp}/.venv/bin/pylyzer`;
|
||||||
.get<string>("executablePath", "");
|
if (fs.existsSync(venvExecutablePath)) {
|
||||||
|
return venvExecutablePath;
|
||||||
|
}
|
||||||
|
const executablePath = workspace.getConfiguration("pylyzer").get<string>("executablePath", "");
|
||||||
return executablePath === "" ? "pylyzer" : executablePath;
|
return executablePath === "" ? "pylyzer" : executablePath;
|
||||||
})();
|
})();
|
||||||
const enableDiagnostics = workspace.getConfiguration("pylyzer").get<boolean>("diagnostics", true);
|
const enableDiagnostics = workspace.getConfiguration("pylyzer").get<boolean>("diagnostics", true);
|
||||||
const enableInlayHints = workspace.getConfiguration("pylyzer").get<boolean>("inlayHints", false);
|
const enableInlayHints = workspace.getConfiguration("pylyzer").get<boolean>("inlayHints", false);
|
||||||
const enableSemanticTokens = workspace.getConfiguration("pylyzer").get<boolean>("semanticTokens", true);
|
const enableSemanticTokens = workspace.getConfiguration("pylyzer").get<boolean>("semanticTokens", false);
|
||||||
const enableHover = workspace.getConfiguration("pylyzer").get<boolean>("hover", true);
|
const enableHover = workspace.getConfiguration("pylyzer").get<boolean>("hover", true);
|
||||||
|
const enableCompletion = workspace.getConfiguration("pylyzer").get<boolean>("completion", true);
|
||||||
const smartCompletion = workspace.getConfiguration("pylyzer").get<boolean>("smartCompletion", true);
|
const smartCompletion = workspace.getConfiguration("pylyzer").get<boolean>("smartCompletion", true);
|
||||||
|
const deepCompletion = workspace.getConfiguration("pylyzer").get<boolean>("deepCompletion", true);
|
||||||
|
const enableSignatureHelp = workspace.getConfiguration("pylyzer").get<boolean>("signatureHelp", true);
|
||||||
|
const enableDocumentLink = workspace.getConfiguration("pylyzer").get<boolean>("documentLink", true);
|
||||||
|
const enableCodeAction = workspace.getConfiguration("pylyzer").get<boolean>("codeAction", true);
|
||||||
|
const enableCodeLens = workspace.getConfiguration("pylyzer").get<boolean>("codeLens", true);
|
||||||
/* optional features */
|
/* optional features */
|
||||||
const checkOnType = workspace.getConfiguration("pylyzer").get<boolean>("checkOnType", false);
|
const checkOnType = workspace.getConfiguration("pylyzer").get<boolean>("checkOnType", false);
|
||||||
const args = ["--server"];
|
const args = ["--server"];
|
||||||
|
|
@ -41,10 +47,34 @@ async function startLanguageClient(context: ExtensionContext) {
|
||||||
args.push("--disable");
|
args.push("--disable");
|
||||||
args.push("hover");
|
args.push("hover");
|
||||||
}
|
}
|
||||||
|
if (!enableCompletion) {
|
||||||
|
args.push("--disable");
|
||||||
|
args.push("completion");
|
||||||
|
}
|
||||||
if (!smartCompletion) {
|
if (!smartCompletion) {
|
||||||
args.push("--disable");
|
args.push("--disable");
|
||||||
args.push("smartCompletion");
|
args.push("smartCompletion");
|
||||||
}
|
}
|
||||||
|
if (!deepCompletion) {
|
||||||
|
args.push("--disable");
|
||||||
|
args.push("deepCompletion");
|
||||||
|
}
|
||||||
|
if (!enableSignatureHelp) {
|
||||||
|
args.push("--disable");
|
||||||
|
args.push("signatureHelp");
|
||||||
|
}
|
||||||
|
if (!enableDocumentLink) {
|
||||||
|
args.push("--disable");
|
||||||
|
args.push("documentLink");
|
||||||
|
}
|
||||||
|
if (!enableCodeAction) {
|
||||||
|
args.push("--disable");
|
||||||
|
args.push("codeAction");
|
||||||
|
}
|
||||||
|
if (!enableCodeLens) {
|
||||||
|
args.push("--disable");
|
||||||
|
args.push("codeLens");
|
||||||
|
}
|
||||||
if (checkOnType) {
|
if (checkOnType) {
|
||||||
args.push("--enable");
|
args.push("--enable");
|
||||||
args.push("checkOnType");
|
args.push("checkOnType");
|
||||||
|
|
@ -84,13 +114,11 @@ async function restartLanguageClient() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function activate(context: ExtensionContext) {
|
export async function activate(context: ExtensionContext) {
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(commands.registerCommand("pylyzer.restartLanguageServer", () => restartLanguageClient()));
|
||||||
commands.registerCommand("pylyzer.restartLanguageServer", () => restartLanguageClient())
|
|
||||||
);
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
commands.registerCommand("pylyzer.showReferences", async (uri, position, locations) => {
|
commands.registerCommand("pylyzer.showReferences", async (uri, position, locations) => {
|
||||||
await showReferences(client, uri, position, locations)
|
await showReferences(client, uri, position, locations);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
await startLanguageClient(context);
|
await startLanguageClient(context);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import * as path from "path";
|
import * as path from "node:path";
|
||||||
|
|
||||||
import { runTests } from "@vscode/test-electron";
|
import { runTests } from "@vscode/test-electron";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import * as assert from "assert";
|
import * as assert from "node:assert";
|
||||||
|
|
||||||
// You can import and use all API from the 'vscode' module
|
// You can import and use all API from the 'vscode' module
|
||||||
// as well as import your extension to test it
|
// as well as import your extension to test it
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import * as path from "path";
|
import * as path from "node:path";
|
||||||
import Mocha from "mocha";
|
|
||||||
import glob from "glob";
|
import glob from "glob";
|
||||||
|
import Mocha from "mocha";
|
||||||
|
|
||||||
export function run(): Promise<void> {
|
export function run(): Promise<void> {
|
||||||
// Create the mocha test
|
// Create the mocha test
|
||||||
|
|
@ -18,7 +18,9 @@ export function run(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add files to the test suite
|
// Add files to the test suite
|
||||||
files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));
|
for (const f of files) {
|
||||||
|
mocha.addFile(path.resolve(testsRoot, f));
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Run the mocha test
|
// Run the mocha test
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
//@ts-check
|
//@ts-check
|
||||||
|
|
||||||
"use strict";
|
const path = require("node:path");
|
||||||
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
//@ts-check
|
//@ts-check
|
||||||
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 415 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
cp README.md docs/index.md
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
os=$(uname -s)
|
||||||
|
if [ "$os" = "Darwin" ]; then
|
||||||
|
platform="macos"
|
||||||
|
elif [ "$os" = "Linux" ]; then
|
||||||
|
platform="linux"
|
||||||
|
else
|
||||||
|
echo "Unsupported platform: $os"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cibuildwheel --output-dir dist --platform $platform
|
||||||
|
twine upload -u mtshiba -p $PYPI_PASSWORD --skip-existing dist/*
|
||||||
|
|
@ -1,15 +1,2 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["maturin>=0.12"]
|
requires = ["setuptools", "setuptools-rust", "wheel", "tomli"]
|
||||||
build-backend = "maturin"
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "pylyzer"
|
|
||||||
description = "A fast static code analyzer & language server for Python"
|
|
||||||
requires-python = ">=3.7"
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 2 - Pre-Alpha",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
"Programming Language :: Python",
|
|
||||||
"Programming Language :: Rust",
|
|
||||||
"Topic :: Software Development :: Quality Assurance",
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
# To build the package, run `python -m build --wheel`.
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
from glob import glob
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from setuptools import setup, Command
|
||||||
|
from setuptools_rust import RustBin
|
||||||
|
import tomli
|
||||||
|
|
||||||
|
try:
|
||||||
|
# setuptools >= 70.1.0
|
||||||
|
from setuptools.command.bdist_wheel import bdist_wheel
|
||||||
|
except ImportError:
|
||||||
|
from wheel.bdist_wheel import bdist_wheel
|
||||||
|
|
||||||
|
def removeprefix(string, prefix):
|
||||||
|
if string.startswith(prefix):
|
||||||
|
return string[len(prefix):]
|
||||||
|
else:
|
||||||
|
return string
|
||||||
|
|
||||||
|
class BdistWheel(bdist_wheel):
|
||||||
|
def get_tag(self):
|
||||||
|
_, _, plat = super().get_tag()
|
||||||
|
return "py3", "none", plat
|
||||||
|
|
||||||
|
class Clean(Command):
|
||||||
|
user_options = []
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
def run(self):
|
||||||
|
# super().run()
|
||||||
|
for d in ["build", "dist", "src/pylyzer.egg-info"]:
|
||||||
|
shutil.rmtree(d, ignore_errors=True)
|
||||||
|
|
||||||
|
with open("README.md", encoding="utf-8", errors="ignore") as fp:
|
||||||
|
long_description = fp.read()
|
||||||
|
|
||||||
|
with open("Cargo.toml", "rb") as fp:
|
||||||
|
toml = tomli.load(fp)
|
||||||
|
name = toml["package"]["name"]
|
||||||
|
description = toml["package"]["description"]
|
||||||
|
version = toml["workspace"]["package"]["version"]
|
||||||
|
license = toml["workspace"]["package"]["license"]
|
||||||
|
url = toml["workspace"]["package"]["repository"]
|
||||||
|
|
||||||
|
cargo_args = ["--no-default-features"]
|
||||||
|
|
||||||
|
home = os.path.expanduser("~")
|
||||||
|
file_and_dirs = glob(home + "/" + ".erg/lib/**", recursive=True)
|
||||||
|
paths = [Path(path) for path in file_and_dirs if os.path.isfile(path)]
|
||||||
|
files = [(removeprefix(str(path.parent), home), str(path)) for path in paths]
|
||||||
|
data_files = {}
|
||||||
|
for key, value in files:
|
||||||
|
if key in data_files:
|
||||||
|
data_files[key].append(value)
|
||||||
|
else:
|
||||||
|
data_files[key] = [value]
|
||||||
|
data_files = list(data_files.items())
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name=name,
|
||||||
|
author="mtshiba",
|
||||||
|
author_email="sbym1346@gmail.com",
|
||||||
|
url=url,
|
||||||
|
description=description,
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
version=version,
|
||||||
|
license=license,
|
||||||
|
python_requires=">=3.7",
|
||||||
|
rust_extensions=[
|
||||||
|
RustBin("pylyzer", args=cargo_args, cargo_manifest_args=["--locked"])
|
||||||
|
],
|
||||||
|
cmdclass={
|
||||||
|
"clean": Clean,
|
||||||
|
"bdist_wheel": BdistWheel,
|
||||||
|
},
|
||||||
|
classifiers=[
|
||||||
|
"Development Status :: 2 - Pre-Alpha",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Rust",
|
||||||
|
"Topic :: Software Development :: Quality Assurance",
|
||||||
|
],
|
||||||
|
data_files=data_files,
|
||||||
|
)
|
||||||
147
src/config.rs
147
src/config.rs
|
|
@ -1,10 +1,36 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use erg_common::config::{ErgConfig, ErgMode};
|
use erg_common::config::{ErgConfig, ErgMode};
|
||||||
use erg_common::io::Input;
|
use erg_common::io::Input;
|
||||||
|
use erg_common::pathutil::project_entry_file_of;
|
||||||
use erg_common::switch_lang;
|
use erg_common::switch_lang;
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
|
||||||
|
use crate::copy::clear_cache;
|
||||||
|
|
||||||
|
fn entry_file() -> Option<PathBuf> {
|
||||||
|
project_entry_file_of(&env::current_dir().ok()?).or_else(|| {
|
||||||
|
let mut opt_path = None;
|
||||||
|
for ent in Path::new(".").read_dir().ok()? {
|
||||||
|
let ent = ent.ok()?;
|
||||||
|
if ent.file_type().ok()?.is_file() {
|
||||||
|
let path = ent.path();
|
||||||
|
if path.file_name().is_some_and(|name| name == "__init__.py") {
|
||||||
|
return Some(path);
|
||||||
|
} else if path.extension().is_some_and(|ext| ext == "py") {
|
||||||
|
if opt_path.is_some() {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
opt_path = Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opt_path
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn command_message() -> &'static str {
|
fn command_message() -> &'static str {
|
||||||
switch_lang!(
|
switch_lang!(
|
||||||
|
|
@ -21,7 +47,10 @@ OPTIONS
|
||||||
--version/-V バージョンを表示
|
--version/-V バージョンを表示
|
||||||
--verbose 0|1|2 冗長性レベルを指定
|
--verbose 0|1|2 冗長性レベルを指定
|
||||||
--server Language Serverを起動
|
--server Language Serverを起動
|
||||||
--code/-c cmd 文字列をプログラムに渡す",
|
--clear-cache キャッシュをクリア
|
||||||
|
--code/-c cmd 文字列をプログラムに渡す
|
||||||
|
--dump-decl 型宣言ファイルを出力
|
||||||
|
--disable feat 指定した機能を無効化",
|
||||||
|
|
||||||
"simplified_chinese" =>
|
"simplified_chinese" =>
|
||||||
"\
|
"\
|
||||||
|
|
@ -36,7 +65,10 @@ OPTIONS
|
||||||
--version/-V 显示版本
|
--version/-V 显示版本
|
||||||
--verbose 0|1|2 指定细致程度
|
--verbose 0|1|2 指定细致程度
|
||||||
--server 启动 Language Server
|
--server 启动 Language Server
|
||||||
--code/-c cmd 作为字符串传入程序",
|
--clear-cache 清除缓存
|
||||||
|
--code/-c cmd 作为字符串传入程序
|
||||||
|
--dump-decl 输出类型声明文件
|
||||||
|
--disable feat 禁用指定功能",
|
||||||
|
|
||||||
"traditional_chinese" =>
|
"traditional_chinese" =>
|
||||||
"\
|
"\
|
||||||
|
|
@ -51,7 +83,10 @@ OPTIONS
|
||||||
--version/-V 顯示版本
|
--version/-V 顯示版本
|
||||||
--verbose 0|1|2 指定細緻程度
|
--verbose 0|1|2 指定細緻程度
|
||||||
--server 啟動 Language Server
|
--server 啟動 Language Server
|
||||||
--code/-c cmd 作為字串傳入程式",
|
--clear-cache 清除快取
|
||||||
|
--code/-c cmd 作為字串傳入程式
|
||||||
|
--dump-decl 輸出類型宣告檔案
|
||||||
|
--disable feat 禁用指定功能",
|
||||||
|
|
||||||
"english" =>
|
"english" =>
|
||||||
"\
|
"\
|
||||||
|
|
@ -66,7 +101,10 @@ OPTIONS
|
||||||
--version/-V show version
|
--version/-V show version
|
||||||
--verbose 0|1|2 verbosity level
|
--verbose 0|1|2 verbosity level
|
||||||
--server start the Language Server
|
--server start the Language Server
|
||||||
--code/-c cmd program passed in as string",
|
--clear-cache clear cache
|
||||||
|
--code/-c cmd program passed in as string
|
||||||
|
--dump-decl output type declaration file
|
||||||
|
--disable feat disable specified features",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,12 +112,18 @@ OPTIONS
|
||||||
pub(crate) fn parse_args() -> ErgConfig {
|
pub(crate) fn parse_args() -> ErgConfig {
|
||||||
let mut args = env::args();
|
let mut args = env::args();
|
||||||
args.next(); // "pylyzer"
|
args.next(); // "pylyzer"
|
||||||
let mut cfg = ErgConfig::default();
|
let mut cfg = ErgConfig {
|
||||||
|
effect_check: false,
|
||||||
|
ownership_check: false,
|
||||||
|
respect_pyi: true,
|
||||||
|
..ErgConfig::default()
|
||||||
|
};
|
||||||
|
let mut runtime_args: Vec<&'static str> = Vec::new();
|
||||||
while let Some(arg) = args.next() {
|
while let Some(arg) = args.next() {
|
||||||
match &arg[..] {
|
match &arg[..] {
|
||||||
"--" => {
|
"--" => {
|
||||||
for arg in args {
|
for arg in args {
|
||||||
cfg.runtime_args.push(Box::leak(arg.into_boxed_str()));
|
runtime_args.push(Box::leak(arg.into_boxed_str()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -104,10 +148,34 @@ pub(crate) fn parse_args() -> ErgConfig {
|
||||||
.parse::<u8>()
|
.parse::<u8>()
|
||||||
.expect("the value of `--verbose` is not a number");
|
.expect("the value of `--verbose` is not a number");
|
||||||
}
|
}
|
||||||
|
"--disable" => {
|
||||||
|
let arg = args.next().expect("the value of `--disable` is not passed");
|
||||||
|
runtime_args.push(Box::leak(arg.into_boxed_str()));
|
||||||
|
}
|
||||||
"-V" | "--version" => {
|
"-V" | "--version" => {
|
||||||
println!("pylyzer {}", env!("CARGO_PKG_VERSION"));
|
println!("pylyzer {}", env!("CARGO_PKG_VERSION"));
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
"--clear-cache" => {
|
||||||
|
clear_cache();
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
"--no-infer-fn-type" => {
|
||||||
|
cfg.no_infer_fn_type = true;
|
||||||
|
}
|
||||||
|
"--fast-error-report" => {
|
||||||
|
cfg.fast_error_report = true;
|
||||||
|
}
|
||||||
|
"--hurry" => {
|
||||||
|
cfg.no_infer_fn_type = true;
|
||||||
|
cfg.fast_error_report = true;
|
||||||
|
}
|
||||||
|
"--do-not-show-ext-errors" => {
|
||||||
|
cfg.do_not_show_ext_errors = true;
|
||||||
|
}
|
||||||
|
"--do-not-respect-pyi" => {
|
||||||
|
cfg.respect_pyi = false;
|
||||||
|
}
|
||||||
other if other.starts_with('-') => {
|
other if other.starts_with('-') => {
|
||||||
println!(
|
println!(
|
||||||
"\
|
"\
|
||||||
|
|
@ -127,12 +195,75 @@ For more information try `pylyzer --help`"
|
||||||
);
|
);
|
||||||
if let Some("--") = args.next().as_ref().map(|s| &s[..]) {
|
if let Some("--") = args.next().as_ref().map(|s| &s[..]) {
|
||||||
for arg in args {
|
for arg in args {
|
||||||
cfg.runtime_args.push(Box::leak(arg.into_boxed_str()));
|
runtime_args.push(Box::leak(arg.into_boxed_str()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !cfg.mode.is_language_server() && cfg.input.is_repl() {
|
||||||
|
if let Some(entry) = entry_file() {
|
||||||
|
cfg.input = Input::file(entry);
|
||||||
|
} else {
|
||||||
|
eprintln!("No entry file found in the current project");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.runtime_args = runtime_args.into();
|
||||||
cfg
|
cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn files_to_be_checked() -> IndexSet<Result<PathBuf, String>> {
|
||||||
|
let mut file_or_patterns = vec![];
|
||||||
|
let mut args = env::args().skip(1);
|
||||||
|
while let Some(arg) = &args.next() {
|
||||||
|
match arg.as_str() {
|
||||||
|
"--" => {
|
||||||
|
// Discard runtime args
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
"--code" | "-c" | "--disable" | "--verbose" => {
|
||||||
|
// Skip options
|
||||||
|
let _ = &args.next();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
file_or_pattern if file_or_pattern.starts_with("-") => {
|
||||||
|
// Skip flags
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_or_pattern => file_or_patterns.push(file_or_pattern.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut files = IndexSet::new();
|
||||||
|
for file_or_pattern in file_or_patterns {
|
||||||
|
if PathBuf::from(&file_or_pattern).is_file() {
|
||||||
|
files.insert(Ok(PathBuf::from(&file_or_pattern)));
|
||||||
|
} else {
|
||||||
|
let entries = glob::glob(&file_or_pattern);
|
||||||
|
match entries {
|
||||||
|
Err(_) => {
|
||||||
|
files.insert(Err(file_or_pattern));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Ok(entries) => {
|
||||||
|
let mut entries = entries.into_iter().peekable();
|
||||||
|
if entries.peek().is_none() {
|
||||||
|
files.insert(Err(file_or_pattern));
|
||||||
|
}
|
||||||
|
for entry in entries {
|
||||||
|
match entry {
|
||||||
|
Err(e) => eprintln!("err: {e}"),
|
||||||
|
Ok(path) if path.is_file() => {
|
||||||
|
files.insert(Ok(path));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
use std::fs::{copy, create_dir_all, read_dir, remove_file, DirEntry};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use erg_common::env::{erg_path, python_site_packages};
|
||||||
|
|
||||||
|
fn copy_dir(from: impl AsRef<Path>, to: impl AsRef<Path>) -> std::io::Result<()> {
|
||||||
|
let from = from.as_ref();
|
||||||
|
let to = to.as_ref();
|
||||||
|
if !from.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if !to.exists() {
|
||||||
|
create_dir_all(to)?;
|
||||||
|
}
|
||||||
|
for entry in read_dir(from)? {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type()?.is_dir() {
|
||||||
|
copy_dir(entry.path(), to.join(entry.file_name()))?;
|
||||||
|
} else {
|
||||||
|
copy(entry.path(), to.join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn copy_dot_erg() {
|
||||||
|
if erg_path().exists() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for site_packages in python_site_packages() {
|
||||||
|
if site_packages.join(".erg").exists() {
|
||||||
|
println!("Copying site-package/.erg to {}", erg_path().display());
|
||||||
|
copy_dir(site_packages.join(".erg"), erg_path()).expect("Failed to copy .erg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear_cache() {
|
||||||
|
for dir in read_dir(".").expect("Failed to read dir") {
|
||||||
|
let Ok(dir) = dir else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
rec_clear_cache(dir);
|
||||||
|
}
|
||||||
|
for site_packages in python_site_packages() {
|
||||||
|
for pkg in site_packages
|
||||||
|
.read_dir()
|
||||||
|
.expect("Failed to read site-packages")
|
||||||
|
{
|
||||||
|
let Ok(pkg) = pkg else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
rec_clear_cache(pkg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rec_clear_cache(pkg: DirEntry) {
|
||||||
|
if pkg.file_type().expect("Failed to get file type").is_dir() {
|
||||||
|
let cache = if pkg.path().ends_with("__pycache__") {
|
||||||
|
pkg.path()
|
||||||
|
} else {
|
||||||
|
pkg.path().join("__pycache__")
|
||||||
|
};
|
||||||
|
if cache.exists() {
|
||||||
|
let Ok(dir) = cache.read_dir() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for cache_file in dir {
|
||||||
|
let Ok(cache_file) = cache_file else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if cache_file.file_name().to_string_lossy().ends_with(".d.er") {
|
||||||
|
println!("Removing cache file {}", cache_file.path().display());
|
||||||
|
remove_file(cache_file.path()).expect("Failed to remove cache file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Ok(dir) = pkg.path().read_dir() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for entry in dir {
|
||||||
|
let Ok(entry) = entry else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
rec_clear_cache(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
use erg_common::error::ErrorKind;
|
|
||||||
use erg_common::log;
|
|
||||||
use erg_common::style::remove_style;
|
|
||||||
// use erg_common::style::{remove_style, StyledString, Color};
|
|
||||||
use erg_compiler::context::ModuleContext;
|
|
||||||
use erg_compiler::error::{CompileError, CompileErrors};
|
|
||||||
|
|
||||||
pub(crate) fn filter_errors(ctx: &ModuleContext, errors: CompileErrors) -> CompileErrors {
|
|
||||||
errors
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|error| filter_error(ctx, error))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filter_error(_ctx: &ModuleContext, mut error: CompileError) -> Option<CompileError> {
|
|
||||||
match error.core.kind {
|
|
||||||
ErrorKind::FeatureError => {
|
|
||||||
log!(err "this error is ignored:");
|
|
||||||
log!(err "{error}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
ErrorKind::InheritanceError => None,
|
|
||||||
ErrorKind::VisibilityError => None,
|
|
||||||
// exclude doc strings
|
|
||||||
ErrorKind::UnusedWarning => {
|
|
||||||
let code = error.input.reread_lines(
|
|
||||||
error.core.loc.ln_begin().unwrap_or(1) as usize,
|
|
||||||
error.core.loc.ln_end().unwrap_or(1) as usize,
|
|
||||||
);
|
|
||||||
if code[0].trim().starts_with("\"\"\"") {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
for sub in error.core.sub_messages.iter_mut() {
|
|
||||||
if let Some(hint) = &mut sub.hint {
|
|
||||||
*hint = remove_style(hint);
|
|
||||||
*hint = hint.replace("use discard function", "bind to `_` (`_ = ...`)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ErrorKind::AssignError => handle_assign_error(error),
|
|
||||||
_ => Some(error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
mod analyze;
|
|
||||||
mod config;
|
|
||||||
mod handle_err;
|
|
||||||
|
|
||||||
pub use analyze::PythonAnalyzer;
|
|
||||||
43
src/main.rs
43
src/main.rs
|
|
@ -1,22 +1,49 @@
|
||||||
mod analyze;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod handle_err;
|
mod copy;
|
||||||
|
|
||||||
use analyze::{PythonAnalyzer, SimplePythonParser};
|
|
||||||
use els::Server;
|
use els::Server;
|
||||||
use erg_common::config::ErgMode;
|
use erg_common::config::ErgMode;
|
||||||
use erg_common::spawn::exec_new_thread;
|
use erg_common::spawn::exec_new_thread;
|
||||||
|
use erg_common::style::colors::RED;
|
||||||
|
use erg_common::style::RESET;
|
||||||
|
use pylyzer_core::{PythonAnalyzer, SimplePythonParser};
|
||||||
|
|
||||||
|
use crate::config::files_to_be_checked;
|
||||||
|
use crate::copy::copy_dot_erg;
|
||||||
|
|
||||||
fn run() {
|
fn run() {
|
||||||
|
copy_dot_erg();
|
||||||
let cfg = config::parse_args();
|
let cfg = config::parse_args();
|
||||||
if cfg.mode == ErgMode::LanguageServer {
|
if cfg.mode == ErgMode::LanguageServer {
|
||||||
let mut lang_server = Server::<PythonAnalyzer, SimplePythonParser>::new(cfg);
|
let lang_server = Server::<PythonAnalyzer, SimplePythonParser>::new(cfg, None);
|
||||||
lang_server.run().unwrap_or_else(|_| {
|
lang_server.run();
|
||||||
std::process::exit(1);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
|
let mut code = 0;
|
||||||
|
let files = files_to_be_checked();
|
||||||
|
if files.is_empty() {
|
||||||
let mut analyzer = PythonAnalyzer::new(cfg);
|
let mut analyzer = PythonAnalyzer::new(cfg);
|
||||||
analyzer.run();
|
code = analyzer.run();
|
||||||
|
} else {
|
||||||
|
for path in files {
|
||||||
|
match path {
|
||||||
|
Err(invalid_file_or_pattern) => {
|
||||||
|
if code == 0 {
|
||||||
|
code = 1;
|
||||||
|
}
|
||||||
|
println!("{RED}Invalid file or pattern{RESET}: {invalid_file_or_pattern}");
|
||||||
|
}
|
||||||
|
Ok(path) => {
|
||||||
|
let cfg = cfg.inherit(path);
|
||||||
|
let mut analyzer = PythonAnalyzer::new(cfg);
|
||||||
|
let c = analyzer.run();
|
||||||
|
if c != 0 {
|
||||||
|
code = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::process::exit(code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
class Vec(Sequence):
|
||||||
|
x: list[int]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.x = []
|
||||||
|
|
||||||
|
def __getitem__(self, i: int) -> int:
|
||||||
|
return self.x[i]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.x)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.x)
|
||||||
|
|
||||||
|
def __contains__(self, i: int) -> bool:
|
||||||
|
return i in self.x
|
||||||
|
|
@ -8,3 +8,17 @@ def f(x, y=1):
|
||||||
print(f(1, 2)) # OK
|
print(f(1, 2)) # OK
|
||||||
print(f(1)) # OK
|
print(f(1)) # OK
|
||||||
print(f(1, y="a")) # ERR
|
print(f(1, y="a")) # ERR
|
||||||
|
|
||||||
|
def g(first, second):
|
||||||
|
pass
|
||||||
|
|
||||||
|
g(**{"first": "bar", "second": 1}) # OK
|
||||||
|
g(**[1, 2]) # ERR
|
||||||
|
g(1, *[2]) # OK
|
||||||
|
g(*[1, 2]) # OK
|
||||||
|
g(1, 2, *[3, 4]) # ERR
|
||||||
|
g(*1) # ERR
|
||||||
|
g(*[1], **{"second": 1}) # OK
|
||||||
|
|
||||||
|
_ = f(1, *[2]) # OK
|
||||||
|
_ = f(**{"x": 1, "y": 2}) # OK
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,12 @@ assert isinstance(s, int) # ERR
|
||||||
# force cast to int
|
# force cast to int
|
||||||
i = typing.cast(int, s)
|
i = typing.cast(int, s)
|
||||||
print(i + 1) # OK
|
print(i + 1) # OK
|
||||||
|
|
||||||
|
l = typing.cast(list[str], [1, 2, 3])
|
||||||
|
_ = map(lambda x: x + "a", l) # OK
|
||||||
|
|
||||||
|
d = typing.cast(dict[str, int], [1, 2, 3])
|
||||||
|
_ = map(lambda x: d["a"] + 1, d) # OK
|
||||||
|
|
||||||
|
t = typing.cast(tuple[str, str], [1, 2, 3])
|
||||||
|
_ = map(lambda x: x + "a", t) # OK
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Self
|
from typing import Self, List
|
||||||
|
|
||||||
class Empty: pass
|
class Empty: pass
|
||||||
emp = Empty()
|
emp = Empty()
|
||||||
|
|
@ -19,6 +19,8 @@ class C:
|
||||||
return self.x
|
return self.x
|
||||||
def id(self) -> Self:
|
def id(self) -> Self:
|
||||||
return self
|
return self
|
||||||
|
def id2(self) -> "C":
|
||||||
|
return self
|
||||||
|
|
||||||
c = C(1, 2)
|
c = C(1, 2)
|
||||||
assert c.x == 1
|
assert c.x == 1
|
||||||
|
|
@ -49,6 +51,8 @@ class D:
|
||||||
class E(D):
|
class E(D):
|
||||||
def __add__(self, other: E):
|
def __add__(self, other: E):
|
||||||
return E(self.c + other.c)
|
return E(self.c + other.c)
|
||||||
|
def invalid(self):
|
||||||
|
return self.d # ERR: E object has no attribute `d`
|
||||||
|
|
||||||
c1 = D(1).c + 1
|
c1 = D(1).c + 1
|
||||||
d = D(1) + D(2)
|
d = D(1) + D(2)
|
||||||
|
|
@ -57,3 +61,79 @@ ok = D(1) - C(1, 2) # OK
|
||||||
assert D(1) > D(0)
|
assert D(1) > D(0)
|
||||||
c = -d # OK
|
c = -d # OK
|
||||||
e = E(1)
|
e = E(1)
|
||||||
|
|
||||||
|
class F:
|
||||||
|
def __init__(self, x: int, y: int = 1, z: int = 2):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.z = z
|
||||||
|
|
||||||
|
_ = F(1)
|
||||||
|
_ = F(1, 2)
|
||||||
|
_ = F(1, z=1, y=2)
|
||||||
|
|
||||||
|
class G(DoesNotExist): # ERR
|
||||||
|
def foo(self):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
g = G()
|
||||||
|
assert g.foo() == 1
|
||||||
|
|
||||||
|
class Value:
|
||||||
|
value: object
|
||||||
|
|
||||||
|
class H(Value):
|
||||||
|
value: int
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def incremented(self):
|
||||||
|
return H(self.value + 1)
|
||||||
|
|
||||||
|
class MyList(list):
|
||||||
|
@staticmethod
|
||||||
|
def try_new(lis) -> "MyList" | None:
|
||||||
|
if isinstance(lis, list):
|
||||||
|
return MyList(lis)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Implicit:
|
||||||
|
def __init__(self):
|
||||||
|
self.foo = False
|
||||||
|
|
||||||
|
def set_foo(self):
|
||||||
|
self.foo = True
|
||||||
|
|
||||||
|
class Cs:
|
||||||
|
cs: list[C]
|
||||||
|
cs2: List[C]
|
||||||
|
cs_list: list[list[C]]
|
||||||
|
|
||||||
|
def __init__(self, cs: list[C]):
|
||||||
|
self.cs = cs
|
||||||
|
self.cs2 = cs
|
||||||
|
self.cs_list = []
|
||||||
|
|
||||||
|
def add(self, c: C):
|
||||||
|
self.cs.append(c)
|
||||||
|
self.cs2.append(c)
|
||||||
|
self.cs_list.append([c])
|
||||||
|
|
||||||
|
class I:
|
||||||
|
def __init__(self):
|
||||||
|
self.ix: int = 1
|
||||||
|
if True:
|
||||||
|
self.init_y()
|
||||||
|
|
||||||
|
def init_y(self):
|
||||||
|
self.iy: int = 2
|
||||||
|
|
||||||
|
def foo(self):
|
||||||
|
self.iz: int = 1 # ERR
|
||||||
|
|
||||||
|
i = I()
|
||||||
|
_ = i.ix
|
||||||
|
_ = i.iy # OK
|
||||||
|
_ = i.iz # ERR
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
i_lis = [0]
|
||||||
|
|
||||||
|
i_lis.append(1)
|
||||||
|
i_lis.append("a") # ERR
|
||||||
|
_ = i_lis[0:0]
|
||||||
|
|
||||||
|
union_arr: list[int | str] = []
|
||||||
|
union_arr.append(1)
|
||||||
|
union_arr.append("a") # OK
|
||||||
|
union_arr.append(None) # ERR
|
||||||
|
|
||||||
|
dic: dict[Literal["a", "b"], int] = {"a": 1}
|
||||||
|
dic["b"] = 2
|
||||||
|
_ = dic["a"]
|
||||||
|
_ = dic["b"]
|
||||||
|
_ = dic["c"] # ERR
|
||||||
|
|
||||||
|
dic2: dict[str, int] = {"a": 1}
|
||||||
|
_ = dic2["c"] # OK
|
||||||
|
|
||||||
|
t: tuple[int, str] = (1, "a")
|
||||||
|
_ = t[0] == 1 # OK
|
||||||
|
_ = t[1] == 1 # ERR
|
||||||
|
_ = t[0:1]
|
||||||
|
|
||||||
|
s: set[int] = {1, 2}
|
||||||
|
s.add(1)
|
||||||
|
s.add("a") # ERR
|
||||||
|
|
||||||
|
def f(s: Str): return None
|
||||||
|
for i in getattr(1, "aaa", ()):
|
||||||
|
f(i)
|
||||||
|
|
||||||
|
assert 1 in [1, 2]
|
||||||
|
assert 1 in {1, 2}
|
||||||
|
assert 1 in {1: "a"}
|
||||||
|
assert 1 in (1, 2)
|
||||||
|
assert 1 in map(lambda x: x + 1, [0, 1, 2])
|
||||||
|
|
||||||
|
def func(d: dict, t: tuple, s: set):
|
||||||
|
_ = d.get("a")
|
||||||
|
s.add(1)
|
||||||
|
for i in t:
|
||||||
|
print(i)
|
||||||
|
|
||||||
|
list_comp = [i + 1 for i in range(10)]
|
||||||
|
assert list_comp[0] == 1
|
||||||
|
set_comp = {i + 1 for i in range(10)}
|
||||||
|
assert 1 in set_comp
|
||||||
|
dict_comp = {i: i + 1 for i in range(10)}
|
||||||
|
assert dict_comp[0] == 1
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
i_arr = [0]
|
|
||||||
|
|
||||||
i_arr.append(1)
|
|
||||||
i_arr.append("a") # ERR
|
|
||||||
|
|
||||||
union_arr: list[int | str] = []
|
|
||||||
union_arr.append(1)
|
|
||||||
union_arr.append("a") # OK
|
|
||||||
union_arr.append(None) # ERR
|
|
||||||
|
|
||||||
dic = {"a": 1}
|
|
||||||
dic["b"] = 2
|
|
||||||
_ = dic["a"]
|
|
||||||
_ = dic["b"]
|
|
||||||
_ = dic["c"] # ERR
|
|
||||||
|
|
||||||
dic2: dict[str, int] = {"a": 1}
|
|
||||||
_ = dic2["c"] # OK
|
|
||||||
|
|
||||||
t: tuple[int, str] = (1, "a")
|
|
||||||
_ = t[0] == 1 # OK
|
|
||||||
_ = t[1] == 1 # ERR
|
|
||||||
|
|
||||||
def f(s: Str): return None
|
|
||||||
for i in getattr(1, "aaa", ()):
|
|
||||||
f(i)
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
i: int
|
||||||
|
if True:
|
||||||
|
i = 1
|
||||||
|
else:
|
||||||
|
i = 2
|
||||||
|
|
||||||
|
j: int
|
||||||
|
if True:
|
||||||
|
j = "1" # ERR
|
||||||
|
else:
|
||||||
|
j = "2"
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
dic = {"a": 1, "b": 2}
|
||||||
|
|
||||||
|
def f():
|
||||||
|
dic = {"a": 1}
|
||||||
|
_ = dic["b"] # ERR
|
||||||
|
|
||||||
|
|
||||||
|
class TaskManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.tasks: list[dict[str, int]] = []
|
||||||
|
|
||||||
|
def add_task(self, title: str, id: int):
|
||||||
|
task = {title: id}
|
||||||
|
self.tasks.append(task)
|
||||||
|
|
||||||
|
def add_task2(self, title: str, id: int):
|
||||||
|
task = {id: title}
|
||||||
|
self.tasks.append(task) # ERR
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
class Foo:
|
||||||
|
def invalid_append(self):
|
||||||
|
paths: list[str] = []
|
||||||
|
paths.append(self) # ERR
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
foos: list[Foo]
|
||||||
|
|
||||||
|
def __init__(self, foos: list[Foo]) -> None:
|
||||||
|
self.foos = foos
|
||||||
|
|
||||||
|
def add_foo(self, foo: Foo):
|
||||||
|
self.foos.append(foo)
|
||||||
|
|
||||||
|
def invalid_add_foo(self):
|
||||||
|
self.foos.append(1) # ERR
|
||||||
|
|
||||||
|
_ = Bar([Bar([])]) # ERR
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
class Foo:
|
||||||
|
x: int
|
||||||
|
def __init__(self, x):
|
||||||
|
self.x = x
|
||||||
|
|
||||||
|
@property
|
||||||
|
def foo(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
f = Foo(1)
|
||||||
|
print(f.foo + "a") # ERR
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
from typing import Callable, Mapping
|
||||||
|
|
||||||
|
_: Mapping[int, str, str] = ... # ERR
|
||||||
|
_: Mapping[int] = ... # ERR
|
||||||
|
_: Callable[[int, str]] = ... # ERR
|
||||||
|
_: Callable[int] = ... # ERR
|
||||||
|
_: dict[int] = ... # ERR
|
||||||
|
_: dict[int, int, int] = ... # ERR
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from .bar import i
|
from .bar import i, Bar, Baz, Qux
|
||||||
|
|
||||||
from . import bar
|
from . import bar
|
||||||
from . import baz
|
from . import baz
|
||||||
|
|
|
||||||
|
|
@ -1 +1,12 @@
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
CONST = "foo.bar"
|
||||||
|
def f(self): return 1
|
||||||
|
|
||||||
|
class Baz(Exception):
|
||||||
|
CONST = "foo.baz"
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Qux(Baz):
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
|
class Bar:
|
||||||
|
CONST = "foo.baz.bar"
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,8 @@ def h(x: str):
|
||||||
if True:
|
if True:
|
||||||
x = "a" # OK
|
x = "a" # OK
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
def var(*varargs, **kwargs):
|
||||||
|
return varargs, kwargs
|
||||||
|
|
||||||
|
_ = var(1, 2, 3, a=1, b=2, c=3)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import export
|
import export
|
||||||
import foo
|
import foo
|
||||||
from foo import bar
|
from . import foo
|
||||||
|
from foo import bar, Bar
|
||||||
|
from foo.bar import Baz
|
||||||
from foo import baz
|
from foo import baz
|
||||||
import random
|
import random
|
||||||
from random import randint as rdi
|
from random import randint as rdi
|
||||||
|
|
@ -8,6 +10,7 @@ from datetime import datetime, timedelta
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
from http.client import HTTPResponse
|
from http.client import HTTPResponse
|
||||||
import http
|
import http
|
||||||
|
from math import *
|
||||||
|
|
||||||
i = random.randint(0, 1)
|
i = random.randint(0, 1)
|
||||||
print(i + 1)
|
print(i + 1)
|
||||||
|
|
@ -27,6 +30,13 @@ assert d.x == 1
|
||||||
assert d.y == 2
|
assert d.y == 2
|
||||||
|
|
||||||
assert foo.i == 0
|
assert foo.i == 0
|
||||||
|
assert Bar().f() == 1
|
||||||
|
assert Bar.CONST == "foo.bar"
|
||||||
|
assert Baz.CONST == "foo.baz"
|
||||||
|
|
||||||
|
from foo.baz import Bar
|
||||||
|
|
||||||
|
assert Bar.CONST == "foo.baz.bar"
|
||||||
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
print(glob("*"))
|
print(glob("*"))
|
||||||
|
|
@ -39,3 +49,7 @@ assert dt.datetime.max == max_date
|
||||||
|
|
||||||
Resp = http.client.HTTPResponse
|
Resp = http.client.HTTPResponse
|
||||||
assert export.http.client.HTTPResponse == Resp
|
assert export.http.client.HTTPResponse == Resp
|
||||||
|
|
||||||
|
_ = bar.Baz
|
||||||
|
|
||||||
|
_ = sin(acos(exp(0))) # OK
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
l = [1, 2, 3]
|
||||||
|
_ = l[1:2]
|
||||||
|
_ = l[:]
|
||||||
|
_ = l[1:]
|
||||||
|
_ = l[:1]
|
||||||
|
_ = l[1:1:1]
|
||||||
|
print(l[2])
|
||||||
|
print(l["a"]) # ERR
|
||||||
|
|
||||||
|
# OK
|
||||||
|
for i in range(3):
|
||||||
|
print(l[i])
|
||||||
|
# ERR
|
||||||
|
for i in "abcd":
|
||||||
|
print(l[i])
|
||||||
|
|
||||||
|
lis = "a,b,c".split(",") if True is not None else []
|
||||||
|
if "a" in lis:
|
||||||
|
lis.remove("a") # OK
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
name = "John"
|
||||||
|
|
||||||
|
print(f"Hello, {name}!")
|
||||||
|
print(f"Hello, {nome}!") # ERR
|
||||||
|
print(f"Hello, {name + 1}!") # ERR
|
||||||
|
|
@ -7,3 +7,14 @@ def f(x: int | None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
f(1)
|
f(1)
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
x: Optional[int] = None
|
||||||
|
if x is not None:
|
||||||
|
x += 1
|
||||||
|
|
||||||
|
def sb(s: str | bytes) -> None:
|
||||||
|
if not isinstance(s, str):
|
||||||
|
str(s, "ascii")
|
||||||
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,51 @@
|
||||||
def imaginary(x):
|
def imaginary(x):
|
||||||
x.imag
|
return x.imag
|
||||||
|
|
||||||
|
def imaginary2(x):
|
||||||
|
return imaginary(x)
|
||||||
|
|
||||||
assert imaginary(1) == 0
|
assert imaginary(1) == 0
|
||||||
assert imaginary(1.0) <= 0.0
|
assert imaginary(1.0) <= 0.0
|
||||||
|
assert imaginary2(1) == 0
|
||||||
|
assert imaginary2(1.0) <= 0.0
|
||||||
print(imaginary("a")) # ERR
|
print(imaginary("a")) # ERR
|
||||||
|
|
||||||
class C:
|
class C:
|
||||||
def method(self, x): return x
|
def method(self, x): return x
|
||||||
def call_method(obj, x):
|
def call_method(obj, x):
|
||||||
obj.method(x)
|
return obj.method(x)
|
||||||
|
def call_method2(obj, x):
|
||||||
|
return call_method(obj, x)
|
||||||
|
|
||||||
|
def call_foo(x):
|
||||||
|
return x.foo("foo") # OK
|
||||||
|
|
||||||
c = C()
|
c = C()
|
||||||
assert call_method(c, 1) == 1
|
assert call_method(c, 1) == 1
|
||||||
assert call_method(c, 1) == "a" # ERR
|
assert call_method(c, 1) == "a" # ERR
|
||||||
|
assert call_method2(c, 1) == 1
|
||||||
print(call_method(1, 1)) # ERR
|
print(call_method(1, 1)) # ERR
|
||||||
print(call_method(c)) # ERR
|
print(call_method(c)) # ERR
|
||||||
|
|
||||||
|
def x_and_y(a):
|
||||||
|
z: int = a.y
|
||||||
|
return a.x + z
|
||||||
|
|
||||||
|
class A:
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
class B:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
def __init__(self, x):
|
||||||
|
self.x = x
|
||||||
|
|
||||||
|
a = A(1, 2)
|
||||||
|
assert x_and_y(a) == 3
|
||||||
|
b = B(3)
|
||||||
|
_ = x_and_y(b) # ERR: B object has no attribute `y`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
class Foo:
|
||||||
|
x: int
|
||||||
|
def __init__(self, x):
|
||||||
|
self.x = x
|
||||||
|
|
||||||
|
@property
|
||||||
|
def foo(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
f = Foo(1)
|
||||||
|
assert f.foo + 1 == 2
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
x = 1
|
||||||
|
x + "a" # OK, because x: Any
|
||||||
|
|
||||||
|
def f(x, y):
|
||||||
|
return x + y
|
||||||
|
|
||||||
|
class C:
|
||||||
|
y = 1
|
||||||
|
def __init__(self, x):
|
||||||
|
self.x = x
|
||||||
|
def f(self, x):
|
||||||
|
return self.x + x
|
||||||
|
|
||||||
|
print(f(1, 2)) # OK
|
||||||
|
print(f("a", "b")) # ERR*2
|
||||||
|
c = C(1)
|
||||||
|
print(c.f(2)) # OK
|
||||||
|
print(c.f("a")) # ERR
|
||||||
|
_ = C("a") # ERR
|
||||||
|
|
||||||
|
def g(x):
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(g(c)) # OK
|
||||||
|
print(g(1)) # ERR
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import typing
|
||||||
|
|
||||||
|
x: typing.Any
|
||||||
|
|
||||||
|
def f(x: int, y: int) -> int: ...
|
||||||
|
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
def __init__(self, x: int): ...
|
||||||
|
def f(self, x: int) -> int: ...
|
||||||
|
|
||||||
|
def g[T: C](x: T) -> T: ...
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
i: int = 0
|
i: int = 0
|
||||||
i: str = "a" # OK
|
i: str = "a" # OK
|
||||||
|
|
||||||
|
|
@ -12,3 +14,49 @@ while False:
|
||||||
def f(x: int):
|
def f(x: int):
|
||||||
i = 1 # OK
|
i = 1 # OK
|
||||||
return x + i
|
return x + i
|
||||||
|
|
||||||
|
if True:
|
||||||
|
pass
|
||||||
|
elif True:
|
||||||
|
for i in []: pass
|
||||||
|
pass
|
||||||
|
elif True:
|
||||||
|
for i in []: pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
if True:
|
||||||
|
pass
|
||||||
|
elif True:
|
||||||
|
with open("") as x:
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
elif True:
|
||||||
|
with open("") as x:
|
||||||
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
if True:
|
||||||
|
left, right = 1, 2
|
||||||
|
if True:
|
||||||
|
left, _ = 1, 2
|
||||||
|
|
||||||
|
def func(label: str) -> str:
|
||||||
|
if True:
|
||||||
|
try:
|
||||||
|
label_bytes = "aaa"
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
return label
|
||||||
|
else:
|
||||||
|
label_bytes = label
|
||||||
|
|
||||||
|
if True:
|
||||||
|
label_bytes = label_bytes[1:]
|
||||||
|
return label_bytes
|
||||||
|
|
||||||
|
if True:
|
||||||
|
y = 1
|
||||||
|
else:
|
||||||
|
y = "a"
|
||||||
|
y: int | str
|
||||||
|
y: Literal[1, "a"] # OK
|
||||||
|
y: Literal[1, "b"] # ERR
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,8 @@ def add2(x: int, y: int) -> str: # ERR
|
||||||
|
|
||||||
print(add2(1, 2))
|
print(add2(1, 2))
|
||||||
|
|
||||||
# ERR
|
|
||||||
for i in [1, 2, 3]:
|
for i in [1, 2, 3]:
|
||||||
j = i + "aa"
|
j = i + "aa" # ERR
|
||||||
print(j)
|
print(j)
|
||||||
|
|
||||||
a: int # OK
|
a: int # OK
|
||||||
|
|
@ -31,26 +30,12 @@ class C:
|
||||||
dic = {"a": 1, "b": 2}
|
dic = {"a": 1, "b": 2}
|
||||||
print(dic["c"]) # ERR
|
print(dic["c"]) # ERR
|
||||||
|
|
||||||
arr = [1, 2, 3]
|
|
||||||
print(arr[4]) # ERR
|
|
||||||
|
|
||||||
# OK
|
|
||||||
for i in range(3):
|
|
||||||
print(arr[i])
|
|
||||||
# ERR
|
|
||||||
for i in range(4):
|
|
||||||
print(arr[i])
|
|
||||||
|
|
||||||
def f(d1, d2: dict[str, int]):
|
def f(d1, d2: dict[str, int]):
|
||||||
_ = d1["b"] # OK
|
_ = d1["b"] # OK
|
||||||
_ = d2["a"] # OK
|
_ = d2["a"] # OK
|
||||||
_ = d2[1] # ERR
|
_ = d2[1] # ERR
|
||||||
dic = {"a": 1}
|
dic = {"a": 1}
|
||||||
_ = dic["b"] # ERR
|
_ = dic["b"] # ERR
|
||||||
arr = [1, 2, 3]
|
|
||||||
_ = arr[4] # ERR
|
|
||||||
for i in range(4):
|
|
||||||
print(arr[i]) # ERR
|
|
||||||
|
|
||||||
i, j = 1, 2
|
i, j = 1, 2
|
||||||
assert i == 1
|
assert i == 1
|
||||||
|
|
@ -59,3 +44,6 @@ assert j == 2
|
||||||
with open("test.py") as f:
|
with open("test.py") as f:
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
print("line: " + line)
|
print("line: " + line)
|
||||||
|
|
||||||
|
print(x := 1)
|
||||||
|
print(x)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use erg_common::config::ErgConfig;
|
use erg_common::config::ErgConfig;
|
||||||
use erg_common::io::Input;
|
use erg_common::io::Input;
|
||||||
use erg_common::spawn::exec_new_thread;
|
use erg_common::spawn::exec_new_thread;
|
||||||
use erg_common::traits::Stream;
|
|
||||||
use erg_compiler::artifact::{CompleteArtifact, IncompleteArtifact};
|
use erg_compiler::artifact::{CompleteArtifact, IncompleteArtifact};
|
||||||
use pylyzer::PythonAnalyzer;
|
use pylyzer_core::PythonAnalyzer;
|
||||||
|
|
||||||
|
#[allow(clippy::result_large_err)]
|
||||||
pub fn exec_analyzer(file_path: &'static str) -> Result<CompleteArtifact, IncompleteArtifact> {
|
pub fn exec_analyzer(file_path: &'static str) -> Result<CompleteArtifact, IncompleteArtifact> {
|
||||||
let cfg = ErgConfig {
|
let cfg = ErgConfig {
|
||||||
input: Input::file(PathBuf::from(file_path)),
|
input: Input::file(PathBuf::from(file_path)),
|
||||||
|
effect_check: false,
|
||||||
|
ownership_check: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut analyzer = PythonAnalyzer::new(cfg);
|
let mut analyzer = PythonAnalyzer::new(cfg);
|
||||||
|
|
@ -22,6 +24,7 @@ fn _expect(file_path: &'static str, warns: usize, errors: usize) -> Result<(), S
|
||||||
match exec_analyzer(file_path) {
|
match exec_analyzer(file_path) {
|
||||||
Ok(artifact) => {
|
Ok(artifact) => {
|
||||||
if artifact.warns.len() != warns {
|
if artifact.warns.len() != warns {
|
||||||
|
eprintln!("warns: {}", artifact.warns);
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Expected {warns} warnings, found {}",
|
"Expected {warns} warnings, found {}",
|
||||||
artifact.warns.len()
|
artifact.warns.len()
|
||||||
|
|
@ -34,12 +37,14 @@ fn _expect(file_path: &'static str, warns: usize, errors: usize) -> Result<(), S
|
||||||
}
|
}
|
||||||
Err(artifact) => {
|
Err(artifact) => {
|
||||||
if artifact.warns.len() != warns {
|
if artifact.warns.len() != warns {
|
||||||
|
eprintln!("warns: {}", artifact.warns);
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Expected {warns} warnings, found {}",
|
"Expected {warns} warnings, found {}",
|
||||||
artifact.warns.len()
|
artifact.warns.len()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if artifact.errors.len() != errors {
|
if artifact.errors.len() != errors {
|
||||||
|
eprintln!("errors: {}", artifact.errors);
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Expected {errors} errors, found {}",
|
"Expected {errors} errors, found {}",
|
||||||
artifact.errors.len()
|
artifact.errors.len()
|
||||||
|
|
@ -54,16 +59,35 @@ pub fn expect(file_path: &'static str, warns: usize, errors: usize) -> Result<()
|
||||||
exec_new_thread(move || _expect(file_path, warns, errors), file_path)
|
exec_new_thread(move || _expect(file_path, warns, errors), file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_abc() -> Result<(), String> {
|
||||||
|
expect("tests/abc.py", 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_test() -> Result<(), String> {
|
fn exec_test() -> Result<(), String> {
|
||||||
expect("tests/test.py", 0, 15)
|
expect("tests/test.py", 0, 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_import() -> Result<(), String> {
|
fn exec_import() -> Result<(), String> {
|
||||||
|
if Path::new("tests/__pycache__").exists() {
|
||||||
|
std::fs::remove_dir_all("tests/__pycache__").unwrap();
|
||||||
|
}
|
||||||
|
if Path::new("tests/foo/__pycache__").exists() {
|
||||||
|
std::fs::remove_dir_all("tests/foo/__pycache__").unwrap();
|
||||||
|
}
|
||||||
|
if Path::new("tests/bar/__pycache__").exists() {
|
||||||
|
std::fs::remove_dir_all("tests/bar/__pycache__").unwrap();
|
||||||
|
}
|
||||||
expect("tests/import.py", 1, 2)
|
expect("tests/import.py", 1, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_dict() -> Result<(), String> {
|
||||||
|
expect("tests/dict.py", 0, 2)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_export() -> Result<(), String> {
|
fn exec_export() -> Result<(), String> {
|
||||||
expect("tests/export.py", 0, 0)
|
expect("tests/export.py", 0, 0)
|
||||||
|
|
@ -76,7 +100,12 @@ fn exec_func() -> Result<(), String> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_class() -> Result<(), String> {
|
fn exec_class() -> Result<(), String> {
|
||||||
expect("tests/class.py", 0, 4)
|
expect("tests/class.py", 0, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_class_err() -> Result<(), String> {
|
||||||
|
expect("tests/err/class.py", 0, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -91,12 +120,37 @@ fn exec_warns() -> Result<(), String> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_typespec() -> Result<(), String> {
|
fn exec_typespec() -> Result<(), String> {
|
||||||
expect("tests/typespec.py", 0, 7)
|
expect("tests/typespec.py", 0, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_projection() -> Result<(), String> {
|
fn exec_projection() -> Result<(), String> {
|
||||||
expect("tests/projection.py", 0, 4)
|
expect("tests/projection.py", 0, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_property() -> Result<(), String> {
|
||||||
|
expect("tests/property.py", 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_property_err() -> Result<(), String> {
|
||||||
|
expect("tests/err/property.py", 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_pyi() -> Result<(), String> {
|
||||||
|
expect("tests/pyi.py", 0, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_list() -> Result<(), String> {
|
||||||
|
expect("tests/list.py", 0, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_literal() -> Result<(), String> {
|
||||||
|
expect("tests/literal.py", 0, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -106,22 +160,42 @@ fn exec_narrowing() -> Result<(), String> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_casting() -> Result<(), String> {
|
fn exec_casting() -> Result<(), String> {
|
||||||
expect("tests/casting.py", 1, 1)
|
expect("tests/casting.py", 4, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_collections() -> Result<(), String> {
|
fn exec_collection() -> Result<(), String> {
|
||||||
expect("tests/collections.py", 0, 4)
|
expect("tests/collection.py", 0, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_call() -> Result<(), String> {
|
fn exec_call() -> Result<(), String> {
|
||||||
expect("tests/call.py", 0, 3)
|
expect("tests/call.py", 0, 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_decl() -> Result<(), String> {
|
||||||
|
expect("tests/decl.py", 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn exec_shadowing() -> Result<(), String> {
|
fn exec_shadowing() -> Result<(), String> {
|
||||||
expect("tests/shadowing.py", 0, 3)
|
expect("tests/shadowing.py", 0, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_typevar() -> Result<(), String> {
|
||||||
|
expect("tests/typevar.py", 0, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_type_spec() -> Result<(), String> {
|
||||||
|
expect("tests/err/type_spec.py", 0, 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exec_union() -> Result<(), String> {
|
||||||
|
expect("tests/union.py", 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import Union, Optional, Literal, Callable
|
from typing import Union, Optional, Literal, Callable
|
||||||
from collections.abc import Iterable, Mapping
|
from collections.abc import Iterable, Mapping
|
||||||
|
import collections
|
||||||
|
|
||||||
i: Union[int, str] = 1 # OK
|
i: Union[int, str] = 1 # OK
|
||||||
j: Union[int, str] = "aa" # OK
|
j: Union[int, str] = "aa" # OK
|
||||||
|
|
@ -9,6 +10,23 @@ o: Optional[int] = None # OK
|
||||||
p: Optional[int] = "a" # ERR
|
p: Optional[int] = "a" # ERR
|
||||||
weekdays: Literal[1, 2, 3, 4, 5, 6, 7] = 1 # OK
|
weekdays: Literal[1, 2, 3, 4, 5, 6, 7] = 1 # OK
|
||||||
weekdays: Literal[1, 2, 3, 4, 5, 6, 7] = 8 # ERR
|
weekdays: Literal[1, 2, 3, 4, 5, 6, 7] = 8 # ERR
|
||||||
|
_: tuple[int, ...] = (1, 2, 3)
|
||||||
|
_: tuple[int, str] = (1, "a", 1) # OK, tuple[T, U, V] <: tuple[T, U]
|
||||||
|
_: list[tuple[int, ...]] = [(1, 2, 3)]
|
||||||
|
_: dict[str, dict[str, Union[int, str]]] = {"a": {"b": 1}}
|
||||||
|
_: dict[str, dict[str, list[int]]] = {"a": {"b": [1]}}
|
||||||
|
_: dict[str, dict[str, dict[str, int]]] = {"a": {"b": {"c": 1}}}
|
||||||
|
_: dict[str, dict[str, Optional[int]]] = {"a": {"b": 1}}
|
||||||
|
_: dict[str, dict[str, Literal[1, 2]]] = {"a": {"b": 1}}
|
||||||
|
_: dict[str, dict[str, Callable[[int], int]]] = {"a": {"b": abs}}
|
||||||
|
_: dict[str, dict[str, Callable[[int], None]]] = {"a": {"b": print}}
|
||||||
|
_: dict[str, dict[str, Opional[int]]] = {"a": {"b": 1}} # ERR
|
||||||
|
_: dict[str, dict[str, Union[int, str]]] = {"a": {"b": None}} # ERR
|
||||||
|
_: dict[str, dict[str, list[int]]] = {"a": {"b": ["c"]}} # ERR
|
||||||
|
_: dict[str, dict[str, Callable[[int], int]]] = {"a": {"b": print}} # ERR
|
||||||
|
_: dict[str, dict[str, Optional[int]]] = {"a": {"b": "c"}} # ERR
|
||||||
|
_: dict[str, dict[str, Literal[1, 2]]] = {"a": {"b": 3}} # ERR
|
||||||
|
_: list[tuple[int, ...]] = [(1, "a", 3)] # ERR
|
||||||
|
|
||||||
def f(x: Union[int, str]) -> None:
|
def f(x: Union[int, str]) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
@ -36,3 +54,21 @@ def f(x: Union[int, str, None]):
|
||||||
f(1)
|
f(1)
|
||||||
f("a")
|
f("a")
|
||||||
f(None)
|
f(None)
|
||||||
|
|
||||||
|
i1 = 1 # type: int
|
||||||
|
# ERR
|
||||||
|
i2 = 1 # type: str
|
||||||
|
i3 = 1 # type: ignore
|
||||||
|
i3 + "a" # OK
|
||||||
|
|
||||||
|
def f(it: Iterable):
|
||||||
|
for i in it:
|
||||||
|
print(i)
|
||||||
|
|
||||||
|
def f2(it: collections.abc.Iterable):
|
||||||
|
for i in it:
|
||||||
|
print(i)
|
||||||
|
|
||||||
|
def g(it: Iterable):
|
||||||
|
for i in it:
|
||||||
|
print(i + "a") # ERR
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
U = TypeVar("U", bound=int)
|
||||||
|
IS = TypeVar("IS", int, str)
|
||||||
|
def id(x: T) -> T:
|
||||||
|
return x
|
||||||
|
|
||||||
|
def id_int(x: U) -> U:
|
||||||
|
return x
|
||||||
|
|
||||||
|
def id_int_or_str(x: IS) -> IS:
|
||||||
|
return x
|
||||||
|
|
||||||
|
_ = id(1) + 1 # OK
|
||||||
|
_ = id("a") + "b" # OK
|
||||||
|
_ = id_int(1) # OK
|
||||||
|
_ = id_int("a") # ERR
|
||||||
|
_ = id_int_or_str(1) # OK
|
||||||
|
_ = id_int_or_str("a") # OK
|
||||||
|
_ = id_int_or_str(None) # ERR
|
||||||
|
|
||||||
|
def id2[T](x: T) -> T:
|
||||||
|
return x
|
||||||
|
|
||||||
|
def id_int2[T: int](x: T) -> T:
|
||||||
|
return x
|
||||||
|
|
||||||
|
_ = id2(1) + 1 # OK
|
||||||
|
_ = id2("a") + "b" # OK
|
||||||
|
_ = id_int2(1) # OK
|
||||||
|
_ = id_int2("a") # ERR
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
s: str | bytes = ""
|
||||||
|
s2 = s.capitalize()
|
||||||
|
s3 = s2.center(1)
|
||||||
|
|
||||||
|
s4: str | bytes | bytearray = ""
|
||||||
|
_ = s4.__len__()
|
||||||
|
|
||||||
|
def f(x: str | bytes):
|
||||||
|
return x.isalnum()
|
||||||
|
|
||||||
|
def check(s: str | bytes | bytearray):
|
||||||
|
if isinstance(s, (bytes, bytearray)):
|
||||||
|
pass
|
||||||
|
|
@ -3,3 +3,8 @@ if True:
|
||||||
b = True
|
b = True
|
||||||
if True:
|
if True:
|
||||||
b = "a" # ERR
|
b = "a" # ERR
|
||||||
|
|
||||||
|
counter = 100 # counter: Literal[100]
|
||||||
|
while counter > 0:
|
||||||
|
counter -= 1 # counter: Int
|
||||||
|
counter -= 1.0 # counter: Float
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue