Skip to content

Commit 13b536b

Browse files
committed
Fix Scala case class mapping to source files #1875
In Scala, case classes and inner classes defined within sealed traits or objects compile to separate .class files that may not follow the standard himBHsseparated naming convention. This fix implements a fallback mechanism to handle these cases. Changes: - Added custom get_normalized_path() method to ScalaLanguage class - Enhanced _map_jvm_to_class_resource() with Scala-specific fallback that searches for source files in the same package directory when exact match is not found - Added comprehensive test case for Scala case class mapping Fixes #1875 Signed-off-by: pradhyum6144 <pradhyum314@gmail.com>
1 parent 77f03b3 commit 13b536b

3 files changed

Lines changed: 68 additions & 6 deletions

File tree

scanpipe/pipes/d2d.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,22 +164,31 @@ def map_checksum(project, checksum_field, logger=None):
164164
def _map_jvm_to_class_resource(
165165
to_resource, from_resources, from_classes_index, jvm_lang: jvm.JvmLanguage
166166
):
167-
"""
168-
Map the ``to_resource`` .class file Resource with a Resource in
169-
``from_resources`` source files, using the ``from_classes_index`` index of
170-
from/ fully qualified binary files.
171-
"""
172167
for extension in jvm_lang.source_extensions:
173168
normalized_path = jvm_lang.get_normalized_path(
174169
path=to_resource.path, extension=extension
175170
)
176171
match = pathmap.find_paths(path=normalized_path, index=from_classes_index)
177172
if not match:
173+
if jvm_lang.name == "scala":
174+
package_path = str(Path(normalized_path).parent)
175+
potential_sources = from_resources.filter(
176+
path__startswith=package_path,
177+
extension__in=jvm_lang.source_extensions
178+
)
179+
for from_resource in potential_sources:
180+
from_source_root_parts = from_resource.path.strip("/").split("/")
181+
from_source_root = "/".join(from_source_root_parts[:-1])
182+
pipes.make_relation(
183+
from_resource=from_resource,
184+
to_resource=to_resource,
185+
map_type=jvm_lang.binary_map_type,
186+
extra_data={"from_source_root": f"{from_source_root}/"},
187+
)
178188
return
179189

180190
for resource_id in match.resource_ids:
181191
from_resource = from_resources.get(id=resource_id)
182-
# compute the root of the packages on the source side
183192
from_source_root_parts = from_resource.path.strip("/").split("/")
184193
from_source_root = "/".join(
185194
from_source_root_parts[: -match.matched_path_length]

scanpipe/pipes/jvm.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,22 @@ class ScalaLanguage(JvmLanguage):
182182
package_regex = re.compile(r"^\s*package\s+([\w\.]+)\s*;?")
183183
binary_map_type = "scala_to_class"
184184

185+
@classmethod
186+
def get_normalized_path(cls, path, extension):
187+
if not path.endswith(cls.binary_extensions):
188+
raise ValueError(
189+
f"Only path ending with {cls.binary_extensions} are supported."
190+
)
191+
path_obj = Path(path.strip("/"))
192+
class_name = path_obj.name
193+
194+
if "$" in class_name:
195+
class_name, _, _ = class_name.partition("$")
196+
else:
197+
class_name, _, _ = class_name.partition(".")
198+
199+
return str(path_obj.parent / f"{class_name}{extension}")
200+
185201

186202
class KotlinLanguage(JvmLanguage):
187203
name = "kotlin"

scanpipe/tests/pipes/test_d2d.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,43 @@ def test_scanpipe_pipes_d2d_scala_ignore_pattern(self):
633633
expected = "Ignoring 2 to/ resources with ecosystem specific configurations."
634634
self.assertIn(expected, buffer.getvalue())
635635

636+
def test_scanpipe_pipes_d2d_map_scala_case_classes_to_source(self):
637+
from1 = make_resource_file(
638+
self.project1,
639+
path="from/pekko-cluster-sharding-typed/org/apache/pekko/cluster/sharding/typed/"
640+
"ClusterShardingQuery.scala",
641+
extra_data={"scala_package": "org.apache.pekko.cluster.sharding.typed"},
642+
)
643+
to1 = make_resource_file(
644+
self.project1,
645+
path="to/pekko-cluster-sharding-typed/org/apache/pekko/cluster/sharding/typed/"
646+
"GetClusterShardingStats.class",
647+
)
648+
to2 = make_resource_file(
649+
self.project1,
650+
path="to/pekko-cluster-sharding-typed/org/apache/pekko/cluster/sharding/typed/"
651+
"GetShardRegionState.class",
652+
)
653+
to3 = make_resource_file(
654+
self.project1,
655+
path="to/pekko-cluster-sharding-typed/org/apache/pekko/cluster/sharding/typed/"
656+
"ClusterShardingQuery.class",
657+
)
658+
659+
buffer = io.StringIO()
660+
d2d.map_jvm_to_class(
661+
self.project1, logger=buffer.write, jvm_lang=jvm.ScalaLanguage
662+
)
663+
664+
expected = "Mapping 3 .class resources to 1 ('.scala',)"
665+
self.assertIn(expected, buffer.getvalue())
666+
self.assertEqual(3, self.project1.codebaserelations.count())
667+
668+
for to_resource in [to1, to2, to3]:
669+
relation = self.project1.codebaserelations.get(to_resource=to_resource)
670+
self.assertEqual(from1, relation.from_resource)
671+
self.assertEqual("scala_to_class", relation.map_type)
672+
636673
def test_scanpipe_pipes_d2d_map_jar_to_kotlin_source(self):
637674
from1 = make_resource_file(
638675
self.project1,

0 commit comments

Comments
 (0)