Skip to content

feat: forbid confusing syntax (WPS364); add visitor and tests#3516

Merged
sobolevn merged 4 commits intowemake-services:masterfrom
djeada:feature/forbid-not-a-in-b
Aug 27, 2025
Merged

feat: forbid confusing syntax (WPS364); add visitor and tests#3516
sobolevn merged 4 commits intowemake-services:masterfrom
djeada:feature/forbid-not-a-in-b

Conversation

@djeada
Copy link
Copy Markdown
Contributor

@djeada djeada commented Aug 25, 2025

Forbid confusing not a in b (WPS364)

This PR forbids the confusing historical syntax "not a in b".

  • Adds WPS364 NotInWithUnaryOpViolation
  • Implements NotInUnaryVisitor and registers it
  • Adds tests covering not a in b and related forms
  • Updates noqa fixtures and counts

All tests pass locally with 100% coverage.

Rationale: not a in b is parsed as not (a in b), which many read
ambiguously. The explicit a not in b is clearer and idiomatic.

Checklist

  • I have double checked that there are no unrelated changes in this pull request (old patches, accidental config files, etc)
  • I have created at least one test case for the changes I have made
  • I have updated the documentation for the changes I have made
  • I have added my changes to the CHANGELOG.md

Related issues

#3509

Copy link
Copy Markdown
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

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

Thanks!

Comment thread tests/fixtures/noqa/noqa.py Outdated
my_print(3 / 3)
my_print('no zero division error')

# New violation example for WPS364:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
# New violation example for WPS364:


"""

error_template = 'Found `not ... in ...`, use `not in`'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
error_template = 'Found `not ... in ...`, use `not in`'
error_template = 'Found legacy `not ... in`, use `... not in` instead'

Comment on lines +254 to +260
if isinstance(node.op, ast.Not) and isinstance(
node.operand,
ast.Compare,
):
comp = node.operand
if len(comp.ops) == 1 and isinstance(comp.ops[0], ast.In):
self.add_violation(NotInWithUnaryOpViolation(node))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if isinstance(node.op, ast.Not) and isinstance(
node.operand,
ast.Compare,
):
comp = node.operand
if len(comp.ops) == 1 and isinstance(comp.ops[0], ast.In):
self.add_violation(NotInWithUnaryOpViolation(node))
def _check_legacy_not_int(self, node: ast.UnaryOp) -> None:
if isinstance(node.op, ast.Not) and isinstance(
node.operand,
ast.Compare,
):
comp = node.operand
if len(comp.ops) == 1 and isinstance(comp.ops[0], ast.In):
self.add_violation(NotInWithUnaryOpViolation(node))

and call this method in visit_UnaryOp

"""Forbids ``not a in b`` which should be written as ``a not in b``."""

def visit_UnaryOp(self, node: ast.UnaryOp) -> None:
"""Detects ``not (a in b)`` pattern syntactically.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"""Detects ``not (a in b)`` pattern syntactically.
"""
Detects ``not (a in b)`` pattern syntactically.

if len(comp.ops) == 1 and isinstance(comp.ops[0], ast.In):
self.add_violation(NotInWithUnaryOpViolation(node))
self.generic_visit(node)
# Split condition to keep per-line Jones Complexity low (WPS221):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
# Split condition to keep per-line Jones Complexity low (WPS221):

Comment on lines +265 to +268
if len(comp.ops) == 1:
first_op = comp.ops[0]
if isinstance(first_op, ast.In):
self.add_violation(NotInWithUnaryOpViolation(node))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if len(comp.ops) == 1:
first_op = comp.ops[0]
if isinstance(first_op, ast.In):
self.add_violation(NotInWithUnaryOpViolation(node))
if (
len(comp.ops) == 1
and isinstance(comp.ops[0], ast.In)
):
self.add_violation(NotInWithUnaryOpViolation(node))

Comment on lines 259 to 263
if isinstance(node.op, ast.Not) and isinstance(
node.operand,
ast.Compare,
):
comp = node.operand
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please, convert this to use an early return pattern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

@codecov
Copy link
Copy Markdown

codecov Bot commented Aug 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (69312a7) to head (38eaf45).
⚠️ Report is 41 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##            master     #3516   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          363       364    +1     
  Lines        12071     12106   +35     
  Branches       823       827    +4     
=========================================
+ Hits         12071     12106   +35     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

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

Great stuff! Thanks!

@sobolevn sobolevn merged commit 1854472 into wemake-services:master Aug 27, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants