mirror of
https://github.com/Instagram/LibCST.git
synced 2025-12-23 10:35:53 +00:00
Compare commits
No commits in common. "main" and "v0.3.14" have entirely different histories.
441 changed files with 6386 additions and 50166 deletions
|
|
@ -1,11 +0,0 @@
|
|||
[target.x86_64-apple-darwin]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-undefined",
|
||||
"-C", "link-arg=dynamic_lookup",
|
||||
]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-undefined",
|
||||
"-C", "link-arg=dynamic_lookup",
|
||||
]
|
||||
12
.circleci/.pyre_configuration
Normal file
12
.circleci/.pyre_configuration
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"source_directories": [
|
||||
"."
|
||||
],
|
||||
"search_path": [
|
||||
"stubs", "/tmp/libcst-env/lib/python3.7/site-packages"
|
||||
],
|
||||
"exclude": [
|
||||
".*/\\.tox/.*"
|
||||
],
|
||||
"strict": true
|
||||
}
|
||||
114
.circleci/config.yml
Normal file
114
.circleci/config.yml
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Python CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-python/ for more details
|
||||
#
|
||||
version: 2.1
|
||||
workflows:
|
||||
version: 2
|
||||
test:
|
||||
jobs:
|
||||
- lint
|
||||
- docs
|
||||
- pyre
|
||||
- test-38
|
||||
- test-37
|
||||
- test-36
|
||||
- test-coverage
|
||||
|
||||
commands:
|
||||
tox:
|
||||
description: "setup tox env and run tox command giving env parameter"
|
||||
parameters:
|
||||
env:
|
||||
type: string
|
||||
default: test
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: tox-v1-{{ checksum "tox.ini" }}-{{ checksum "requirements.txt" }}-{{ checksum "requirements-dev.txt" }}-{{ checksum "setup.py" }}-{{ checksum ".circleci/config.yml" }}-<< parameters.env >>
|
||||
- run:
|
||||
name: install tox
|
||||
command: pip install --user tox
|
||||
- run:
|
||||
name: run tox
|
||||
command: ~/.local/bin/tox -e << parameters.env >>
|
||||
- save_cache:
|
||||
key: tox-v1-{{ checksum "tox.ini" }}-{{ checksum "requirements.txt" }}-{{ checksum "requirements-dev.txt" }}-{{ checksum "setup.py" }}-{{ checksum ".circleci/config.yml" }}-<< parameters.env >>
|
||||
paths:
|
||||
- '.tox'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
docker:
|
||||
- image: circleci/python:3.7
|
||||
steps:
|
||||
- tox:
|
||||
env: "lint"
|
||||
|
||||
docs:
|
||||
docker:
|
||||
- image: circleci/python:3.7
|
||||
steps:
|
||||
- run:
|
||||
command: sudo apt-get install graphviz
|
||||
- tox:
|
||||
env: "docs"
|
||||
- store_artifacts:
|
||||
path: docs/build
|
||||
destination: doc
|
||||
|
||||
pyre:
|
||||
docker:
|
||||
- image: circleci/python:3.7
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: pyre-v1-{{ checksum "tox.ini" }}-{{ checksum "requirements.txt" }}-{{ checksum "requirements-dev.txt" }}-{{ checksum "setup.py" }}-{{ checksum ".circleci/config.yml" }}
|
||||
- run:
|
||||
name: run pyre
|
||||
command: |
|
||||
test -d /tmp/libcst-env/ || python3 -m venv /tmp/libcst-env/
|
||||
source /tmp/libcst-env/bin/activate
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements.txt -r requirements-dev.txt
|
||||
pip uninstall -y libcst
|
||||
pip install -e .
|
||||
cp .circleci/.pyre_configuration .
|
||||
pyre check
|
||||
PYTHONPATH=`pwd` python libcst/tests/test_pyre_integration.py
|
||||
git diff --exit-code # verify no generated changes
|
||||
- save_cache:
|
||||
key: pyre-v1-{{ checksum "tox.ini" }}-{{ checksum "requirements.txt" }}-{{ checksum "requirements-dev.txt" }}-{{ checksum "setup.py" }}-{{ checksum ".circleci/config.yml" }}
|
||||
paths:
|
||||
- '/tmp/libcst-env/'
|
||||
|
||||
test-38:
|
||||
docker:
|
||||
- image: circleci/python:3.8
|
||||
steps:
|
||||
- tox:
|
||||
env: "py38"
|
||||
|
||||
test-37:
|
||||
docker:
|
||||
- image: circleci/python:3.7
|
||||
steps:
|
||||
- tox:
|
||||
env: "py37"
|
||||
|
||||
test-coverage:
|
||||
docker:
|
||||
- image: circleci/python:3.7
|
||||
steps:
|
||||
- tox:
|
||||
env: "py37"
|
||||
- tox:
|
||||
env: "coverage"
|
||||
|
||||
test-36:
|
||||
docker:
|
||||
- image: circleci/python:3.6
|
||||
steps:
|
||||
- tox:
|
||||
env: "py36"
|
||||
|
||||
|
|
@ -1,14 +1,10 @@
|
|||
root = true
|
||||
|
||||
[*.{py,pyi,rs,toml,md}]
|
||||
charset = utf-8
|
||||
[*.{py,pyi,toml,md}]
|
||||
charset = "utf-8"
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 88
|
||||
|
||||
[*.rs]
|
||||
# https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md
|
||||
max_line_length = 100
|
||||
|
|
|
|||
211
.flake8
211
.flake8
|
|
@ -1,126 +1,69 @@
|
|||
[flake8]
|
||||
|
||||
ignore =
|
||||
# unnecessary list comprehension; A generator only better than a list
|
||||
# comprehension if we don't always need to iterate through all items in
|
||||
# the generator (based on the use case).
|
||||
C407,
|
||||
C407, # unnecessary list comprehension; A generator only better than a list
|
||||
# comprehension if we don't always need to iterate through all items in
|
||||
# the generator (based on the use case).
|
||||
|
||||
# The following codes belong to pycodestyle, and overlap with black:
|
||||
# indentation contains mixed spaces and tabs
|
||||
E101,
|
||||
# indentation is not a multiple of four
|
||||
E111,
|
||||
# expected an indented block
|
||||
E112,
|
||||
# unexpected indentation
|
||||
E113,
|
||||
# indentation is not a multiple of four (comment)
|
||||
E114,
|
||||
# expected an indented block (comment)
|
||||
E115,
|
||||
# unexpected indentation (comment)
|
||||
E116,
|
||||
# continuation line under-indented for hanging indent
|
||||
E121,
|
||||
# continuation line missing indentation or outdented
|
||||
E122,
|
||||
# closing bracket does not match indentation of opening bracket’s line
|
||||
E123,
|
||||
# closing bracket does not match visual indentation
|
||||
E124,
|
||||
# continuation line with same indent as next logical line
|
||||
E125,
|
||||
# continuation line over-indented for hanging indent
|
||||
E126,
|
||||
# continuation line over-indented for visual indent; is harmless
|
||||
# (over-indent is visually unambiguous) and currently generates too
|
||||
# many warnings for existing code.
|
||||
E127,
|
||||
|
||||
# continuation line under-indented for visual indent
|
||||
E128,
|
||||
# visually indented line with same indent as next logical line
|
||||
E129,
|
||||
# continuation line unaligned for hanging indent
|
||||
E131,
|
||||
# closing bracket is missing indentation
|
||||
E133,
|
||||
# whitespace after ‘(‘
|
||||
E201,
|
||||
# whitespace before ‘)’
|
||||
E202,
|
||||
# whitespace before ‘:’; this warning is invalid for slices
|
||||
E203,
|
||||
# whitespace before ‘(‘
|
||||
E211,
|
||||
# multiple spaces before operator
|
||||
E221,
|
||||
# multiple spaces after operator
|
||||
E222,
|
||||
# tab before operator
|
||||
E223,
|
||||
# tab after operator
|
||||
E224,
|
||||
# missing whitespace around operator
|
||||
E225,
|
||||
# missing whitespace around arithmetic operator
|
||||
E226,
|
||||
# missing whitespace around bitwise or shift operator
|
||||
E227,
|
||||
# missing whitespace around modulo operator
|
||||
E228,
|
||||
# missing whitespace after ‘,’, ‘;’, or ‘:’
|
||||
E231,
|
||||
# multiple spaces after ‘,’
|
||||
E241,
|
||||
# tab after ‘,’
|
||||
E242,
|
||||
# unexpected spaces around keyword / parameter equals
|
||||
E251,
|
||||
# at least two spaces before inline comment
|
||||
E261,
|
||||
# inline comment should start with ‘# ‘
|
||||
E262,
|
||||
# block comment should start with ‘# ‘
|
||||
E265,
|
||||
# too many leading ‘#’ for block comment
|
||||
E266,
|
||||
# multiple spaces after keyword
|
||||
E271,
|
||||
# multiple spaces before keyword
|
||||
E272,
|
||||
# tab after keyword
|
||||
E273,
|
||||
# tab before keyword
|
||||
E274,
|
||||
# missing whitespace after keyword
|
||||
E275,
|
||||
# expected 1 blank line, found 0
|
||||
E301,
|
||||
# expected 2 blank lines, found 0
|
||||
E302,
|
||||
# too many blank lines (3)
|
||||
E303,
|
||||
# blank lines found after function decorator
|
||||
E304,
|
||||
# expected 2 blank lines after end of function or class
|
||||
E305,
|
||||
# expected 1 blank line before a nested definition
|
||||
E306,
|
||||
# multiple imports on one line
|
||||
E401,
|
||||
# line too long (> 79 characters)
|
||||
E501,
|
||||
# the backslash is redundant between brackets
|
||||
E502,
|
||||
# multiple statements on one line (colon)
|
||||
E701,
|
||||
# multiple statements on one line (semicolon)
|
||||
E702,
|
||||
# statement ends with a semicolon
|
||||
E703,
|
||||
# multiple statements on one line (def)
|
||||
E704,
|
||||
E101, # indentation contains mixed spaces and tabs
|
||||
E111, # indentation is not a multiple of four
|
||||
E112, # expected an indented block
|
||||
E113, # unexpected indentation
|
||||
E114, # indentation is not a multiple of four (comment)
|
||||
E115, # expected an indented block (comment)
|
||||
E116, # unexpected indentation (comment)
|
||||
E121, # continuation line under-indented for hanging indent
|
||||
E122, # continuation line missing indentation or outdented
|
||||
E123, # closing bracket does not match indentation of opening bracket’s line
|
||||
E124, # closing bracket does not match visual indentation
|
||||
E125, # continuation line with same indent as next logical line
|
||||
E126, # continuation line over-indented for hanging indent
|
||||
E127, # continuation line over-indented for visual indent; is harmless
|
||||
# (over-indent is visually unambiguous) and currently generates too
|
||||
# many warnings for existing code.
|
||||
E128, # continuation line under-indented for visual indent
|
||||
E129, # visually indented line with same indent as next logical line
|
||||
E131, # continuation line unaligned for hanging indent
|
||||
E133, # closing bracket is missing indentation
|
||||
E201, # whitespace after ‘(‘
|
||||
E202, # whitespace before ‘)’
|
||||
E203, # whitespace before ‘:’; this warning is invalid for slices
|
||||
E211, # whitespace before ‘(‘
|
||||
E221, # multiple spaces before operator
|
||||
E222, # multiple spaces after operator
|
||||
E223, # tab before operator
|
||||
E224, # tab after operator
|
||||
E225, # missing whitespace around operator
|
||||
E226, # missing whitespace around arithmetic operator
|
||||
E227, # missing whitespace around bitwise or shift operator
|
||||
E228, # missing whitespace around modulo operator
|
||||
E231, # missing whitespace after ‘,’, ‘;’, or ‘:’
|
||||
E241, # multiple spaces after ‘,’
|
||||
E242, # tab after ‘,’
|
||||
E251, # unexpected spaces around keyword / parameter equals
|
||||
E261, # at least two spaces before inline comment
|
||||
E262, # inline comment should start with ‘# ‘
|
||||
E265, # block comment should start with ‘# ‘
|
||||
E266, # too many leading ‘#’ for block comment
|
||||
E271, # multiple spaces after keyword
|
||||
E272, # multiple spaces before keyword
|
||||
E273, # tab after keyword
|
||||
E274, # tab before keyword
|
||||
E275, # missing whitespace after keyword
|
||||
E301, # expected 1 blank line, found 0
|
||||
E302, # expected 2 blank lines, found 0
|
||||
E303, # too many blank lines (3)
|
||||
E304, # blank lines found after function decorator
|
||||
E305, # expected 2 blank lines after end of function or class
|
||||
E306, # expected 1 blank line before a nested definition
|
||||
E401, # multiple imports on one line
|
||||
E501, # line too long (> 79 characters)
|
||||
E502, # the backslash is redundant between brackets
|
||||
E701, # multiple statements on one line (colon)
|
||||
E702, # multiple statements on one line (semicolon)
|
||||
E703, # statement ends with a semicolon
|
||||
E704, # multiple statements on one line (def)
|
||||
# These are pycodestyle lints that black doesn't catch:
|
||||
# E711, # comparison to None should be ‘if cond is None:’
|
||||
# E712, # comparison to True should be ‘if cond is True:’ or ‘if cond:’
|
||||
|
|
@ -135,25 +78,16 @@ ignore =
|
|||
# I think these are internal to pycodestyle?
|
||||
# E901, # SyntaxError or IndentationError
|
||||
# E902, # IOError
|
||||
# isn't aware of type-only imports, results in false-positives
|
||||
F811,
|
||||
# indentation contains tabs
|
||||
W191,
|
||||
# trailing whitespace
|
||||
W291,
|
||||
# no newline at end of file
|
||||
W292,
|
||||
# blank line contains whitespace
|
||||
W293,
|
||||
# blank line at end of file
|
||||
W391,
|
||||
# line break before binary operator; binary operator in a new line is
|
||||
# the standard
|
||||
W503,
|
||||
# line break after binary operator
|
||||
W504,
|
||||
# not part of PEP8; doc line too long (> 79 characters)
|
||||
W505,
|
||||
F811, # isn't aware of type-only imports, results in false-positives
|
||||
W191, # indentation contains tabs
|
||||
W291, # trailing whitespace
|
||||
W292, # no newline at end of file
|
||||
W293, # blank line contains whitespace
|
||||
W391, # blank line at end of file
|
||||
W503, # line break before binary operator; binary operator in a new line is
|
||||
# the standard
|
||||
W504, # line break after binary operator
|
||||
W505, # not part of PEP8; doc line too long (> 79 characters)
|
||||
# These are pycodestyle lints that black doesn't catch:
|
||||
# W601, # .has_key() is deprecated, use ‘in’
|
||||
# W602, # deprecated form of raising exception
|
||||
|
|
@ -172,7 +106,6 @@ exclude =
|
|||
.pyre,
|
||||
__pycache__,
|
||||
.tox,
|
||||
native,
|
||||
|
||||
max-complexity = 12
|
||||
|
||||
|
|
|
|||
31
.github/build-matrix.json
vendored
31
.github/build-matrix.json
vendored
|
|
@ -1,31 +0,0 @@
|
|||
[
|
||||
{
|
||||
"vers": "x86_64",
|
||||
"os": "ubuntu-20.04"
|
||||
},
|
||||
{
|
||||
"vers": "i686",
|
||||
"os": "ubuntu-20.04"
|
||||
},
|
||||
{
|
||||
"vers": "arm64",
|
||||
"os": "macos-latest"
|
||||
},
|
||||
{
|
||||
"vers": "auto64",
|
||||
"os": "macos-latest"
|
||||
},
|
||||
{
|
||||
"vers": "auto64",
|
||||
"os": "windows-2019"
|
||||
},
|
||||
{
|
||||
"vers": "aarch64",
|
||||
"os": [
|
||||
"self-hosted",
|
||||
"linux",
|
||||
"ARM64"
|
||||
],
|
||||
"on_ref_regex": "^refs/(heads/main|tags/.*)$"
|
||||
}
|
||||
]
|
||||
18
.github/dependabot.yml
vendored
18
.github/dependabot.yml
vendored
|
|
@ -1,18 +0,0 @@
|
|||
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
- package-ecosystem: cargo
|
||||
directory: "/native"
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
45
.github/workflows/build.yml
vendored
45
.github/workflows/build.yml
vendored
|
|
@ -1,45 +0,0 @@
|
|||
name: build
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
# Build python wheels
|
||||
build:
|
||||
name: Build wheels on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
[
|
||||
macos-latest,
|
||||
ubuntu-latest,
|
||||
ubuntu-24.04-arm,
|
||||
windows-latest,
|
||||
windows-11-arm,
|
||||
]
|
||||
env:
|
||||
SCCACHE_VERSION: 0.2.13
|
||||
GITHUB_WORKSPACE: "${{github.workspace}}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Disable scmtools local scheme
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
run: >-
|
||||
echo LIBCST_NO_LOCAL_SCHEME=1 >> $GITHUB_ENV
|
||||
- name: Enable building wheels for pre-release CPython versions
|
||||
if: github.event_name != 'release'
|
||||
run: echo CIBW_ENABLE=cpython-prerelease >> $GITHUB_ENV
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v3.2.1
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: wheelhouse/*.whl
|
||||
name: wheels-${{matrix.os}}
|
||||
142
.github/workflows/ci.yml
vendored
142
.github/workflows/ci.yml
vendored
|
|
@ -1,142 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.13t"
|
||||
- "3.14"
|
||||
- "3.14t"
|
||||
steps:
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.7.13"
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Build LibCST
|
||||
run: uv sync --locked --dev
|
||||
- name: Native Parser Tests
|
||||
run: uv run poe test
|
||||
- name: Coverage
|
||||
run: uv run coverage report
|
||||
|
||||
# Run linters
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.7.13"
|
||||
python-version: "3.10"
|
||||
- run: uv run poe lint
|
||||
- run: uv run poe fixtures
|
||||
|
||||
# Run pyre typechecker
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.7.13"
|
||||
python-version: "3.10"
|
||||
- run: uv run poe typecheck
|
||||
|
||||
# Build the docs
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.7.13"
|
||||
python-version: "3.10"
|
||||
- uses: ts-graphviz/setup-graphviz@v2
|
||||
- run: uv run --group docs poe docs
|
||||
- name: Archive Docs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sphinx-docs
|
||||
path: docs/build
|
||||
|
||||
# Test rust parts
|
||||
native:
|
||||
name: Rust unit tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
python-version: ["3.10", "3.13t"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: test
|
||||
run: cargo test --manifest-path=native/Cargo.toml --release
|
||||
- name: test without python
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: cargo test --manifest-path=native/Cargo.toml --release --no-default-features
|
||||
- name: clippy
|
||||
run: cargo clippy --manifest-path=native/Cargo.toml --all-targets --all-features
|
||||
- name: compile-benchmarks
|
||||
run: cargo bench --manifest-path=native/Cargo.toml --no-run
|
||||
|
||||
rustfmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- run: rustup component add rustfmt
|
||||
- name: format
|
||||
run: cargo fmt --all --manifest-path=native/Cargo.toml -- --check
|
||||
build:
|
||||
# only trigger here for pull requests - regular pushes are handled in pypi_upload
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: Instagram/LibCST/.github/workflows/build.yml@main
|
||||
60
.github/workflows/pypi_upload.yml
vendored
60
.github/workflows/pypi_upload.yml
vendored
|
|
@ -1,60 +0,0 @@
|
|||
name: pypi_upload
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: Instagram/LibCST/.github/workflows/build.yml@main
|
||||
upload_release:
|
||||
name: Upload wheels to pypi
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Download binary wheels
|
||||
id: download
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: wheels-*
|
||||
path: wheelhouse
|
||||
merge-multiple: true
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.7.13"
|
||||
enable-cache: false
|
||||
- name: Build a source tarball
|
||||
env:
|
||||
LIBCST_NO_LOCAL_SCHEME: 1
|
||||
OUTDIR: ${{ steps.download.outputs.download-path }}
|
||||
run: >-
|
||||
uv run python -m
|
||||
build
|
||||
--sdist
|
||||
--outdir "$OUTDIR"
|
||||
- name: Publish distribution 📦 to Test PyPI
|
||||
if: github.event_name == 'push'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
packages-dir: ${{ steps.download.outputs.download-path }}
|
||||
- name: Publish distribution 📦 to PyPI
|
||||
if: github.event_name == 'release'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
packages-dir: ${{ steps.download.outputs.download-path }}
|
||||
35
.github/workflows/zizmor.yml
vendored
35
.github/workflows/zizmor.yml
vendored
|
|
@ -1,35 +0,0 @@
|
|||
name: GitHub Actions Security Analysis with zizmor 🌈
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
|
||||
jobs:
|
||||
zizmor:
|
||||
name: zizmor latest via PyPI
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
|
||||
- name: Run zizmor 🌈
|
||||
run: uvx zizmor --format sarif . > results.sarif
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
category: zizmor
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -1,11 +1,8 @@
|
|||
*.swp
|
||||
*.swo
|
||||
*.pyc
|
||||
*.pyd
|
||||
*.pyo
|
||||
*.so
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
.pyre/
|
||||
__pycache__/
|
||||
.tox/
|
||||
|
|
@ -13,11 +10,6 @@ docs/build/
|
|||
dist/
|
||||
docs/source/.ipynb_checkpoints/
|
||||
build/
|
||||
libcst/_version.py
|
||||
.coverage
|
||||
.hypothesis/
|
||||
.python-version
|
||||
target/
|
||||
venv/
|
||||
.venv/
|
||||
.idea/
|
||||
.pyre_configuration
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"exclude": [
|
||||
".*\/native\/.*"
|
||||
],
|
||||
"ignore_all_errors": [
|
||||
".venv"
|
||||
],
|
||||
"source_directories": [
|
||||
"."
|
||||
],
|
||||
"search_path": [
|
||||
"stubs", {"site-package": "setuptools_rust"}
|
||||
],
|
||||
"workers": 3,
|
||||
"strict": true
|
||||
}
|
||||
12
.pyre_configuration.example
Normal file
12
.pyre_configuration.example
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"source_directories": [
|
||||
"."
|
||||
],
|
||||
"search_path": [
|
||||
"stubs"
|
||||
],
|
||||
"exclude": [
|
||||
".*/\\.tox/.*"
|
||||
],
|
||||
"strict": true
|
||||
}
|
||||
|
|
@ -5,18 +5,12 @@ sphinx:
|
|||
|
||||
formats: all
|
||||
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3"
|
||||
rust: "1.70"
|
||||
apt_packages:
|
||||
- graphviz
|
||||
|
||||
python:
|
||||
version: 3.7
|
||||
install:
|
||||
- requirements: requirements.txt
|
||||
- requirements: requirements-dev.txt
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- dev
|
||||
system_packages: true
|
||||
|
||||
|
|
|
|||
1037
CHANGELOG.md
1037
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
|
@ -1,80 +1,5 @@
|
|||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
This Code of Conduct also applies outside the project spaces when there is a
|
||||
reasonable belief that an individual's behavior may have a negative impact on
|
||||
the project or its community.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at <opensource-conduct@fb.com>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to.
|
||||
Please read the [full text](https://code.fb.com/codeofconduct/)
|
||||
so that you can understand what actions will and will not be tolerated.
|
||||
|
|
|
|||
|
|
@ -3,38 +3,18 @@ We want to make contributing to this project as easy and transparent as
|
|||
possible.
|
||||
|
||||
## Our Development Process
|
||||
This github repo is the source of truth and all changes need to be reviewed in
|
||||
This github repo is the source of truth and all changes need to be reviewed in
|
||||
pull requests.
|
||||
|
||||
## Pull Requests
|
||||
We actively welcome your pull requests.
|
||||
|
||||
### Setup Your Environment
|
||||
|
||||
1. Install a [Rust toolchain](https://rustup.rs) and [uv](https://docs.astral.sh/uv/)
|
||||
2. Fork the repo on your side
|
||||
3. Clone the repo
|
||||
> git clone [your fork.git] libcst
|
||||
> cd libcst
|
||||
4. Sync with the main libcst version package
|
||||
> git fetch --tags https://github.com/instagram/libcst
|
||||
5. Setup the env
|
||||
> uv sync
|
||||
|
||||
You are now ready to create your own branch from main, and contribute.
|
||||
Please provide tests (using unittest), and update the documentation (both docstrings
|
||||
and sphinx doc), if applicable.
|
||||
|
||||
### Before Submitting Your Pull Request
|
||||
|
||||
1. Format your code
|
||||
> uv run poe format
|
||||
2. Run the type checker
|
||||
> uv run poe typecheck
|
||||
3. Test your changes
|
||||
> uv run poe test
|
||||
4. Check linters
|
||||
> uv run poe lint
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes by `tox test`.
|
||||
5. Make sure your code lints.
|
||||
6. If you haven't already, complete the Contributor License Agreement ("CLA").
|
||||
|
||||
## Contributor License Agreement ("CLA")
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need
|
||||
|
|
@ -50,8 +30,8 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
|||
disclosure of security bugs. In those cases, please go through the process
|
||||
outlined on that page and do not file a public issue.
|
||||
|
||||
## Coding Style
|
||||
We use flake8 and ufmt to enforce coding style.
|
||||
## Coding Style
|
||||
We use flake8, isort and black to enforce coding style.
|
||||
|
||||
## License
|
||||
By contributing to LibCST, you agree that your contributions will be licensed
|
||||
|
|
|
|||
8
LICENSE
8
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
All contributions towards LibCST are MIT licensed.
|
||||
|
||||
Some Python files have been derived from the standard library and are therefore
|
||||
Some Python files have been taken from the standard library and are therefore
|
||||
PSF licensed. Modifications on these files are dual licensed (both MIT and
|
||||
PSF). These files are:
|
||||
|
||||
|
|
@ -8,13 +8,11 @@ PSF). These files are:
|
|||
- libcst/_parser/parso/utils.py
|
||||
- libcst/_parser/parso/pgen2/generator.py
|
||||
- libcst/_parser/parso/pgen2/grammar_parser.py
|
||||
- libcst/_parser/parso/python/py_token.py
|
||||
- libcst/_parser/parso/python/token.py
|
||||
- libcst/_parser/parso/python/tokenize.py
|
||||
- libcst/_parser/parso/tests/test_fstring.py
|
||||
- libcst/_parser/parso/tests/test_tokenize.py
|
||||
- libcst/_parser/parso/tests/test_utils.py
|
||||
- native/libcst/src/tokenizer/core/mod.rs
|
||||
- native/libcst/src/tokenizer/core/string_types.rs
|
||||
|
||||
Some Python files have been taken from dataclasses and are therefore Apache
|
||||
licensed. Modifications on these files are licensed under Apache 2.0 license.
|
||||
|
|
@ -26,7 +24,7 @@ These files are:
|
|||
|
||||
MIT License
|
||||
|
||||
Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
# How to make a new release
|
||||
|
||||
1. Add a new entry to `CHANGELOG.md` (I normally use the [new release page](https://github.com/Instagram/LibCST/releases/new) to generate a changelog, then manually group)
|
||||
1. Follow the existing format: `Fixed`, `Added`, `Updated`, `Deprecated`, `Removed`, `New Contributors` sections, and the full changelog link at the bottom.
|
||||
1. Mention only user-visible changes - improvements to CI, tests, or development workflow aren't noteworthy enough
|
||||
1. Version bumps are generally not worth mentioning with some notable exceptions (like pyo3)
|
||||
1. Group related PRs into one bullet point if it makes sense
|
||||
2. manually bump versions in `Cargo.toml` files in the repo
|
||||
3. run `cargo update -p libcst`
|
||||
4. make a new PR with the above changes, get it reviewed and landed
|
||||
5. make a new release on Github, create a new tag on publish, and copy the contents of the changelog entry in there
|
||||
6. after publishing, check out the repo at the new tag, and run `cd native; cargo +nightly publish -Z package-workspace -p libcst_derive -p libcst`
|
||||
|
|
@ -1,5 +1 @@
|
|||
include README.rst LICENSE CODE_OF_CONDUCT.md CONTRIBUTING.md docs/source/*.rst libcst/py.typed
|
||||
|
||||
include native/Cargo.toml
|
||||
recursive-include native *
|
||||
recursive-exclude native/target *
|
||||
include README.rst LICENSE CODE_OF_CONDUCT.md CONTRIBUTING.md requirements.txt requirements-dev.txt docs/source/*.rst libcst/py.typed
|
||||
|
|
|
|||
103
README.rst
103
README.rst
|
|
@ -4,19 +4,19 @@
|
|||
|
||||
A Concrete Syntax Tree (CST) parser and serializer library for Python
|
||||
|
||||
|support-ukraine| |readthedocs-badge| |ci-badge| |pypi-badge| |pypi-download| |notebook-badge| |types-badge|
|
||||
|readthedocs-badge| |circleci-badge| |codecov-badge| |pypi-badge| |pypi-download| |notebook-badge|
|
||||
|
||||
.. |support-ukraine| image:: https://img.shields.io/badge/Support-Ukraine-FFD500?style=flat&labelColor=005BBB
|
||||
:alt: Support Ukraine - Help Provide Humanitarian Aid to Ukraine.
|
||||
:target: https://opensource.fb.com/support-ukraine
|
||||
|
||||
.. |readthedocs-badge| image:: https://readthedocs.org/projects/libcst/badge/?version=latest&style=flat
|
||||
.. |readthedocs-badge| image:: https://readthedocs.org/projects/pip/badge/?version=latest&style=flat
|
||||
:target: https://libcst.readthedocs.io/en/latest/
|
||||
:alt: Documentation
|
||||
|
||||
.. |ci-badge| image:: https://github.com/Instagram/LibCST/actions/workflows/build.yml/badge.svg
|
||||
:target: https://github.com/Instagram/LibCST/actions/workflows/build.yml?query=branch%3Amain
|
||||
:alt: Github Actions
|
||||
.. |circleci-badge| image:: https://circleci.com/gh/Instagram/LibCST/tree/master.svg?style=shield&circle-token=f89ff46c689cf53116308db295a492d687bf5732
|
||||
:target: https://circleci.com/gh/Instagram/LibCST/tree/master
|
||||
:alt: CircleCI
|
||||
|
||||
.. |codecov-badge| image:: https://codecov.io/gh/Instagram/LibCST/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/Instagram/LibCST/branch/master
|
||||
:alt: CodeCov
|
||||
|
||||
.. |pypi-badge| image:: https://img.shields.io/pypi/v/libcst.svg
|
||||
:target: https://pypi.org/project/libcst
|
||||
|
|
@ -28,16 +28,12 @@ A Concrete Syntax Tree (CST) parser and serializer library for Python
|
|||
|
||||
|
||||
.. |notebook-badge| image:: https://img.shields.io/badge/notebook-run-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC
|
||||
:target: https://mybinder.org/v2/gh/Instagram/LibCST/main?filepath=docs%2Fsource%2Ftutorial.ipynb
|
||||
:target: https://mybinder.org/v2/gh/Instagram/LibCST/master?filepath=docs%2Fsource%2Ftutorial.ipynb
|
||||
:alt: Notebook
|
||||
|
||||
.. |types-badge| image:: https://img.shields.io/pypi/types/libcst
|
||||
:target: https://pypi.org/project/libcst
|
||||
:alt: PYPI - Types
|
||||
|
||||
.. intro-start
|
||||
|
||||
LibCST parses Python 3.0 -> 3.14 source code as a CST tree that keeps
|
||||
LibCST parses Python 3.0, 3.1, 3.3, 3.5, 3.6, 3.7 or 3.8 source code as a CST tree that keeps
|
||||
all formatting details (comments, whitespaces, parentheses, etc). It's useful for
|
||||
building automated refactoring (codemod) applications and linters.
|
||||
|
||||
|
|
@ -56,15 +52,13 @@ You can learn more about `the value that LibCST provides
|
|||
motivations for the project
|
||||
<https://libcst.readthedocs.io/en/latest/motivation.html>`__
|
||||
in `our documentation <https://libcst.readthedocs.io/en/latest/index.html>`__.
|
||||
Try it out with `notebook examples <https://mybinder.org/v2/gh/Instagram/LibCST/main?filepath=docs%2Fsource%2Ftutorial.ipynb>`__.
|
||||
Try it out with `notebook examples <https://mybinder.org/v2/gh/Instagram/LibCST/master?filepath=docs%2Fsource%2Ftutorial.ipynb>`__.
|
||||
|
||||
Example expression::
|
||||
|
||||
1 + 2
|
||||
|
||||
CST representation:
|
||||
|
||||
.. code-block:: python
|
||||
CST representation::
|
||||
|
||||
BinaryOperation(
|
||||
left=Integer(
|
||||
|
|
@ -127,7 +121,7 @@ For a more detailed usage example, `see our documentation
|
|||
Installation
|
||||
------------
|
||||
|
||||
LibCST requires Python 3.9+ and can be easily installed using most common Python
|
||||
LibCST requires Python 3.6+ and can be easily installed using most common Python
|
||||
packaging tools. We recommend installing the latest stable release from
|
||||
`PyPI <https://pypi.org/project/libcst/>`_ with pip:
|
||||
|
||||
|
|
@ -135,11 +129,6 @@ packaging tools. We recommend installing the latest stable release from
|
|||
|
||||
pip install libcst
|
||||
|
||||
For parsing, LibCST ships with a native extension, so releases are distributed as binary
|
||||
wheels as well as the source code. If a binary wheel is not available for your system
|
||||
(Linux/Windows x86/x64 and Mac x64/arm are covered), you'll need a recent
|
||||
`Rust toolchain <https://rustup.rs>`_ for installing.
|
||||
|
||||
Further Reading
|
||||
---------------
|
||||
- `Static Analysis at Scale: An Instagram Story. <https://instagram-engineering.com/static-analysis-at-scale-an-instagram-story-8f498ab71a0c>`_
|
||||
|
|
@ -148,49 +137,67 @@ Further Reading
|
|||
Development
|
||||
-----------
|
||||
|
||||
See `CONTRIBUTING.md <CONTRIBUTING.md>`_ for more details.
|
||||
|
||||
Building
|
||||
~~~~~~~~
|
||||
|
||||
In order to build LibCST, which includes a native parser module, you
|
||||
will need to have the Rust build tool ``cargo`` on your path. You can
|
||||
usually install ``cargo`` using your system package manager, but the
|
||||
most popular way to install cargo is using
|
||||
`rustup <https://rustup.rs/>`_.
|
||||
|
||||
To build just the native parser, do the following from the ``native``
|
||||
directory:
|
||||
Start by setting up and activating a virtualenv:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
cargo build
|
||||
git clone git@github.com:Instagram/LibCST.git libcst
|
||||
cd libcst
|
||||
python3 -m venv ../libcst-env/ # just an example, put this wherever you want
|
||||
source ../libcst-env/bin/activate
|
||||
pip install --upgrade pip # optional, if you have an old system version of pip
|
||||
pip install -r requirements.txt -r requirements-dev.txt
|
||||
# If you're done with the virtualenv, you can leave it by running:
|
||||
deactivate
|
||||
|
||||
The ``libcst.native`` module should be rebuilt automatically, but to force it:
|
||||
We use `isort <https://isort.readthedocs.io/en/stable/>`_ and `black <https://black.readthedocs.io/en/stable/>`_
|
||||
to format code. To format changes to be conformant, run the following in the root:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
uv sync --reinstall-package libcst
|
||||
tox -e autofix
|
||||
|
||||
Type Checking
|
||||
~~~~~~~~~~~~~
|
||||
To run all tests, you'll need to install `tox <https://tox.readthedocs.io/en/latest/>`_
|
||||
and do the following in the root:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
tox -e py37
|
||||
|
||||
You can also run individual tests by using unittest and specifying a module like
|
||||
this:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
python -m unittest libcst.tests.test_batched_visitor
|
||||
|
||||
See the `unittest documentation <https://docs.python.org/3/library/unittest.html>`_
|
||||
for more examples of how to run tests.
|
||||
|
||||
We use `Pyre <https://github.com/facebook/pyre-check>`_ for type-checking.
|
||||
|
||||
To set up pyre check environment:
|
||||
|
||||
1. Copy the example Pyre config: ``cp .pyre_configuration.example .pyre_configuration``.
|
||||
2. In the config file, add your venv site-packages dir to "search_path". (e.g. add "/workspace/libcst-env/lib/python3.7/site-packages")
|
||||
3. Remove installed LibCST and install from the source code:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pip uninstall -y libcst
|
||||
pip install -e .
|
||||
|
||||
To verify types for the library, do the following in the root:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
uv run poe typecheck
|
||||
|
||||
Generating Documents
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
pyre check
|
||||
|
||||
To generate documents, do the following in the root:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
uv run --group docs poe docs
|
||||
tox -e docs
|
||||
|
||||
Future
|
||||
======
|
||||
|
|
|
|||
2
apt.txt
2
apt.txt
|
|
@ -1,2 +0,0 @@
|
|||
rustc
|
||||
cargo
|
||||
4
codecov.yml
Normal file
4
codecov.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
coverage:
|
||||
status:
|
||||
project: no
|
||||
patch: yes
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 92 KiB |
|
|
@ -1,4 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
|
|
@ -7,25 +9,29 @@
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
width="132.62682mm"
|
||||
height="103.25121mm"
|
||||
viewBox="0 0 132.62682 103.2512"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0.2 (e86c8708, 2021-01-15)"
|
||||
sodipodi:docname="drawing.svg"
|
||||
inkscape:export-filename="/Users/lpetre/Desktop/rect846-0.png"
|
||||
inkscape:export-xdpi="191.53999"
|
||||
inkscape:export-ydpi="191.53999">
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
sodipodi:docname="python-scopes.svg"
|
||||
inkscape:export-filename="/home/bgw/Documents/lint-docs-pycon/python-scopes.png"
|
||||
inkscape:export-xdpi="200.09"
|
||||
inkscape:export-ydpi="200.09"
|
||||
enable-background="new">
|
||||
<defs
|
||||
id="defs2">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 148.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="210 : 148.5 : 1"
|
||||
inkscape:persp3d-origin="105 : 99 : 1"
|
||||
id="perspective178" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
style="color-interpolation-filters:sRGB"
|
||||
id="filter862">
|
||||
<feBlend
|
||||
inkscape:collect="always"
|
||||
mode="lighten"
|
||||
in2="BackgroundImage"
|
||||
id="feBlend864" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
|
|
@ -34,17 +40,16 @@
|
|||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.5810773"
|
||||
inkscape:cx="394.23269"
|
||||
inkscape:cy="326.05841"
|
||||
inkscape:zoom="0.7071068"
|
||||
inkscape:cx="114.72171"
|
||||
inkscape:cy="244.35769"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer8"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1458"
|
||||
inkscape:window-height="932"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-width="1574"
|
||||
inkscape:window-height="978"
|
||||
inkscape:window-x="474"
|
||||
inkscape:window-y="327"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
|
|
@ -59,439 +64,222 @@
|
|||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"
|
||||
transform="translate(-5.9299994,-39.669584)"
|
||||
style="filter:url(#filter862)">
|
||||
<rect
|
||||
style="opacity:1;fill:#b8d9f8;fill-opacity:1;stroke:#3e99ed;stroke-width:0.84;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect846"
|
||||
width="131.78682"
|
||||
height="102.41121"
|
||||
x="6.3499994"
|
||||
y="40.089584" />
|
||||
<rect
|
||||
style="opacity:1;fill:#e7c2ee;fill-opacity:1;stroke:#a132b8;stroke-width:0.83999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect846-3"
|
||||
width="127.44389"
|
||||
height="79.862946"
|
||||
x="8.0697889"
|
||||
y="52.789581" />
|
||||
<rect
|
||||
style="opacity:1;fill:#f7b9be;fill-opacity:1;stroke:#eb4b59;stroke-width:0.83999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
|
||||
id="rect846-3-9"
|
||||
width="112.51761"
|
||||
height="56.470001"
|
||||
x="20.108335"
|
||||
y="73.691681" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer8"
|
||||
inkscape:label="builtin"
|
||||
style="display:inline">
|
||||
id="g851"
|
||||
transform="translate(5.4713964,-0.41999999)">
|
||||
<rect
|
||||
style="display:inline;fill:#3ad96d;fill-opacity:1;stroke:#3e9961;stroke-width:0.84;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
|
||||
id="rect846-0"
|
||||
width="131.78682"
|
||||
height="102.41121"
|
||||
x="32.380394"
|
||||
y="-127.55679"
|
||||
transform="matrix(1.0238813,0,0,1.1721179,3.6866905,175.82934)"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<g
|
||||
id="g851-0"
|
||||
transform="translate(38.926913,-13.869216)"
|
||||
style="display:inline"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<rect
|
||||
y="40.089584"
|
||||
x="94.592438"
|
||||
height="8.467"
|
||||
width="38.492985"
|
||||
id="rect846-9-3"
|
||||
style="opacity:1;fill:#3e9961;fill-opacity:1;stroke:none;stroke-width:0.214711;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.644134, 0.214711;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<text
|
||||
id="text880-9"
|
||||
y="40.089584"
|
||||
x="94.592438"
|
||||
height="8.467"
|
||||
width="38.492985"
|
||||
id="rect846-9"
|
||||
style="opacity:1;fill:#3e99ed;fill-opacity:1;stroke:none;stroke-width:0.21471134;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.64413405, 0.21471134;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<text
|
||||
id="text880"
|
||||
y="45.703152"
|
||||
x="130.23187"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';text-align:end;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke-width:0.26458332px"
|
||||
y="45.703152"
|
||||
x="130.23187"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';text-align:end;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke-width:0.264583px"
|
||||
y="45.703152"
|
||||
x="130.23187"
|
||||
id="tspan878-57"
|
||||
sodipodi:role="line">builtin scope</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
id="layer1-6-3-4"
|
||||
transform="translate(-38.485254,-185.17167)"
|
||||
style="display:inline;"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="14.287499"
|
||||
y="18.393749"
|
||||
id="text817-0-1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';letter-spacing:0px;word-spacing:0px;fill:#1c1e20;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="78.052086"
|
||||
y="218.07857"
|
||||
id="text821-5-5"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"><tspan
|
||||
sodipodi:role="line"
|
||||
x="78.052086"
|
||||
y="218.07857"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan1835">class range(stop)</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="78.052086"
|
||||
y="225.13412"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan1841"> ...</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="78.052086"
|
||||
y="232.18967"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan1839" /></text>
|
||||
</g>
|
||||
id="tspan878"
|
||||
sodipodi:role="line">global scope</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="global"
|
||||
style="display:inline">
|
||||
id="g841"
|
||||
transform="translate(5.0972261,-2.5366649)">
|
||||
<rect
|
||||
style="fill:#b8d9f8;fill-opacity:1;stroke:#3e99ed;stroke-width:0.84;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;opacity:1;"
|
||||
id="rect846"
|
||||
width="131.78682"
|
||||
height="102.41121"
|
||||
x="38.218014"
|
||||
y="41.986538"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<g
|
||||
id="g851"
|
||||
transform="translate(37.339412,1.476952)"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<rect
|
||||
y="40.089584"
|
||||
x="94.592438"
|
||||
height="8.467"
|
||||
width="38.492985"
|
||||
id="rect846-9"
|
||||
style="opacity:1;fill:#3e99ed;fill-opacity:1;stroke:none;stroke-width:0.214711;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.644134, 0.21471100000000001;stroke-dashoffset:0;stroke-opacity:1;" />
|
||||
<text
|
||||
id="text880"
|
||||
y="45.703152"
|
||||
x="130.23187"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';text-align:end;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
y="45.703152"
|
||||
x="130.23187"
|
||||
id="tspan878"
|
||||
sodipodi:role="line">global scope</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
id="layer1-6"
|
||||
transform="translate(31.868016,1.896952)"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="14.287499"
|
||||
y="18.393749"
|
||||
id="text817" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';letter-spacing:0px;word-spacing:0px;fill:#1c1e20;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="9.260417"
|
||||
y="47.233334"
|
||||
id="text821"><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="47.233334"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan823">ITERATIONS = 10</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="54.288883"
|
||||
id="tspan879" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="61.344433"
|
||||
id="tspan729" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="68.399986"
|
||||
id="tspan731" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="75.455536"
|
||||
id="tspan733" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="82.511086"
|
||||
id="tspan735" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="89.566635"
|
||||
id="tspan737" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="96.622185"
|
||||
id="tspan739" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="103.67773"
|
||||
id="tspan741" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="110.73328"
|
||||
id="tspan743" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="117.78883"
|
||||
id="tspan745" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="124.84438"
|
||||
id="tspan747" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="131.89993"
|
||||
id="tspan749" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="138.95549"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan841">Cls().fn()</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="class"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="display:inline;fill:#e7c2ee;fill-opacity:1;stroke:#a132b8;stroke-width:0.84;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
|
||||
id="rect846-3"
|
||||
width="127.44389"
|
||||
height="79.862946"
|
||||
x="39.937805"
|
||||
y="54.686535"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<g
|
||||
id="g841"
|
||||
transform="translate(36.965242,-0.639713)"
|
||||
style="display:inline;"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<rect
|
||||
y="54.906246"
|
||||
x="95.938049"
|
||||
height="8.467"
|
||||
width="34.898407"
|
||||
id="rect846-3-0"
|
||||
style="opacity:1;fill:#a132b8;fill-opacity:1;stroke:none;stroke-width:0.204441;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.613322, 0.20444100000000001;stroke-dashoffset:0;stroke-opacity:1;" />
|
||||
<text
|
||||
id="text880-4"
|
||||
y="54.906246"
|
||||
x="95.938049"
|
||||
height="8.467"
|
||||
width="34.898407"
|
||||
id="rect846-3-0"
|
||||
style="opacity:1;fill:#a132b8;fill-opacity:1;stroke:none;stroke-width:0.20444053;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.61332162, 0.20444055;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<text
|
||||
id="text880-4"
|
||||
y="60.584724"
|
||||
x="128.49477"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
y="60.584724"
|
||||
x="128.49477"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
|
||||
y="60.584724"
|
||||
x="128.49477"
|
||||
id="tspan878-5"
|
||||
sodipodi:role="line">class scope</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
id="layer1-6-3"
|
||||
transform="translate(-35.19329,-156.57945)"
|
||||
style="display:inline;"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="14.287499"
|
||||
y="18.393749"
|
||||
id="text817-0" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';letter-spacing:0px;word-spacing:0px;fill:#1c1e20;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="78.052086"
|
||||
y="218.07857"
|
||||
id="text821-5"><tspan
|
||||
sodipodi:role="line"
|
||||
x="78.052086"
|
||||
y="218.07857"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan843-7">class Cls:</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="78.052086"
|
||||
y="225.13412"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan939-2"> class_attribute = 20</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="78.052086"
|
||||
y="232.18967"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan841-4" /></text>
|
||||
</g>
|
||||
id="tspan878-5"
|
||||
sodipodi:role="line">class scope</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="function"
|
||||
style="display:inline">
|
||||
id="g846"
|
||||
transform="translate(4.72303,-0.41999999)">
|
||||
<rect
|
||||
style="display:inline;fill:#f7b9be;fill-opacity:1;stroke:#eb4b59;stroke-width:0.84;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;"
|
||||
id="rect846-3-9"
|
||||
width="112.51761"
|
||||
height="56.470001"
|
||||
x="51.976353"
|
||||
y="75.588638"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<g
|
||||
id="g846"
|
||||
transform="translate(36.591047,1.476952)"
|
||||
style="display:inline;"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<rect
|
||||
y="73.691681"
|
||||
x="84.802856"
|
||||
height="8.4666672"
|
||||
width="43.520058"
|
||||
id="rect846-3-9-9"
|
||||
style="opacity:1;fill:#eb4b59;fill-opacity:1;stroke:none;stroke-width:0.228297;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.684891, 0.22829700000000000;stroke-dashoffset:0;stroke-opacity:1;" />
|
||||
<text
|
||||
transform="scale(1.0012369,0.99876463)"
|
||||
id="text880-2"
|
||||
y="73.691681"
|
||||
x="84.802856"
|
||||
height="8.4666672"
|
||||
width="43.520058"
|
||||
id="rect846-3-9-9"
|
||||
style="opacity:1;fill:#eb4b59;fill-opacity:1;stroke:none;stroke-width:0.22829711;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.68489136, 0.22829713;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<text
|
||||
transform="scale(1.0012369,0.99876463)"
|
||||
id="text880-2"
|
||||
y="79.502075"
|
||||
x="125.36142"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
y="79.502075"
|
||||
x="125.36142"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
|
||||
y="79.502075"
|
||||
x="125.36142"
|
||||
id="tspan878-0"
|
||||
sodipodi:role="line">function scope</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
id="layer1-6-1"
|
||||
transform="translate(-51.06829,-6.1449257)"
|
||||
style="display:inline;"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="14.287499"
|
||||
y="18.393749"
|
||||
id="text817-1" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';letter-spacing:0px;word-spacing:0px;fill:#1c1e20;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="97.706848"
|
||||
y="89.566666"
|
||||
id="text821-2"><tspan
|
||||
sodipodi:role="line"
|
||||
x="97.706848"
|
||||
y="89.566666"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan827-7"> def fn():</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="97.706848"
|
||||
y="96.622215"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan829-3"> for i in range(ITERATIONS):</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="97.706848"
|
||||
y="103.67776"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan831-4"> ...</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="97.706848"
|
||||
y="110.73332"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan841-0" /></text>
|
||||
</g>
|
||||
id="tspan878-0"
|
||||
sodipodi:role="line">function scope</tspan></text>
|
||||
</g>
|
||||
<rect
|
||||
style="opacity:1;fill:#fdd2b3;fill-opacity:1;stroke:#fb8d3f;stroke-width:0.83999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
|
||||
id="rect846-3-9-3"
|
||||
width="95.492546"
|
||||
height="26.722916"
|
||||
x="34.081287"
|
||||
y="101.19372" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer6"
|
||||
inkscape:label="comprehension"
|
||||
style="display:inline">
|
||||
id="g846-6"
|
||||
transform="translate(1.6709308,27.08203)">
|
||||
<rect
|
||||
style="display:inline;fill:#fdd2b3;fill-opacity:1;stroke:#fb8d3f;stroke-width:0.84;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;"
|
||||
id="rect846-3-9-3"
|
||||
width="95.492546"
|
||||
height="26.722916"
|
||||
x="65.949303"
|
||||
y="103.09068"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<g
|
||||
id="g846-6"
|
||||
transform="translate(33.538946,28.978982)"
|
||||
style="display:inline;"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<rect
|
||||
y="73.691681"
|
||||
x="65.532722"
|
||||
height="8.4666672"
|
||||
width="62.79018"
|
||||
id="rect846-3-9-9-7"
|
||||
style="opacity:1;fill:#fb8d3f;fill-opacity:1;stroke:none;stroke-width:0.231105;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.693314, 0.23110500000000000;stroke-dashoffset:0;stroke-opacity:1;" />
|
||||
<text
|
||||
transform="scale(1.0012369,0.99876463)"
|
||||
id="text880-2-5"
|
||||
y="73.691681"
|
||||
x="65.532722"
|
||||
height="8.4666672"
|
||||
width="62.79018"
|
||||
id="rect846-3-9-9-7"
|
||||
style="opacity:1;fill:#fb8d3f;fill-opacity:1;stroke:none;stroke-width:0.23110457;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.69331375, 0.23110459;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<text
|
||||
transform="scale(1.0012369,0.99876463)"
|
||||
id="text880-2-5"
|
||||
y="79.502075"
|
||||
x="125.36142"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
y="79.502075"
|
||||
x="125.36142"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Helvetica Neue';-inkscape-font-specification:'Helvetica Neue Medium';font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:end;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:end;white-space:normal;shape-padding:0;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
|
||||
y="79.502075"
|
||||
x="125.36142"
|
||||
id="tspan878-0-3"
|
||||
sodipodi:role="line">comprehension scope</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
id="layer1-6-1-7"
|
||||
transform="translate(-73.368884,-116.51398)"
|
||||
style="display:inline;"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="14.287499"
|
||||
y="18.393749"
|
||||
id="text817-1-3" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444000000000035px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';letter-spacing:0px;word-spacing:0px;fill:#1c1e20;fill-opacity:1;stroke:none;stroke-width:0.26458300000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"
|
||||
x="125.67709"
|
||||
y="228.66191"
|
||||
id="text821-2-6"><tspan
|
||||
sodipodi:role="line"
|
||||
x="125.67709"
|
||||
y="228.66191"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan833-7-2"> return [</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="125.67709"
|
||||
y="235.71745"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan870-2-9"> i for i in range(10)</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="125.67709"
|
||||
y="242.77301"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan872-56-7"> ]</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="125.67709"
|
||||
y="249.82855"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458300000000001px;"
|
||||
id="tspan841-0-8" /></text>
|
||||
</g>
|
||||
id="tspan878-0-3"
|
||||
sodipodi:role="line">comprehension scope</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-5.9299994,-39.669584)"
|
||||
sodipodi:insensitive="true">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="14.287499"
|
||||
y="18.393749"
|
||||
id="text817"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan815"
|
||||
x="14.287499"
|
||||
y="23.332638"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro';stroke-width:0.26458332px" /></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:5.64444447px;line-height:125%;font-family:'Source Sans Pro';-inkscape-font-specification:'Source Sans Pro Light';letter-spacing:0px;word-spacing:0px;fill:#1c1e20;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="9.260417"
|
||||
y="47.233334"
|
||||
id="text821"><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="47.233334"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan823">ITERATIONS = 10</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="54.288891"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan837" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="61.344444"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan843">class Cls:</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="68.400002"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan939"> class_attribute = 20</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="75.455559"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan918" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="82.511108"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan827"> def fn():</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="89.566666"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan829"> for i in range(ITERATIONS):</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="96.622223"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan831"> ...</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="103.67778"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan876" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="110.73334"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan833"> return [</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="117.78889"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan870"> i for i in range(10)</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="124.84444"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan872"> ]</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="131.89999"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan879" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="9.260417"
|
||||
y="138.95555"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Source Code Pro';-inkscape-font-specification:'Source Code Pro';fill:#1c1e20;fill-opacity:1;stroke-width:0.26458332px"
|
||||
id="tspan841">Cls().fn()</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 19 KiB |
|
|
@ -26,7 +26,7 @@ then edit the produced ``.libcst.codemod.yaml`` file::
|
|||
python3 -m libcst.tool initialize .
|
||||
|
||||
The file includes provisions for customizing any generated code marker, calling an
|
||||
external code formatter such as `black <https://pypi.org/project/black/>`_, blacklisting
|
||||
external code formatter such as `black <https://pypi.org/project/black/>`_, blackisting
|
||||
patterns of files you never wish to touch and a list of modules that contain valid
|
||||
codemods that can be executed. If you want to write and run codemods specific to your
|
||||
repository or organization, you can add an in-repo module location to the list of
|
||||
|
|
@ -135,18 +135,16 @@ replaces any string which matches our string command-line argument with a consta
|
|||
It also takes care of adding the import required for the constant to be defined properly.
|
||||
|
||||
Cool! Let's look at the command-line help for this codemod. Let's assume you saved it
|
||||
as ``constant_folding.py``. You can get help for the
|
||||
as ``constant_folding.py`` inside ``libcst.codemod.commands``. You can get help for the
|
||||
codemod by running the following command::
|
||||
|
||||
python3 -m libcst.tool codemod -x constant_folding.ConvertConstantCommand --help
|
||||
python3 -m libcst.tool codemod constant_folding.ConvertConstantCommand --help
|
||||
|
||||
Notice that along with the default arguments, the ``--string`` and ``--constant``
|
||||
arguments are present in the help, and the command-line description has been updated
|
||||
with the codemod's description string. You'll notice that the codemod also shows up
|
||||
on ``libcst.tool list``.
|
||||
|
||||
And ``-x`` flag allows to load any module as a codemod in addition to the standard ones.
|
||||
|
||||
----------------
|
||||
Testing Codemods
|
||||
----------------
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = "LibCST"
|
||||
copyright = "Meta Platforms, Inc. and affiliates"
|
||||
copyright = "2019, Facebook"
|
||||
author = "Benjamin Woodruff, Jennifer Taylor, Carl Meyer, Jimmy Lai, Ray Zeng"
|
||||
|
||||
# The short X.Y version
|
||||
|
|
@ -71,7 +71,7 @@ master_doc = "index"
|
|||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = "en"
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
|
|
@ -196,7 +196,6 @@ intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
|
|||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
|
||||
# -- autodoc customization
|
||||
def strip_class_signature(app, what, name, obj, options, signature, return_annotation):
|
||||
if what == "class":
|
||||
|
|
@ -219,7 +218,7 @@ def setup(app):
|
|||
|
||||
|
||||
nbsphinx_prolog = r"""
|
||||
{% set docname = 'docs/source/' + env.doc2path(env.docname, base=None)|string%}
|
||||
{% set docname = 'docs/source/' + env.doc2path(env.docname, base=None) %}
|
||||
|
||||
.. only:: html
|
||||
|
||||
|
|
@ -228,6 +227,6 @@ nbsphinx_prolog = r"""
|
|||
Interactive online tutorial: |notebook-badge|
|
||||
|
||||
.. |notebook-badge| image:: https://img.shields.io/badge/notebook-run-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC
|
||||
:target: https://mybinder.org/v2/gh/Instagram/LibCST/main?filepath={{ docname }}
|
||||
:target: https://mybinder.org/v2/gh/Instagram/LibCST/master?filepath={{ docname }}
|
||||
:alt: Notebook
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -32,18 +32,3 @@ Functions that assist in traversing an existing LibCST tree.
|
|||
.. autofunction:: libcst.helpers.get_full_name_for_node
|
||||
.. autofunction:: libcst.helpers.get_full_name_for_node_or_raise
|
||||
.. autofunction:: libcst.helpers.ensure_type
|
||||
|
||||
Node fields filtering Helpers
|
||||
-----------------------------
|
||||
|
||||
Function that assist when handling CST nodes' fields.
|
||||
|
||||
.. autofunction:: libcst.helpers.filter_node_fields
|
||||
|
||||
And lower level functions:
|
||||
|
||||
.. autofunction:: libcst.helpers.get_node_fields
|
||||
.. autofunction:: libcst.helpers.is_whitespace_node_field
|
||||
.. autofunction:: libcst.helpers.is_syntax_node_field
|
||||
.. autofunction:: libcst.helpers.is_default_node_field
|
||||
.. autofunction:: libcst.helpers.get_field_default_value
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Matchers
|
|||
========
|
||||
|
||||
Matchers are provided as a way of asking whether a particular LibCST node and its
|
||||
children match a particular shape. It is possible to write a visitor that
|
||||
children match the a particular shape. It is possible to write a visitor that
|
||||
tracks attributes using ``visit_<Node>`` methods. It is also possible to implement
|
||||
manual instance checking and traversal of a node's children. However, both are
|
||||
cumbersome to write and hard to understand. Matchers offer a more concise way of
|
||||
|
|
@ -13,7 +13,7 @@ defining what attributes on a node matter when matching against predefined patte
|
|||
|
||||
To accomplish this, a matcher has been created which corresponds to each LibCST
|
||||
node documented in :ref:`libcst-nodes`. Matchers default each of their attributes
|
||||
to the special sentinel matcher :func:`~libcst.matchers.DoNotCare`. When constructing
|
||||
to the special sentinal matcher :func:`~libcst.matchers.DoNotCare`. When constructing
|
||||
a matcher, you can initialize the node with only the values of attributes that
|
||||
you are concerned with, leaving the rest of the attributes set to
|
||||
:func:`~libcst.matchers.DoNotCare` in order to skip comparing against them.
|
||||
|
|
@ -79,7 +79,7 @@ Traversal Order
|
|||
^^^^^^^^^^^^^^^
|
||||
|
||||
Visit and leave functions created using :func:`~libcst.matchers.visit` or
|
||||
:func:`~libcst.matchers.leave` follow the traversal order rules laid out in
|
||||
:func:`~libcst.matchers.leave` follow the traveral order rules laid out in
|
||||
LibCST's visitor :ref:`libcst-visitor-traversal` with one additional rule. Any
|
||||
visit function created using the :func:`~libcst.matchers.visit` decorator will be
|
||||
called **before** a ``visit_<Node>`` function if it is defined for your visitor.
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@
|
|||
"into your :ref:`libcst-visitors` in order to identify which nodes you care ",
|
||||
"about. Matcher :ref:`libcst-matcher-decorators` help reduce that boilerplate.\n",
|
||||
"\n",
|
||||
"Say you wanted to invert the boolean literals in functions which ",
|
||||
"Say you wanted to invert the the boolean literals in functions which ",
|
||||
"match the above ``best_is_call_with_booleans``. You could build something ",
|
||||
"that looks like the following:"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ numbers of nodes through the :class:`~libcst.metadata.PositionProvider`:
|
|||
.. code-block:: python
|
||||
|
||||
class NamePrinter(cst.CSTVisitor):
|
||||
METADATA_DEPENDENCIES = (cst.metadata.PositionProvider,)
|
||||
METADATA_DEPENDENCIES = (cst.PositionProvider,)
|
||||
|
||||
def visit_Name(self, node: cst.Name) -> None:
|
||||
pos = self.get_metadata(cst.metadata.PositionProvider, node).start
|
||||
pos = self.get_metadata(cst.PositionProvider, node).start
|
||||
print(f"{node.value} found at line {pos.line}, column {pos.column}")
|
||||
|
||||
wrapper = cst.metadata.MetadataWrapper(cst.parse_module("x = 1"))
|
||||
|
|
@ -40,8 +40,8 @@ The wrapper provides a :func:`~libcst.metadata.MetadataWrapper.resolve` function
|
|||
.. autoclass:: libcst.metadata.MetadataWrapper
|
||||
:special-members: __init__
|
||||
|
||||
If you're working with visitors, which extend :class:`~libcst.MetadataDependent`,
|
||||
metadata dependencies will be automatically computed when visited by a
|
||||
If you're working with visitors, which extend :class:`~libcst.MetadataDependent`,
|
||||
metadata dependencies will be automatically computed when visited by a
|
||||
:class:`~libcst.metadata.MetadataWrapper` and are accessible through
|
||||
:func:`~libcst.MetadataDependent.get_metadata`
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ declaring one of :class:`~libcst.metadata.PositionProvider` or
|
|||
most cases, :class:`~libcst.metadata.PositionProvider` is what you probably
|
||||
want.
|
||||
|
||||
Node positions are represented with :class:`~libcst.metadata.CodeRange`
|
||||
Node positions are is represented with :class:`~libcst.metadata.CodeRange`
|
||||
objects. See :ref:`the above example<libcst-metadata-position-example>`.
|
||||
|
||||
.. autoclass:: libcst.metadata.PositionProvider
|
||||
|
|
@ -134,15 +134,14 @@ New scopes are created for classes, functions, and comprehensions. Other block
|
|||
constructs like conditional statements, loops, and try…except don't create their
|
||||
own scope.
|
||||
|
||||
There are five different types of scopes in Python:
|
||||
:class:`~libcst.metadata.BuiltinScope`,
|
||||
There are four different type of scope in Python:
|
||||
:class:`~libcst.metadata.GlobalScope`,
|
||||
:class:`~libcst.metadata.ClassScope`,
|
||||
:class:`~libcst.metadata.FunctionScope`, and
|
||||
:class:`~libcst.metadata.ComprehensionScope`.
|
||||
|
||||
.. image:: _static/img/python_scopes.png
|
||||
:alt: Diagram showing how the above 5 scopes are nested in each other
|
||||
:alt: Diagram showing how the above 4 scopes are nested in each other
|
||||
:width: 400
|
||||
:align: center
|
||||
|
||||
|
|
@ -176,9 +175,6 @@ assigned or accessed within.
|
|||
:no-undoc-members:
|
||||
:special-members: __contains__, __getitem__, __iter__
|
||||
|
||||
.. autoclass:: libcst.metadata.BuiltinScope
|
||||
:no-undoc-members:
|
||||
|
||||
.. autoclass:: libcst.metadata.GlobalScope
|
||||
:no-undoc-members:
|
||||
|
||||
|
|
@ -203,18 +199,10 @@ We don't call it `fully qualified name <https://en.wikipedia.org/wiki/Fully_qual
|
|||
because the name refers to the current module which doesn't consider the hierarchy of
|
||||
code repository.
|
||||
|
||||
For fully qualified names, there's :class:`~libcst.metadata.FullyQualifiedNameProvider`
|
||||
which is similar to the above but takes the current module's location (relative to some
|
||||
python root folder, usually the repository's root) into account.
|
||||
|
||||
|
||||
.. autoclass:: libcst.metadata.QualifiedNameSource
|
||||
.. autoclass:: libcst.metadata.QualifiedName
|
||||
.. autoclass:: libcst.metadata.QualifiedNameProvider
|
||||
:no-undoc-members:
|
||||
|
||||
.. autoclass:: libcst.metadata.FullyQualifiedNameProvider
|
||||
:no-undoc-members:
|
||||
|
||||
Parent Node Metadata
|
||||
--------------------
|
||||
|
|
@ -226,14 +214,6 @@ We provide :class:`~libcst.metadata.ParentNodeProvider` for those use cases.
|
|||
.. autoclass:: libcst.metadata.ParentNodeProvider
|
||||
:no-undoc-members:
|
||||
|
||||
File Path Metadata
|
||||
------------------
|
||||
This provides the absolute file path on disk for any module being visited.
|
||||
Requires an active :class:`~libcst.metadata.FullRepoManager` when using this provider.
|
||||
|
||||
.. autoclass:: libcst.metadata.FilePathProvider
|
||||
:no-undoc-members:
|
||||
|
||||
Type Inference Metadata
|
||||
-----------------------
|
||||
`Type inference <https://en.wikipedia.org/wiki/Type_inference>`__ is to automatically infer
|
||||
|
|
@ -242,8 +222,8 @@ In Python, type checkers like `Mypy <https://github.com/python/mypy>`_ or
|
|||
`Pyre <https://pyre-check.org/>`__ analyze `type annotations <https://docs.python.org/3/library/typing.html>`__
|
||||
and infer types for expressions.
|
||||
:class:`~libcst.metadata.TypeInferenceProvider` is provided by `Pyre Query API <https://pyre-check.org/docs/querying-pyre.html>`__
|
||||
which requires `setup watchman <https://pyre-check.org/docs/getting-started/>`_ for incremental typechecking.
|
||||
:class:`~libcst.metadata.FullRepoManager` is built for manage the inter process communication to Pyre.
|
||||
which requires `setup watchman <https://pyre-check.org/docs/watchman-integration.html>`_ for incremental typechecking.
|
||||
:class:`~libcst.metadata.FullRepoManger` is built for manage the inter process communication to Pyre.
|
||||
|
||||
.. autoclass:: libcst.metadata.TypeInferenceProvider
|
||||
:no-undoc-members:
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@
|
|||
"source": [
|
||||
"Warn on unused imports and undefined references\n",
|
||||
"===============================================\n",
|
||||
"To find all unused imports, we iterate through :attr:`~libcst.metadata.Scope.assignments` and an assignment is unused when its :attr:`~libcst.metadata.BaseAssignment.references` is empty. To find all undefined references, we iterate through :attr:`~libcst.metadata.Scope.accesses` (we focus on :class:`~libcst.Import`/:class:`~libcst.ImportFrom` assignments) and an access is undefined reference when its :attr:`~libcst.metadata.Access.referents` is empty. When reporting the warning to the developer, we'll want to report the line number and column offset along with the suggestion to make it more clear. We can get position information from :class:`~libcst.metadata.PositionProvider` and print the warnings as follows.\n"
|
||||
"To find all unused imports, we iterate through :attr:`~libcst.metadata.Scope.assignments` and an assignment is unused when its :attr:`~libcst.metadata.BaseAssignment.references` is empty. To find all undefined references, we iterate through :attr:`~libcst.metadata.Scope.accesses` (we focus on :class:`~libcst.Import`/:class:`~libcst.ImportFrom` assignments) and an access is undefined reference when its :attr:`~libcst.metadata.Access.referents` is empty. When reporting the warning to developer, we'll want to report the line number and column offset along with the suggestion to make it more clear. We can get position information from :class:`~libcst.metadata.PositionProvider` and print the warnings as follows.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -136,13 +136,13 @@
|
|||
"Automatically Remove Unused Import\n",
|
||||
"==================================\n",
|
||||
"Unused import is a commmon code suggestion provided by lint tool like `flake8 F401 <https://lintlyci.github.io/Flake8Rules/rules/F401.html>`_ ``imported but unused``.\n",
|
||||
"Even though reporting unused imports is already useful, with LibCST we can provide an automatic fix to remove unused imports. That can make the suggestion more actionable and save developer's time.\n",
|
||||
"Even though reporting unused import is already useful, with LibCST we can provide automatic fix to remove unused import. That can make the suggestion more actionable and save developer's time.\n",
|
||||
"\n",
|
||||
"An import statement may import multiple names, we want to remove those unused names from the import statement. If all the names in the import statement are not used, we remove the entire import.\n",
|
||||
"To remove the unused name, we implement ``RemoveUnusedImportTransformer`` by subclassing :class:`~libcst.CSTTransformer`. We overwrite ``leave_Import`` and ``leave_ImportFrom`` to modify the import statements.\n",
|
||||
"When we find the import node in the lookup table, we iterate through all ``names`` and keep used names in ``names_to_keep``.\n",
|
||||
"When we find the import node in lookup table, we iterate through all ``names`` and keep used names in ``names_to_keep``.\n",
|
||||
"If ``names_to_keep`` is empty, all names are unused and we remove the entire import node.\n",
|
||||
"Otherwise, we update the import node and just remove partial names."
|
||||
"Otherwise, we update the import node and just removing partial names."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -195,7 +195,7 @@
|
|||
"raw_mimetype": "text/restructuredtext"
|
||||
},
|
||||
"source": [
|
||||
"After the transform, we use ``.code`` to generate the fixed code and all unused names are fixed as expected! The difflib is used to show only the changed part and only imported lines are updated as expected."
|
||||
"After the transform, we use ``.code`` to generate fixed code and all unused names are fixed as expected! The difflib is used to show only changed part and only import lines are updated as expected."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "raw",
|
||||
"metadata": {
|
||||
"raw_mimetype": "text/restructuredtext"
|
||||
},
|
||||
"cell_type": "raw",
|
||||
"source": [
|
||||
"====================\n",
|
||||
"Parsing and Visiting\n",
|
||||
"====================\n",
|
||||
"\n",
|
||||
"LibCST provides helpers to parse source code string as a concrete syntax tree. In order to perform static analysis to identify patterns in the tree or modify the tree programmatically, we can use the visitor pattern to traverse the tree. In this tutorial, we demonstrate a common four-step-workflow to build an automated refactoring (codemod) application:\n",
|
||||
"LibCST provides helpers to parse source code string as concrete syntax tree. In order to perform static analysis to identify patterns in the tree or modify the tree programmatically, we can use visitor pattern to traverse the tree. In this tutorial, we demonstrate a common three-step-workflow to build an automated refactoring (codemod) application:\n",
|
||||
"\n",
|
||||
"1. `Parse Source Code <#Parse-Source-Code>`_\n",
|
||||
"2. `Display The Source Code CST <#Display-Source-Code-CST>`_\n",
|
||||
"3. `Build Visitor or Transformer <#Build-Visitor-or-Transformer>`_\n",
|
||||
"4. `Generate Source Code <#Generate-Source-Code>`_\n",
|
||||
"2. `Build Visitor or Transformer <#Build-Visitor-or-Transformer>`_\n",
|
||||
"3. `Generate Source Code <#Generate-Source-Code>`_\n",
|
||||
"\n",
|
||||
"Parse Source Code\n",
|
||||
"=================\n",
|
||||
"LibCST provides various helpers to parse source code as a concrete syntax tree: :func:`~libcst.parse_module`, :func:`~libcst.parse_expression` and :func:`~libcst.parse_statement` (see :doc:`Parsing <parser>` for more detail)."
|
||||
"LibCST provides various helpers to parse source code as concrete syntax tree: :func:`~libcst.parse_module`, :func:`~libcst.parse_expression` and :func:`~libcst.parse_statement` (see :doc:`Parsing <parser>` for more detail). The default :class:`~libcst.CSTNode` repr provides pretty print formatting for reading the tree easily."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -42,42 +41,7 @@
|
|||
"source": [
|
||||
"import libcst as cst\n",
|
||||
"\n",
|
||||
"source_tree = cst.parse_expression(\"1 + 2\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"raw_mimetype": "text/restructuredtext"
|
||||
},
|
||||
"cell_type": "raw",
|
||||
"source": [
|
||||
"|\n",
|
||||
"Display Source Code CST\n",
|
||||
"=======================\n",
|
||||
"The default :class:`~libcst.CSTNode` repr provides pretty print formatting for displaying the entire CST tree."
|
||||
]
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"execution_count": null,
|
||||
"source": "print(source_tree)"
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "raw",
|
||||
"source": "The entire CST tree may be overwhelming at times. To only focus on essential elements of the CST tree, LibCST provides the ``dump`` helper."
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"from libcst.display import dump\n",
|
||||
"\n",
|
||||
"print(dump(source_tree))"
|
||||
"cst.parse_expression(\"1 + 2\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -86,11 +50,9 @@
|
|||
"raw_mimetype": "text/restructuredtext"
|
||||
},
|
||||
"source": [
|
||||
" \n",
|
||||
"|\n",
|
||||
"Example: add typing annotation from pyi stub file to Python source\n",
|
||||
"------------------------------------------------------------------\n",
|
||||
"Python `typing annotation <https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html>`_ was added in Python 3.5. Some Python applications add typing annotations in separate ``pyi`` stub files in order to support old Python versions. When applications decide to stop supporting old Python versions, they'll want to automatically copy the type annotation from a pyi file to a source file. Here we demonstrate how to do that easily using LibCST. The first step is to parse the pyi stub and source files as trees."
|
||||
"Python `typing annotation <https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html>`_ was added in Python 3.5. Some Python applications add typing annotations in separate ``pyi`` stub files in order to support old Python versions. When applications decide to stop supporting old Python versions, they'll want to automatically copy the type annotation from a pyi file to a source file. Here we demonstrate how to do that easliy using LibCST. The first step is to parse the pyi stub and source files as trees."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -106,7 +68,7 @@
|
|||
" self._replace(type=self.type.name))\n",
|
||||
"\n",
|
||||
"def tokenize(code, version_info, start_pos=(1, 0)):\n",
|
||||
" \"\"\"Generate tokens from the source code (string).\"\"\"\n",
|
||||
" \"\"\"Generate tokens from a the source code (string).\"\"\"\n",
|
||||
" lines = split_lines(code, keepends=True)\n",
|
||||
" return tokenize_lines(lines, version_info, start_pos=start_pos)\n",
|
||||
"'''\n",
|
||||
|
|
@ -130,11 +92,10 @@
|
|||
"raw_mimetype": "text/restructuredtext"
|
||||
},
|
||||
"source": [
|
||||
"|\n",
|
||||
"Build Visitor or Transformer\n",
|
||||
"============================\n",
|
||||
"For traversing and modifying the tree, LibCST provides Visitor and Transformer classes similar to the `ast module <https://docs.python.org/3/library/ast.html#ast.NodeVisitor>`_. To implement a visitor (read only) or transformer (read/write), simply implement a subclass of :class:`~libcst.CSTVisitor` or :class:`~libcst.CSTTransformer` (see :doc:`Visitors <visitors>` for more detail).\n",
|
||||
"In the typing example, we need to implement a visitor to collect typing annotations from the stub tree and a transformer to copy the annotation to the function signature. In the visitor, we implement ``visit_FunctionDef`` to collect annotations. Later in the transformer, we implement ``leave_FunctionDef`` to add the collected annotations."
|
||||
"In the typing example, we need to implement a visitor to collect typing annotation from the stub tree and a transformer to copy the annotation to the function signature. In the visitor, we implement ``visit_FunctionDef`` to collect annotations. Later in the transformer, we implement ``leave_FunctionDef`` to add the collected annotations."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -152,7 +113,7 @@
|
|||
" self.stack: List[Tuple[str, ...]] = []\n",
|
||||
" # store the annotations\n",
|
||||
" self.annotations: Dict[\n",
|
||||
" Tuple[str, ...], # key: tuple of canonical class/function name\n",
|
||||
" Tuple[str, ...], # key: tuple of cononical class/function name\n",
|
||||
" Tuple[cst.Parameters, Optional[cst.Annotation]], # value: (params, returns)\n",
|
||||
" ] = {}\n",
|
||||
"\n",
|
||||
|
|
@ -179,7 +140,7 @@
|
|||
" self.stack: List[Tuple[str, ...]] = []\n",
|
||||
" # store the annotations\n",
|
||||
" self.annotations: Dict[\n",
|
||||
" Tuple[str, ...], # key: tuple of canonical class/function name\n",
|
||||
" Tuple[str, ...], # key: tuple of cononical class/function name\n",
|
||||
" Tuple[cst.Parameters, Optional[cst.Annotation]], # value: (params, returns)\n",
|
||||
" ] = annotations\n",
|
||||
"\n",
|
||||
|
|
@ -223,10 +184,9 @@
|
|||
"raw_mimetype": "text/restructuredtext"
|
||||
},
|
||||
"source": [
|
||||
"|\n",
|
||||
"Generate Source Code\n",
|
||||
"====================\n",
|
||||
"Generating the source code from a cst tree is as easy as accessing the :attr:`~libcst.Module.code` attribute on :class:`~libcst.Module`. After the code generation, we often use `ufmt <https://ufmt.omnilib.dev/en/stable/>`_ to reformat the code to keep a consistent coding style."
|
||||
"Generating the source code from a cst tree is as easy as accessing the :attr:`~libcst.Module.code` attribute on :class:`~libcst.Module`. After the code generation, we often use `Black <https://black.readthedocs.io/en/stable/>`_ and `isort <https://isort.readthedocs.io/en/stable/>`_ to reformate the code to keep a consistent coding style."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ Visitors
|
|||
.. autoclass:: libcst.CSTTransformer
|
||||
.. autofunction:: libcst.RemoveFromParent
|
||||
.. autoclass:: libcst.RemovalSentinel
|
||||
.. autoclass:: libcst.FlattenSentinel
|
||||
|
||||
Visit and Leave Helper Functions
|
||||
--------------------------------
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from libcst._batched_visitor import BatchableCSTVisitor, visit_batched
|
||||
from libcst._exceptions import CSTLogicError, MetadataException, ParserSyntaxError
|
||||
from libcst._flatten_sentinel import FlattenSentinel
|
||||
from libcst._exceptions import MetadataException, ParserSyntaxError
|
||||
from libcst._maybe_sentinel import MaybeSentinel
|
||||
from libcst._metadata_dependent import MetadataDependent
|
||||
from libcst._nodes.base import CSTNode, CSTValidationError
|
||||
|
|
@ -29,7 +28,6 @@ from libcst._nodes.expression import (
|
|||
BaseSimpleComp,
|
||||
BaseSlice,
|
||||
BaseString,
|
||||
BaseTemplatedStringContent,
|
||||
BinaryOperation,
|
||||
BooleanOperation,
|
||||
Call,
|
||||
|
|
@ -76,9 +74,6 @@ from libcst._nodes.expression import (
|
|||
StarredElement,
|
||||
Subscript,
|
||||
SubscriptElement,
|
||||
TemplatedString,
|
||||
TemplatedStringExpression,
|
||||
TemplatedStringText,
|
||||
Tuple,
|
||||
UnaryOperation,
|
||||
Yield,
|
||||
|
|
@ -157,7 +152,6 @@ from libcst._nodes.statement import (
|
|||
Del,
|
||||
Else,
|
||||
ExceptHandler,
|
||||
ExceptStarHandler,
|
||||
Expr,
|
||||
Finally,
|
||||
For,
|
||||
|
|
@ -168,38 +162,14 @@ from libcst._nodes.statement import (
|
|||
ImportAlias,
|
||||
ImportFrom,
|
||||
IndentedBlock,
|
||||
Match,
|
||||
MatchAs,
|
||||
MatchCase,
|
||||
MatchClass,
|
||||
MatchKeywordElement,
|
||||
MatchList,
|
||||
MatchMapping,
|
||||
MatchMappingElement,
|
||||
MatchOr,
|
||||
MatchOrElement,
|
||||
MatchPattern,
|
||||
MatchSequence,
|
||||
MatchSequenceElement,
|
||||
MatchSingleton,
|
||||
MatchStar,
|
||||
MatchTuple,
|
||||
MatchValue,
|
||||
NameItem,
|
||||
Nonlocal,
|
||||
ParamSpec,
|
||||
Pass,
|
||||
Raise,
|
||||
Return,
|
||||
SimpleStatementLine,
|
||||
SimpleStatementSuite,
|
||||
Try,
|
||||
TryStar,
|
||||
TypeAlias,
|
||||
TypeParam,
|
||||
TypeParameters,
|
||||
TypeVar,
|
||||
TypeVarTuple,
|
||||
While,
|
||||
With,
|
||||
WithItem,
|
||||
|
|
@ -219,12 +189,8 @@ from libcst._parser.types.config import (
|
|||
PartialParserConfig,
|
||||
)
|
||||
from libcst._removal_sentinel import RemovalSentinel, RemoveFromParent
|
||||
from libcst._version import LIBCST_VERSION
|
||||
from libcst._visitors import CSTNodeT, CSTTransformer, CSTVisitor, CSTVisitorT
|
||||
|
||||
try:
|
||||
from libcst._version import version as LIBCST_VERSION
|
||||
except ImportError:
|
||||
LIBCST_VERSION = "unknown"
|
||||
from libcst.helpers import ( # from libcst import ensure_type is deprecated, will be removed in 0.4.0
|
||||
ensure_type,
|
||||
)
|
||||
|
|
@ -235,6 +201,7 @@ from libcst.metadata.base_provider import (
|
|||
)
|
||||
from libcst.metadata.wrapper import MetadataWrapper
|
||||
|
||||
|
||||
__all__ = [
|
||||
"KNOWN_PYTHON_VERSION_STRINGS",
|
||||
"LIBCST_VERSION",
|
||||
|
|
@ -244,9 +211,7 @@ __all__ = [
|
|||
"CSTValidationError",
|
||||
"CSTVisitor",
|
||||
"CSTVisitorT",
|
||||
"FlattenSentinel",
|
||||
"MaybeSentinel",
|
||||
"CSTLogicError",
|
||||
"MetadataException",
|
||||
"ParserSyntaxError",
|
||||
"PartialParserConfig",
|
||||
|
|
@ -272,7 +237,6 @@ __all__ = [
|
|||
"BaseElement",
|
||||
"BaseExpression",
|
||||
"BaseFormattedStringContent",
|
||||
"BaseTemplatedStringContent",
|
||||
"BaseList",
|
||||
"BaseNumber",
|
||||
"BaseSet",
|
||||
|
|
@ -296,9 +260,6 @@ __all__ = [
|
|||
"FormattedString",
|
||||
"FormattedStringExpression",
|
||||
"FormattedStringText",
|
||||
"TemplatedString",
|
||||
"TemplatedStringText",
|
||||
"TemplatedStringExpression",
|
||||
"From",
|
||||
"GeneratorExp",
|
||||
"IfExp",
|
||||
|
|
@ -401,7 +362,6 @@ __all__ = [
|
|||
"Del",
|
||||
"Else",
|
||||
"ExceptHandler",
|
||||
"ExceptStarHandler",
|
||||
"Expr",
|
||||
"Finally",
|
||||
"For",
|
||||
|
|
@ -412,23 +372,6 @@ __all__ = [
|
|||
"ImportAlias",
|
||||
"ImportFrom",
|
||||
"IndentedBlock",
|
||||
"Match",
|
||||
"MatchCase",
|
||||
"MatchAs",
|
||||
"MatchClass",
|
||||
"MatchKeywordElement",
|
||||
"MatchList",
|
||||
"MatchMapping",
|
||||
"MatchMappingElement",
|
||||
"MatchOr",
|
||||
"MatchOrElement",
|
||||
"MatchPattern",
|
||||
"MatchSequence",
|
||||
"MatchSequenceElement",
|
||||
"MatchSingleton",
|
||||
"MatchStar",
|
||||
"MatchTuple",
|
||||
"MatchValue",
|
||||
"NameItem",
|
||||
"Nonlocal",
|
||||
"Pass",
|
||||
|
|
@ -437,7 +380,6 @@ __all__ = [
|
|||
"SimpleStatementLine",
|
||||
"SimpleStatementSuite",
|
||||
"Try",
|
||||
"TryStar",
|
||||
"While",
|
||||
"With",
|
||||
"WithItem",
|
||||
|
|
@ -453,10 +395,4 @@ __all__ = [
|
|||
"VisitorMetadataProvider",
|
||||
"MetadataDependent",
|
||||
"MetadataWrapper",
|
||||
"TypeVar",
|
||||
"TypeVarTuple",
|
||||
"ParamSpec",
|
||||
"TypeParam",
|
||||
"TypeParameters",
|
||||
"TypeAlias",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
# This file is derived from github.com/ericvsmith/dataclasses, and is Apache 2 licensed.
|
||||
# https://github.com/ericvsmith/dataclasses/blob/ae712dd993420d43444f188f452/LICENSE.txt
|
||||
# https://github.com/ericvsmith/dataclasses/blob/ae712dd993420d43444f/dataclass_tools.py
|
||||
# Changed: takes slots in base classes into account when creating slots
|
||||
|
||||
import dataclasses
|
||||
from itertools import chain, filterfalse
|
||||
from typing import Any, Mapping, Type, TypeVar
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
|
|
@ -21,14 +20,7 @@ def add_slots(cls: Type[_T]) -> Type[_T]:
|
|||
# Create a new dict for our new class.
|
||||
cls_dict = dict(cls.__dict__)
|
||||
field_names = tuple(f.name for f in dataclasses.fields(cls))
|
||||
inherited_slots = set(
|
||||
chain.from_iterable(
|
||||
superclass.__dict__.get("__slots__", ()) for superclass in cls.mro()
|
||||
)
|
||||
)
|
||||
cls_dict["__slots__"] = tuple(
|
||||
filterfalse(inherited_slots.__contains__, field_names)
|
||||
)
|
||||
cls_dict["__slots__"] = field_names
|
||||
for field_name in field_names:
|
||||
# Remove our attributes, if present. They'll still be
|
||||
# available in _MARKER.
|
||||
|
|
@ -38,10 +30,15 @@ def add_slots(cls: Type[_T]) -> Type[_T]:
|
|||
|
||||
# Create the class.
|
||||
qualname = getattr(cls, "__qualname__", None)
|
||||
|
||||
# pyre-fixme[9]: cls has type `Type[Variable[_T]]`; used as `_T`.
|
||||
# pyre-fixme[19]: Expected 0 positional arguments.
|
||||
cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
|
||||
try:
|
||||
# GenericMeta in py3.6 requires us to track __orig_bases__. This is fixed in py3.7
|
||||
# by the removal of GenericMeta. We should just be able to use cls.__bases__ in the
|
||||
# future.
|
||||
bases = getattr(cls, "__orig_bases__", cls.__bases__)
|
||||
cls = type(cls)(cls.__name__, bases, cls_dict)
|
||||
except TypeError:
|
||||
# We're in py3.7 and should use cls.__bases__
|
||||
cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
|
||||
if qualname is not None:
|
||||
cls.__qualname__ = qualname
|
||||
|
||||
|
|
@ -50,14 +47,12 @@ def add_slots(cls: Type[_T]) -> Type[_T]:
|
|||
|
||||
def __getstate__(self: object) -> Mapping[str, Any]:
|
||||
return {
|
||||
field.name: getattr(self, field.name)
|
||||
for field in dataclasses.fields(self)
|
||||
if hasattr(self, field.name)
|
||||
slot: getattr(self, slot) for slot in self.__slots__ if hasattr(self, slot)
|
||||
}
|
||||
|
||||
def __setstate__(self: object, state: Mapping[str, Any]) -> None:
|
||||
for fieldname, value in state.items():
|
||||
object.__setattr__(self, fieldname, value)
|
||||
for slot, value in state.items():
|
||||
object.__setattr__(self, slot, value)
|
||||
|
||||
cls.__getstate__ = __getstate__
|
||||
cls.__setstate__ = __setstate__
|
||||
|
|
|
|||
|
|
@ -1,24 +1,25 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import inspect
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
cast,
|
||||
Iterable,
|
||||
List,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
TYPE_CHECKING,
|
||||
cast,
|
||||
)
|
||||
|
||||
from libcst._metadata_dependent import MetadataDependent
|
||||
from libcst._typed_visitor import CSTTypedVisitorFunctions
|
||||
from libcst._visitors import CSTNodeT, CSTVisitor
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from libcst._nodes.base import CSTNode # noqa: F401
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from enum import auto, Enum
|
||||
from typing import Any, Callable, final, Optional, Sequence, Tuple
|
||||
from enum import Enum, auto
|
||||
from typing import Any, Callable, Iterable, Optional, Sequence, Tuple, Union
|
||||
|
||||
from typing_extensions import final
|
||||
|
||||
from libcst._parser.parso.pgen2.generator import ReservedString
|
||||
from libcst._parser.parso.python.token import PythonTokenTypes, TokenType
|
||||
from libcst._parser.types.token import Token
|
||||
from libcst._tabs import expand_tabs
|
||||
|
||||
|
||||
_EOF_STR: str = "end of file (EOF)"
|
||||
_INDENT_STR: str = "an indent"
|
||||
_DEDENT_STR: str = "a dedent"
|
||||
_NEWLINE_CHARS: str = "\r\n"
|
||||
|
||||
|
||||
|
|
@ -16,10 +24,42 @@ class EOFSentinel(Enum):
|
|||
EOF = auto()
|
||||
|
||||
|
||||
class CSTLogicError(Exception):
|
||||
"""General purpose internal error within LibCST itself."""
|
||||
def get_expected_str(
|
||||
encountered: Union[Token, EOFSentinel],
|
||||
expected: Union[Iterable[Union[TokenType, ReservedString]], EOFSentinel],
|
||||
) -> str:
|
||||
if (
|
||||
isinstance(encountered, EOFSentinel)
|
||||
or encountered.type is PythonTokenTypes.ENDMARKER
|
||||
):
|
||||
encountered_str = _EOF_STR
|
||||
elif encountered.type is PythonTokenTypes.INDENT:
|
||||
encountered_str = _INDENT_STR
|
||||
elif encountered.type is PythonTokenTypes.DEDENT:
|
||||
encountered_str = _DEDENT_STR
|
||||
else:
|
||||
encountered_str = repr(encountered.string)
|
||||
|
||||
pass
|
||||
if isinstance(expected, EOFSentinel):
|
||||
expected_names = [_EOF_STR]
|
||||
else:
|
||||
expected_names = sorted(
|
||||
[
|
||||
repr(el.name) if isinstance(el, TokenType) else repr(el.value)
|
||||
for el in expected
|
||||
]
|
||||
)
|
||||
|
||||
if len(expected_names) > 10:
|
||||
# There's too many possibilities, so it's probably not useful to list them.
|
||||
# Instead, let's just abbreviate the message.
|
||||
return f"Unexpectedly encountered {encountered_str}."
|
||||
else:
|
||||
if len(expected_names) == 1:
|
||||
expected_str = expected_names[0]
|
||||
else:
|
||||
expected_str = f"{', '.join(expected_names[:-1])}, or {expected_names[-1]}"
|
||||
return f"Encountered {encountered_str}, but expected {expected_str}."
|
||||
|
||||
|
||||
# pyre-fixme[2]: 'Any' type isn't pyre-strict.
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import sys
|
||||
|
||||
# PEP 585
|
||||
if sys.version_info < (3, 9):
|
||||
from typing import Iterable, Sequence
|
||||
else:
|
||||
from collections.abc import Iterable, Sequence
|
||||
|
||||
from libcst._types import CSTNodeT_co
|
||||
|
||||
|
||||
class FlattenSentinel(Sequence[CSTNodeT_co]):
|
||||
"""
|
||||
A :class:`FlattenSentinel` may be returned by a :meth:`CSTTransformer.on_leave`
|
||||
method when one wants to replace a node with multiple nodes. The replaced
|
||||
node must be contained in a `Sequence` attribute such as
|
||||
:attr:`~libcst.Module.body`. This is generally the case for
|
||||
:class:`~libcst.BaseStatement` and :class:`~libcst.BaseSmallStatement`.
|
||||
For example to insert a print before every return::
|
||||
|
||||
def leave_Return(
|
||||
self, original_node: cst.Return, updated_node: cst.Return
|
||||
) -> Union[cst.Return, cst.RemovalSentinel, cst.FlattenSentinel[cst.BaseSmallStatement]]:
|
||||
log_stmt = cst.Expr(cst.parse_expression("print('returning')"))
|
||||
return cst.FlattenSentinel([log_stmt, updated_node])
|
||||
|
||||
Returning an empty :class:`FlattenSentinel` is equivalent to returning
|
||||
:attr:`cst.RemovalSentinel.REMOVE` and is subject to its requirements.
|
||||
"""
|
||||
|
||||
nodes: Sequence[CSTNodeT_co]
|
||||
|
||||
def __init__(self, nodes: Iterable[CSTNodeT_co]) -> None:
|
||||
self.nodes = tuple(nodes)
|
||||
|
||||
def __getitem__(self, idx: int) -> CSTNodeT_co:
|
||||
return self.nodes[idx]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.nodes)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from enum import auto, Enum
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class MaybeSentinel(Enum):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -7,19 +7,17 @@ import inspect
|
|||
from abc import ABC
|
||||
from contextlib import contextmanager
|
||||
from typing import (
|
||||
Callable,
|
||||
cast,
|
||||
TYPE_CHECKING,
|
||||
ClassVar,
|
||||
Collection,
|
||||
Generic,
|
||||
Iterator,
|
||||
Mapping,
|
||||
Type,
|
||||
TYPE_CHECKING,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Circular dependency for typing reasons only
|
||||
from libcst._nodes.base import CSTNode # noqa: F401
|
||||
|
|
@ -32,28 +30,7 @@ if TYPE_CHECKING:
|
|||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class _UNDEFINED_DEFAULT:
|
||||
pass
|
||||
|
||||
|
||||
class LazyValue(Generic[_T]):
|
||||
"""
|
||||
The class for implementing a lazy metadata loading mechanism that improves the
|
||||
performance when retriving expensive metadata (e.g., qualified names). Providers
|
||||
including :class:`~libcst.metadata.QualifiedNameProvider` use this class to load
|
||||
the metadata of a certain node lazily when calling
|
||||
:func:`~libcst.MetadataDependent.get_metadata`.
|
||||
"""
|
||||
|
||||
def __init__(self, callable: Callable[[], _T]) -> None:
|
||||
self.callable = callable
|
||||
self.return_value: Union[_T, Type[_UNDEFINED_DEFAULT]] = _UNDEFINED_DEFAULT
|
||||
|
||||
def __call__(self) -> _T:
|
||||
if self.return_value is _UNDEFINED_DEFAULT:
|
||||
self.return_value = self.callable()
|
||||
return cast(_T, self.return_value)
|
||||
_UNDEFINED_DEFAULT = object()
|
||||
|
||||
|
||||
class MetadataDependent(ABC):
|
||||
|
|
@ -131,9 +108,6 @@ class MetadataDependent(ABC):
|
|||
)
|
||||
|
||||
if default is not _UNDEFINED_DEFAULT:
|
||||
value = self.metadata[key].get(node, default)
|
||||
return cast(_T, self.metadata[key].get(node, default))
|
||||
else:
|
||||
value = self.metadata[key][node]
|
||||
if isinstance(value, LazyValue):
|
||||
value = value()
|
||||
return cast(_T, value)
|
||||
return cast(_T, self.metadata[key][node])
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -6,16 +6,15 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass, field, fields, replace
|
||||
from typing import Any, cast, ClassVar, Dict, List, Mapping, Sequence, TypeVar, Union
|
||||
from typing import Any, Dict, List, Mapping, Sequence, TypeVar, Union, cast
|
||||
|
||||
from libcst import CSTLogicError
|
||||
from libcst._flatten_sentinel import FlattenSentinel
|
||||
from libcst._nodes.internal import CodegenState
|
||||
from libcst._removal_sentinel import RemovalSentinel
|
||||
from libcst._type_enforce import is_value_of_type
|
||||
from libcst._types import CSTNodeT
|
||||
from libcst._visitors import CSTTransformer, CSTVisitor, CSTVisitorT
|
||||
|
||||
|
||||
_CSTNodeSelfT = TypeVar("_CSTNodeSelfT", bound="CSTNode")
|
||||
_EMPTY_SEQUENCE: Sequence["CSTNode"] = ()
|
||||
|
||||
|
|
@ -110,8 +109,6 @@ def _clone(val: object) -> object:
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class CSTNode(ABC):
|
||||
__slots__: ClassVar[Sequence[str]] = ()
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
# PERF: It might make more sense to move validation work into the visitor, which
|
||||
# would allow us to avoid validating the tree when parsing a file.
|
||||
|
|
@ -210,7 +207,7 @@ class CSTNode(ABC):
|
|||
|
||||
def visit(
|
||||
self: _CSTNodeSelfT, visitor: CSTVisitorT
|
||||
) -> Union[_CSTNodeSelfT, RemovalSentinel, FlattenSentinel[_CSTNodeSelfT]]:
|
||||
) -> Union[_CSTNodeSelfT, RemovalSentinel]:
|
||||
"""
|
||||
Visits the current node, its children, and all transitive children using
|
||||
the given visitor's callbacks.
|
||||
|
|
@ -237,8 +234,8 @@ class CSTNode(ABC):
|
|||
leave_result = visitor.on_leave(self, with_updated_children)
|
||||
|
||||
# validate return type of the user-defined `visitor.on_leave` method
|
||||
if not isinstance(leave_result, (CSTNode, RemovalSentinel, FlattenSentinel)):
|
||||
raise CSTValidationError(
|
||||
if not isinstance(leave_result, (CSTNode, RemovalSentinel)):
|
||||
raise Exception(
|
||||
"Expected a node of type CSTNode or a RemovalSentinel, "
|
||||
+ f"but got a return value of {type(leave_result).__name__}"
|
||||
)
|
||||
|
|
@ -293,7 +290,8 @@ class CSTNode(ABC):
|
|||
return False
|
||||
|
||||
@abstractmethod
|
||||
def _codegen_impl(self, state: CodegenState) -> None: ...
|
||||
def _codegen_impl(self, state: CodegenState) -> None:
|
||||
...
|
||||
|
||||
def _codegen(self, state: CodegenState, **kwargs: Any) -> None:
|
||||
state.before_codegen(self)
|
||||
|
|
@ -381,9 +379,9 @@ class CSTNode(ABC):
|
|||
child, all instances will be replaced.
|
||||
"""
|
||||
new_tree = self.visit(_ChildReplacementTransformer(old_node, new_node))
|
||||
if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)):
|
||||
# The above transform never returns *Sentinel, so this isn't possible
|
||||
raise CSTLogicError("Logic error, cannot get a *Sentinel here!")
|
||||
if isinstance(new_tree, RemovalSentinel):
|
||||
# The above transform never returns RemovalSentinel, so this isn't possible
|
||||
raise Exception("Logic error, cannot get a RemovalSentinel here!")
|
||||
return new_tree
|
||||
|
||||
def deep_remove(
|
||||
|
|
@ -394,16 +392,10 @@ class CSTNode(ABC):
|
|||
have previously modified the tree in a way that ``old_node`` appears more than
|
||||
once as a deep child, all instances will be removed.
|
||||
"""
|
||||
new_tree = self.visit(
|
||||
return self.visit(
|
||||
_ChildReplacementTransformer(old_node, RemovalSentinel.REMOVE)
|
||||
)
|
||||
|
||||
if isinstance(new_tree, FlattenSentinel):
|
||||
# The above transform never returns FlattenSentinel, so this isn't possible
|
||||
raise CSTLogicError("Logic error, cannot get a FlattenSentinel here!")
|
||||
|
||||
return new_tree
|
||||
|
||||
def with_deep_changes(
|
||||
self: _CSTNodeSelfT, old_node: "CSTNode", **changes: Any
|
||||
) -> _CSTNodeSelfT:
|
||||
|
|
@ -420,12 +412,12 @@ class CSTNode(ABC):
|
|||
similar API in the future.
|
||||
"""
|
||||
new_tree = self.visit(_ChildWithChangesTransformer(old_node, changes))
|
||||
if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)):
|
||||
if isinstance(new_tree, RemovalSentinel):
|
||||
# This is impossible with the above transform.
|
||||
raise CSTLogicError("Logic error, cannot get a *Sentinel here!")
|
||||
raise Exception("Logic error, cannot get a RemovalSentinel here!")
|
||||
return new_tree
|
||||
|
||||
def __eq__(self: _CSTNodeSelfT, other: object) -> bool:
|
||||
def __eq__(self: _CSTNodeSelfT, other: _CSTNodeSelfT) -> bool:
|
||||
"""
|
||||
CSTNodes are only treated as equal by identity. This matches the behavior of
|
||||
CPython's AST nodes.
|
||||
|
|
@ -470,8 +462,6 @@ class CSTNode(ABC):
|
|||
|
||||
|
||||
class BaseLeaf(CSTNode, ABC):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def children(self) -> Sequence[CSTNode]:
|
||||
# override this with an optimized implementation
|
||||
|
|
@ -491,8 +481,6 @@ class BaseValueToken(BaseLeaf, ABC):
|
|||
into the parent CSTNode, and hard-coded into the implementation of _codegen.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
value: str
|
||||
|
||||
def _codegen_impl(self, state: CodegenState) -> None:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -9,15 +9,15 @@ from abc import ABC, abstractmethod
|
|||
from ast import literal_eval
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass, field
|
||||
from enum import auto, Enum
|
||||
from enum import Enum, auto
|
||||
from tokenize import (
|
||||
Floatnumber as FLOATNUMBER_RE,
|
||||
Imagnumber as IMAGNUMBER_RE,
|
||||
Intnumber as INTNUMBER_RE,
|
||||
)
|
||||
from typing import Callable, Generator, Literal, Optional, Sequence, Union
|
||||
from typing import Callable, Generator, Optional, Sequence, Union
|
||||
|
||||
from libcst import CSTLogicError
|
||||
from typing_extensions import Literal
|
||||
|
||||
from libcst._add_slots import add_slots
|
||||
from libcst._maybe_sentinel import MaybeSentinel
|
||||
|
|
@ -222,8 +222,6 @@ class _BaseParenthesizedNode(CSTNode, ABC):
|
|||
this to get that functionality.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
lpar: Sequence[LeftParen] = ()
|
||||
# Sequence of parenthesis for precedence dictation.
|
||||
rpar: Sequence[RightParen] = ()
|
||||
|
|
@ -256,8 +254,6 @@ class BaseExpression(_BaseParenthesizedNode, ABC):
|
|||
An base class for all expressions. :class:`BaseExpression` contains no fields.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def _safe_to_use_with_word_operator(self, position: ExpressionPosition) -> bool:
|
||||
"""
|
||||
Returns true if this expression is safe to be use with a word operator
|
||||
|
|
@ -300,7 +296,7 @@ class BaseAssignTargetExpression(BaseExpression, ABC):
|
|||
<https://github.com/python/cpython/blob/v3.8.0a4/Python/ast.c#L1120>`_.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
pass
|
||||
|
||||
|
||||
class BaseDelTargetExpression(BaseExpression, ABC):
|
||||
|
|
@ -320,7 +316,7 @@ class BaseDelTargetExpression(BaseExpression, ABC):
|
|||
<https://github.com/python/cpython/blob/v3.8.0a4/Python/compile.c#L4854>`_.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
pass
|
||||
|
||||
|
||||
@add_slots
|
||||
|
|
@ -354,7 +350,7 @@ class Name(BaseAssignTargetExpression, BaseDelTargetExpression):
|
|||
if len(self.value) == 0:
|
||||
raise CSTValidationError("Cannot have empty name identifier.")
|
||||
if not self.value.isidentifier():
|
||||
raise CSTValidationError(f"Name {self.value!r} is not a valid identifier.")
|
||||
raise CSTValidationError("Name is not a valid identifier.")
|
||||
|
||||
def _codegen_impl(self, state: CodegenState) -> None:
|
||||
with self._parenthesize(state):
|
||||
|
|
@ -397,8 +393,6 @@ class BaseNumber(BaseExpression, ABC):
|
|||
used anywhere that you need to explicitly take any number type.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def _safe_to_use_with_word_operator(self, position: ExpressionPosition) -> bool:
|
||||
"""
|
||||
Numbers are funny. The expression "5in [1,2,3,4,5]" is a valid expression
|
||||
|
|
@ -528,15 +522,13 @@ class BaseString(BaseExpression, ABC):
|
|||
:class:`SimpleString`, :class:`ConcatenatedString`, and :class:`FormattedString`.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
pass
|
||||
|
||||
|
||||
StringQuoteLiteral = Literal['"', "'", '"""', "'''"]
|
||||
|
||||
|
||||
class _BasePrefixedString(BaseString, ABC):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def prefix(self) -> str:
|
||||
"""
|
||||
|
|
@ -655,20 +647,14 @@ class SimpleString(_BasePrefixedString):
|
|||
if len(quote) == 2:
|
||||
# Let's assume this is an empty string.
|
||||
quote = quote[:1]
|
||||
elif 3 < len(quote) <= 6:
|
||||
# Let's assume this can be one of the following:
|
||||
# >>> """"foo"""
|
||||
# '"foo'
|
||||
# >>> """""bar"""
|
||||
# '""bar'
|
||||
# >>> """"""
|
||||
# ''
|
||||
elif len(quote) == 6:
|
||||
# Let's assume this is an empty triple-quoted string.
|
||||
quote = quote[:3]
|
||||
|
||||
if len(quote) not in {1, 3}:
|
||||
# We shouldn't get here due to construction validation logic,
|
||||
# but handle the case anyway.
|
||||
raise CSTLogicError(f"Invalid string {self.value}")
|
||||
raise Exception("Invalid string {self.value}")
|
||||
|
||||
# pyre-ignore We know via the above validation that we will only
|
||||
# ever return one of the four string literals.
|
||||
|
|
@ -699,7 +685,7 @@ class SimpleString(_BasePrefixedString):
|
|||
state.add_token(self.value)
|
||||
|
||||
@property
|
||||
def evaluated_value(self) -> Union[str, bytes]:
|
||||
def evaluated_value(self) -> str:
|
||||
"""
|
||||
Return an :func:`ast.literal_eval` evaluated str of :py:attr:`value`.
|
||||
"""
|
||||
|
|
@ -713,7 +699,7 @@ class BaseFormattedStringContent(CSTNode, ABC):
|
|||
sequence of :class:`BaseFormattedStringContent` parts.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
pass
|
||||
|
||||
|
||||
@add_slots
|
||||
|
|
@ -958,253 +944,6 @@ class FormattedString(_BasePrefixedString):
|
|||
state.add_token(self.end)
|
||||
|
||||
|
||||
class BaseTemplatedStringContent(CSTNode, ABC):
|
||||
"""
|
||||
The base type for :class:`TemplatedStringText` and
|
||||
:class:`TemplatedStringExpression`. A :class:`TemplatedString` is composed of a
|
||||
sequence of :class:`BaseTemplatedStringContent` parts.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
class TemplatedStringText(BaseTemplatedStringContent):
|
||||
"""
|
||||
Part of a :class:`TemplatedString` that is not inside curly braces (``{`` or ``}``).
|
||||
For example, in::
|
||||
|
||||
f"ab{cd}ef"
|
||||
|
||||
``ab`` and ``ef`` are :class:`TemplatedStringText` nodes, but ``{cd}`` is a
|
||||
:class:`TemplatedStringExpression`.
|
||||
"""
|
||||
|
||||
#: The raw string value, including any escape characters present in the source
|
||||
#: code, not including any enclosing quotes.
|
||||
value: str
|
||||
|
||||
def _visit_and_replace_children(
|
||||
self, visitor: CSTVisitorT
|
||||
) -> "TemplatedStringText":
|
||||
return TemplatedStringText(value=self.value)
|
||||
|
||||
def _codegen_impl(self, state: CodegenState) -> None:
|
||||
state.add_token(self.value)
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
class TemplatedStringExpression(BaseTemplatedStringContent):
|
||||
"""
|
||||
Part of a :class:`TemplatedString` that is inside curly braces (``{`` or ``}``),
|
||||
including the surrounding curly braces. For example, in::
|
||||
|
||||
f"ab{cd}ef"
|
||||
|
||||
``{cd}`` is a :class:`TemplatedStringExpression`, but ``ab`` and ``ef`` are
|
||||
:class:`TemplatedStringText` nodes.
|
||||
|
||||
An t-string expression may contain ``conversion`` and ``format_spec`` suffixes that
|
||||
control how the expression is converted to a string.
|
||||
"""
|
||||
|
||||
#: The expression we will evaluate and render when generating the string.
|
||||
expression: BaseExpression
|
||||
|
||||
#: An optional conversion specifier, such as ``!s``, ``!r`` or ``!a``.
|
||||
conversion: Optional[str] = None
|
||||
|
||||
#: An optional format specifier following the `format specification mini-language
|
||||
#: <https://docs.python.org/3/library/string.html#formatspec>`_.
|
||||
format_spec: Optional[Sequence[BaseTemplatedStringContent]] = None
|
||||
|
||||
#: Whitespace after the opening curly brace (``{``), but before the ``expression``.
|
||||
whitespace_before_expression: BaseParenthesizableWhitespace = (
|
||||
SimpleWhitespace.field("")
|
||||
)
|
||||
|
||||
#: Whitespace after the ``expression``, but before the ``conversion``,
|
||||
#: ``format_spec`` and the closing curly brace (``}``). Python does not
|
||||
#: allow whitespace inside or after a ``conversion`` or ``format_spec``.
|
||||
whitespace_after_expression: BaseParenthesizableWhitespace = SimpleWhitespace.field(
|
||||
""
|
||||
)
|
||||
|
||||
#: Equal sign for Templated string expression uses self-documenting expressions,
|
||||
#: such as ``f"{x=}"``. See the `Python 3.8 release notes
|
||||
#: <https://docs.python.org/3/whatsnew/3.8.html#f-strings-support-for-self-documenting-expressions-and-debugging>`_.
|
||||
equal: Optional[AssignEqual] = None
|
||||
|
||||
def _validate(self) -> None:
|
||||
if self.conversion is not None and self.conversion not in ("s", "r", "a"):
|
||||
raise CSTValidationError("Invalid t-string conversion.")
|
||||
|
||||
def _visit_and_replace_children(
|
||||
self, visitor: CSTVisitorT
|
||||
) -> "TemplatedStringExpression":
|
||||
format_spec = self.format_spec
|
||||
return TemplatedStringExpression(
|
||||
whitespace_before_expression=visit_required(
|
||||
self,
|
||||
"whitespace_before_expression",
|
||||
self.whitespace_before_expression,
|
||||
visitor,
|
||||
),
|
||||
expression=visit_required(self, "expression", self.expression, visitor),
|
||||
equal=visit_optional(self, "equal", self.equal, visitor),
|
||||
whitespace_after_expression=visit_required(
|
||||
self,
|
||||
"whitespace_after_expression",
|
||||
self.whitespace_after_expression,
|
||||
visitor,
|
||||
),
|
||||
conversion=self.conversion,
|
||||
format_spec=(
|
||||
visit_sequence(self, "format_spec", format_spec, visitor)
|
||||
if format_spec is not None
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
||||
def _codegen_impl(self, state: CodegenState) -> None:
|
||||
state.add_token("{")
|
||||
self.whitespace_before_expression._codegen(state)
|
||||
self.expression._codegen(state)
|
||||
equal = self.equal
|
||||
if equal is not None:
|
||||
equal._codegen(state)
|
||||
self.whitespace_after_expression._codegen(state)
|
||||
conversion = self.conversion
|
||||
if conversion is not None:
|
||||
state.add_token("!")
|
||||
state.add_token(conversion)
|
||||
format_spec = self.format_spec
|
||||
if format_spec is not None:
|
||||
state.add_token(":")
|
||||
for spec in format_spec:
|
||||
spec._codegen(state)
|
||||
state.add_token("}")
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
class TemplatedString(_BasePrefixedString):
|
||||
"""
|
||||
An "t-string". Template strings are a generalization of f-strings,
|
||||
using a t in place of the f prefix. Instead of evaluating to str,
|
||||
t-strings evaluate to a new type: Template
|
||||
|
||||
T-Strings are defined in 'PEP 750'
|
||||
|
||||
>>> import libcst as cst
|
||||
>>> cst.parse_expression('t"ab{cd}ef"')
|
||||
TemplatedString(
|
||||
parts=[
|
||||
TemplatedStringText(
|
||||
value='ab',
|
||||
),
|
||||
TemplatedStringExpression(
|
||||
expression=Name(
|
||||
value='cd',
|
||||
lpar=[],
|
||||
rpar=[],
|
||||
),
|
||||
conversion=None,
|
||||
format_spec=None,
|
||||
whitespace_before_expression=SimpleWhitespace(
|
||||
value='',
|
||||
),
|
||||
whitespace_after_expression=SimpleWhitespace(
|
||||
value='',
|
||||
),
|
||||
equal=None,
|
||||
),
|
||||
TemplatedStringText(
|
||||
value='ef',
|
||||
),
|
||||
],
|
||||
start='t"',
|
||||
end='"',
|
||||
lpar=[],
|
||||
rpar=[],
|
||||
)
|
||||
>>>
|
||||
"""
|
||||
|
||||
#: A templated string is composed as a series of :class:`TemplatedStringText` and
|
||||
#: :class:`TemplatedStringExpression` parts.
|
||||
parts: Sequence[BaseTemplatedStringContent]
|
||||
|
||||
#: The string prefix and the leading quote, such as ``t"``, ``T'``, ``tr"``, or
|
||||
#: ``t"""``.
|
||||
start: str = 't"'
|
||||
|
||||
#: The trailing quote. This must match the type of quote used in ``start``.
|
||||
end: Literal['"', "'", '"""', "'''"] = '"'
|
||||
|
||||
lpar: Sequence[LeftParen] = ()
|
||||
#: Sequence of parenthesis for precidence dictation.
|
||||
rpar: Sequence[RightParen] = ()
|
||||
|
||||
def _validate(self) -> None:
|
||||
super(_BasePrefixedString, self)._validate()
|
||||
|
||||
# Validate any prefix
|
||||
prefix = self.prefix
|
||||
if prefix not in ("t", "tr", "rt"):
|
||||
raise CSTValidationError("Invalid t-string prefix.")
|
||||
|
||||
# Validate wrapping quotes
|
||||
starttoken = self.start[len(prefix) :]
|
||||
if starttoken != self.end:
|
||||
raise CSTValidationError("t-string must have matching enclosing quotes.")
|
||||
|
||||
# Validate valid wrapping quote usage
|
||||
if starttoken not in ('"', "'", '"""', "'''"):
|
||||
raise CSTValidationError("Invalid t-string enclosing quotes.")
|
||||
|
||||
@property
|
||||
def prefix(self) -> str:
|
||||
"""
|
||||
Returns the string's prefix, if any exists. The prefix can be ``t``,
|
||||
``tr``, or ``rt``.
|
||||
"""
|
||||
|
||||
prefix = ""
|
||||
for c in self.start:
|
||||
if c in ['"', "'"]:
|
||||
break
|
||||
prefix += c
|
||||
return prefix.lower()
|
||||
|
||||
@property
|
||||
def quote(self) -> StringQuoteLiteral:
|
||||
"""
|
||||
Returns the quotation used to denote the string. Can be either ``'``,
|
||||
``"``, ``'''`` or ``\"\"\"``.
|
||||
"""
|
||||
|
||||
return self.end
|
||||
|
||||
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "TemplatedString":
|
||||
return TemplatedString(
|
||||
lpar=visit_sequence(self, "lpar", self.lpar, visitor),
|
||||
start=self.start,
|
||||
parts=visit_sequence(self, "parts", self.parts, visitor),
|
||||
end=self.end,
|
||||
rpar=visit_sequence(self, "rpar", self.rpar, visitor),
|
||||
)
|
||||
|
||||
def _codegen_impl(self, state: CodegenState) -> None:
|
||||
with self._parenthesize(state):
|
||||
state.add_token(self.start)
|
||||
for part in self.parts:
|
||||
part._codegen(state)
|
||||
state.add_token(self.end)
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
class ConcatenatedString(BaseString):
|
||||
|
|
@ -1259,7 +998,7 @@ class ConcatenatedString(BaseString):
|
|||
elif isinstance(right, FormattedString):
|
||||
rightbytes = "b" in right.prefix
|
||||
else:
|
||||
raise CSTLogicError("Logic error!")
|
||||
raise Exception("Logic error!")
|
||||
if leftbytes != rightbytes:
|
||||
raise CSTValidationError("Cannot concatenate string and bytes.")
|
||||
|
||||
|
|
@ -1281,7 +1020,7 @@ class ConcatenatedString(BaseString):
|
|||
self.right._codegen(state)
|
||||
|
||||
@property
|
||||
def evaluated_value(self) -> Union[str, bytes, None]:
|
||||
def evaluated_value(self) -> Optional[str]:
|
||||
"""
|
||||
Return an :func:`ast.literal_eval` evaluated str of recursively concatenated :py:attr:`left` and :py:attr:`right`
|
||||
if and only if both :py:attr:`left` and :py:attr:`right` are composed by :class:`SimpleString` or :class:`ConcatenatedString`
|
||||
|
|
@ -1295,11 +1034,7 @@ class ConcatenatedString(BaseString):
|
|||
right_val = right.evaluated_value
|
||||
if right_val is None:
|
||||
return None
|
||||
if isinstance(left_val, bytes) and isinstance(right_val, bytes):
|
||||
return left_val + right_val
|
||||
if isinstance(left_val, str) and isinstance(right_val, str):
|
||||
return left_val + right_val
|
||||
return None
|
||||
return left_val + right_val
|
||||
|
||||
|
||||
@add_slots
|
||||
|
|
@ -1680,8 +1415,6 @@ class BaseSlice(CSTNode, ABC):
|
|||
This node is purely for typing.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -1694,29 +1427,10 @@ class Index(BaseSlice):
|
|||
#: The index value itself.
|
||||
value: BaseExpression
|
||||
|
||||
#: An optional string with an asterisk appearing before the name. This is
|
||||
#: expanded into variable number of positional arguments. See PEP-646
|
||||
star: Optional[Literal["*"]] = None
|
||||
|
||||
#: Whitespace after the ``star`` (if it exists), but before the ``value``.
|
||||
whitespace_after_star: Optional[BaseParenthesizableWhitespace] = None
|
||||
|
||||
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "Index":
|
||||
return Index(
|
||||
star=self.star,
|
||||
whitespace_after_star=visit_optional(
|
||||
self, "whitespace_after_star", self.whitespace_after_star, visitor
|
||||
),
|
||||
value=visit_required(self, "value", self.value, visitor),
|
||||
)
|
||||
return Index(value=visit_required(self, "value", self.value, visitor))
|
||||
|
||||
def _codegen_impl(self, state: CodegenState) -> None:
|
||||
star = self.star
|
||||
if star is not None:
|
||||
state.add_token(star)
|
||||
ws = self.whitespace_after_star
|
||||
if ws is not None:
|
||||
ws._codegen(state)
|
||||
self.value._codegen(state)
|
||||
|
||||
|
||||
|
|
@ -1896,9 +1610,9 @@ class Annotation(CSTNode):
|
|||
#: colon or arrow.
|
||||
annotation: BaseExpression
|
||||
|
||||
whitespace_before_indicator: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
|
||||
MaybeSentinel.DEFAULT
|
||||
)
|
||||
whitespace_before_indicator: Union[
|
||||
BaseParenthesizableWhitespace, MaybeSentinel
|
||||
] = MaybeSentinel.DEFAULT
|
||||
whitespace_after_indicator: BaseParenthesizableWhitespace = SimpleWhitespace.field(
|
||||
" "
|
||||
)
|
||||
|
|
@ -1937,7 +1651,7 @@ class Annotation(CSTNode):
|
|||
if default_indicator == "->":
|
||||
state.add_token(" ")
|
||||
else:
|
||||
raise CSTLogicError("Logic error!")
|
||||
raise Exception("Logic error!")
|
||||
|
||||
# Now, output the indicator and the rest of the annotation
|
||||
state.add_token(default_indicator)
|
||||
|
|
@ -1982,26 +1696,15 @@ class ParamSlash(CSTNode):
|
|||
.. _PEP 570: https://www.python.org/dev/peps/pep-0570/#specification
|
||||
"""
|
||||
|
||||
#: Optional comma that comes after the slash. This comma doesn't own the whitespace
|
||||
#: between ``/`` and ``,``.
|
||||
# Optional comma that comes after the slash.
|
||||
comma: Union[Comma, MaybeSentinel] = MaybeSentinel.DEFAULT
|
||||
|
||||
#: Whitespace after the ``/`` character. This is captured here in case there is a
|
||||
#: comma.
|
||||
whitespace_after: BaseParenthesizableWhitespace = SimpleWhitespace.field("")
|
||||
|
||||
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "ParamSlash":
|
||||
return ParamSlash(
|
||||
comma=visit_sentinel(self, "comma", self.comma, visitor),
|
||||
whitespace_after=visit_required(
|
||||
self, "whitespace_after", self.whitespace_after, visitor
|
||||
),
|
||||
)
|
||||
return ParamSlash(comma=visit_sentinel(self, "comma", self.comma, visitor))
|
||||
|
||||
def _codegen_impl(self, state: CodegenState, default_comma: bool = False) -> None:
|
||||
state.add_token("/")
|
||||
|
||||
self.whitespace_after._codegen(state)
|
||||
comma = self.comma
|
||||
if comma is MaybeSentinel.DEFAULT and default_comma:
|
||||
state.add_token(", ")
|
||||
|
|
@ -2141,6 +1844,7 @@ class Parameters(CSTNode):
|
|||
if len(vals) == 0:
|
||||
return
|
||||
for val in vals:
|
||||
# pyre-ignore Pyre seems to think val.star.__eq__ is not callable
|
||||
if isinstance(val.star, str) and val.star != "":
|
||||
raise CSTValidationError(
|
||||
f"Expecting a star prefix of '' for {section} Param."
|
||||
|
|
@ -2160,8 +1864,6 @@ class Parameters(CSTNode):
|
|||
|
||||
def _validate_defaults(self) -> None:
|
||||
seen_default = False
|
||||
# pyre-fixme[60]: Concatenation not yet support for multiple variadic
|
||||
# tuples: `*self.posonly_params, *self.params`.
|
||||
for param in (*self.posonly_params, *self.params):
|
||||
if param.default:
|
||||
# Mark that we've moved onto defaults
|
||||
|
|
@ -2189,6 +1891,7 @@ class Parameters(CSTNode):
|
|||
if (
|
||||
isinstance(star_arg, Param)
|
||||
and isinstance(star_arg.star, str)
|
||||
# pyre-ignore Pyre seems to think star_kwarg.star.__eq__ is not callable
|
||||
and star_arg.star != "*"
|
||||
):
|
||||
raise CSTValidationError(
|
||||
|
|
@ -2200,6 +1903,7 @@ class Parameters(CSTNode):
|
|||
if (
|
||||
star_kwarg is not None
|
||||
and isinstance(star_kwarg.star, str)
|
||||
# pyre-ignore Pyre seems to think star_kwarg.star.__eq__ is not callable
|
||||
and star_kwarg.star != "**"
|
||||
):
|
||||
raise CSTValidationError(
|
||||
|
|
@ -2230,25 +1934,6 @@ class Parameters(CSTNode):
|
|||
star_kwarg=visit_optional(self, "star_kwarg", self.star_kwarg, visitor),
|
||||
)
|
||||
|
||||
def _safe_to_join_with_lambda(self) -> bool:
|
||||
"""
|
||||
Determine if Parameters need a space after the `lambda` keyword. Returns True
|
||||
iff it's safe to omit the space between `lambda` and these Parameters.
|
||||
|
||||
See also `BaseExpression._safe_to_use_with_word_operator`.
|
||||
|
||||
For example: `lambda*_: pass`
|
||||
"""
|
||||
if len(self.posonly_params) != 0:
|
||||
return False
|
||||
|
||||
# posonly_ind can't appear if above condition is false
|
||||
|
||||
if len(self.params) > 0 and self.params[0].star not in {"*", "**"}:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _codegen_impl(self, state: CodegenState) -> None: # noqa: C901
|
||||
# Compute the star existence first so we can ask about whether
|
||||
# each element is the last in the list or not.
|
||||
|
|
@ -2350,16 +2035,9 @@ class Lambda(BaseExpression):
|
|||
rpar: Sequence[RightParen] = ()
|
||||
|
||||
#: Whitespace after the lambda keyword, but before any argument or the colon.
|
||||
whitespace_after_lambda: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
|
||||
MaybeSentinel.DEFAULT
|
||||
)
|
||||
|
||||
def _safe_to_use_with_word_operator(self, position: ExpressionPosition) -> bool:
|
||||
if position == ExpressionPosition.LEFT:
|
||||
return len(self.rpar) > 0 or self.body._safe_to_use_with_word_operator(
|
||||
position
|
||||
)
|
||||
return super()._safe_to_use_with_word_operator(position)
|
||||
whitespace_after_lambda: Union[
|
||||
BaseParenthesizableWhitespace, MaybeSentinel
|
||||
] = MaybeSentinel.DEFAULT
|
||||
|
||||
def _validate(self) -> None:
|
||||
# Validate parents
|
||||
|
|
@ -2388,7 +2066,6 @@ class Lambda(BaseExpression):
|
|||
if (
|
||||
isinstance(whitespace_after_lambda, BaseParenthesizableWhitespace)
|
||||
and whitespace_after_lambda.empty
|
||||
and not self.params._safe_to_join_with_lambda()
|
||||
):
|
||||
raise CSTValidationError(
|
||||
"Must have at least one space after lambda when specifying params"
|
||||
|
|
@ -2514,12 +2191,12 @@ class _BaseExpressionWithArgs(BaseExpression, ABC):
|
|||
in typing. So, we have common validation functions here.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
#: Sequence of arguments that will be passed to the function call.
|
||||
args: Sequence[Arg] = ()
|
||||
|
||||
def _check_kwargs_or_keywords(self, arg: Arg) -> None:
|
||||
def _check_kwargs_or_keywords(
|
||||
self, arg: Arg
|
||||
) -> Optional[Callable[[Arg], Callable]]:
|
||||
"""
|
||||
Validates that we only have a mix of "keyword=arg" and "**arg" expansion.
|
||||
"""
|
||||
|
|
@ -2543,7 +2220,7 @@ class _BaseExpressionWithArgs(BaseExpression, ABC):
|
|||
|
||||
def _check_starred_or_keywords(
|
||||
self, arg: Arg
|
||||
) -> Optional[Callable[[Arg], Callable[[Arg], None]]]:
|
||||
) -> Optional[Callable[[Arg], Callable]]:
|
||||
"""
|
||||
Validates that we only have a mix of "*arg" expansion and "keyword=arg".
|
||||
"""
|
||||
|
|
@ -2566,9 +2243,7 @@ class _BaseExpressionWithArgs(BaseExpression, ABC):
|
|||
"Cannot have positional argument after keyword argument."
|
||||
)
|
||||
|
||||
def _check_positional(
|
||||
self, arg: Arg
|
||||
) -> Optional[Callable[[Arg], Callable[[Arg], Callable[[Arg], None]]]]:
|
||||
def _check_positional(self, arg: Arg) -> Optional[Callable[[Arg], Callable]]:
|
||||
"""
|
||||
Validates that we only have a mix of positional args and "*arg" expansion.
|
||||
"""
|
||||
|
|
@ -2592,8 +2267,6 @@ class _BaseExpressionWithArgs(BaseExpression, ABC):
|
|||
# Valid, allowed to have positional arguments here
|
||||
return None
|
||||
|
||||
# pyre-fixme[30]: Pyre gave up inferring some types - function `_validate` was
|
||||
# too complex.
|
||||
def _validate(self) -> None:
|
||||
# Validate any super-class stuff, whatever it may be.
|
||||
super()._validate()
|
||||
|
|
@ -2707,12 +2380,7 @@ class Await(BaseExpression):
|
|||
# Validate any super-class stuff, whatever it may be.
|
||||
super(Await, self)._validate()
|
||||
# Make sure we don't run identifiers together.
|
||||
if (
|
||||
self.whitespace_after_await.empty
|
||||
and not self.expression._safe_to_use_with_word_operator(
|
||||
ExpressionPosition.RIGHT
|
||||
)
|
||||
):
|
||||
if self.whitespace_after_await.empty:
|
||||
raise CSTValidationError("Must have at least one space after await")
|
||||
|
||||
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "Await":
|
||||
|
|
@ -2766,12 +2434,6 @@ class IfExp(BaseExpression):
|
|||
#: Whitespace after the ``else`` keyword, but before the ``orelse`` expression.
|
||||
whitespace_after_else: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")
|
||||
|
||||
def _safe_to_use_with_word_operator(self, position: ExpressionPosition) -> bool:
|
||||
if position == ExpressionPosition.RIGHT:
|
||||
return self.body._safe_to_use_with_word_operator(position)
|
||||
else:
|
||||
return self.orelse._safe_to_use_with_word_operator(position)
|
||||
|
||||
def _validate(self) -> None:
|
||||
# Paren validation and such
|
||||
super(IfExp, self)._validate()
|
||||
|
|
@ -2850,9 +2512,9 @@ class From(CSTNode):
|
|||
item: BaseExpression
|
||||
|
||||
#: The whitespace at the very start of this node.
|
||||
whitespace_before_from: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
|
||||
MaybeSentinel.DEFAULT
|
||||
)
|
||||
whitespace_before_from: Union[
|
||||
BaseParenthesizableWhitespace, MaybeSentinel
|
||||
] = MaybeSentinel.DEFAULT
|
||||
|
||||
#: The whitespace after the ``from`` keyword, but before the ``item``.
|
||||
whitespace_after_from: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")
|
||||
|
|
@ -2911,9 +2573,9 @@ class Yield(BaseExpression):
|
|||
rpar: Sequence[RightParen] = ()
|
||||
|
||||
#: Whitespace after the ``yield`` keyword, but before the ``value``.
|
||||
whitespace_after_yield: Union[BaseParenthesizableWhitespace, MaybeSentinel] = (
|
||||
MaybeSentinel.DEFAULT
|
||||
)
|
||||
whitespace_after_yield: Union[
|
||||
BaseParenthesizableWhitespace, MaybeSentinel
|
||||
] = MaybeSentinel.DEFAULT
|
||||
|
||||
def _validate(self) -> None:
|
||||
# Paren rules and such
|
||||
|
|
@ -2968,8 +2630,6 @@ class _BaseElementImpl(CSTNode, ABC):
|
|||
An internal base class for :class:`Element` and :class:`DictElement`.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
value: BaseExpression
|
||||
comma: Union[Comma, MaybeSentinel] = MaybeSentinel.DEFAULT
|
||||
|
||||
|
|
@ -2997,7 +2657,8 @@ class _BaseElementImpl(CSTNode, ABC):
|
|||
state: CodegenState,
|
||||
default_comma: bool = False,
|
||||
default_comma_whitespace: bool = False, # False for a single-item collection
|
||||
) -> None: ...
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
class BaseElement(_BaseElementImpl, ABC):
|
||||
|
|
@ -3006,8 +2667,6 @@ class BaseElement(_BaseElementImpl, ABC):
|
|||
BaseDictElement.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class BaseDictElement(_BaseElementImpl, ABC):
|
||||
"""
|
||||
|
|
@ -3015,8 +2674,6 @@ class BaseDictElement(_BaseElementImpl, ABC):
|
|||
BaseElement.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -3103,7 +2760,7 @@ class DictElement(BaseDictElement):
|
|||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
class StarredElement(BaseElement, BaseExpression, _BaseParenthesizedNode):
|
||||
class StarredElement(BaseElement, _BaseParenthesizedNode):
|
||||
"""
|
||||
A starred ``*value`` element that expands to represent multiple values in a literal
|
||||
:class:`List`, :class:`Tuple`, or :class:`Set`.
|
||||
|
|
@ -3299,8 +2956,6 @@ class BaseList(BaseExpression, ABC):
|
|||
object when evaluated.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
lbracket: LeftSquareBracket = LeftSquareBracket.field()
|
||||
#: Brackets surrounding the list.
|
||||
rbracket: RightSquareBracket = RightSquareBracket.field()
|
||||
|
|
@ -3381,8 +3036,6 @@ class _BaseSetOrDict(BaseExpression, ABC):
|
|||
shouldn't be exported.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
lbrace: LeftCurlyBrace = LeftCurlyBrace.field()
|
||||
#: Braces surrounding the set or dict.
|
||||
rbrace: RightCurlyBrace = RightCurlyBrace.field()
|
||||
|
|
@ -3408,8 +3061,6 @@ class BaseSet(_BaseSetOrDict, ABC):
|
|||
a set object when evaluated.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -3479,8 +3130,6 @@ class BaseDict(_BaseSetOrDict, ABC):
|
|||
a dict object when evaluated.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -3757,8 +3406,6 @@ class BaseComp(BaseExpression, ABC):
|
|||
:class:`GeneratorExp`, :class:`ListComp`, :class:`SetComp`, and :class:`DictComp`.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
for_in: CompFor
|
||||
|
||||
|
||||
|
|
@ -3769,12 +3416,10 @@ class BaseSimpleComp(BaseComp, ABC):
|
|||
``value``.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
#: The expression evaluated during each iteration of the comprehension. This
|
||||
#: lexically comes before the ``for_in`` clause, but it is semantically the
|
||||
#: inner-most element, evaluated inside the ``for_in`` clause.
|
||||
elt: BaseExpression
|
||||
elt: BaseAssignTargetExpression
|
||||
|
||||
#: The ``for ... in ... if ...`` clause that lexically comes after ``elt``. This may
|
||||
#: be a nested structure for nested comprehensions. See :class:`CompFor` for
|
||||
|
|
@ -3807,7 +3452,7 @@ class GeneratorExp(BaseSimpleComp):
|
|||
"""
|
||||
|
||||
#: The expression evaluated and yielded during each iteration of the generator.
|
||||
elt: BaseExpression
|
||||
elt: BaseAssignTargetExpression
|
||||
|
||||
#: The ``for ... in ... if ...`` clause that comes after ``elt``. This may be a
|
||||
#: nested structure for nested comprehensions. See :class:`CompFor` for details.
|
||||
|
|
@ -3858,7 +3503,7 @@ class ListComp(BaseList, BaseSimpleComp):
|
|||
"""
|
||||
|
||||
#: The expression evaluated and stored during each iteration of the comprehension.
|
||||
elt: BaseExpression
|
||||
elt: BaseAssignTargetExpression
|
||||
|
||||
#: The ``for ... in ... if ...`` clause that comes after ``elt``. This may be a
|
||||
#: nested structure for nested comprehensions. See :class:`CompFor` for details.
|
||||
|
|
@ -3900,7 +3545,7 @@ class SetComp(BaseSet, BaseSimpleComp):
|
|||
"""
|
||||
|
||||
#: The expression evaluated and stored during each iteration of the comprehension.
|
||||
elt: BaseExpression
|
||||
elt: BaseAssignTargetExpression
|
||||
|
||||
#: The ``for ... in ... if ...`` clause that comes after ``elt``. This may be a
|
||||
#: nested structure for nested comprehensions. See :class:`CompFor` for details.
|
||||
|
|
@ -3942,10 +3587,10 @@ class DictComp(BaseDict, BaseComp):
|
|||
"""
|
||||
|
||||
#: The key inserted into the dictionary during each iteration of the comprehension.
|
||||
key: BaseExpression
|
||||
key: BaseAssignTargetExpression
|
||||
#: The value associated with the ``key`` inserted into the dictionary during each
|
||||
#: iteration of the comprehension.
|
||||
value: BaseExpression
|
||||
value: BaseAssignTargetExpression
|
||||
|
||||
#: The ``for ... in ... if ...`` clause that lexically comes after ``key`` and
|
||||
#: ``value``. This may be a nested structure for nested comprehensions. See
|
||||
|
|
@ -4049,15 +3694,6 @@ class NamedExpr(BaseExpression):
|
|||
rpar=visit_sequence(self, "rpar", self.rpar, visitor),
|
||||
)
|
||||
|
||||
def _safe_to_use_with_word_operator(self, position: ExpressionPosition) -> bool:
|
||||
if position == ExpressionPosition.LEFT:
|
||||
return len(self.rpar) > 0 or self.value._safe_to_use_with_word_operator(
|
||||
position
|
||||
)
|
||||
return len(self.lpar) > 0 or self.target._safe_to_use_with_word_operator(
|
||||
position
|
||||
)
|
||||
|
||||
def _codegen_impl(self, state: CodegenState) -> None:
|
||||
with self._parenthesize(state):
|
||||
self.target._codegen(state)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -6,14 +6,14 @@
|
|||
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Iterable, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union
|
||||
from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Sequence, Union
|
||||
|
||||
from libcst._add_slots import add_slots
|
||||
from libcst._flatten_sentinel import FlattenSentinel
|
||||
from libcst._maybe_sentinel import MaybeSentinel
|
||||
from libcst._removal_sentinel import RemovalSentinel
|
||||
from libcst._types import CSTNodeT
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# These are circular dependencies only used for typing purposes
|
||||
from libcst._nodes.base import CSTNode # noqa: F401
|
||||
|
|
@ -84,13 +84,6 @@ def visit_required(
|
|||
f"We got a RemovalSentinel while visiting a {type(node).__name__}. This "
|
||||
+ "node's parent does not allow it to be removed."
|
||||
)
|
||||
elif isinstance(result, FlattenSentinel):
|
||||
raise TypeError(
|
||||
f"We got a FlattenSentinel while visiting a {type(node).__name__}. This "
|
||||
+ "node's parent does not allow for it to be it to be replaced with a "
|
||||
+ "sequence."
|
||||
)
|
||||
|
||||
visitor.on_leave_attribute(parent, fieldname)
|
||||
return result
|
||||
|
||||
|
|
@ -108,12 +101,6 @@ def visit_optional(
|
|||
return None
|
||||
visitor.on_visit_attribute(parent, fieldname)
|
||||
result = node.visit(visitor)
|
||||
if isinstance(result, FlattenSentinel):
|
||||
raise TypeError(
|
||||
f"We got a FlattenSentinel while visiting a {type(node).__name__}. This "
|
||||
+ "node's parent does not allow for it to be it to be replaced with a "
|
||||
+ "sequence."
|
||||
)
|
||||
visitor.on_leave_attribute(parent, fieldname)
|
||||
return None if isinstance(result, RemovalSentinel) else result
|
||||
|
||||
|
|
@ -134,12 +121,6 @@ def visit_sentinel(
|
|||
return MaybeSentinel.DEFAULT
|
||||
visitor.on_visit_attribute(parent, fieldname)
|
||||
result = node.visit(visitor)
|
||||
if isinstance(result, FlattenSentinel):
|
||||
raise TypeError(
|
||||
f"We got a FlattenSentinel while visiting a {type(node).__name__}. This "
|
||||
+ "node's parent does not allow for it to be it to be replaced with a "
|
||||
+ "sequence."
|
||||
)
|
||||
visitor.on_leave_attribute(parent, fieldname)
|
||||
return MaybeSentinel.DEFAULT if isinstance(result, RemovalSentinel) else result
|
||||
|
||||
|
|
@ -157,9 +138,7 @@ def visit_iterable(
|
|||
visitor.on_visit_attribute(parent, fieldname)
|
||||
for child in children:
|
||||
new_child = child.visit(visitor)
|
||||
if isinstance(new_child, FlattenSentinel):
|
||||
yield from new_child
|
||||
elif not isinstance(new_child, RemovalSentinel):
|
||||
if not isinstance(new_child, RemovalSentinel):
|
||||
yield new_child
|
||||
visitor.on_leave_attribute(parent, fieldname)
|
||||
|
||||
|
|
@ -200,17 +179,11 @@ def visit_body_iterable(
|
|||
# and the new child is. This means a RemovalSentinel
|
||||
# caused a child of this node to be dropped, and it
|
||||
# is now useless.
|
||||
if (not child._is_removable()) and new_child._is_removable():
|
||||
continue
|
||||
|
||||
if isinstance(new_child, FlattenSentinel):
|
||||
for child_ in new_child:
|
||||
if (not child._is_removable()) and child_._is_removable():
|
||||
continue
|
||||
yield child_
|
||||
else:
|
||||
if (not child._is_removable()) and new_child._is_removable():
|
||||
continue
|
||||
# Safe to yield child in this case.
|
||||
yield new_child
|
||||
# Safe to yield child in this case.
|
||||
yield new_child
|
||||
visitor.on_leave_attribute(parent, fieldname)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import cast, Optional, Sequence, TYPE_CHECKING, TypeVar, Union
|
||||
from typing import TYPE_CHECKING, Optional, Sequence, TypeVar, Union, cast
|
||||
|
||||
from libcst._add_slots import add_slots
|
||||
from libcst._nodes.base import CSTNode
|
||||
from libcst._nodes.internal import CodegenState, visit_body_sequence, visit_sequence
|
||||
from libcst._nodes.statement import (
|
||||
BaseCompoundStatement,
|
||||
get_docstring_impl,
|
||||
SimpleStatementLine,
|
||||
get_docstring_impl,
|
||||
)
|
||||
from libcst._nodes.whitespace import EmptyLine
|
||||
from libcst._removal_sentinel import RemovalSentinel
|
||||
from libcst._visitors import CSTVisitorT
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# This is circular, so import the type only in type checking
|
||||
from libcst._parser.types.config import PartialParserConfig
|
||||
|
|
@ -79,6 +80,7 @@ class Module(CSTNode):
|
|||
has_trailing_newline=self.has_trailing_newline,
|
||||
)
|
||||
|
||||
# pyre-fixme[14]: `visit` overrides method defined in `CSTNode` inconsistently.
|
||||
def visit(self: _ModuleSelfT, visitor: CSTVisitorT) -> _ModuleSelfT:
|
||||
"""
|
||||
Returns the result of running a visitor over this module.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -19,8 +19,6 @@ class _BaseOneTokenOp(CSTNode, ABC):
|
|||
Any node that has a static value and needs to own whitespace on both sides.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
whitespace_before: BaseParenthesizableWhitespace
|
||||
|
||||
whitespace_after: BaseParenthesizableWhitespace
|
||||
|
|
@ -43,7 +41,8 @@ class _BaseOneTokenOp(CSTNode, ABC):
|
|||
self.whitespace_after._codegen(state)
|
||||
|
||||
@abstractmethod
|
||||
def _get_token(self) -> str: ...
|
||||
def _get_token(self) -> str:
|
||||
...
|
||||
|
||||
|
||||
class _BaseTwoTokenOp(CSTNode, ABC):
|
||||
|
|
@ -52,8 +51,6 @@ class _BaseTwoTokenOp(CSTNode, ABC):
|
|||
in beteween them.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
whitespace_before: BaseParenthesizableWhitespace
|
||||
|
||||
whitespace_between: BaseParenthesizableWhitespace
|
||||
|
|
@ -87,7 +84,8 @@ class _BaseTwoTokenOp(CSTNode, ABC):
|
|||
self.whitespace_after._codegen(state)
|
||||
|
||||
@abstractmethod
|
||||
def _get_tokens(self) -> Tuple[str, str]: ...
|
||||
def _get_tokens(self) -> Tuple[str, str]:
|
||||
...
|
||||
|
||||
|
||||
class BaseUnaryOp(CSTNode, ABC):
|
||||
|
|
@ -95,8 +93,6 @@ class BaseUnaryOp(CSTNode, ABC):
|
|||
Any node that has a static value used in a :class:`UnaryOperation` expression.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
#: Any space that appears directly after this operator.
|
||||
whitespace_after: BaseParenthesizableWhitespace
|
||||
|
||||
|
|
@ -113,7 +109,8 @@ class BaseUnaryOp(CSTNode, ABC):
|
|||
self.whitespace_after._codegen(state)
|
||||
|
||||
@abstractmethod
|
||||
def _get_token(self) -> str: ...
|
||||
def _get_token(self) -> str:
|
||||
...
|
||||
|
||||
|
||||
class BaseBooleanOp(_BaseOneTokenOp, ABC):
|
||||
|
|
@ -122,8 +119,6 @@ class BaseBooleanOp(_BaseOneTokenOp, ABC):
|
|||
This node is purely for typing.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class BaseBinaryOp(CSTNode, ABC):
|
||||
"""
|
||||
|
|
@ -131,8 +126,6 @@ class BaseBinaryOp(CSTNode, ABC):
|
|||
This node is purely for typing.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class BaseCompOp(CSTNode, ABC):
|
||||
"""
|
||||
|
|
@ -140,8 +133,6 @@ class BaseCompOp(CSTNode, ABC):
|
|||
This node is purely for typing.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class BaseAugOp(CSTNode, ABC):
|
||||
"""
|
||||
|
|
@ -149,8 +140,6 @@ class BaseAugOp(CSTNode, ABC):
|
|||
This node is purely for typing.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@add_slots
|
||||
@dataclass(frozen=True)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -137,9 +137,7 @@ class CSTNodeTest(UnitTest):
|
|||
codegen_children,
|
||||
msg=(
|
||||
"The list of children we got from `node.children` differs from the "
|
||||
+ "children that were visited by `node._codegen`. This is probably "
|
||||
+ "due to a mismatch between _visit_and_replace_children and "
|
||||
+ "_codegen_impl."
|
||||
+ "children that were visited by `node._codegen`."
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -239,7 +237,7 @@ class CSTNodeTest(UnitTest):
|
|||
def assert_parses(
|
||||
self,
|
||||
code: str,
|
||||
parser: Callable[[str], cst.CSTNode],
|
||||
parser: Callable[[str], cst.BaseExpression],
|
||||
expect_success: bool,
|
||||
) -> None:
|
||||
if not expect_success:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -422,7 +422,7 @@ class AugAssignTest(CSTNodeTest):
|
|||
operator=cst.Add(),
|
||||
right=cst.Integer("1"),
|
||||
),
|
||||
operator=cst.AddAssign(),
|
||||
operator=cst.Add(),
|
||||
value=cst.Name("y"),
|
||||
)
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -655,48 +655,6 @@ class AtomTest(CSTNodeTest):
|
|||
"parser": _parse_expression_force_38,
|
||||
"expected_position": None,
|
||||
},
|
||||
{
|
||||
"node": cst.FormattedString(
|
||||
parts=(
|
||||
cst.FormattedStringExpression(
|
||||
cst.Yield(
|
||||
value=cst.Integer("1"),
|
||||
whitespace_after_yield=cst.SimpleWhitespace(" "),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
"code": 'f"{yield 1}"',
|
||||
"parser": _parse_expression_force_38,
|
||||
"expected_position": None,
|
||||
},
|
||||
{
|
||||
"node": cst.FormattedString(
|
||||
parts=(
|
||||
cst.FormattedStringText("\\N{X Y}"),
|
||||
cst.FormattedStringExpression(
|
||||
cst.Name(value="Z"),
|
||||
),
|
||||
),
|
||||
),
|
||||
"code": 'f"\\N{X Y}{Z}"',
|
||||
"parser": parse_expression,
|
||||
"expected_position": None,
|
||||
},
|
||||
{
|
||||
"node": cst.FormattedString(
|
||||
parts=(
|
||||
cst.FormattedStringText("\\"),
|
||||
cst.FormattedStringExpression(
|
||||
cst.Name(value="a"),
|
||||
),
|
||||
),
|
||||
start='fr"',
|
||||
),
|
||||
"code": 'fr"\\{a}"',
|
||||
"parser": parse_expression,
|
||||
"expected_position": None,
|
||||
},
|
||||
# Validate parens
|
||||
{
|
||||
"node": cst.FormattedString(
|
||||
|
|
@ -739,69 +697,6 @@ class AtomTest(CSTNodeTest):
|
|||
"parser": parse_expression,
|
||||
"expected_position": None,
|
||||
},
|
||||
# Unpacked tuple
|
||||
{
|
||||
"node": cst.FormattedString(
|
||||
parts=[
|
||||
cst.FormattedStringExpression(
|
||||
expression=cst.Tuple(
|
||||
elements=[
|
||||
cst.Element(
|
||||
value=cst.Name(
|
||||
value="a",
|
||||
),
|
||||
comma=cst.Comma(
|
||||
whitespace_before=cst.SimpleWhitespace(
|
||||
value="",
|
||||
),
|
||||
whitespace_after=cst.SimpleWhitespace(
|
||||
value=" ",
|
||||
),
|
||||
),
|
||||
),
|
||||
cst.Element(
|
||||
value=cst.Name(
|
||||
value="b",
|
||||
),
|
||||
),
|
||||
],
|
||||
lpar=[],
|
||||
rpar=[],
|
||||
),
|
||||
),
|
||||
],
|
||||
start="f'",
|
||||
end="'",
|
||||
),
|
||||
"code": "f'{a, b}'",
|
||||
"parser": parse_expression,
|
||||
"expected_position": None,
|
||||
},
|
||||
# Conditional expression
|
||||
{
|
||||
"node": cst.FormattedString(
|
||||
parts=[
|
||||
cst.FormattedStringExpression(
|
||||
expression=cst.IfExp(
|
||||
test=cst.Name(
|
||||
value="b",
|
||||
),
|
||||
body=cst.Name(
|
||||
value="a",
|
||||
),
|
||||
orelse=cst.Name(
|
||||
value="c",
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
start="f'",
|
||||
end="'",
|
||||
),
|
||||
"code": "f'{a if b else c}'",
|
||||
"parser": parse_expression,
|
||||
"expected_position": None,
|
||||
},
|
||||
# Concatenated strings
|
||||
{
|
||||
"node": cst.ConcatenatedString(
|
||||
|
|
@ -1183,8 +1078,6 @@ class AtomTest(CSTNodeTest):
|
|||
)
|
||||
)
|
||||
def test_versions(self, **kwargs: Any) -> None:
|
||||
if not kwargs.get("expect_success", True):
|
||||
self.skipTest("parse errors are disabled for native parser")
|
||||
self.assert_parses(**kwargs)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
from typing import Any
|
||||
|
||||
import libcst as cst
|
||||
from libcst import parse_expression, parse_statement, PartialParserConfig
|
||||
from libcst import PartialParserConfig, parse_expression, parse_statement
|
||||
from libcst._nodes.tests.base import CSTNodeTest
|
||||
from libcst.metadata import CodeRange
|
||||
from libcst.testing.utils import data_provider
|
||||
|
|
@ -46,14 +46,6 @@ class AwaitTest(CSTNodeTest):
|
|||
),
|
||||
"expected_position": CodeRange((1, 2), (1, 13)),
|
||||
},
|
||||
# Whitespace after await
|
||||
{
|
||||
"node": cst.Await(
|
||||
cst.Name("foo", lpar=[cst.LeftParen()], rpar=[cst.RightParen()]),
|
||||
whitespace_after_await=cst.SimpleWhitespace(""),
|
||||
),
|
||||
"code": "await(foo)",
|
||||
},
|
||||
)
|
||||
)
|
||||
def test_valid_py37(self, **kwargs: Any) -> None:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -174,18 +174,3 @@ class BinaryOperationTest(CSTNodeTest):
|
|||
)
|
||||
def test_invalid(self, **kwargs: Any) -> None:
|
||||
self.assert_invalid(**kwargs)
|
||||
|
||||
@data_provider(
|
||||
(
|
||||
{
|
||||
"code": '"a"' * 6000,
|
||||
"parser": parse_expression,
|
||||
},
|
||||
{
|
||||
"code": "[_" + " for _ in _" * 6000 + "]",
|
||||
"parser": parse_expression,
|
||||
},
|
||||
)
|
||||
)
|
||||
def test_parse_error(self, **kwargs: Any) -> None:
|
||||
self.assert_parses(**kwargs, expect_success=False)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -112,105 +112,6 @@ class ClassDefCreationTest(CSTNodeTest):
|
|||
def test_valid(self, **kwargs: Any) -> None:
|
||||
self.validate_node(**kwargs)
|
||||
|
||||
@data_provider(
|
||||
(
|
||||
{
|
||||
"node": cst.ClassDef(
|
||||
cst.Name("Foo"),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
type_parameters=cst.TypeParameters(
|
||||
(
|
||||
cst.TypeParam(
|
||||
cst.TypeVar(
|
||||
cst.Name("T"),
|
||||
bound=cst.Name("int"),
|
||||
colon=cst.Colon(
|
||||
whitespace_after=cst.SimpleWhitespace(" ")
|
||||
),
|
||||
),
|
||||
cst.Comma(whitespace_after=cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
cst.TypeParam(
|
||||
cst.TypeVarTuple(cst.Name("Ts")),
|
||||
cst.Comma(whitespace_after=cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
cst.TypeParam(cst.ParamSpec(cst.Name("KW"))),
|
||||
)
|
||||
),
|
||||
),
|
||||
"code": "class Foo[T: int, *Ts, **KW]: pass\n",
|
||||
},
|
||||
{
|
||||
"node": cst.ClassDef(
|
||||
cst.Name("Foo"),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
type_parameters=cst.TypeParameters(
|
||||
params=(
|
||||
cst.TypeParam(
|
||||
param=cst.TypeVar(
|
||||
cst.Name("T"),
|
||||
bound=cst.Name("str"),
|
||||
colon=cst.Colon(
|
||||
whitespace_before=cst.SimpleWhitespace(" "),
|
||||
whitespace_after=cst.ParenthesizedWhitespace(
|
||||
empty_lines=(cst.EmptyLine(),),
|
||||
indent=True,
|
||||
),
|
||||
),
|
||||
),
|
||||
comma=cst.Comma(cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
cst.TypeParam(
|
||||
cst.ParamSpec(
|
||||
cst.Name("PS"), cst.SimpleWhitespace(" ")
|
||||
),
|
||||
cst.Comma(cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
)
|
||||
),
|
||||
whitespace_after_type_parameters=cst.SimpleWhitespace(" "),
|
||||
),
|
||||
"code": "class Foo[T :\n\nstr ,** PS ,] : pass\n",
|
||||
},
|
||||
{
|
||||
"node": cst.ClassDef(
|
||||
cst.Name("Foo"),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
type_parameters=cst.TypeParameters(
|
||||
params=(
|
||||
cst.TypeParam(
|
||||
param=cst.TypeVar(
|
||||
cst.Name("T"),
|
||||
bound=cst.Name("str"),
|
||||
colon=cst.Colon(
|
||||
whitespace_before=cst.SimpleWhitespace(" "),
|
||||
whitespace_after=cst.ParenthesizedWhitespace(
|
||||
empty_lines=(cst.EmptyLine(),),
|
||||
indent=True,
|
||||
),
|
||||
),
|
||||
),
|
||||
comma=cst.Comma(cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
cst.TypeParam(
|
||||
cst.ParamSpec(
|
||||
cst.Name("PS"), cst.SimpleWhitespace(" ")
|
||||
),
|
||||
cst.Comma(cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
)
|
||||
),
|
||||
lpar=cst.LeftParen(),
|
||||
rpar=cst.RightParen(),
|
||||
whitespace_after_type_parameters=cst.SimpleWhitespace(" "),
|
||||
),
|
||||
"code": "class Foo[T :\n\nstr ,** PS ,] (): pass\n",
|
||||
},
|
||||
)
|
||||
)
|
||||
def test_valid_native(self, **kwargs: Any) -> None:
|
||||
self.validate_node(**kwargs)
|
||||
|
||||
@data_provider(
|
||||
(
|
||||
# Basic parenthesis tests.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -10,7 +10,8 @@ import libcst as cst
|
|||
from libcst._removal_sentinel import RemovalSentinel
|
||||
from libcst._types import CSTNodeT
|
||||
from libcst._visitors import CSTTransformer
|
||||
from libcst.testing.utils import data_provider, none_throws, UnitTest
|
||||
from libcst.testing.utils import UnitTest, data_provider, none_throws
|
||||
|
||||
|
||||
_EMPTY_SIMPLE_WHITESPACE = cst.SimpleWhitespace("")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -187,6 +187,4 @@ class DictTest(CSTNodeTest):
|
|||
)
|
||||
)
|
||||
def test_versions(self, **kwargs: Any) -> None:
|
||||
if not kwargs.get("expect_success", True):
|
||||
self.skipTest("parse errors are disabled for native parser")
|
||||
self.assert_parses(**kwargs)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -26,17 +26,6 @@ class DictCompTest(CSTNodeTest):
|
|||
"parser": parse_expression,
|
||||
"expected_position": CodeRange((1, 0), (1, 17)),
|
||||
},
|
||||
# non-trivial keys & values in DictComp
|
||||
{
|
||||
"node": cst.DictComp(
|
||||
cst.BinaryOperation(cst.Name("k1"), cst.Add(), cst.Name("k2")),
|
||||
cst.BinaryOperation(cst.Name("v1"), cst.Add(), cst.Name("v2")),
|
||||
cst.CompFor(target=cst.Name("a"), iter=cst.Name("b")),
|
||||
),
|
||||
"code": "{k1 + k2: v1 + v2 for a in b}",
|
||||
"parser": parse_expression,
|
||||
"expected_position": CodeRange((1, 0), (1, 29)),
|
||||
},
|
||||
# custom whitespace around colon
|
||||
{
|
||||
"node": cst.DictComp(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -9,7 +9,7 @@ from typing import Optional
|
|||
|
||||
import libcst as cst
|
||||
from libcst.helpers import ensure_type
|
||||
from libcst.testing.utils import data_provider, UnitTest
|
||||
from libcst.testing.utils import UnitTest, data_provider
|
||||
|
||||
|
||||
class DocstringTest(UnitTest):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from typing import Type, Union
|
||||
|
||||
import libcst as cst
|
||||
from libcst import FlattenSentinel, parse_expression, parse_module, RemovalSentinel
|
||||
from libcst._nodes.tests.base import CSTNodeTest
|
||||
from libcst._types import CSTNodeT
|
||||
from libcst._visitors import CSTTransformer
|
||||
from libcst.testing.utils import data_provider
|
||||
|
||||
|
||||
class InsertPrintBeforeReturn(CSTTransformer):
|
||||
def leave_Return(
|
||||
self, original_node: cst.Return, updated_node: cst.Return
|
||||
) -> Union[cst.Return, RemovalSentinel, FlattenSentinel[cst.BaseSmallStatement]]:
|
||||
return FlattenSentinel(
|
||||
[
|
||||
cst.Expr(parse_expression("print('returning')")),
|
||||
updated_node,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class FlattenLines(CSTTransformer):
|
||||
def on_leave(
|
||||
self, original_node: CSTNodeT, updated_node: CSTNodeT
|
||||
) -> Union[CSTNodeT, RemovalSentinel, FlattenSentinel[cst.SimpleStatementLine]]:
|
||||
if isinstance(updated_node, cst.SimpleStatementLine):
|
||||
return FlattenSentinel(
|
||||
[
|
||||
cst.SimpleStatementLine(
|
||||
[stmt.with_changes(semicolon=cst.MaybeSentinel.DEFAULT)]
|
||||
)
|
||||
for stmt in updated_node.body
|
||||
]
|
||||
)
|
||||
else:
|
||||
return updated_node
|
||||
|
||||
|
||||
class RemoveReturnWithEmpty(CSTTransformer):
|
||||
def leave_Return(
|
||||
self, original_node: cst.Return, updated_node: cst.Return
|
||||
) -> Union[cst.Return, RemovalSentinel, FlattenSentinel[cst.BaseSmallStatement]]:
|
||||
return FlattenSentinel([])
|
||||
|
||||
|
||||
class FlattenBehavior(CSTNodeTest):
|
||||
@data_provider(
|
||||
(
|
||||
("return", "print('returning'); return", InsertPrintBeforeReturn),
|
||||
(
|
||||
"print('returning'); return",
|
||||
"print('returning')\nreturn",
|
||||
FlattenLines,
|
||||
),
|
||||
(
|
||||
"print('returning')\nreturn",
|
||||
"print('returning')",
|
||||
RemoveReturnWithEmpty,
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_flatten_pass_behavior(
|
||||
self, before: str, after: str, visitor: Type[CSTTransformer]
|
||||
) -> None:
|
||||
# Test doesn't have newline termination case
|
||||
before_module = parse_module(before)
|
||||
after_module = before_module.visit(visitor())
|
||||
self.assertEqual(after, after_module.code)
|
||||
|
||||
# Test does have newline termination case
|
||||
before_module = parse_module(before + "\n")
|
||||
after_module = before_module.visit(visitor())
|
||||
self.assertEqual(after + "\n", after_module.code)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
from typing import Any
|
||||
|
||||
import libcst as cst
|
||||
from libcst import parse_statement, PartialParserConfig
|
||||
from libcst import PartialParserConfig, parse_statement
|
||||
from libcst._nodes.tests.base import CSTNodeTest, DummyIndentedBlock
|
||||
from libcst.metadata import CodeRange
|
||||
from libcst.testing.utils import data_provider
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -622,46 +622,6 @@ class FunctionDefCreationTest(CSTNodeTest):
|
|||
"code": "@ bar ( )\n",
|
||||
"expected_position": CodeRange((1, 0), (1, 10)),
|
||||
},
|
||||
# Allow nested calls on decorator
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
cst.Name("foo"),
|
||||
cst.Parameters(),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
(cst.Decorator(cst.Call(func=cst.Call(func=cst.Name("bar")))),),
|
||||
),
|
||||
"code": "@bar()()\ndef foo(): pass\n",
|
||||
},
|
||||
# Allow any expression in decorator
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
cst.Name("foo"),
|
||||
cst.Parameters(),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
(
|
||||
cst.Decorator(
|
||||
cst.BinaryOperation(cst.Name("a"), cst.Add(), cst.Name("b"))
|
||||
),
|
||||
),
|
||||
),
|
||||
"code": "@a + b\ndef foo(): pass\n",
|
||||
},
|
||||
# Allow parentheses around decorator
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
cst.Name("foo"),
|
||||
cst.Parameters(),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
(
|
||||
cst.Decorator(
|
||||
cst.Name(
|
||||
"bar", lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
"code": "@(bar)\ndef foo(): pass\n",
|
||||
},
|
||||
# Parameters
|
||||
{
|
||||
"node": cst.Parameters(
|
||||
|
|
@ -740,154 +700,6 @@ class FunctionDefCreationTest(CSTNodeTest):
|
|||
)
|
||||
)
|
||||
def test_valid(self, **kwargs: Any) -> None:
|
||||
if "native_only" in kwargs:
|
||||
kwargs.pop("native_only")
|
||||
self.validate_node(**kwargs)
|
||||
|
||||
@data_provider(
|
||||
(
|
||||
# PEP 646
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
name=cst.Name(value="foo"),
|
||||
params=cst.Parameters(
|
||||
params=[],
|
||||
star_arg=cst.Param(
|
||||
star="*",
|
||||
name=cst.Name("a"),
|
||||
annotation=cst.Annotation(
|
||||
cst.StarredElement(value=cst.Name("b")),
|
||||
whitespace_before_indicator=cst.SimpleWhitespace(""),
|
||||
),
|
||||
),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
"parser": parse_statement,
|
||||
"code": "def foo(*a: *b): pass\n",
|
||||
},
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
name=cst.Name(value="foo"),
|
||||
params=cst.Parameters(
|
||||
params=[],
|
||||
star_arg=cst.Param(
|
||||
star="*",
|
||||
name=cst.Name("a"),
|
||||
annotation=cst.Annotation(
|
||||
cst.StarredElement(
|
||||
value=cst.Subscript(
|
||||
value=cst.Name("tuple"),
|
||||
slice=[
|
||||
cst.SubscriptElement(
|
||||
cst.Index(cst.Name("int")),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
cst.SubscriptElement(
|
||||
cst.Index(
|
||||
value=cst.Name("Ts"),
|
||||
star="*",
|
||||
whitespace_after_star=cst.SimpleWhitespace(
|
||||
""
|
||||
),
|
||||
),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
cst.SubscriptElement(
|
||||
cst.Index(cst.Ellipsis())
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
whitespace_before_indicator=cst.SimpleWhitespace(""),
|
||||
),
|
||||
),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
"parser": parse_statement,
|
||||
"code": "def foo(*a: *tuple[int,*Ts,...]): pass\n",
|
||||
},
|
||||
# Single type variable
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
cst.Name("foo"),
|
||||
cst.Parameters(),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
type_parameters=cst.TypeParameters(
|
||||
(cst.TypeParam(cst.TypeVar(cst.Name("T"))),)
|
||||
),
|
||||
),
|
||||
"code": "def foo[T](): pass\n",
|
||||
"parser": parse_statement,
|
||||
},
|
||||
# All the type parameters
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
cst.Name("foo"),
|
||||
cst.Parameters(),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
type_parameters=cst.TypeParameters(
|
||||
(
|
||||
cst.TypeParam(
|
||||
cst.TypeVar(
|
||||
cst.Name("T"),
|
||||
bound=cst.Name("int"),
|
||||
colon=cst.Colon(
|
||||
whitespace_after=cst.SimpleWhitespace(" ")
|
||||
),
|
||||
),
|
||||
cst.Comma(whitespace_after=cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
cst.TypeParam(
|
||||
cst.TypeVarTuple(cst.Name("Ts")),
|
||||
cst.Comma(whitespace_after=cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
cst.TypeParam(cst.ParamSpec(cst.Name("KW"))),
|
||||
)
|
||||
),
|
||||
),
|
||||
"code": "def foo[T: int, *Ts, **KW](): pass\n",
|
||||
"parser": parse_statement,
|
||||
},
|
||||
# Type parameters with whitespace
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
cst.Name("foo"),
|
||||
cst.Parameters(),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
type_parameters=cst.TypeParameters(
|
||||
params=(
|
||||
cst.TypeParam(
|
||||
param=cst.TypeVar(
|
||||
cst.Name("T"),
|
||||
bound=cst.Name("str"),
|
||||
colon=cst.Colon(
|
||||
whitespace_before=cst.SimpleWhitespace(" "),
|
||||
whitespace_after=cst.ParenthesizedWhitespace(
|
||||
empty_lines=(cst.EmptyLine(),),
|
||||
indent=True,
|
||||
),
|
||||
),
|
||||
),
|
||||
comma=cst.Comma(cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
cst.TypeParam(
|
||||
cst.ParamSpec(
|
||||
cst.Name("PS"), cst.SimpleWhitespace(" ")
|
||||
),
|
||||
cst.Comma(cst.SimpleWhitespace(" ")),
|
||||
),
|
||||
)
|
||||
),
|
||||
whitespace_after_type_parameters=cst.SimpleWhitespace(" "),
|
||||
),
|
||||
"code": "def foo[T :\n\nstr ,** PS ,] (): pass\n",
|
||||
"parser": parse_statement,
|
||||
},
|
||||
)
|
||||
)
|
||||
def test_valid_native(self, **kwargs: Any) -> None:
|
||||
self.validate_node(**kwargs)
|
||||
|
||||
@data_provider(
|
||||
|
|
@ -1034,6 +846,22 @@ class FunctionDefCreationTest(CSTNodeTest):
|
|||
),
|
||||
r"Expecting a star prefix of '\*\*'",
|
||||
),
|
||||
# Validate decorator name semantics
|
||||
(
|
||||
lambda: cst.FunctionDef(
|
||||
cst.Name("foo"),
|
||||
cst.Parameters(),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
(
|
||||
cst.Decorator(
|
||||
cst.Name(
|
||||
"bar", lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
"Cannot have parens around decorator in a Decorator",
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_invalid(
|
||||
|
|
@ -1047,9 +875,7 @@ def _parse_statement_force_38(code: str) -> cst.BaseCompoundStatement:
|
|||
code, config=cst.PartialParserConfig(python_version="3.8")
|
||||
)
|
||||
if not isinstance(statement, cst.BaseCompoundStatement):
|
||||
raise ValueError(
|
||||
"This function is expecting to parse compound statements only!"
|
||||
)
|
||||
raise Exception("This function is expecting to parse compound statements only!")
|
||||
return statement
|
||||
|
||||
|
||||
|
|
@ -1972,36 +1798,6 @@ class FunctionDefParserTest(CSTNodeTest):
|
|||
),
|
||||
"code": "def foo(bar, baz, /): pass\n",
|
||||
},
|
||||
# Positional only params with whitespace after but no comma
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
cst.Name("foo"),
|
||||
cst.Parameters(
|
||||
posonly_params=(
|
||||
cst.Param(
|
||||
cst.Name("bar"),
|
||||
star="",
|
||||
comma=cst.Comma(
|
||||
whitespace_after=cst.SimpleWhitespace(" ")
|
||||
),
|
||||
),
|
||||
cst.Param(
|
||||
cst.Name("baz"),
|
||||
star="",
|
||||
comma=cst.Comma(
|
||||
whitespace_after=cst.SimpleWhitespace(" ")
|
||||
),
|
||||
),
|
||||
),
|
||||
posonly_ind=cst.ParamSlash(
|
||||
whitespace_after=cst.SimpleWhitespace(" ")
|
||||
),
|
||||
),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
"code": "def foo(bar, baz, / ): pass\n",
|
||||
"native_only": True,
|
||||
},
|
||||
# Typed positional only params
|
||||
{
|
||||
"node": cst.FunctionDef(
|
||||
|
|
@ -2217,7 +2013,7 @@ class FunctionDefParserTest(CSTNodeTest):
|
|||
},
|
||||
)
|
||||
)
|
||||
def test_valid_38(self, node: cst.CSTNode, code: str, **kwargs: Any) -> None:
|
||||
def test_valid_38(self, node: cst.CSTNode, code: str) -> None:
|
||||
self.validate_node(node, code, _parse_statement_force_38)
|
||||
|
||||
@data_provider(
|
||||
|
|
@ -2245,23 +2041,4 @@ class FunctionDefParserTest(CSTNodeTest):
|
|||
)
|
||||
)
|
||||
def test_versions(self, **kwargs: Any) -> None:
|
||||
if not kwargs.get("expect_success", True):
|
||||
self.skipTest("parse errors are disabled for native parser")
|
||||
self.assert_parses(**kwargs)
|
||||
|
||||
@data_provider(
|
||||
(
|
||||
{"code": "A[:*b]"},
|
||||
{"code": "A[*b:]"},
|
||||
{"code": "A[*b:*b]"},
|
||||
{"code": "A[*(1:2)]"},
|
||||
{"code": "A[*:]"},
|
||||
{"code": "A[:*]"},
|
||||
{"code": "A[**b]"},
|
||||
{"code": "def f(x: *b): pass"},
|
||||
{"code": "def f(**x: *b): pass"},
|
||||
{"code": "x: *b"},
|
||||
)
|
||||
)
|
||||
def test_parse_error(self, **kwargs: Any) -> None:
|
||||
self.assert_parses(**kwargs, expect_success=False, parser=parse_statement)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from typing import Any, Callable
|
||||
from typing import Any
|
||||
|
||||
import libcst as cst
|
||||
from libcst import parse_statement
|
||||
|
|
@ -129,21 +129,3 @@ class IfTest(CSTNodeTest):
|
|||
)
|
||||
def test_valid(self, **kwargs: Any) -> None:
|
||||
self.validate_node(**kwargs)
|
||||
|
||||
@data_provider(
|
||||
(
|
||||
# Validate whitespace handling
|
||||
(
|
||||
lambda: cst.If(
|
||||
cst.Name("conditional"),
|
||||
cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
whitespace_before_test=cst.SimpleWhitespace(""),
|
||||
),
|
||||
"Must have at least one space after 'if' keyword.",
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_invalid(
|
||||
self, get_node: Callable[[], cst.CSTNode], expected_re: str
|
||||
) -> None:
|
||||
self.assert_invalid(get_node, expected_re)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -52,41 +52,6 @@ class IfExpTest(CSTNodeTest):
|
|||
"(foo)if(bar)else(baz)",
|
||||
CodeRange((1, 0), (1, 21)),
|
||||
),
|
||||
(
|
||||
cst.IfExp(
|
||||
body=cst.Name("foo"),
|
||||
whitespace_before_if=cst.SimpleWhitespace(" "),
|
||||
whitespace_after_if=cst.SimpleWhitespace(" "),
|
||||
test=cst.Name("bar"),
|
||||
whitespace_before_else=cst.SimpleWhitespace(" "),
|
||||
whitespace_after_else=cst.SimpleWhitespace(""),
|
||||
orelse=cst.IfExp(
|
||||
body=cst.SimpleString("''"),
|
||||
whitespace_before_if=cst.SimpleWhitespace(""),
|
||||
test=cst.Name("bar"),
|
||||
orelse=cst.Name("baz"),
|
||||
),
|
||||
),
|
||||
"foo if bar else''if bar else baz",
|
||||
CodeRange((1, 0), (1, 32)),
|
||||
),
|
||||
(
|
||||
cst.GeneratorExp(
|
||||
elt=cst.IfExp(
|
||||
body=cst.Name("foo"),
|
||||
test=cst.Name("bar"),
|
||||
orelse=cst.SimpleString("''"),
|
||||
whitespace_after_else=cst.SimpleWhitespace(""),
|
||||
),
|
||||
for_in=cst.CompFor(
|
||||
target=cst.Name("_"),
|
||||
iter=cst.Name("_"),
|
||||
whitespace_before=cst.SimpleWhitespace(""),
|
||||
),
|
||||
),
|
||||
"(foo if bar else''for _ in _)",
|
||||
CodeRange((1, 1), (1, 28)),
|
||||
),
|
||||
# Make sure that spacing works
|
||||
(
|
||||
cst.IfExp(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -195,20 +195,6 @@ class ImportCreateTest(CSTNodeTest):
|
|||
),
|
||||
"expected_re": "at least one space",
|
||||
},
|
||||
{
|
||||
"get_node": lambda: cst.Import(
|
||||
names=(
|
||||
cst.ImportAlias(
|
||||
cst.Name("foo"),
|
||||
asname=cst.AsName(
|
||||
cst.Name("bar"),
|
||||
whitespace_before_as=cst.SimpleWhitespace(""),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
"expected_re": "at least one space",
|
||||
},
|
||||
{
|
||||
"get_node": lambda: cst.Import(
|
||||
names=[
|
||||
|
|
@ -578,25 +564,6 @@ class ImportFromCreateTest(CSTNodeTest):
|
|||
),
|
||||
"expected_re": "one space after import",
|
||||
},
|
||||
{
|
||||
"get_node": lambda: cst.ImportFrom(
|
||||
module=cst.Name("foo"),
|
||||
names=(
|
||||
cst.ImportAlias(
|
||||
cst.Name("bar"),
|
||||
asname=cst.AsName(
|
||||
cst.Name(
|
||||
"baz",
|
||||
lpar=(cst.LeftParen(),),
|
||||
rpar=(cst.RightParen(),),
|
||||
),
|
||||
whitespace_before_as=cst.SimpleWhitespace(""),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
"expected_re": "one space before as keyword",
|
||||
},
|
||||
)
|
||||
)
|
||||
def test_invalid(self, **kwargs: Any) -> None:
|
||||
|
|
@ -650,10 +617,8 @@ class ImportFromParseTest(CSTNodeTest):
|
|||
),
|
||||
cst.ImportAlias(cst.Name("baz"), comma=cst.Comma()),
|
||||
),
|
||||
lpar=cst.LeftParen(),
|
||||
rpar=cst.RightParen(),
|
||||
),
|
||||
"code": "from foo import (bar, baz,)",
|
||||
"code": "from foo import bar, baz,",
|
||||
},
|
||||
# Star import statement
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -30,22 +30,6 @@ class LambdaCreationTest(CSTNodeTest):
|
|||
),
|
||||
"code": "lambda bar, baz, /: 5",
|
||||
},
|
||||
# Test basic positional only params with extra trailing whitespace
|
||||
{
|
||||
"node": cst.Lambda(
|
||||
cst.Parameters(
|
||||
posonly_params=(
|
||||
cst.Param(cst.Name("bar")),
|
||||
cst.Param(cst.Name("baz")),
|
||||
),
|
||||
posonly_ind=cst.ParamSlash(
|
||||
whitespace_after=cst.SimpleWhitespace(" ")
|
||||
),
|
||||
),
|
||||
cst.Integer("5"),
|
||||
),
|
||||
"code": "lambda bar, baz, / : 5",
|
||||
},
|
||||
# Test basic positional params
|
||||
(
|
||||
cst.Lambda(
|
||||
|
|
@ -303,6 +287,30 @@ class LambdaCreationTest(CSTNodeTest):
|
|||
),
|
||||
"at least one space after lambda",
|
||||
),
|
||||
(
|
||||
lambda: cst.Lambda(
|
||||
cst.Parameters(star_arg=cst.Param(cst.Name("arg"))),
|
||||
cst.Integer("5"),
|
||||
whitespace_after_lambda=cst.SimpleWhitespace(""),
|
||||
),
|
||||
"at least one space after lambda",
|
||||
),
|
||||
(
|
||||
lambda: cst.Lambda(
|
||||
cst.Parameters(kwonly_params=(cst.Param(cst.Name("arg")),)),
|
||||
cst.Integer("5"),
|
||||
whitespace_after_lambda=cst.SimpleWhitespace(""),
|
||||
),
|
||||
"at least one space after lambda",
|
||||
),
|
||||
(
|
||||
lambda: cst.Lambda(
|
||||
cst.Parameters(star_kwarg=cst.Param(cst.Name("arg"))),
|
||||
cst.Integer("5"),
|
||||
whitespace_after_lambda=cst.SimpleWhitespace(""),
|
||||
),
|
||||
"at least one space after lambda",
|
||||
),
|
||||
(
|
||||
lambda: cst.Lambda(
|
||||
cst.Parameters(
|
||||
|
|
@ -920,53 +928,6 @@ class LambdaParserTest(CSTNodeTest):
|
|||
),
|
||||
"( lambda : 5 )",
|
||||
),
|
||||
# No space between lambda and params
|
||||
(
|
||||
cst.Lambda(
|
||||
cst.Parameters(star_arg=cst.Param(cst.Name("args"), star="*")),
|
||||
cst.Integer("5"),
|
||||
whitespace_after_lambda=cst.SimpleWhitespace(""),
|
||||
),
|
||||
"lambda*args: 5",
|
||||
),
|
||||
(
|
||||
cst.Lambda(
|
||||
cst.Parameters(star_kwarg=cst.Param(cst.Name("kwargs"), star="**")),
|
||||
cst.Integer("5"),
|
||||
whitespace_after_lambda=cst.SimpleWhitespace(""),
|
||||
),
|
||||
"lambda**kwargs: 5",
|
||||
),
|
||||
(
|
||||
cst.Lambda(
|
||||
cst.Parameters(
|
||||
star_arg=cst.ParamStar(
|
||||
comma=cst.Comma(
|
||||
cst.SimpleWhitespace(""), cst.SimpleWhitespace("")
|
||||
)
|
||||
),
|
||||
kwonly_params=[cst.Param(cst.Name("args"), star="")],
|
||||
),
|
||||
cst.Integer("5"),
|
||||
whitespace_after_lambda=cst.SimpleWhitespace(""),
|
||||
),
|
||||
"lambda*,args: 5",
|
||||
),
|
||||
(
|
||||
cst.ListComp(
|
||||
elt=cst.Lambda(
|
||||
params=cst.Parameters(),
|
||||
body=cst.Tuple(()),
|
||||
colon=cst.Colon(),
|
||||
),
|
||||
for_in=cst.CompFor(
|
||||
target=cst.Name("_"),
|
||||
iter=cst.Name("_"),
|
||||
whitespace_before=cst.SimpleWhitespace(""),
|
||||
),
|
||||
),
|
||||
"[lambda:()for _ in _]",
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_valid(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -13,6 +13,7 @@ from libcst.testing.utils import data_provider
|
|||
|
||||
|
||||
class ListTest(CSTNodeTest):
|
||||
|
||||
# A lot of Element/StarredElement tests are provided by the tests for Tuple, so we
|
||||
# we don't need to duplicate them here.
|
||||
@data_provider(
|
||||
|
|
@ -125,6 +126,4 @@ class ListTest(CSTNodeTest):
|
|||
)
|
||||
)
|
||||
def test_versions(self, **kwargs: Any) -> None:
|
||||
if not kwargs.get("expect_success", True):
|
||||
self.skipTest("parse errors are disabled for native parser")
|
||||
self.assert_parses(**kwargs)
|
||||
|
|
|
|||
|
|
@ -1,489 +0,0 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from typing import Any, Callable
|
||||
|
||||
import libcst as cst
|
||||
from libcst import parse_statement
|
||||
from libcst._nodes.tests.base import CSTNodeTest
|
||||
from libcst.testing.utils import data_provider
|
||||
|
||||
parser: Callable[[str], cst.CSTNode] = parse_statement
|
||||
|
||||
|
||||
class MatchTest(CSTNodeTest):
|
||||
@data_provider(
|
||||
(
|
||||
# Values and singletons
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name("x"),
|
||||
cases=[
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchValue(cst.SimpleString('"foo"')),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
],
|
||||
),
|
||||
"code": "match x:\n"
|
||||
+ " case None: pass\n"
|
||||
+ ' case "foo": pass\n',
|
||||
"parser": parser,
|
||||
},
|
||||
# Parenthesized value
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name(
|
||||
value="x",
|
||||
),
|
||||
cases=[
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchAs(
|
||||
pattern=cst.MatchValue(
|
||||
value=cst.Integer(
|
||||
value="1",
|
||||
lpar=[
|
||||
cst.LeftParen(),
|
||||
],
|
||||
rpar=[
|
||||
cst.RightParen(),
|
||||
],
|
||||
),
|
||||
),
|
||||
name=cst.Name(
|
||||
value="z",
|
||||
),
|
||||
whitespace_before_as=cst.SimpleWhitespace(" "),
|
||||
whitespace_after_as=cst.SimpleWhitespace(" "),
|
||||
),
|
||||
body=cst.SimpleStatementSuite([cst.Pass()]),
|
||||
),
|
||||
],
|
||||
),
|
||||
"code": "match x:\n case (1) as z: pass\n",
|
||||
"parser": parser,
|
||||
},
|
||||
# List patterns
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name("x"),
|
||||
cases=[
|
||||
cst.MatchCase( # empty list
|
||||
pattern=cst.MatchList(
|
||||
[],
|
||||
lbracket=cst.LeftSquareBracket(),
|
||||
rbracket=cst.RightSquareBracket(),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # single element list
|
||||
pattern=cst.MatchList(
|
||||
[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None"))
|
||||
)
|
||||
],
|
||||
lbracket=cst.LeftSquareBracket(),
|
||||
rbracket=cst.RightSquareBracket(),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # single element list with trailing comma
|
||||
pattern=cst.MatchList(
|
||||
[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
cst.Comma(),
|
||||
)
|
||||
],
|
||||
lbracket=cst.LeftSquareBracket(),
|
||||
rbracket=cst.RightSquareBracket(),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
],
|
||||
),
|
||||
"code": (
|
||||
"match x:\n"
|
||||
+ " case []: pass\n"
|
||||
+ " case [None]: pass\n"
|
||||
+ " case [None,]: pass\n"
|
||||
),
|
||||
"parser": parser,
|
||||
},
|
||||
# Tuple patterns
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name("x"),
|
||||
cases=[
|
||||
cst.MatchCase( # empty tuple
|
||||
pattern=cst.MatchTuple(
|
||||
[],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # two element tuple
|
||||
pattern=cst.MatchTuple(
|
||||
[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
cst.Comma(),
|
||||
),
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
),
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # single element tuple with trailing comma
|
||||
pattern=cst.MatchTuple(
|
||||
[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
cst.Comma(),
|
||||
)
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # two element tuple
|
||||
pattern=cst.MatchTuple(
|
||||
[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
cst.Comma(),
|
||||
),
|
||||
cst.MatchStar(
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
),
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
],
|
||||
),
|
||||
"code": (
|
||||
"match x:\n"
|
||||
+ " case (): pass\n"
|
||||
+ " case (None,None): pass\n"
|
||||
+ " case (None,): pass\n"
|
||||
+ " case (None,*_,None): pass\n"
|
||||
),
|
||||
"parser": parser,
|
||||
},
|
||||
# Mapping patterns
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name("x"),
|
||||
cases=[
|
||||
cst.MatchCase( # empty mapping
|
||||
pattern=cst.MatchMapping(
|
||||
[],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # two element mapping
|
||||
pattern=cst.MatchMapping(
|
||||
[
|
||||
cst.MatchMappingElement(
|
||||
key=cst.SimpleString('"a"'),
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
cst.MatchMappingElement(
|
||||
key=cst.SimpleString('"b"'),
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
),
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # single element mapping with trailing comma
|
||||
pattern=cst.MatchMapping(
|
||||
[
|
||||
cst.MatchMappingElement(
|
||||
key=cst.SimpleString('"a"'),
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
comma=cst.Comma(),
|
||||
)
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # rest
|
||||
pattern=cst.MatchMapping(
|
||||
rest=cst.Name("rest"),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
],
|
||||
),
|
||||
"code": (
|
||||
"match x:\n"
|
||||
+ " case {}: pass\n"
|
||||
+ ' case {"a": None,"b": None}: pass\n'
|
||||
+ ' case {"a": None,}: pass\n'
|
||||
+ " case {**rest}: pass\n"
|
||||
),
|
||||
"parser": parser,
|
||||
},
|
||||
# Class patterns
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name("x"),
|
||||
cases=[
|
||||
cst.MatchCase( # empty class
|
||||
pattern=cst.MatchClass(
|
||||
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # single pattern class
|
||||
pattern=cst.MatchClass(
|
||||
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
|
||||
patterns=[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None"))
|
||||
)
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # single pattern class with trailing comma
|
||||
pattern=cst.MatchClass(
|
||||
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
|
||||
patterns=[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
comma=cst.Comma(),
|
||||
)
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # single keyword pattern class
|
||||
pattern=cst.MatchClass(
|
||||
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
|
||||
kwds=[
|
||||
cst.MatchKeywordElement(
|
||||
key=cst.Name("foo"),
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
)
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # single keyword pattern class with trailing comma
|
||||
pattern=cst.MatchClass(
|
||||
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
|
||||
kwds=[
|
||||
cst.MatchKeywordElement(
|
||||
key=cst.Name("foo"),
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
comma=cst.Comma(),
|
||||
)
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase( # now all at once
|
||||
pattern=cst.MatchClass(
|
||||
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
|
||||
patterns=[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
cst.Comma(),
|
||||
),
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
cst.Comma(),
|
||||
),
|
||||
],
|
||||
kwds=[
|
||||
cst.MatchKeywordElement(
|
||||
key=cst.Name("foo"),
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
cst.MatchKeywordElement(
|
||||
key=cst.Name("bar"),
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
comma=cst.Comma(),
|
||||
),
|
||||
],
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
],
|
||||
),
|
||||
"code": (
|
||||
"match x:\n"
|
||||
+ " case a.b(): pass\n"
|
||||
+ " case a.b(None): pass\n"
|
||||
+ " case a.b(None,): pass\n"
|
||||
+ " case a.b(foo=None): pass\n"
|
||||
+ " case a.b(foo=None,): pass\n"
|
||||
+ " case a.b(None,None,foo=None,bar=None,): pass\n"
|
||||
),
|
||||
"parser": parser,
|
||||
},
|
||||
# as pattern
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name("x"),
|
||||
cases=[
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchAs(),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchAs(name=cst.Name("foo")),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchAs(
|
||||
pattern=cst.MatchSingleton(cst.Name("None")),
|
||||
name=cst.Name("bar"),
|
||||
whitespace_before_as=cst.SimpleWhitespace(" "),
|
||||
whitespace_after_as=cst.SimpleWhitespace(" "),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
],
|
||||
),
|
||||
"code": "match x:\n"
|
||||
+ " case _: pass\n"
|
||||
+ " case foo: pass\n"
|
||||
+ " case None as bar: pass\n",
|
||||
"parser": parser,
|
||||
},
|
||||
# or pattern
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name("x"),
|
||||
cases=[
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchOr(
|
||||
[
|
||||
cst.MatchOrElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
cst.BitOr(),
|
||||
),
|
||||
cst.MatchOrElement(
|
||||
cst.MatchSingleton(cst.Name("False")),
|
||||
cst.BitOr(),
|
||||
),
|
||||
cst.MatchOrElement(
|
||||
cst.MatchSingleton(cst.Name("True"))
|
||||
),
|
||||
]
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
)
|
||||
],
|
||||
),
|
||||
"code": "match x:\n case None | False | True: pass\n",
|
||||
"parser": parser,
|
||||
},
|
||||
{ # exercise sentinels
|
||||
"node": cst.Match(
|
||||
subject=cst.Name("x"),
|
||||
cases=[
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchList(
|
||||
[cst.MatchStar(), cst.MatchStar()],
|
||||
lbracket=None,
|
||||
rbracket=None,
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchTuple(
|
||||
[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None"))
|
||||
)
|
||||
]
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchAs(
|
||||
pattern=cst.MatchTuple(
|
||||
[
|
||||
cst.MatchSequenceElement(
|
||||
cst.MatchSingleton(cst.Name("None"))
|
||||
)
|
||||
]
|
||||
),
|
||||
name=cst.Name("bar"),
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchOr(
|
||||
[
|
||||
cst.MatchOrElement(
|
||||
cst.MatchSingleton(cst.Name("None")),
|
||||
),
|
||||
cst.MatchOrElement(
|
||||
cst.MatchSingleton(cst.Name("False")),
|
||||
),
|
||||
cst.MatchOrElement(
|
||||
cst.MatchSingleton(cst.Name("True"))
|
||||
),
|
||||
]
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
),
|
||||
],
|
||||
),
|
||||
"code": "match x:\n"
|
||||
+ " case *_, *_: pass\n"
|
||||
+ " case (None,): pass\n"
|
||||
+ " case (None,) as bar: pass\n"
|
||||
+ " case None | False | True: pass\n",
|
||||
"parser": None,
|
||||
},
|
||||
# Match without whitespace between keyword and the expr
|
||||
{
|
||||
"node": cst.Match(
|
||||
subject=cst.Name(
|
||||
"x", lpar=[cst.LeftParen()], rpar=[cst.RightParen()]
|
||||
),
|
||||
cases=[
|
||||
cst.MatchCase(
|
||||
pattern=cst.MatchSingleton(
|
||||
cst.Name(
|
||||
"None",
|
||||
lpar=[cst.LeftParen()],
|
||||
rpar=[cst.RightParen()],
|
||||
)
|
||||
),
|
||||
body=cst.SimpleStatementSuite((cst.Pass(),)),
|
||||
whitespace_after_case=cst.SimpleWhitespace(
|
||||
value="",
|
||||
),
|
||||
),
|
||||
],
|
||||
whitespace_after_match=cst.SimpleWhitespace(
|
||||
value="",
|
||||
),
|
||||
),
|
||||
"code": "match(x):\n case(None): pass\n",
|
||||
"parser": parser,
|
||||
},
|
||||
)
|
||||
)
|
||||
def test_valid(self, **kwargs: Any) -> None:
|
||||
self.validate_node(**kwargs)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -69,6 +69,4 @@ class NamedExprTest(CSTNodeTest):
|
|||
)
|
||||
)
|
||||
def test_versions(self, **kwargs: Any) -> None:
|
||||
if not kwargs.get("expect_success", True):
|
||||
self.skipTest("parse errors are disabled for native parser")
|
||||
self.assert_parses(**kwargs)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from typing import cast, Tuple
|
||||
from typing import Tuple, cast
|
||||
|
||||
import libcst as cst
|
||||
from libcst import parse_module, parse_statement
|
||||
from libcst._nodes.tests.base import CSTNodeTest
|
||||
|
||||
from libcst.metadata import CodeRange, MetadataWrapper, PositionProvider
|
||||
from libcst.testing.utils import data_provider
|
||||
|
||||
|
|
@ -84,7 +83,6 @@ class ModuleTest(CSTNodeTest):
|
|||
"empty_program_with_newline": {
|
||||
"code": "\n",
|
||||
"expected": cst.Module([], has_trailing_newline=True),
|
||||
"enabled_for_native": False,
|
||||
},
|
||||
"empty_program_with_comments": {
|
||||
"code": "# some comment\n",
|
||||
|
|
@ -114,11 +112,7 @@ class ModuleTest(CSTNodeTest):
|
|||
},
|
||||
}
|
||||
)
|
||||
def test_parser(
|
||||
self, *, code: str, expected: cst.Module, enabled_for_native: bool = True
|
||||
) -> None:
|
||||
if not enabled_for_native:
|
||||
self.skipTest("Disabled for native parser")
|
||||
def test_parser(self, *, code: str, expected: cst.Module) -> None:
|
||||
self.assertEqual(parse_module(code), expected)
|
||||
|
||||
@data_provider(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -22,9 +22,7 @@ def _parse_statement_force_38(code: str) -> cst.BaseCompoundStatement:
|
|||
code, config=cst.PartialParserConfig(python_version="3.8")
|
||||
)
|
||||
if not isinstance(statement, cst.BaseCompoundStatement):
|
||||
raise ValueError(
|
||||
"This function is expecting to parse compound statements only!"
|
||||
)
|
||||
raise Exception("This function is expecting to parse compound statements only!")
|
||||
return statement
|
||||
|
||||
|
||||
|
|
@ -168,22 +166,6 @@ class NamedExprTest(CSTNodeTest):
|
|||
"parser": _parse_expression_force_38,
|
||||
"expected_position": None,
|
||||
},
|
||||
{
|
||||
"node": cst.ListComp(
|
||||
elt=cst.NamedExpr(
|
||||
cst.Name("_"),
|
||||
cst.SimpleString("''"),
|
||||
whitespace_after_walrus=cst.SimpleWhitespace(""),
|
||||
whitespace_before_walrus=cst.SimpleWhitespace(""),
|
||||
),
|
||||
for_in=cst.CompFor(
|
||||
target=cst.Name("_"),
|
||||
iter=cst.Name("_"),
|
||||
whitespace_before=cst.SimpleWhitespace(""),
|
||||
),
|
||||
),
|
||||
"code": "[_:=''for _ in _]",
|
||||
},
|
||||
)
|
||||
)
|
||||
def test_valid(self, **kwargs: Any) -> None:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
from typing import Type, Union
|
||||
|
||||
import libcst as cst
|
||||
from libcst import parse_module, RemovalSentinel
|
||||
from libcst import RemovalSentinel, parse_module
|
||||
from libcst._nodes.tests.base import CSTNodeTest
|
||||
from libcst._types import CSTNodeT
|
||||
from libcst._visitors import CSTTransformer
|
||||
|
|
@ -95,7 +95,7 @@ class RemovalBehavior(CSTNodeTest):
|
|||
self, before: str, after: str, visitor: Type[CSTTransformer]
|
||||
) -> None:
|
||||
if before.endswith("\n") or after.endswith("\n"):
|
||||
raise ValueError("Test cases should not be newline-terminated!")
|
||||
raise Exception("Test cases should not be newline-terminated!")
|
||||
|
||||
# Test doesn't have newline termination case
|
||||
before_module = parse_module(before)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -12,6 +12,7 @@ from libcst.testing.utils import data_provider
|
|||
|
||||
|
||||
class ListTest(CSTNodeTest):
|
||||
|
||||
# A lot of Element/StarredElement tests are provided by the tests for Tuple, so we
|
||||
# we don't need to duplicate them here.
|
||||
@data_provider(
|
||||
|
|
@ -132,6 +133,4 @@ class ListTest(CSTNodeTest):
|
|||
)
|
||||
)
|
||||
def test_versions(self, **kwargs: Any) -> None:
|
||||
if not kwargs.get("expect_success", True):
|
||||
self.skipTest("parse errors are disabled for native parser")
|
||||
self.assert_parses(**kwargs)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
from typing import Any, Callable
|
||||
|
||||
import libcst as cst
|
||||
from libcst import parse_expression, parse_statement, PartialParserConfig
|
||||
from libcst import PartialParserConfig, parse_expression, parse_statement
|
||||
from libcst._nodes.tests.base import CSTNodeTest
|
||||
from libcst.metadata import CodeRange
|
||||
from libcst.testing.utils import data_provider
|
||||
|
|
@ -41,33 +41,6 @@ class SimpleCompTest(CSTNodeTest):
|
|||
"code": "{a for b in c}",
|
||||
"parser": parse_expression,
|
||||
},
|
||||
# non-trivial elt in GeneratorExp
|
||||
{
|
||||
"node": cst.GeneratorExp(
|
||||
cst.BinaryOperation(cst.Name("a1"), cst.Add(), cst.Name("a2")),
|
||||
cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")),
|
||||
),
|
||||
"code": "(a1 + a2 for b in c)",
|
||||
"parser": parse_expression,
|
||||
},
|
||||
# non-trivial elt in ListComp
|
||||
{
|
||||
"node": cst.ListComp(
|
||||
cst.BinaryOperation(cst.Name("a1"), cst.Add(), cst.Name("a2")),
|
||||
cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")),
|
||||
),
|
||||
"code": "[a1 + a2 for b in c]",
|
||||
"parser": parse_expression,
|
||||
},
|
||||
# non-trivial elt in SetComp
|
||||
{
|
||||
"node": cst.SetComp(
|
||||
cst.BinaryOperation(cst.Name("a1"), cst.Add(), cst.Name("a2")),
|
||||
cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")),
|
||||
),
|
||||
"code": "{a1 + a2 for b in c}",
|
||||
"parser": parse_expression,
|
||||
},
|
||||
# async GeneratorExp
|
||||
{
|
||||
"node": cst.GeneratorExp(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import unittest
|
||||
|
||||
import libcst as cst
|
||||
|
||||
|
||||
class TestSimpleString(unittest.TestCase):
|
||||
def test_quote(self) -> None:
|
||||
test_cases = [
|
||||
('"a"', '"'),
|
||||
("'b'", "'"),
|
||||
('""', '"'),
|
||||
("''", "'"),
|
||||
('"""c"""', '"""'),
|
||||
("'''d'''", "'''"),
|
||||
('""""e"""', '"""'),
|
||||
("''''f'''", "'''"),
|
||||
('"""""g"""', '"""'),
|
||||
("'''''h'''", "'''"),
|
||||
('""""""', '"""'),
|
||||
("''''''", "'''"),
|
||||
]
|
||||
|
||||
for s, expected_quote in test_cases:
|
||||
simple_string = cst.SimpleString(s)
|
||||
actual = simple_string.quote
|
||||
self.assertEqual(expected_quote, actual)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
|
|
|||
|
|
@ -1,183 +0,0 @@
|
|||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from typing import Callable, Optional
|
||||
|
||||
import libcst as cst
|
||||
from libcst import parse_expression
|
||||
from libcst._nodes.tests.base import CSTNodeTest
|
||||
from libcst.metadata import CodeRange
|
||||
from libcst.testing.utils import data_provider
|
||||
|
||||
|
||||
class TemplatedStringTest(CSTNodeTest):
|
||||
@data_provider(
|
||||
(
|
||||
# Simple t-string with only text
|
||||
(
|
||||
cst.TemplatedString(
|
||||
parts=(cst.TemplatedStringText("hello world"),),
|
||||
),
|
||||
't"hello world"',
|
||||
True,
|
||||
),
|
||||
# t-string with one expression
|
||||
(
|
||||
cst.TemplatedString(
|
||||
parts=(
|
||||
cst.TemplatedStringText("hello "),
|
||||
cst.TemplatedStringExpression(
|
||||
expression=cst.Name("name"),
|
||||
),
|
||||
),
|
||||
),
|
||||
't"hello {name}"',
|
||||
True,
|
||||
),
|
||||
# t-string with multiple expressions
|
||||
(
|
||||
cst.TemplatedString(
|
||||
parts=(
|
||||
cst.TemplatedStringText("a="),
|
||||
cst.TemplatedStringExpression(expression=cst.Name("a")),
|
||||
cst.TemplatedStringText(", b="),
|
||||
cst.TemplatedStringExpression(expression=cst.Name("b")),
|
||||
),
|
||||
),
|
||||
't"a={a}, b={b}"',
|
||||
True,
|
||||
CodeRange((1, 0), (1, 15)),
|
||||
),
|
||||
# t-string with nested expression
|
||||
(
|
||||
cst.TemplatedString(
|
||||
parts=(
|
||||
cst.TemplatedStringText("sum="),
|
||||
cst.TemplatedStringExpression(
|
||||
expression=cst.BinaryOperation(
|
||||
left=cst.Name("a"),
|
||||
operator=cst.Add(),
|
||||
right=cst.Name("b"),
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
't"sum={a + b}"',
|
||||
True,
|
||||
),
|
||||
# t-string with spacing in expression
|
||||
(
|
||||
cst.TemplatedString(
|
||||
parts=(
|
||||
cst.TemplatedStringText("x = "),
|
||||
cst.TemplatedStringExpression(
|
||||
whitespace_before_expression=cst.SimpleWhitespace(" "),
|
||||
expression=cst.Name("x"),
|
||||
whitespace_after_expression=cst.SimpleWhitespace(" "),
|
||||
),
|
||||
),
|
||||
),
|
||||
't"x = { x }"',
|
||||
True,
|
||||
),
|
||||
# t-string with escaped braces
|
||||
(
|
||||
cst.TemplatedString(
|
||||
parts=(cst.TemplatedStringText("{{foo}}"),),
|
||||
),
|
||||
't"{{foo}}"',
|
||||
True,
|
||||
),
|
||||
# t-string with only an expression
|
||||
(
|
||||
cst.TemplatedString(
|
||||
parts=(
|
||||
cst.TemplatedStringExpression(expression=cst.Name("value")),
|
||||
),
|
||||
),
|
||||
't"{value}"',
|
||||
True,
|
||||
),
|
||||
# t-string with whitespace and newlines
|
||||
(
|
||||
cst.TemplatedString(
|
||||
parts=(
|
||||
cst.TemplatedStringText("line1\\n"),
|
||||
cst.TemplatedStringExpression(expression=cst.Name("x")),
|
||||
cst.TemplatedStringText("\\nline2"),
|
||||
),
|
||||
),
|
||||
't"line1\\n{x}\\nline2"',
|
||||
True,
|
||||
),
|
||||
# t-string with parenthesis (not typical, but test node construction)
|
||||
(
|
||||
cst.TemplatedString(
|
||||
lpar=(cst.LeftParen(),),
|
||||
parts=(cst.TemplatedStringText("foo"),),
|
||||
rpar=(cst.RightParen(),),
|
||||
),
|
||||
'(t"foo")',
|
||||
True,
|
||||
),
|
||||
# t-string with whitespace in delimiters
|
||||
(
|
||||
cst.TemplatedString(
|
||||
lpar=(cst.LeftParen(whitespace_after=cst.SimpleWhitespace(" ")),),
|
||||
parts=(cst.TemplatedStringText("foo"),),
|
||||
rpar=(cst.RightParen(whitespace_before=cst.SimpleWhitespace(" ")),),
|
||||
),
|
||||
'( t"foo" )',
|
||||
True,
|
||||
),
|
||||
# Test TemplatedStringText and TemplatedStringExpression individually
|
||||
(
|
||||
cst.TemplatedStringText("abc"),
|
||||
"abc",
|
||||
False,
|
||||
CodeRange((1, 0), (1, 3)),
|
||||
),
|
||||
(
|
||||
cst.TemplatedStringExpression(expression=cst.Name("foo")),
|
||||
"{foo}",
|
||||
False,
|
||||
CodeRange((1, 0), (1, 5)),
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_valid(
|
||||
self,
|
||||
node: cst.CSTNode,
|
||||
code: str,
|
||||
check_parsing: bool,
|
||||
position: Optional[CodeRange] = None,
|
||||
) -> None:
|
||||
if check_parsing:
|
||||
self.validate_node(node, code, parse_expression, expected_position=position)
|
||||
else:
|
||||
self.validate_node(node, code, expected_position=position)
|
||||
|
||||
@data_provider(
|
||||
(
|
||||
(
|
||||
lambda: cst.TemplatedString(
|
||||
parts=(cst.TemplatedStringText("foo"),),
|
||||
lpar=(cst.LeftParen(),),
|
||||
),
|
||||
"left paren without right paren",
|
||||
),
|
||||
(
|
||||
lambda: cst.TemplatedString(
|
||||
parts=(cst.TemplatedStringText("foo"),),
|
||||
rpar=(cst.RightParen(),),
|
||||
),
|
||||
"right paren without left paren",
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_invalid(
|
||||
self, get_node: Callable[[], cst.CSTNode], expected_re: str
|
||||
) -> None:
|
||||
self.assert_invalid(get_node, expected_re)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue