Compare commits

..

No commits in common. "main" and "v0.3.14" have entirely different histories.

441 changed files with 6386 additions and 50166 deletions

View file

@ -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",
]

View 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
View 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"

View file

@ -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
View file

@ -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 brackets 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 brackets 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

View file

@ -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/.*)$"
}
]

View file

@ -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

View file

@ -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}}

View file

@ -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

View file

@ -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 }}

View file

@ -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
View file

@ -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

View file

@ -1,16 +0,0 @@
{
"exclude": [
".*\/native\/.*"
],
"ignore_all_errors": [
".venv"
],
"source_directories": [
"."
],
"search_path": [
"stubs", {"site-package": "setuptools_rust"}
],
"workers": 3,
"strict": true
}

View file

@ -0,0 +1,12 @@
{
"source_directories": [
"."
],
"search_path": [
"stubs"
],
"exclude": [
".*/\\.tox/.*"
],
"strict": true
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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`

View file

@ -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

View file

@ -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
======

View file

@ -1,2 +0,0 @@
rustc
cargo

4
codecov.yml Normal file
View file

@ -0,0 +1,4 @@
coverage:
status:
project: no
patch: yes

View file

@ -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

Before After
Before After

View file

@ -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

Before After
Before After

View file

@ -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
----------------

View file

@ -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
"""

View file

@ -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

View file

@ -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.

View file

@ -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:"
]

View file

@ -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:

View file

@ -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."
]
},
{

View file

@ -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."
]
},
{

View file

@ -7,7 +7,6 @@ Visitors
.. autoclass:: libcst.CSTTransformer
.. autofunction:: libcst.RemoveFromParent
.. autoclass:: libcst.RemovalSentinel
.. autoclass:: libcst.FlattenSentinel
Visit and Leave Helper Functions
--------------------------------

View file

@ -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",
]

View file

@ -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__

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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):

View file

@ -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])

View file

@ -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.

View file

@ -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:

View file

@ -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.

View file

@ -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)

View file

@ -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)

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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:

View file

@ -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.

View file

@ -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"),
)
),

View file

@ -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)

View file

@ -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.

View file

@ -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:

View file

@ -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)

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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("")

View file

@ -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.

View file

@ -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)

View file

@ -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(

View file

@ -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):

View file

@ -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.

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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.

View file

@ -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)

View file

@ -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(

View file

@ -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
{

View file

@ -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.

View file

@ -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(

View file

@ -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.

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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(

View file

@ -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:

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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)

View file

@ -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.

View file

@ -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)

View file

@ -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(

View file

@ -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.

View file

@ -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)

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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