Skip to content

Commit

Permalink
New codemod: use-set-literal
Browse files Browse the repository at this point in the history
  • Loading branch information
drdavella committed Dec 22, 2023
1 parent bf7d7cb commit 9fbd9fd
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 0 deletions.
29 changes: 29 additions & 0 deletions integration_tests/test_use_set_literal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from core_codemods.use_set_literal import UseSetLiteral
from integration_tests.base_test import (
BaseIntegrationTest,
original_and_expected_from_code_path,
)


class TestUseSetLiteral(BaseIntegrationTest):
codemod = UseSetLiteral
code_path = "tests/samples/set_literal.py"

original_code, expected_new_code = original_and_expected_from_code_path(
code_path,
[(0, "x = {1, 2, 3}\n"), (1, "y = set()\n")],
)

expected_diff = """\
---
+++
@@ -1,2 +1,2 @@
-x = set([1, 2, 3])
-y = set([])
+x = {1, 2, 3}
+y = set()
"""

expected_line_change = "1"
num_changes = 2
change_description = UseSetLiteral.CHANGE_DESCRIPTION
4 changes: 4 additions & 0 deletions src/codemodder/scripts/generate_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ class DocMetadata:
importance="Low",
guidance_explained="Since literals and new objects have their own identities, comparisons against them using `is` operators are most likely a bug and thus we deem the change safe.",
),
"use-set-literal": DocMetadata(
importance="Low",
guidance_explained="We believe this change is safe and will not cause any issues.",
),
}


Expand Down
2 changes: 2 additions & 0 deletions src/core_codemods/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .url_sandbox import UrlSandbox
from .use_defused_xml import UseDefusedXml
from .use_generator import UseGenerator
from .use_set_literal import UseSetLiteral
from .use_walrus_if import UseWalrusIf
from .with_threading_lock import WithThreadingLock
from .secure_flask_session_config import SecureFlaskSessionConfig
Expand Down Expand Up @@ -71,6 +72,7 @@
UrlSandbox,
UseDefusedXml,
UseGenerator,
UseSetLiteral,
UseWalrusIf,
WithThreadingLock,
SQLQueryParameterization,
Expand Down
7 changes: 7 additions & 0 deletions src/core_codemods/docs/pixee_python_use-set-literal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This codemod converts Python set constructions using literal list arguments into more efficient and readable set literals. It simplifies expressions like `set([1, 2, 3])` to `{1, 2, 3}`, enhancing both performance and code clarity.

Our changes look like this:
```diff
-x = set([1, 2, 3])
+x = {1, 2, 3}
```
28 changes: 28 additions & 0 deletions src/core_codemods/use_set_literal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import libcst as cst

from codemodder.codemods.api import BaseCodemod, ReviewGuidance
from codemodder.codemods.utils_mixin import NameResolutionMixin


class UseSetLiteral(BaseCodemod, NameResolutionMixin):
NAME = "use-set-literal"
SUMMARY = "Use Set Literals Instead of Sets from Lists"
REVIEW_GUIDANCE = ReviewGuidance.MERGE_WITHOUT_REVIEW
DESCRIPTION = "Replace sets from lists with set literals"
REFERENCES: list = []

def leave_Call(self, original_node: cst.Call, updated_node: cst.Call):
match original_node.func:
case cst.Name("set"):
if self.is_builtin_function(original_node):
match original_node.args:
case [cst.Arg(value=cst.List(elements=elements))]:
# Can't use set literal for empty set
if len(elements) == 0:
self.add_change(original_node, self.CHANGE_DESCRIPTION)
return updated_node.with_changes(args=[])

self.add_change(original_node, self.CHANGE_DESCRIPTION)
return cst.Set(elements=elements)

return updated_node
43 changes: 43 additions & 0 deletions tests/codemods/test_use_set_literal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from core_codemods.use_set_literal import UseSetLiteral
from tests.codemods.base_codemod_test import BaseCodemodTest


class TestUseSetLiteral(BaseCodemodTest):
codemod = UseSetLiteral

def test_simple(self, tmpdir):
original_code = """
x = set([1, 2, 3])
"""
expected_code = """
x = {1, 2, 3}
"""
self.run_and_assert(tmpdir, original_code, expected_code)

def test_empty_list(self, tmpdir):
original_code = """
x = set([])
"""
expected_code = """
x = set()
"""
self.run_and_assert(tmpdir, original_code, expected_code)

def test_already_empty(self, tmpdir):
original_code = """
x = set()
"""
self.run_and_assert(tmpdir, original_code, original_code)

def test_not_builtin(self, tmpdir):
original_code = """
from whatever import set
x = set([1, 2, 3])
"""
self.run_and_assert(tmpdir, original_code, original_code)

def test_not_list_literal(self, tmpdir):
original_code = """
x = set(some_previously_defined_list)
"""
self.run_and_assert(tmpdir, original_code, original_code)
2 changes: 2 additions & 0 deletions tests/samples/set_literal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x = set([1, 2, 3])
y = set([])

0 comments on commit 9fbd9fd

Please sign in to comment.