{"owner":"pylint-dev","repo":"pylint","hasSkills":true,"totalSkillsCount":2,"totalTokensCount":4001,"categories":["copilot-instructions","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":[".github/copilot-instructions.md","AGENTS.md"],"skills":{".github/copilot-instructions.md":"# Pylint Development Instructions\n\nAlways follow these instructions first and fallback to additional search and context\ngathering only if the information in these instructions is incomplete or found to be in\nerror.\n\n## Issue Label Guidelines\n\nBefore attempting to fix any issue, check the GitHub issue labels using the GitHub API:\n\n- If an issue is labeled with \"Astroid\", \"Needs astroid update\", \"Needs astroid\n  constraint\", or \"Needs astroid Brain 🧠\", **ONLY** create regression tests\n- Do **NOT** attempt to fix astroid-related issues as you cannot modify astroid from\n  this repository\n- For astroid-related issues, focus on creating comprehensive regression tests that\n  reproduce the problem\n- All other issues can be fixed normally following the standard development workflow\n\n## Development Environment Setup\n\n### Basic Installation\n\nClone and set up pylint development environment:\n\n- `git clone https://github.com/pylint-dev/pylint` -- clone repository\n- `cd pylint` -- enter directory\n- `python3 -m venv venv` -- create virtual environment\n- `source venv/bin/activate` -- activate virtual environment (Linux/Mac)\n- `pip install -r requirements_test_min.txt` -- install test dependencies (~30 seconds)\n- `pip install -e .` -- install pylint in editable mode (~30-60 seconds)\n\n### Optional Setup Steps\n\n- `pre-commit install` -- enable pre-commit hooks for autoformatting\n- `pip install pre-commit` -- install pre-commit separately if needed\n\n### Astroid Development (if needed)\n\nIf working on astroid changes:\n\n- `git clone https://github.com/pylint-dev/astroid.git` -- clone astroid\n- `pip install -e astroid/` -- install astroid in editable mode\n- `cd astroid/ && git switch my-astroid-dev-branch` -- switch to development branch\n\n## Running Tests\n\n### Core Test Commands\n\n- `pytest tests/test_functional.py -k test_functional` -- run functional tests (~60\n  seconds, NEVER CANCEL, set timeout to 120+ seconds)\n- `pytest tests/` -- run all tests (several minutes, NEVER CANCEL, set timeout to 300+\n  seconds)\n- `python3 -m pytest` -- run tests with local python\n- `pytest tests/test_check_parallel.py -v` -- quick test file (~2 seconds)\n\n### Specific Test Types\n\n- **Functional tests:**\n  `pytest \"tests/test_functional.py::test_functional[missing_kwoa_py3]\"` -- single\n  functional test (~1 second)\n- **Unit tests:** Located in `/tests/` directory, test specific pylint functionality\n- **Configuration tests:** In `/tests/config/functional/` for testing configuration\n  loading\n- **Primer tests:** `pytest -m primer_stdlib --primer-stdlib` -- test on stdlib for\n  crashes\n\n### Test with Coverage\n\n- `pytest tests/message/ --cov=pylint.message` -- run with coverage\n- `coverage html` -- generate HTML coverage report\n\n### Tox Usage (Optional)\n\n- `python -m tox` -- run all tox environments\n- `python -m tox -epy313` -- run Python 3.13 suite only\n- `python -m tox -epylint` -- run pylint on pylint's codebase\n- `python -m tox -eformatting` -- run formatting checks\n- `python -m tox --recreate` -- recreate environments (recommended)\n- `python -m tox -e py311 -- -k test_functional` -- run specific tests in tox\n\n## Documentation\n\n### Building Documentation\n\n- `make -C doc/ install-dependencies` -- install doc dependencies (~10 seconds)\n- `make -C doc/ html` -- build documentation (~3 minutes, NEVER CANCEL, set timeout to\n  300+ seconds)\n- `make -C doc/ clean` -- clean build files when starting from scratch\n- `tox -e docs` -- alternative way to build docs\n\n**Network dependency:** Documentation build requires internet access to fetch external\ninventories.\n\n## Validation and Quality Checks\n\n### Running Pylint on Code\n\n- `pylint --help` -- verify pylint installation works\n- `pylint --disable=all --enable=E,F pylint/` -- run pylint on itself for errors only\n  (~20 seconds)\n- `pylint --rcfile=pylintrc --fail-on=I path/to/your/changes.py` -- standard pylint run\n- `pylint --disable=all --enable=E,F,W path/to/your/changes.py` -- focus on errors and\n  warnings\n\n### Pre-commit and Formatting\n\n- `pre-commit run --all-files` -- run all formatting checks (requires network for\n  initial setup)\n- **Network dependency:** pre-commit may fail in isolated environments due to hook\n  downloads\n\n### Validation Test Scenarios\n\nAlways test your changes with these validation scenarios:\n\n- `echo \"def badFunction(): pass\" > /tmp/test_sample.py && pylint --enable=C0103 /tmp/test_sample.py`\n  -- should find naming issues\n- `pylint --help` and `pylint --list-msgs | head -10` -- verify CLI functionality\n- `pylint --help-msg=C0103` -- should show invalid-name help\n- `pylint --rcfile=pylintrc --fail-on=I pylint/__init__.py` -- should get 10.00/10\n  rating\n\n## Writing Tests\n\n### Functional Tests\n\nLocated in `/tests/functional/`, consists of `.py` test files with corresponding `.txt`\nexpected output files:\n\n- Annotate lines where messages are expected:\n  `a, b, c = 1 # [unbalanced-tuple-unpacking]`\n- Multiple messages on same line:\n  `a, b, c = 1.test # [unbalanced-tuple-unpacking, no-member]`\n- Use offset syntax for special cases: `# +1: [singleton-comparison]`\n- **Run and update:**\n  `python tests/test_functional.py --update-functional-output -k \"test_functional[test_name]\"`\n\n### Test File Organization\n\n- **New checkers:** Create `new_checker_message.py` in `/tests/functional/n/`\n- **Extensions:** Place in `/tests/functional/ext/extension_name/`\n- **Regression tests:** Place in `/tests/r/regression/` with `regression_` prefix\n- **Configuration tests:** Place in `/tests/config/functional/`\n\n### Configuration Test Files\n\nCreate `.result.json` files with configuration differences from standard config:\n\n```json\n{\n  \"functional_append\": {\n    \"disable\": [[\"a-message-to-be-added\"]]\n  },\n  \"jobs\": 10\n}\n```\n\n## Codebase Structure\n\n```\npylint/                    # Main package\n├── checkers/             # All pylint checkers (rules implementation)\n├── config/               # Configuration handling and parsing\n├── message/              # Message system and formatting\n├── reporters/            # Output formatters (text, json, etc.)\n├── testutils/            # Testing utilities and helpers\n└── extensions/           # Optional extensions and plugins\n\ntests/                     # Test suite\n├── functional/           # Functional test files (.py + .txt expected output)\n├── config/functional/    # Configuration functional tests\n├── r/regression/         # Regression tests\n├── test_*.py            # Unit tests\n└── regrtest_data/       # Test data files\n\ndoc/                      # Documentation\n├── user_guide/          # User documentation\n├── development_guide/   # Developer and contributor documentation\n│   ├── contributor_guide/    # Setup, testing, contribution guidelines\n│   ├── technical_reference/  # Technical implementation details\n│   └── how_tos/             # Guides for custom checkers, plugins\n└── additional_tools/    # Tools documentation\n\nscript/                   # Development utility scripts\n```\n\n### Key Files\n\n- `pyproject.toml` -- Main configuration (dependencies, build, tools)\n- `tox.ini` -- Multi-environment testing configuration\n- `.pre-commit-config.yaml` -- Code quality checks configuration\n- `pylintrc` -- Pylint's own configuration\n- `requirements_test_min.txt` -- Minimal test dependencies\n- `.gitignore` do not add the 'venv' inside the .gitignore, don't commit the venv in the\n  first place (humans add it to their global gitignore)\n\n## Creating New Checkers\n\n### Getting Started\n\n- `python script/get_unused_message_id_category.py` -- get next available message ID\n- Study existing checkers in `pylint/checkers/` for patterns\n- Read technical reference documentation in `doc/development_guide/technical_reference/`\n- Use `astroid.extract_node` for AST manipulation\n\n### Workflow\n\n1. Create checker class in appropriate `pylint/checkers/` file\n2. Add functional tests in `tests/functional/`\n3. Search existing code for warning message to find where logic exists\n4. Test with sample code to ensure functionality works\n\n## Pull Request Guidelines\n\n### Before Submitting\n\n- Use Python 3.8+ for development (required for latest AST parser and pre-commit hooks)\n- Write comprehensive commit messages relating to tracker issues\n- Keep changes small and separate consensual from opinionated changes\n- Add news fragment: `towncrier create <IssueNumber>.<type>`\n- Always launch `pre-commit run -a` before committing\n\n### Documentation Changes\n\n- Document non-trivial changes\n- Generate docs with `tox -e docs`\n- Maintainers may label issues `skip-news` if no changelog needed\n\n### Contribution Credits\n\n- Add emails/names to `script/.contributors_aliases.json` if using multiple identities\n\n## Critical Timing Information\n\n- **NEVER CANCEL:** All operations that show \"NEVER CANCEL\" may take significant time\n- **Full test suite:** 60+ seconds (set timeout to 120+ seconds)\n- **Documentation build:** 180 seconds (set timeout to 300+ seconds)\n- **Functional tests:** 60 seconds (set timeout to 120+ seconds)\n- **Pylint self-check:** 20 seconds (set timeout to 60+ seconds)\n- **Individual test files:** 1-15 seconds\n- **Installation steps:** 30-60 seconds each\n\n## Environment Limitations and Workarounds\n\n- **Network connectivity required:** Documentation build and pre-commit setup require\n  internet access\n- **Tox failures:** In isolated environments, use direct pytest and pip commands instead\n  of tox\n- **Import errors in self-check:** Some import errors when running pylint on itself are\n  expected (git dependencies not installed)\n- **Build environments:** Use direct pip/pytest commands when tox environments fail to\n  build\n\nAlways validate your changes by running pylint on sample code to ensure functionality\nworks correctly.\n","AGENTS.md":"# Agent guidelines\n\n## Before opening a pull request\n\n### Check for existing work\n\nDo this twice: before you start working on an issue, and again right before you open\nyour pull request. Someone may have opened one in between.\n\n**Rule: if the issue already has an `OPEN` pull request, do not open another one.**\nReview that pull request or suggest improvements on it instead. Two pull requests for\nthe same fix waste review time, and one of them will be closed.\n\n#### Step 1: list the pull requests linked to the issue\n\nOn GitHub, the pull requests that will close an issue appear in the \"Development\" box of\nthe issue's sidebar. Get that list with this command. Only change `11459` to your issue\nnumber:\n\n```shell\ngh api graphql -F number=11459 -f query='\nquery($number: Int!) {\n  repository(owner: \"pylint-dev\", name: \"pylint\") {\n    issue(number: $number) {\n      closedByPullRequestsReferences(first: 50, includeClosedPrs: true) {\n        nodes { number state url author { login } }\n      }\n    }\n  }\n}' --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | \"\\(.state) \\(.url) by \\(.author.login)\"'\n```\n\nEach output line is one pull request. Example output:\n\n```text\nOPEN https://github.com/pylint-dev/pylint/pull/11460 by agustin18\nCLOSED https://github.com/pylint-dev/pylint/pull/11542 by aipd506\n```\n\n- A line starting with `OPEN`: someone is already working on it. Stop and follow the\n  rule above.\n- `MERGED`: the fix may already be on `main`. Check whether the bug still happens there.\n- `CLOSED`: that attempt was abandoned. Read why before you start, so you do not repeat\n  it.\n- No output at all: no pull request is linked. Go to step 2.\n\nIf `gh` is not installed or not logged in, read the same data from the issue's web page.\nIt works without a GitHub account. Only change `11459` to your issue number:\n\n```shell\ncurl -sL https://github.com/pylint-dev/pylint/issues/11459 | python3 -c '\nimport json, sys\npage = sys.stdin.read()\nkey = \"\\\"closedByPullRequestsReferences\\\":\"\nstart = page.find(key)\nif start == -1:\n    sys.exit(\"Development data not found: is this an issue URL, not a pull request URL?\")\ndata, _ = json.JSONDecoder().raw_decode(page, start + len(key))\nfor pr in data[\"nodes\"]:\n    print(pr[\"state\"], pr[\"url\"])\n'\n```\n\nThe output and its meaning are the same as above, without the author.\n\n#### Step 2: search for pull requests that are not linked\n\nStep 1 only finds pull requests that say `Closes #<number>` (or `Fixes`, `Resolves`), or\nthat a maintainer linked by hand. A pull request that only mentions the issue, or forgot\nto mention it, is not in that list. Search for those too:\n\n```shell\ngh pr list --repo pylint-dev/pylint --state open --search \"11459\"\ngh pr list --repo pylint-dev/pylint --state open --search \"<key words from the issue title>\"\n```\n\n#### Step 3: check if someone said they are working on it\n\nList the issue's comments with their dates. Only change `11459` to your issue number:\n\n```shell\ngh issue view 11459 --repo pylint-dev/pylint --json comments --jq '.comments[] | \"\\(.createdAt[:10]) \\(.author.login): \\(.body | gsub(\"\\n\"; \" \") | .[:100])\"'\n```\n\nWithout `gh`, read the comments on the issue's web page.\n\nLook for a comment like \"I'm working on this\" or \"Can I take this?\". People often say\nthis and then never open a pull request, so a claim does not last forever:\n\n- The claim is **active** if the person who made it did something in the last 15 days: a\n  comment on the issue, or a commit or pull request for it. Do not work on the issue.\n- The claim is **expired** if that person has done nothing for 15 days or more. You may\n  work on the issue. Say in your pull request description that the issue was claimed by\n  `@<their login>` on `<date>`, with no activity since.\n\n### Cover every new line and branch\n\nNew and changed code in `pylint/` must be fully covered by tests: every line, and both\nsides of every branch (`if`/`else`, early `return`, `and`/`or` short-circuit used as a\nguard). Codecov requires 100% patch coverage. A guard that no test ever takes is either\ndead code to remove or a missing test case to add.\n\nCheck it locally on the tests you touched:\n\n```shell\npytest --cov=pylint --cov-branch --cov-report=term-missing tests/test_functional.py -k \"<test name>\"\n```\n\nEach new or changed line in the diff should be absent from the `Missing` column, and no\nbranch involving it should be listed as partial.\n\n## AST-based checking\n\nPylint is an AST-based linter built on [astroid](https://github.com/pylint-dev/astroid).\nWhen writing or modifying checkers, prefer **`isinstance` against concrete astroid node\ntypes** combined with domain knowledge of Python syntax, rather than duck-typing with\n`getattr`/`hasattr`.\n\nThe visitor pattern from `BaseChecker` already narrows the node type for you: a\n`visit_call` method only receives `nodes.Call`, a `visit_assign` only receives\n`nodes.Assign`, and so on. Inside such a method, the node's structure is known — its\nattributes follow from the grammar (e.g. a `nodes.Call` always has `.func` and `.args`).\nWalk and type-check that known structure with `isinstance` instead of probing for\nattributes defensively.\n\nGood:\n\n```python\ndef visit_call(self, node: nodes.Call) -> None:\n    if isinstance(node.func, nodes.Attribute):\n        ...\n```\n\nAvoid:\n\n```python\ndef visit_call(self, node) -> None:\n    if hasattr(node.func, \"attrname\"):  # don't probe — check the type\n        ...\n```\n\nThis keeps checks precise, readable, and aligned with astroid's typed node API.\n\n### Caveat: astroid proxies and `Uninferable`\n\n`isinstance` checks the _static_ node type. Some astroid objects — `bases.Instance`,\n`Generator`, `BoundMethod`/`UnboundMethod`, and `util.Uninferable` — resolve attributes\nthrough `__getattr__`, proxying to a wrapped node. So `hasattr(obj, \"x\")` can be True at\nruntime on an object whose class has no `x`, and a naive `isinstance(obj, ConcreteNode)`\nwill _drop_ cases the old `hasattr` caught (e.g. an exception inferred to an `Instance`\nthat proxies `ancestors`, or an `AsyncGenerator` that proxies `locals`).\n\nWhen replacing such a guard:\n\n- Include the proxy base in the type tuple — e.g. `(nodes.ClassDef, bases.Instance)`, or\n  `(nodes.LocalsDictNodeNG, bases.Proxy)` for anything exposing `qname`.\n- If the check is a behavioral _capability_ spanning heterogeneous nodes with no common\n  base — or the proxied node may legitimately lack the attribute (a `BoundMethod` can\n  wrap a `Lambda`, which has no `.decorators`) — keep `hasattr`. That is honest\n  duck-typing, not a grammar check, and `isinstance` cannot express it safely.\n"},"files":{".github/copilot-instructions.md":"# Pylint Development Instructions\n\nAlways follow these instructions first and fallback to additional search and context\ngathering only if the information in these instructions is incomplete or found to be in\nerror.\n\n## Issue Label Guidelines\n\nBefore attempting to fix any issue, check the GitHub issue labels using the GitHub API:\n\n- If an issue is labeled with \"Astroid\", \"Needs astroid update\", \"Needs astroid\n  constraint\", or \"Needs astroid Brain 🧠\", **ONLY** create regression tests\n- Do **NOT** attempt to fix astroid-related issues as you cannot modify astroid from\n  this repository\n- For astroid-related issues, focus on creating comprehensive regression tests that\n  reproduce the problem\n- All other issues can be fixed normally following the standard development workflow\n\n## Development Environment Setup\n\n### Basic Installation\n\nClone and set up pylint development environment:\n\n- `git clone https://github.com/pylint-dev/pylint` -- clone repository\n- `cd pylint` -- enter directory\n- `python3 -m venv venv` -- create virtual environment\n- `source venv/bin/activate` -- activate virtual environment (Linux/Mac)\n- `pip install -r requirements_test_min.txt` -- install test dependencies (~30 seconds)\n- `pip install -e .` -- install pylint in editable mode (~30-60 seconds)\n\n### Optional Setup Steps\n\n- `pre-commit install` -- enable pre-commit hooks for autoformatting\n- `pip install pre-commit` -- install pre-commit separately if needed\n\n### Astroid Development (if needed)\n\nIf working on astroid changes:\n\n- `git clone https://github.com/pylint-dev/astroid.git` -- clone astroid\n- `pip install -e astroid/` -- install astroid in editable mode\n- `cd astroid/ && git switch my-astroid-dev-branch` -- switch to development branch\n\n## Running Tests\n\n### Core Test Commands\n\n- `pytest tests/test_functional.py -k test_functional` -- run functional tests (~60\n  seconds, NEVER CANCEL, set timeout to 120+ seconds)\n- `pytest tests/` -- run all tests (several minutes, NEVER CANCEL, set timeout to 300+\n  seconds)\n- `python3 -m pytest` -- run tests with local python\n- `pytest tests/test_check_parallel.py -v` -- quick test file (~2 seconds)\n\n### Specific Test Types\n\n- **Functional tests:**\n  `pytest \"tests/test_functional.py::test_functional[missing_kwoa_py3]\"` -- single\n  functional test (~1 second)\n- **Unit tests:** Located in `/tests/` directory, test specific pylint functionality\n- **Configuration tests:** In `/tests/config/functional/` for testing configuration\n  loading\n- **Primer tests:** `pytest -m primer_stdlib --primer-stdlib` -- test on stdlib for\n  crashes\n\n### Test with Coverage\n\n- `pytest tests/message/ --cov=pylint.message` -- run with coverage\n- `coverage html` -- generate HTML coverage report\n\n### Tox Usage (Optional)\n\n- `python -m tox` -- run all tox environments\n- `python -m tox -epy313` -- run Python 3.13 suite only\n- `python -m tox -epylint` -- run pylint on pylint's codebase\n- `python -m tox -eformatting` -- run formatting checks\n- `python -m tox --recreate` -- recreate environments (recommended)\n- `python -m tox -e py311 -- -k test_functional` -- run specific tests in tox\n\n## Documentation\n\n### Building Documentation\n\n- `make -C doc/ install-dependencies` -- install doc dependencies (~10 seconds)\n- `make -C doc/ html` -- build documentation (~3 minutes, NEVER CANCEL, set timeout to\n  300+ seconds)\n- `make -C doc/ clean` -- clean build files when starting from scratch\n- `tox -e docs` -- alternative way to build docs\n\n**Network dependency:** Documentation build requires internet access to fetch external\ninventories.\n\n## Validation and Quality Checks\n\n### Running Pylint on Code\n\n- `pylint --help` -- verify pylint installation works\n- `pylint --disable=all --enable=E,F pylint/` -- run pylint on itself for errors only\n  (~20 seconds)\n- `pylint --rcfile=pylintrc --fail-on=I path/to/your/changes.py` -- standard pylint run\n- `pylint --disable=all --enable=E,F,W path/to/your/changes.py` -- focus on errors and\n  warnings\n\n### Pre-commit and Formatting\n\n- `pre-commit run --all-files` -- run all formatting checks (requires network for\n  initial setup)\n- **Network dependency:** pre-commit may fail in isolated environments due to hook\n  downloads\n\n### Validation Test Scenarios\n\nAlways test your changes with these validation scenarios:\n\n- `echo \"def badFunction(): pass\" > /tmp/test_sample.py && pylint --enable=C0103 /tmp/test_sample.py`\n  -- should find naming issues\n- `pylint --help` and `pylint --list-msgs | head -10` -- verify CLI functionality\n- `pylint --help-msg=C0103` -- should show invalid-name help\n- `pylint --rcfile=pylintrc --fail-on=I pylint/__init__.py` -- should get 10.00/10\n  rating\n\n## Writing Tests\n\n### Functional Tests\n\nLocated in `/tests/functional/`, consists of `.py` test files with corresponding `.txt`\nexpected output files:\n\n- Annotate lines where messages are expected:\n  `a, b, c = 1 # [unbalanced-tuple-unpacking]`\n- Multiple messages on same line:\n  `a, b, c = 1.test # [unbalanced-tuple-unpacking, no-member]`\n- Use offset syntax for special cases: `# +1: [singleton-comparison]`\n- **Run and update:**\n  `python tests/test_functional.py --update-functional-output -k \"test_functional[test_name]\"`\n\n### Test File Organization\n\n- **New checkers:** Create `new_checker_message.py` in `/tests/functional/n/`\n- **Extensions:** Place in `/tests/functional/ext/extension_name/`\n- **Regression tests:** Place in `/tests/r/regression/` with `regression_` prefix\n- **Configuration tests:** Place in `/tests/config/functional/`\n\n### Configuration Test Files\n\nCreate `.result.json` files with configuration differences from standard config:\n\n```json\n{\n  \"functional_append\": {\n    \"disable\": [[\"a-message-to-be-added\"]]\n  },\n  \"jobs\": 10\n}\n```\n\n## Codebase Structure\n\n```\npylint/                    # Main package\n├── checkers/             # All pylint checkers (rules implementation)\n├── config/               # Configuration handling and parsing\n├── message/              # Message system and formatting\n├── reporters/            # Output formatters (text, json, etc.)\n├── testutils/            # Testing utilities and helpers\n└── extensions/           # Optional extensions and plugins\n\ntests/                     # Test suite\n├── functional/           # Functional test files (.py + .txt expected output)\n├── config/functional/    # Configuration functional tests\n├── r/regression/         # Regression tests\n├── test_*.py            # Unit tests\n└── regrtest_data/       # Test data files\n\ndoc/                      # Documentation\n├── user_guide/          # User documentation\n├── development_guide/   # Developer and contributor documentation\n│   ├── contributor_guide/    # Setup, testing, contribution guidelines\n│   ├── technical_reference/  # Technical implementation details\n│   └── how_tos/             # Guides for custom checkers, plugins\n└── additional_tools/    # Tools documentation\n\nscript/                   # Development utility scripts\n```\n\n### Key Files\n\n- `pyproject.toml` -- Main configuration (dependencies, build, tools)\n- `tox.ini` -- Multi-environment testing configuration\n- `.pre-commit-config.yaml` -- Code quality checks configuration\n- `pylintrc` -- Pylint's own configuration\n- `requirements_test_min.txt` -- Minimal test dependencies\n- `.gitignore` do not add the 'venv' inside the .gitignore, don't commit the venv in the\n  first place (humans add it to their global gitignore)\n\n## Creating New Checkers\n\n### Getting Started\n\n- `python script/get_unused_message_id_category.py` -- get next available message ID\n- Study existing checkers in `pylint/checkers/` for patterns\n- Read technical reference documentation in `doc/development_guide/technical_reference/`\n- Use `astroid.extract_node` for AST manipulation\n\n### Workflow\n\n1. Create checker class in appropriate `pylint/checkers/` file\n2. Add functional tests in `tests/functional/`\n3. Search existing code for warning message to find where logic exists\n4. Test with sample code to ensure functionality works\n\n## Pull Request Guidelines\n\n### Before Submitting\n\n- Use Python 3.8+ for development (required for latest AST parser and pre-commit hooks)\n- Write comprehensive commit messages relating to tracker issues\n- Keep changes small and separate consensual from opinionated changes\n- Add news fragment: `towncrier create <IssueNumber>.<type>`\n- Always launch `pre-commit run -a` before committing\n\n### Documentation Changes\n\n- Document non-trivial changes\n- Generate docs with `tox -e docs`\n- Maintainers may label issues `skip-news` if no changelog needed\n\n### Contribution Credits\n\n- Add emails/names to `script/.contributors_aliases.json` if using multiple identities\n\n## Critical Timing Information\n\n- **NEVER CANCEL:** All operations that show \"NEVER CANCEL\" may take significant time\n- **Full test suite:** 60+ seconds (set timeout to 120+ seconds)\n- **Documentation build:** 180 seconds (set timeout to 300+ seconds)\n- **Functional tests:** 60 seconds (set timeout to 120+ seconds)\n- **Pylint self-check:** 20 seconds (set timeout to 60+ seconds)\n- **Individual test files:** 1-15 seconds\n- **Installation steps:** 30-60 seconds each\n\n## Environment Limitations and Workarounds\n\n- **Network connectivity required:** Documentation build and pre-commit setup require\n  internet access\n- **Tox failures:** In isolated environments, use direct pytest and pip commands instead\n  of tox\n- **Import errors in self-check:** Some import errors when running pylint on itself are\n  expected (git dependencies not installed)\n- **Build environments:** Use direct pip/pytest commands when tox environments fail to\n  build\n\nAlways validate your changes by running pylint on sample code to ensure functionality\nworks correctly.\n","AGENTS.md":"# Agent guidelines\n\n## Before opening a pull request\n\n### Check for existing work\n\nDo this twice: before you start working on an issue, and again right before you open\nyour pull request. Someone may have opened one in between.\n\n**Rule: if the issue already has an `OPEN` pull request, do not open another one.**\nReview that pull request or suggest improvements on it instead. Two pull requests for\nthe same fix waste review time, and one of them will be closed.\n\n#### Step 1: list the pull requests linked to the issue\n\nOn GitHub, the pull requests that will close an issue appear in the \"Development\" box of\nthe issue's sidebar. Get that list with this command. Only change `11459` to your issue\nnumber:\n\n```shell\ngh api graphql -F number=11459 -f query='\nquery($number: Int!) {\n  repository(owner: \"pylint-dev\", name: \"pylint\") {\n    issue(number: $number) {\n      closedByPullRequestsReferences(first: 50, includeClosedPrs: true) {\n        nodes { number state url author { login } }\n      }\n    }\n  }\n}' --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | \"\\(.state) \\(.url) by \\(.author.login)\"'\n```\n\nEach output line is one pull request. Example output:\n\n```text\nOPEN https://github.com/pylint-dev/pylint/pull/11460 by agustin18\nCLOSED https://github.com/pylint-dev/pylint/pull/11542 by aipd506\n```\n\n- A line starting with `OPEN`: someone is already working on it. Stop and follow the\n  rule above.\n- `MERGED`: the fix may already be on `main`. Check whether the bug still happens there.\n- `CLOSED`: that attempt was abandoned. Read why before you start, so you do not repeat\n  it.\n- No output at all: no pull request is linked. Go to step 2.\n\nIf `gh` is not installed or not logged in, read the same data from the issue's web page.\nIt works without a GitHub account. Only change `11459` to your issue number:\n\n```shell\ncurl -sL https://github.com/pylint-dev/pylint/issues/11459 | python3 -c '\nimport json, sys\npage = sys.stdin.read()\nkey = \"\\\"closedByPullRequestsReferences\\\":\"\nstart = page.find(key)\nif start == -1:\n    sys.exit(\"Development data not found: is this an issue URL, not a pull request URL?\")\ndata, _ = json.JSONDecoder().raw_decode(page, start + len(key))\nfor pr in data[\"nodes\"]:\n    print(pr[\"state\"], pr[\"url\"])\n'\n```\n\nThe output and its meaning are the same as above, without the author.\n\n#### Step 2: search for pull requests that are not linked\n\nStep 1 only finds pull requests that say `Closes #<number>` (or `Fixes`, `Resolves`), or\nthat a maintainer linked by hand. A pull request that only mentions the issue, or forgot\nto mention it, is not in that list. Search for those too:\n\n```shell\ngh pr list --repo pylint-dev/pylint --state open --search \"11459\"\ngh pr list --repo pylint-dev/pylint --state open --search \"<key words from the issue title>\"\n```\n\n#### Step 3: check if someone said they are working on it\n\nList the issue's comments with their dates. Only change `11459` to your issue number:\n\n```shell\ngh issue view 11459 --repo pylint-dev/pylint --json comments --jq '.comments[] | \"\\(.createdAt[:10]) \\(.author.login): \\(.body | gsub(\"\\n\"; \" \") | .[:100])\"'\n```\n\nWithout `gh`, read the comments on the issue's web page.\n\nLook for a comment like \"I'm working on this\" or \"Can I take this?\". People often say\nthis and then never open a pull request, so a claim does not last forever:\n\n- The claim is **active** if the person who made it did something in the last 15 days: a\n  comment on the issue, or a commit or pull request for it. Do not work on the issue.\n- The claim is **expired** if that person has done nothing for 15 days or more. You may\n  work on the issue. Say in your pull request description that the issue was claimed by\n  `@<their login>` on `<date>`, with no activity since.\n\n### Cover every new line and branch\n\nNew and changed code in `pylint/` must be fully covered by tests: every line, and both\nsides of every branch (`if`/`else`, early `return`, `and`/`or` short-circuit used as a\nguard). Codecov requires 100% patch coverage. A guard that no test ever takes is either\ndead code to remove or a missing test case to add.\n\nCheck it locally on the tests you touched:\n\n```shell\npytest --cov=pylint --cov-branch --cov-report=term-missing tests/test_functional.py -k \"<test name>\"\n```\n\nEach new or changed line in the diff should be absent from the `Missing` column, and no\nbranch involving it should be listed as partial.\n\n## AST-based checking\n\nPylint is an AST-based linter built on [astroid](https://github.com/pylint-dev/astroid).\nWhen writing or modifying checkers, prefer **`isinstance` against concrete astroid node\ntypes** combined with domain knowledge of Python syntax, rather than duck-typing with\n`getattr`/`hasattr`.\n\nThe visitor pattern from `BaseChecker` already narrows the node type for you: a\n`visit_call` method only receives `nodes.Call`, a `visit_assign` only receives\n`nodes.Assign`, and so on. Inside such a method, the node's structure is known — its\nattributes follow from the grammar (e.g. a `nodes.Call` always has `.func` and `.args`).\nWalk and type-check that known structure with `isinstance` instead of probing for\nattributes defensively.\n\nGood:\n\n```python\ndef visit_call(self, node: nodes.Call) -> None:\n    if isinstance(node.func, nodes.Attribute):\n        ...\n```\n\nAvoid:\n\n```python\ndef visit_call(self, node) -> None:\n    if hasattr(node.func, \"attrname\"):  # don't probe — check the type\n        ...\n```\n\nThis keeps checks precise, readable, and aligned with astroid's typed node API.\n\n### Caveat: astroid proxies and `Uninferable`\n\n`isinstance` checks the _static_ node type. Some astroid objects — `bases.Instance`,\n`Generator`, `BoundMethod`/`UnboundMethod`, and `util.Uninferable` — resolve attributes\nthrough `__getattr__`, proxying to a wrapped node. So `hasattr(obj, \"x\")` can be True at\nruntime on an object whose class has no `x`, and a naive `isinstance(obj, ConcreteNode)`\nwill _drop_ cases the old `hasattr` caught (e.g. an exception inferred to an `Instance`\nthat proxies `ancestors`, or an `AsyncGenerator` that proxies `locals`).\n\nWhen replacing such a guard:\n\n- Include the proxy base in the type tuple — e.g. `(nodes.ClassDef, bases.Instance)`, or\n  `(nodes.LocalsDictNodeNG, bases.Proxy)` for anything exposing `qname`.\n- If the check is a behavioral _capability_ spanning heterogeneous nodes with no common\n  base — or the proxied node may legitimately lack the attribute (a `BoundMethod` can\n  wrap a `Lambda`, which has no `.decorators`) — keep `hasattr`. That is honest\n  duck-typing, not a grammar check, and `isinstance` cannot express it safely.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/pylint-dev/pylint/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Agent guidelines\n\n## Before opening a pull request\n\n### Check for existing work\n\nDo this twice: before you start working on an issue, and again right before you open\nyour pull request. Someone may have opened one in between.\n\n**Rule: if the issue already has an `OPEN` pull request, do not open another one.**\nReview that pull request or suggest improvements on it instead. Two pull requests for\nthe same fix waste review time, and one of them will be closed.\n\n#### Step 1: list the pull requests linked to the issue\n\nOn GitHub, the pull requests that will close an issue appear in the \"Development\" box of\nthe issue's sidebar. Get that list with this command. Only change `11459` to your issue\nnumber:\n\n```shell\ngh api graphql -F number=11459 -f query='\nquery($number: Int!) {\n  repository(owner: \"pylint-dev\", name: \"pylint\") {\n    issue(number: $number) {\n      closedByPullRequestsReferences(first: 50, includeClosedPrs: true) {\n        nodes { number state url author { login } }\n      }\n    }\n  }\n}' --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | \"\\(.state) \\(.url) by \\(.author.login)\"'\n```\n\nEach output line is one pull request. Example output:\n\n```text\nOPEN https://github.com/pylint-dev/pylint/pull/11460 by agustin18\nCLOSED https://github.com/pylint-dev/pylint/pull/11542 by aipd506\n```\n\n- A line starting with `OPEN`: someone is already working on it. Stop and follow the\n  rule above.\n- `MERGED`: the fix may already be on `main`. Check whether the bug still happens there.\n- `CLOSED`: that attempt was abandoned. Read why before you start, so you do not repeat\n  it.\n- No output at all: no pull request is linked. Go to step 2.\n\nIf `gh` is not installed or not logged in, read the same data from the issue's web page.\nIt works without a GitHub account. Only change `11459` to your issue number:\n\n```shell\ncurl -sL https://github.com/pylint-dev/pylint/issues/11459 | python3 -c '\nimport json, sys\npage = sys.stdin.read()\nkey = \"\\\"closedByPullRequestsReferences\\\":\"\nstart = page.find(key)\nif start == -1:\n    sys.exit(\"Development data not found: is this an issue URL, not a pull request URL?\")\ndata, _ = json.JSONDecoder().raw_decode(page, start + len(key))\nfor pr in data[\"nodes\"]:\n    print(pr[\"state\"], pr[\"url\"])\n'\n```\n\nThe output and its meaning are the same as above, without the author.\n\n#### Step 2: search for pull requests that are not linked\n\nStep 1 only finds pull requests that say `Closes #<number>` (or `Fixes`, `Resolves`), or\nthat a maintainer linked by hand. A pull request that only mentions the issue, or forgot\nto mention it, is not in that list. Search for those too:\n\n```shell\ngh pr list --repo pylint-dev/pylint --state open --search \"11459\"\ngh pr list --repo pylint-dev/pylint --state open --search \"<key words from the issue title>\"\n```\n\n#### Step 3: check if someone said they are working on it\n\nList the issue's comments with their dates. Only change `11459` to your issue number:\n\n```shell\ngh issue view 11459 --repo pylint-dev/pylint --json comments --jq '.comments[] | \"\\(.createdAt[:10]) \\(.author.login): \\(.body | gsub(\"\\n\"; \" \") | .[:100])\"'\n```\n\nWithout `gh`, read the comments on the issue's web page.\n\nLook for a comment like \"I'm working on this\" or \"Can I take this?\". People often say\nthis and then never open a pull request, so a claim does not last forever:\n\n- The claim is **active** if the person who made it did something in the last 15 days: a\n  comment on the issue, or a commit or pull request for it. Do not work on the issue.\n- The claim is **expired** if that person has done nothing for 15 days or more. You may\n  work on the issue. Say in your pull request description that the issue was claimed by\n  `@<their login>` on `<date>`, with no activity since.\n\n### Cover every new line and branch\n\nNew and changed code in `pylint/` must be fully covered by tests: every line, and both\nsides of every branch (`if`/`else`, early `return`, `and`/`or` short-circuit used as a\nguard). Codecov requires 100% patch coverage. A guard that no test ever takes is either\ndead code to remove or a missing test case to add.\n\nCheck it locally on the tests you touched:\n\n```shell\npytest --cov=pylint --cov-branch --cov-report=term-missing tests/test_functional.py -k \"<test name>\"\n```\n\nEach new or changed line in the diff should be absent from the `Missing` column, and no\nbranch involving it should be listed as partial.\n\n## AST-based checking\n\nPylint is an AST-based linter built on [astroid](https://github.com/pylint-dev/astroid).\nWhen writing or modifying checkers, prefer **`isinstance` against concrete astroid node\ntypes** combined with domain knowledge of Python syntax, rather than duck-typing with\n`getattr`/`hasattr`.\n\nThe visitor pattern from `BaseChecker` already narrows the node type for you: a\n`visit_call` method only receives `nodes.Call`, a `visit_assign` only receives\n`nodes.Assign`, and so on. Inside such a method, the node's structure is known — its\nattributes follow from the grammar (e.g. a `nodes.Call` always has `.func` and `.args`).\nWalk and type-check that known structure with `isinstance` instead of probing for\nattributes defensively.\n\nGood:\n\n```python\ndef visit_call(self, node: nodes.Call) -> None:\n    if isinstance(node.func, nodes.Attribute):\n        ...\n```\n\nAvoid:\n\n```python\ndef visit_call(self, node) -> None:\n    if hasattr(node.func, \"attrname\"):  # don't probe — check the type\n        ...\n```\n\nThis keeps checks precise, readable, and aligned with astroid's typed node API.\n\n### Caveat: astroid proxies and `Uninferable`\n\n`isinstance` checks the _static_ node type. Some astroid objects — `bases.Instance`,\n`Generator`, `BoundMethod`/`UnboundMethod`, and `util.Uninferable` — resolve attributes\nthrough `__getattr__`, proxying to a wrapped node. So `hasattr(obj, \"x\")` can be True at\nruntime on an object whose class has no `x`, and a naive `isinstance(obj, ConcreteNode)`\nwill _drop_ cases the old `hasattr` caught (e.g. an exception inferred to an `Instance`\nthat proxies `ancestors`, or an `AsyncGenerator` that proxies `locals`).\n\nWhen replacing such a guard:\n\n- Include the proxy base in the type tuple — e.g. `(nodes.ClassDef, bases.Instance)`, or\n  `(nodes.LocalsDictNodeNG, bases.Proxy)` for anything exposing `qname`.\n- If the check is a behavioral _capability_ spanning heterogeneous nodes with no common\n  base — or the proxied node may legitimately lack the attribute (a `BoundMethod` can\n  wrap a `Lambda`, which has no `.decorators`) — keep `hasattr`. That is honest\n  duck-typing, not a grammar check, and `isinstance` cannot express it safely.\n","isInternal":false,"tokens":1682,"sizeBytes":6595},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/pylint-dev/pylint/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# Pylint Development Instructions\n\nAlways follow these instructions first and fallback to additional search and context\ngathering only if the information in these instructions is incomplete or found to be in\nerror.\n\n## Issue Label Guidelines\n\nBefore attempting to fix any issue, check the GitHub issue labels using the GitHub API:\n\n- If an issue is labeled with \"Astroid\", \"Needs astroid update\", \"Needs astroid\n  constraint\", or \"Needs astroid Brain 🧠\", **ONLY** create regression tests\n- Do **NOT** attempt to fix astroid-related issues as you cannot modify astroid from\n  this repository\n- For astroid-related issues, focus on creating comprehensive regression tests that\n  reproduce the problem\n- All other issues can be fixed normally following the standard development workflow\n\n## Development Environment Setup\n\n### Basic Installation\n\nClone and set up pylint development environment:\n\n- `git clone https://github.com/pylint-dev/pylint` -- clone repository\n- `cd pylint` -- enter directory\n- `python3 -m venv venv` -- create virtual environment\n- `source venv/bin/activate` -- activate virtual environment (Linux/Mac)\n- `pip install -r requirements_test_min.txt` -- install test dependencies (~30 seconds)\n- `pip install -e .` -- install pylint in editable mode (~30-60 seconds)\n\n### Optional Setup Steps\n\n- `pre-commit install` -- enable pre-commit hooks for autoformatting\n- `pip install pre-commit` -- install pre-commit separately if needed\n\n### Astroid Development (if needed)\n\nIf working on astroid changes:\n\n- `git clone https://github.com/pylint-dev/astroid.git` -- clone astroid\n- `pip install -e astroid/` -- install astroid in editable mode\n- `cd astroid/ && git switch my-astroid-dev-branch` -- switch to development branch\n\n## Running Tests\n\n### Core Test Commands\n\n- `pytest tests/test_functional.py -k test_functional` -- run functional tests (~60\n  seconds, NEVER CANCEL, set timeout to 120+ seconds)\n- `pytest tests/` -- run all tests (several minutes, NEVER CANCEL, set timeout to 300+\n  seconds)\n- `python3 -m pytest` -- run tests with local python\n- `pytest tests/test_check_parallel.py -v` -- quick test file (~2 seconds)\n\n### Specific Test Types\n\n- **Functional tests:**\n  `pytest \"tests/test_functional.py::test_functional[missing_kwoa_py3]\"` -- single\n  functional test (~1 second)\n- **Unit tests:** Located in `/tests/` directory, test specific pylint functionality\n- **Configuration tests:** In `/tests/config/functional/` for testing configuration\n  loading\n- **Primer tests:** `pytest -m primer_stdlib --primer-stdlib` -- test on stdlib for\n  crashes\n\n### Test with Coverage\n\n- `pytest tests/message/ --cov=pylint.message` -- run with coverage\n- `coverage html` -- generate HTML coverage report\n\n### Tox Usage (Optional)\n\n- `python -m tox` -- run all tox environments\n- `python -m tox -epy313` -- run Python 3.13 suite only\n- `python -m tox -epylint` -- run pylint on pylint's codebase\n- `python -m tox -eformatting` -- run formatting checks\n- `python -m tox --recreate` -- recreate environments (recommended)\n- `python -m tox -e py311 -- -k test_functional` -- run specific tests in tox\n\n## Documentation\n\n### Building Documentation\n\n- `make -C doc/ install-dependencies` -- install doc dependencies (~10 seconds)\n- `make -C doc/ html` -- build documentation (~3 minutes, NEVER CANCEL, set timeout to\n  300+ seconds)\n- `make -C doc/ clean` -- clean build files when starting from scratch\n- `tox -e docs` -- alternative way to build docs\n\n**Network dependency:** Documentation build requires internet access to fetch external\ninventories.\n\n## Validation and Quality Checks\n\n### Running Pylint on Code\n\n- `pylint --help` -- verify pylint installation works\n- `pylint --disable=all --enable=E,F pylint/` -- run pylint on itself for errors only\n  (~20 seconds)\n- `pylint --rcfile=pylintrc --fail-on=I path/to/your/changes.py` -- standard pylint run\n- `pylint --disable=all --enable=E,F,W path/to/your/changes.py` -- focus on errors and\n  warnings\n\n### Pre-commit and Formatting\n\n- `pre-commit run --all-files` -- run all formatting checks (requires network for\n  initial setup)\n- **Network dependency:** pre-commit may fail in isolated environments due to hook\n  downloads\n\n### Validation Test Scenarios\n\nAlways test your changes with these validation scenarios:\n\n- `echo \"def badFunction(): pass\" > /tmp/test_sample.py && pylint --enable=C0103 /tmp/test_sample.py`\n  -- should find naming issues\n- `pylint --help` and `pylint --list-msgs | head -10` -- verify CLI functionality\n- `pylint --help-msg=C0103` -- should show invalid-name help\n- `pylint --rcfile=pylintrc --fail-on=I pylint/__init__.py` -- should get 10.00/10\n  rating\n\n## Writing Tests\n\n### Functional Tests\n\nLocated in `/tests/functional/`, consists of `.py` test files with corresponding `.txt`\nexpected output files:\n\n- Annotate lines where messages are expected:\n  `a, b, c = 1 # [unbalanced-tuple-unpacking]`\n- Multiple messages on same line:\n  `a, b, c = 1.test # [unbalanced-tuple-unpacking, no-member]`\n- Use offset syntax for special cases: `# +1: [singleton-comparison]`\n- **Run and update:**\n  `python tests/test_functional.py --update-functional-output -k \"test_functional[test_name]\"`\n\n### Test File Organization\n\n- **New checkers:** Create `new_checker_message.py` in `/tests/functional/n/`\n- **Extensions:** Place in `/tests/functional/ext/extension_name/`\n- **Regression tests:** Place in `/tests/r/regression/` with `regression_` prefix\n- **Configuration tests:** Place in `/tests/config/functional/`\n\n### Configuration Test Files\n\nCreate `.result.json` files with configuration differences from standard config:\n\n```json\n{\n  \"functional_append\": {\n    \"disable\": [[\"a-message-to-be-added\"]]\n  },\n  \"jobs\": 10\n}\n```\n\n## Codebase Structure\n\n```\npylint/                    # Main package\n├── checkers/             # All pylint checkers (rules implementation)\n├── config/               # Configuration handling and parsing\n├── message/              # Message system and formatting\n├── reporters/            # Output formatters (text, json, etc.)\n├── testutils/            # Testing utilities and helpers\n└── extensions/           # Optional extensions and plugins\n\ntests/                     # Test suite\n├── functional/           # Functional test files (.py + .txt expected output)\n├── config/functional/    # Configuration functional tests\n├── r/regression/         # Regression tests\n├── test_*.py            # Unit tests\n└── regrtest_data/       # Test data files\n\ndoc/                      # Documentation\n├── user_guide/          # User documentation\n├── development_guide/   # Developer and contributor documentation\n│   ├── contributor_guide/    # Setup, testing, contribution guidelines\n│   ├── technical_reference/  # Technical implementation details\n│   └── how_tos/             # Guides for custom checkers, plugins\n└── additional_tools/    # Tools documentation\n\nscript/                   # Development utility scripts\n```\n\n### Key Files\n\n- `pyproject.toml` -- Main configuration (dependencies, build, tools)\n- `tox.ini` -- Multi-environment testing configuration\n- `.pre-commit-config.yaml` -- Code quality checks configuration\n- `pylintrc` -- Pylint's own configuration\n- `requirements_test_min.txt` -- Minimal test dependencies\n- `.gitignore` do not add the 'venv' inside the .gitignore, don't commit the venv in the\n  first place (humans add it to their global gitignore)\n\n## Creating New Checkers\n\n### Getting Started\n\n- `python script/get_unused_message_id_category.py` -- get next available message ID\n- Study existing checkers in `pylint/checkers/` for patterns\n- Read technical reference documentation in `doc/development_guide/technical_reference/`\n- Use `astroid.extract_node` for AST manipulation\n\n### Workflow\n\n1. Create checker class in appropriate `pylint/checkers/` file\n2. Add functional tests in `tests/functional/`\n3. Search existing code for warning message to find where logic exists\n4. Test with sample code to ensure functionality works\n\n## Pull Request Guidelines\n\n### Before Submitting\n\n- Use Python 3.8+ for development (required for latest AST parser and pre-commit hooks)\n- Write comprehensive commit messages relating to tracker issues\n- Keep changes small and separate consensual from opinionated changes\n- Add news fragment: `towncrier create <IssueNumber>.<type>`\n- Always launch `pre-commit run -a` before committing\n\n### Documentation Changes\n\n- Document non-trivial changes\n- Generate docs with `tox -e docs`\n- Maintainers may label issues `skip-news` if no changelog needed\n\n### Contribution Credits\n\n- Add emails/names to `script/.contributors_aliases.json` if using multiple identities\n\n## Critical Timing Information\n\n- **NEVER CANCEL:** All operations that show \"NEVER CANCEL\" may take significant time\n- **Full test suite:** 60+ seconds (set timeout to 120+ seconds)\n- **Documentation build:** 180 seconds (set timeout to 300+ seconds)\n- **Functional tests:** 60 seconds (set timeout to 120+ seconds)\n- **Pylint self-check:** 20 seconds (set timeout to 60+ seconds)\n- **Individual test files:** 1-15 seconds\n- **Installation steps:** 30-60 seconds each\n\n## Environment Limitations and Workarounds\n\n- **Network connectivity required:** Documentation build and pre-commit setup require\n  internet access\n- **Tox failures:** In isolated environments, use direct pytest and pip commands instead\n  of tox\n- **Import errors in self-check:** Some import errors when running pylint on itself are\n  expected (git dependencies not installed)\n- **Build environments:** Use direct pip/pytest commands when tox environments fail to\n  build\n\nAlways validate your changes by running pylint on sample code to ensure functionality\nworks correctly.\n","isInternal":false,"tokens":2319,"sizeBytes":9807}],"systemPromptSnippet":"<yakaai_skills repo=\"pylint-dev/pylint\">\n<!-- File: AGENTS.md (Tokens: ~1682 | Category: root-instruction) -->\n# Agent guidelines\n\n## Before opening a pull request\n\n### Check for existing work\n\nDo this twice: before you start working on an issue, and again right before you open\nyour pull request. Someone may have opened one in between.\n\n**Rule: if the issue already has an `OPEN` pull request, do not open another one.**\nReview that pull request or suggest improvements on it instead. Two pull requests for\nthe same fix waste review time, and one of them will be closed.\n\n#### Step 1: list the pull requests linked to the issue\n\nOn GitHub, the pull requests that will close an issue appear in the \"Development\" box of\nthe issue's sidebar. Get that list with this command. Only change `11459` to your issue\nnumber:\n\n```shell\ngh api graphql -F number=11459 -f query='\nquery($number: Int!) {\n  repository(owner: \"pylint-dev\", name: \"pylint\") {\n    issue(number: $number) {\n      closedByPullRequestsReferences(first: 50, includeClosedPrs: true) {\n        nodes { number state url author { login } }\n      }\n    }\n  }\n}' --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | \"\\(.state) \\(.url) by \\(.author.login)\"'\n```\n\nEach output line is one pull request. Example output:\n\n```text\nOPEN https://github.com/pylint-dev/pylint/pull/11460 by agustin18\nCLOSED https://github.com/pylint-dev/pylint/pull/11542 by aipd506\n```\n\n- A line starting with `OPEN`: someone is already working on it. Stop and follow the\n  rule above.\n- `MERGED`: the fix may already be on `main`. Check whether the bug still happens there.\n- `CLOSED`: that attempt was abandoned. Read why before you start, so you do not repeat\n  it.\n- No output at all: no pull request is linked. Go to step 2.\n\nIf `gh` is not installed or not logged in, read the same data from the issue's web page.\nIt works without a GitHub account. Only change `11459` to your issue number:\n\n```shell\ncurl -sL https://github.com/pylint-dev/pylint/issues/11459 | python3 -c '\nimport json, sys\npage = sys.stdin.read()\nkey = \"\\\"closedByPullRequestsReferences\\\":\"\nstart = page.find(key)\nif start == -1:\n    sys.exit(\"Development data not found: is this an issue URL, not a pull request URL?\")\ndata, _ = json.JSONDecoder().raw_decode(page, start + len(key))\nfor pr in data[\"nodes\"]:\n    print(pr[\"state\"], pr[\"url\"])\n'\n```\n\nThe output and its meaning are the same as above, without the author.\n\n#### Step 2: search for pull requests that are not linked\n\nStep 1 only finds pull requests that say `Closes #<number>` (or `Fixes`, `Resolves`), or\nthat a maintainer linked by hand. A pull request that only mentions the issue, or forgot\nto mention it, is not in that list. Search for those too:\n\n```shell\ngh pr list --repo pylint-dev/pylint --state open --search \"11459\"\ngh pr list --repo pylint-dev/pylint --state open --search \"<key words from the issue title>\"\n```\n\n#### Step 3: check if someone said they are working on it\n\nList the issue's comments with their dates. Only change `11459` to your issue number:\n\n```shell\ngh issue view 11459 --repo pylint-dev/pylint --json comments --jq '.comments[] | \"\\(.createdAt[:10]) \\(.author.login): \\(.body | gsub(\"\\n\"; \" \") | .[:100])\"'\n```\n\nWithout `gh`, read the comments on the issue's web page.\n\nLook for a comment like \"I'm working on this\" or \"Can I take this?\". People often say\nthis and then never open a pull request, so a claim does not last forever:\n\n- The claim is **active** if the person who made it did something in the last 15 days: a\n  comment on the issue, or a commit or pull request for it. Do not work on the issue.\n- The claim is **expired** if that person has done nothing for 15 days or more. You may\n  work on the issue. Say in your pull request description that the issue was claimed by\n  `@<their login>` on `<date>`, with no activity since.\n\n### Cover every new line and branch\n\nNew and changed code in `pylint/` must be fully covered by tests: every line, and both\nsides of every branch (`if`/`else`, early `return`, `and`/`or` short-circuit used as a\nguard). Codecov requires 100% patch coverage. A guard that no test ever takes is either\ndead code to remove or a missing test case to add.\n\nCheck it locally on the tests you touched:\n\n```shell\npytest --cov=pylint --cov-branch --cov-report=term-missing tests/test_functional.py -k \"<test name>\"\n```\n\nEach new or changed line in the diff should be absent from the `Missing` column, and no\nbranch involving it should be listed as partial.\n\n## AST-based checking\n\nPylint is an AST-based linter built on [astroid](https://github.com/pylint-dev/astroid).\nWhen writing or modifying checkers, prefer **`isinstance` against concrete astroid node\ntypes** combined with domain knowledge of Python syntax, rather than duck-typing with\n`getattr`/`hasattr`.\n\nThe visitor pattern from `BaseChecker` already narrows the node type for you: a\n`visit_call` method only receives `nodes.Call`, a `visit_assign` only receives\n`nodes.Assign`, and so on. Inside such a method, the node's structure is known — its\nattributes follow from the grammar (e.g. a `nodes.Call` always has `.func` and `.args`).\nWalk and type-check that known structure with `isinstance` instead of probing for\nattributes defensively.\n\nGood:\n\n```python\ndef visit_call(self, node: nodes.Call) -> None:\n    if isinstance(node.func, nodes.Attribute):\n        ...\n```\n\nAvoid:\n\n```python\ndef visit_call(self, node) -> None:\n    if hasattr(node.func, \"attrname\"):  # don't probe — check the type\n        ...\n```\n\nThis keeps checks precise, readable, and aligned with astroid's typed node API.\n\n### Caveat: astroid proxies and `Uninferable`\n\n`isinstance` checks the _static_ node type. Some astroid objects — `bases.Instance`,\n`Generator`, `BoundMethod`/`UnboundMethod`, and `util.Uninferable` — resolve attributes\nthrough `__getattr__`, proxying to a wrapped node. So `hasattr(obj, \"x\")` can be True at\nruntime on an object whose class has no `x`, and a naive `isinstance(obj, ConcreteNode)`\nwill _drop_ cases the old `hasattr` caught (e.g. an exception inferred to an `Instance`\nthat proxies `ancestors`, or an `AsyncGenerator` that proxies `locals`).\n\nWhen replacing such a guard:\n\n- Include the proxy base in the type tuple — e.g. `(nodes.ClassDef, bases.Instance)`, or\n  `(nodes.LocalsDictNodeNG, bases.Proxy)` for anything exposing `qname`.\n- If the check is a behavioral _capability_ spanning heterogeneous nodes with no common\n  base — or the proxied node may legitimately lack the attribute (a `BoundMethod` can\n  wrap a `Lambda`, which has no `.decorators`) — keep `hasattr`. That is honest\n  duck-typing, not a grammar check, and `isinstance` cannot express it safely.\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~2319 | Category: copilot-instructions) -->\n# Pylint Development Instructions\n\nAlways follow these instructions first and fallback to additional search and context\ngathering only if the information in these instructions is incomplete or found to be in\nerror.\n\n## Issue Label Guidelines\n\nBefore attempting to fix any issue, check the GitHub issue labels using the GitHub API:\n\n- If an issue is labeled with \"Astroid\", \"Needs astroid update\", \"Needs astroid\n  constraint\", or \"Needs astroid Brain 🧠\", **ONLY** create regression tests\n- Do **NOT** attempt to fix astroid-related issues as you cannot modify astroid from\n  this repository\n- For astroid-related issues, focus on creating comprehensive regression tests that\n  reproduce the problem\n- All other issues can be fixed normally following the standard development workflow\n\n## Development Environment Setup\n\n### Basic Installation\n\nClone and set up pylint development environment:\n\n- `git clone https://github.com/pylint-dev/pylint` -- clone repository\n- `cd pylint` -- enter directory\n- `python3 -m venv venv` -- create virtual environment\n- `source venv/bin/activate` -- activate virtual environment (Linux/Mac)\n- `pip install -r requirements_test_min.txt` -- install test dependencies (~30 seconds)\n- `pip install -e .` -- install pylint in editable mode (~30-60 seconds)\n\n### Optional Setup Steps\n\n- `pre-commit install` -- enable pre-commit hooks for autoformatting\n- `pip install pre-commit` -- install pre-commit separately if needed\n\n### Astroid Development (if needed)\n\nIf working on astroid changes:\n\n- `git clone https://github.com/pylint-dev/astroid.git` -- clone astroid\n- `pip install -e astroid/` -- install astroid in editable mode\n- `cd astroid/ && git switch my-astroid-dev-branch` -- switch to development branch\n\n## Running Tests\n\n### Core Test Commands\n\n- `pytest tests/test_functional.py -k test_functional` -- run functional tests (~60\n  seconds, NEVER CANCEL, set timeout to 120+ seconds)\n- `pytest tests/` -- run all tests (several minutes, NEVER CANCEL, set timeout to 300+\n  seconds)\n- `python3 -m pytest` -- run tests with local python\n- `pytest tests/test_check_parallel.py -v` -- quick test file (~2 seconds)\n\n### Specific Test Types\n\n- **Functional tests:**\n  `pytest \"tests/test_functional.py::test_functional[missing_kwoa_py3]\"` -- single\n  functional test (~1 second)\n- **Unit tests:** Located in `/tests/` directory, test specific pylint functionality\n- **Configuration tests:** In `/tests/config/functional/` for testing configuration\n  loading\n- **Primer tests:** `pytest -m primer_stdlib --primer-stdlib` -- test on stdlib for\n  crashes\n\n### Test with Coverage\n\n- `pytest tests/message/ --cov=pylint.message` -- run with coverage\n- `coverage html` -- generate HTML coverage report\n\n### Tox Usage (Optional)\n\n- `python -m tox` -- run all tox environments\n- `python -m tox -epy313` -- run Python 3.13 suite only\n- `python -m tox -epylint` -- run pylint on pylint's codebase\n- `python -m tox -eformatting` -- run formatting checks\n- `python -m tox --recreate` -- recreate environments (recommended)\n- `python -m tox -e py311 -- -k test_functional` -- run specific tests in tox\n\n## Documentation\n\n### Building Documentation\n\n- `make -C doc/ install-dependencies` -- install doc dependencies (~10 seconds)\n- `make -C doc/ html` -- build documentation (~3 minutes, NEVER CANCEL, set timeout to\n  300+ seconds)\n- `make -C doc/ clean` -- clean build files when starting from scratch\n- `tox -e docs` -- alternative way to build docs\n\n**Network dependency:** Documentation build requires internet access to fetch external\ninventories.\n\n## Validation and Quality Checks\n\n### Running Pylint on Code\n\n- `pylint --help` -- verify pylint installation works\n- `pylint --disable=all --enable=E,F pylint/` -- run pylint on itself for errors only\n  (~20 seconds)\n- `pylint --rcfile=pylintrc --fail-on=I path/to/your/changes.py` -- standard pylint run\n- `pylint --disable=all --enable=E,F,W path/to/your/changes.py` -- focus on errors and\n  warnings\n\n### Pre-commit and Formatting\n\n- `pre-commit run --all-files` -- run all formatting checks (requires network for\n  initial setup)\n- **Network dependency:** pre-commit may fail in isolated environments due to hook\n  downloads\n\n### Validation Test Scenarios\n\nAlways test your changes with these validation scenarios:\n\n- `echo \"def badFunction(): pass\" > /tmp/test_sample.py && pylint --enable=C0103 /tmp/test_sample.py`\n  -- should find naming issues\n- `pylint --help` and `pylint --list-msgs | head -10` -- verify CLI functionality\n- `pylint --help-msg=C0103` -- should show invalid-name help\n- `pylint --rcfile=pylintrc --fail-on=I pylint/__init__.py` -- should get 10.00/10\n  rating\n\n## Writing Tests\n\n### Functional Tests\n\nLocated in `/tests/functional/`, consists of `.py` test files with corresponding `.txt`\nexpected output files:\n\n- Annotate lines where messages are expected:\n  `a, b, c = 1 # [unbalanced-tuple-unpacking]`\n- Multiple messages on same line:\n  `a, b, c = 1.test # [unbalanced-tuple-unpacking, no-member]`\n- Use offset syntax for special cases: `# +1: [singleton-comparison]`\n- **Run and update:**\n  `python tests/test_functional.py --update-functional-output -k \"test_functional[test_name]\"`\n\n### Test File Organization\n\n- **New checkers:** Create `new_checker_message.py` in `/tests/functional/n/`\n- **Extensions:** Place in `/tests/functional/ext/extension_name/`\n- **Regression tests:** Place in `/tests/r/regression/` with `regression_` prefix\n- **Configuration tests:** Place in `/tests/config/functional/`\n\n### Configuration Test Files\n\nCreate `.result.json` files with configuration differences from standard config:\n\n```json\n{\n  \"functional_append\": {\n    \"disable\": [[\"a-message-to-be-added\"]]\n  },\n  \"jobs\": 10\n}\n```\n\n## Codebase Structure\n\n```\npylint/                    # Main package\n├── checkers/             # All pylint checkers (rules implementation)\n├── config/               # Configuration handling and parsing\n├── message/              # Message system and formatting\n├── reporters/            # Output formatters (text, json, etc.)\n├── testutils/            # Testing utilities and helpers\n└── extensions/           # Optional extensions and plugins\n\ntests/                     # Test suite\n├── functional/           # Functional test files (.py + .txt expected output)\n├── config/functional/    # Configuration functional tests\n├── r/regression/         # Regression tests\n├── test_*.py            # Unit tests\n└── regrtest_data/       # Test data files\n\ndoc/                      # Documentation\n├── user_guide/          # User documentation\n├── development_guide/   # Developer and contributor documentation\n│   ├── contributor_guide/    # Setup, testing, contribution guidelines\n│   ├── technical_reference/  # Technical implementation details\n│   └── how_tos/             # Guides for custom checkers, plugins\n└── additional_tools/    # Tools documentation\n\nscript/                   # Development utility scripts\n```\n\n### Key Files\n\n- `pyproject.toml` -- Main configuration (dependencies, build, tools)\n- `tox.ini` -- Multi-environment testing configuration\n- `.pre-commit-config.yaml` -- Code quality checks configuration\n- `pylintrc` -- Pylint's own configuration\n- `requirements_test_min.txt` -- Minimal test dependencies\n- `.gitignore` do not add the 'venv' inside the .gitignore, don't commit the venv in the\n  first place (humans add it to their global gitignore)\n\n## Creating New Checkers\n\n### Getting Started\n\n- `python script/get_unused_message_id_category.py` -- get next available message ID\n- Study existing checkers in `pylint/checkers/` for patterns\n- Read technical reference documentation in `doc/development_guide/technical_reference/`\n- Use `astroid.extract_node` for AST manipulation\n\n### Workflow\n\n1. Create checker class in appropriate `pylint/checkers/` file\n2. Add functional tests in `tests/functional/`\n3. Search existing code for warning message to find where logic exists\n4. Test with sample code to ensure functionality works\n\n## Pull Request Guidelines\n\n### Before Submitting\n\n- Use Python 3.8+ for development (required for latest AST parser and pre-commit hooks)\n- Write comprehensive commit messages relating to tracker issues\n- Keep changes small and separate consensual from opinionated changes\n- Add news fragment: `towncrier create <IssueNumber>.<type>`\n- Always launch `pre-commit run -a` before committing\n\n### Documentation Changes\n\n- Document non-trivial changes\n- Generate docs with `tox -e docs`\n- Maintainers may label issues `skip-news` if no changelog needed\n\n### Contribution Credits\n\n- Add emails/names to `script/.contributors_aliases.json` if using multiple identities\n\n## Critical Timing Information\n\n- **NEVER CANCEL:** All operations that show \"NEVER CANCEL\" may take significant time\n- **Full test suite:** 60+ seconds (set timeout to 120+ seconds)\n- **Documentation build:** 180 seconds (set timeout to 300+ seconds)\n- **Functional tests:** 60 seconds (set timeout to 120+ seconds)\n- **Pylint self-check:** 20 seconds (set timeout to 60+ seconds)\n- **Individual test files:** 1-15 seconds\n- **Installation steps:** 30-60 seconds each\n\n## Environment Limitations and Workarounds\n\n- **Network connectivity required:** Documentation build and pre-commit setup require\n  internet access\n- **Tox failures:** In isolated environments, use direct pytest and pip commands instead\n  of tox\n- **Import errors in self-check:** Some import errors when running pylint on itself are\n  expected (git dependencies not installed)\n- **Build environments:** Use direct pip/pytest commands when tox environments fail to\n  build\n\nAlways validate your changes by running pylint on sample code to ensure functionality\nworks correctly.\n</yakaai_skills>"}