Skip to content

[pylint] Allow dunder submodules and improve diagnostic range (PLC2701)#22804

Merged
ntBre merged 4 commits intoastral-sh:mainfrom
Jkhall81:fix/22187-PLC2701-false-positive
Feb 4, 2026
Merged

[pylint] Allow dunder submodules and improve diagnostic range (PLC2701)#22804
ntBre merged 4 commits intoastral-sh:mainfrom
Jkhall81:fix/22187-PLC2701-false-positive

Conversation

@Jkhall81
Copy link
Contributor

@Jkhall81 Jkhall81 commented Jan 22, 2026

Summary

This PR addresses two improvements for the PLC2701 (import-private-name) rule:

  1. Excludes dunder submodules: Specifically handles the false positive where __main__ was being flagged as a private name import. It now ensures that any segment identified as a "dunder" (starting and ending with __) is ignored by the rule.
  2. Improves diagnostic ranges: Refines the error highlight to be more surgical. Instead of pointing to the end of the import binding, the linter now pinpoint's the exact private segment—whether it's in the module path (e.g., from pkg._private import func) or the imported member (e.g., from pkg import _private).

Closes #22187

Test Plan

  • Manual Verification: Verified using a repro.py script covering three scenarios:
    • from pkg import _private (Surgical highlight on member)
    • from pkg._private import func (Surgical highlight on module path)
    • from pkg.__main__ import func (Correctly ignored)
  • Snapshot Testing: Updated existing test snapshots in crates/ruff_linter/src/rules/pylint/snapshots/. The diffs confirm that diagnostic ranges have shifted from the imported name to the actual private name causing the violation.

@Jkhall81 Jkhall81 force-pushed the fix/22187-PLC2701-false-positive branch from 42d25ca to 8312a9a Compare January 22, 2026 16:52
@astral-sh-bot
Copy link

astral-sh-bot bot commented Jan 22, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+978 -987 violations, +0 -0 fixes in 15 projects; 40 projects unchanged)

DisnakeDev/disnake (+2 -2 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ tests/ui/test_action_row.py:19:17: PLC2701 Private name import `_types` from external module `disnake.ui`
+ tests/ui/test_action_row.py:19:17: PLC2701 Private name import `_types` from external module `disnake.ui`
- tests/ui/test_action_row.py:19:31: PLC2701 Private name import `_types` from external module `disnake.ui`
- tests/ui/test_action_row.py:19:58: PLC2701 Private name import `_types` from external module `disnake.ui`

PlasmaPy/PlasmaPy (+2 -2 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

- src/plasmapy/tests/_helpers/exceptions.py:16:30: PLC2701 Private name import `_pytest`
+ src/plasmapy/tests/_helpers/exceptions.py:16:6: PLC2701 Private name import `_pytest`
+ tests/particles/conftest.py:6:25: PLC2701 Private name import `_special_particles` from external module `plasmapy.particles`
- tests/particles/conftest.py:6:51: PLC2701 Private name import `_special_particles` from external module `plasmapy.particles`

apache/airflow (+238 -238 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ airflow-core/tests/integration/otel/test_otel.py:30:14: PLC2701 Private name import `_shared` from external module `airflow`
- airflow-core/tests/integration/otel/test_otel.py:30:39: PLC2701 Private name import `_shared` from external module `airflow`
+ airflow-core/tests/unit/api_fastapi/auth/test_tokens.py:31:14: PLC2701 Private name import `_shared` from external module `airflow`
- airflow-core/tests/unit/api_fastapi/auth/test_tokens.py:31:39: PLC2701 Private name import `_shared` from external module `airflow`
+ airflow-core/tests/unit/api_fastapi/common/db/test_dags.py:24:14: PLC2701 Private name import `_shared` from external module `airflow`
- airflow-core/tests/unit/api_fastapi/common/db/test_dags.py:24:48: PLC2701 Private name import `_shared` from external module `airflow`
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py:27:14: PLC2701 Private name import `_shared` from external module `airflow`
- airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py:27:39: PLC2701 Private name import `_shared` from external module `airflow`
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_backfills.py:27:14: PLC2701 Private name import `_shared` from external module `airflow`
- airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_backfills.py:27:39: PLC2701 Private name import `_shared` from external module `airflow`
+ airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py:28:14: PLC2701 Private name import `_shared` from external module `airflow`
- airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py:28:39: PLC2701 Private name import `_shared` from external module `airflow`
... 464 additional changes omitted for project

apache/superset (+3 -3 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

+ superset/config.py:47:13: PLC2701 Private name import `_libs` from external module `pandas`
- superset/config.py:47:34: PLC2701 Private name import `_libs` from external module `pandas`
+ tests/integration_tests/db_engine_specs/mysql_tests.py:55:22: PLC2701 Private name import `_exceptions` from external module `MySQLdb`
- tests/integration_tests/db_engine_specs/mysql_tests.py:55:41: PLC2701 Private name import `_exceptions` from external module `MySQLdb`
+ tests/unit_tests/dataframe_test.py:23:13: PLC2701 Private name import `_libs` from external module `pandas`
- tests/unit_tests/dataframe_test.py:23:33: PLC2701 Private name import `_libs` from external module `pandas`

aws/aws-sam-cli (+59 -59 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ tests/integration/package/test_package_command_image.py:13:22: PLC2701 Private name import `_utils` from external module `samcli.commands`
- tests/integration/package/test_package_command_image.py:13:45: PLC2701 Private name import `_utils` from external module `samcli.commands`
+ tests/integration/sync/test_sync_adl.py:5:22: PLC2701 Private name import `_utils` from external module `samcli.commands`
+ tests/integration/sync/test_sync_adl.py:5:22: PLC2701 Private name import `_utils` from external module `samcli.commands`
- tests/integration/sync/test_sync_adl.py:5:49: PLC2701 Private name import `_utils` from external module `samcli.commands`
- tests/integration/sync/test_sync_adl.py:5:67: PLC2701 Private name import `_utils` from external module `samcli.commands`
+ tests/unit/commands/_utils/custom_options/test_hook_package_id_option.py:7:22: PLC2701 Private name import `_utils` from external module `samcli.commands`
+ tests/unit/commands/_utils/custom_options/test_hook_package_id_option.py:7:22: PLC2701 Private name import `_utils` from external module `samcli.commands`
- tests/unit/commands/_utils/custom_options/test_hook_package_id_option.py:7:68: PLC2701 Private name import `_utils` from external module `samcli.commands`
- tests/unit/commands/_utils/custom_options/test_hook_package_id_option.py:7:84: PLC2701 Private name import `_utils` from external module `samcli.commands`
... 108 additional changes omitted for project

bokeh/bokeh (+88 -89 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select ALL

- tests/unit/bokeh/command/subcommands/test_json__subcommands.py:29:31: PLC2701 Private name import `_util_subcommands`
+ tests/unit/bokeh/command/subcommands/test_json__subcommands.py:29:6: PLC2701 Private name import `_util_subcommands`
- tests/unit/bokeh/core/property/test_aliases.py:29:28: PLC2701 Private name import `_util_property`
- tests/unit/bokeh/core/property/test_aliases.py:29:43: PLC2701 Private name import `_util_property`
+ tests/unit/bokeh/core/property/test_aliases.py:29:6: PLC2701 Private name import `_util_property`
+ tests/unit/bokeh/core/property/test_aliases.py:29:6: PLC2701 Private name import `_util_property`
- tests/unit/bokeh/core/property/test_any.py:22:28: PLC2701 Private name import `_util_property`
- tests/unit/bokeh/core/property/test_any.py:22:43: PLC2701 Private name import `_util_property`
+ tests/unit/bokeh/core/property/test_any.py:22:6: PLC2701 Private name import `_util_property`
+ tests/unit/bokeh/core/property/test_any.py:22:6: PLC2701 Private name import `_util_property`
... 167 additional changes omitted for project

ibis-project/ibis (+3 -3 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ ibis/backends/sql/compilers/bigquery/udf/core.py:11:21: PLC2701 Private name import `_empty` from external module `inspect`
- ibis/backends/sql/compilers/bigquery/udf/core.py:11:31: PLC2701 Private name import `_empty` from external module `inspect`
+ ibis/backends/sql/compilers/bigquery/udf/rewrite.py:4:21: PLC2701 Private name import `_empty` from external module `inspect`
- ibis/backends/sql/compilers/bigquery/udf/rewrite.py:4:31: PLC2701 Private name import `_empty` from external module `inspect`
+ ibis/tests/benchmarks/benchfuncs.py:6:13: PLC2701 Private name import `_libs` from external module `pandas`
- ibis/tests/benchmarks/benchfuncs.py:6:34: PLC2701 Private name import `_libs` from external module `pandas`

langchain-ai/langchain (+112 -112 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ libs/core/tests/unit_tests/_api/test_beta_decorator.py:8:21: PLC2701 Private name import `_api` from external module `langchain_core`
+ libs/core/tests/unit_tests/_api/test_beta_decorator.py:8:21: PLC2701 Private name import `_api` from external module `langchain_core`
- libs/core/tests/unit_tests/_api/test_beta_decorator.py:8:48: PLC2701 Private name import `_api` from external module `langchain_core`
- libs/core/tests/unit_tests/_api/test_beta_decorator.py:8:54: PLC2701 Private name import `_api` from external module `langchain_core`
- libs/core/tests/unit_tests/_api/test_deprecation.py:10:5: PLC2701 Private name import `_api` from external module `langchain_core`
- libs/core/tests/unit_tests/_api/test_deprecation.py:11:5: PLC2701 Private name import `_api` from external module `langchain_core`
+ libs/core/tests/unit_tests/_api/test_deprecation.py:8:21: PLC2701 Private name import `_api` from external module `langchain_core`
+ libs/core/tests/unit_tests/_api/test_deprecation.py:8:21: PLC2701 Private name import `_api` from external module `langchain_core`
+ libs/core/tests/unit_tests/_api/test_deprecation.py:8:21: PLC2701 Private name import `_api` from external module `langchain_core`
- libs/core/tests/unit_tests/_api/test_deprecation.py:9:5: PLC2701 Private name import `_api` from external module `langchain_core`
... 214 additional changes omitted for project

latchbio/latch (+8 -8 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ src/latch_cli/click_utils.py:5:12: PLC2701 Private name import `_compat` from external module `click`
- src/latch_cli/click_utils.py:5:27: PLC2701 Private name import `_compat` from external module `click`
+ src/latch_cli/services/cp/main.py:10:18: PLC2701 Private name import `_transfer` from external module `latch.ldata`
- src/latch_cli/services/cp/main.py:10:52: PLC2701 Private name import `_transfer` from external module `latch.ldata`
+ src/latch_cli/services/cp/main.py:7:18: PLC2701 Private name import `_transfer` from external module `latch.ldata`
- src/latch_cli/services/cp/main.py:7:56: PLC2701 Private name import `_transfer` from external module `latch.ldata`
+ src/latch_cli/services/cp/main.py:8:18: PLC2701 Private name import `_transfer` from external module `latch.ldata`
- src/latch_cli/services/cp/main.py:8:44: PLC2701 Private name import `_transfer` from external module `latch.ldata`
+ src/latch_cli/services/cp/main.py:9:18: PLC2701 Private name import `_transfer` from external module `latch.ldata`
- src/latch_cli/services/cp/main.py:9:62: PLC2701 Private name import `_transfer` from external module `latch.ldata`
... 6 additional changes omitted for project

... Truncated remaining completed project reports due to GitHub comment length restrictions

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
PLC2701 1965 978 987 0 0

@ntBre ntBre added bug Something isn't working rule Implementing or modifying a lint rule preview Related to preview mode features labels Jan 22, 2026
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! I had one small nit about the code, and then some suggestions for refining the diagnostic range a bit more.

@Jkhall81 Jkhall81 force-pushed the fix/22187-PLC2701-false-positive branch from 8312a9a to 436cad2 Compare January 22, 2026 23:27
@Jkhall81 Jkhall81 requested a review from ntBre January 22, 2026 23:39
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is looking good. I pushed a commit simplifying the implementation slightly, but then I thought of a very tricky edge case where simply summing the lengths of the qualified name would fail. I think we should try to locate the next token instead. See my inline comment for one example I found in another rule.

@Jkhall81 Jkhall81 force-pushed the fix/22187-PLC2701-false-positive branch from b6f5b5a to d284772 Compare February 4, 2026 01:35
@Jkhall81 Jkhall81 requested a review from ntBre February 4, 2026 01:50
@ntBre ntBre changed the title fix PLC2701 false positive for dunder submodules and improve diagnost… [pylint] Allow dunder submodules and improve diagnostic range (PLC2701) Feb 4, 2026
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@ntBre ntBre merged commit 80dbc62 into astral-sh:main Feb 4, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working preview Related to preview mode features rule Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

possible PLC2701 false positive

2 participants