Skip to content

Commit df27a3d

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
1 parent ae24479 commit df27a3d

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

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
@@ -178,6 +178,22 @@ class ScalaLanguage(JvmLanguage):
178178
package_regex = re.compile(r"^\s*package\s+([\w\.]+)\s*;?")
179179
binary_map_type = "scala_to_class"
180180

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

182198
class KotlinLanguage(JvmLanguage):
183199
name = "kotlin"

scanpipe/tests/pipes/test_d2d.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,44 @@ def test_scanpipe_pipes_d2d_map_jar_to_scala_source(self):
612612
self.assertEqual(from2, relation.from_resource)
613613
self.assertEqual(to_jar, relation.to_resource)
614614

615+
def test_scanpipe_pipes_d2d_map_scala_case_classes_to_source(self):
616+
from1 = make_resource_file(
617+
self.project1,
618+
path="from/pekko-cluster-sharding-typed/org/apache/pekko/cluster/sharding/typed/"
619+
"ClusterShardingQuery.scala",
620+
extra_data={"scala_package": "org.apache.pekko.cluster.sharding.typed"},
621+
)
622+
to1 = make_resource_file(
623+
self.project1,
624+
path="to/pekko-cluster-sharding-typed/org/apache/pekko/cluster/sharding/typed/"
625+
"GetClusterShardingStats.class",
626+
)
627+
to2 = make_resource_file(
628+
self.project1,
629+
path="to/pekko-cluster-sharding-typed/org/apache/pekko/cluster/sharding/typed/"
630+
"GetShardRegionState.class",
631+
)
632+
to3 = make_resource_file(
633+
self.project1,
634+
path="to/pekko-cluster-sharding-typed/org/apache/pekko/cluster/sharding/typed/"
635+
"ClusterShardingQuery.class",
636+
)
637+
638+
buffer = io.StringIO()
639+
d2d.map_jvm_to_class(
640+
self.project1, logger=buffer.write, jvm_lang=jvm.ScalaLanguage
641+
)
642+
643+
expected = "Mapping 3 .class resources to 1 ('.scala',)"
644+
self.assertIn(expected, buffer.getvalue())
645+
self.assertEqual(3, self.project1.codebaserelations.count())
646+
647+
for to_resource in [to1, to2, to3]:
648+
relation = self.project1.codebaserelations.get(to_resource=to_resource)
649+
self.assertEqual(from1, relation.from_resource)
650+
self.assertEqual("scala_to_class", relation.map_type)
651+
652+
615653
def test_scanpipe_pipes_d2d_map_jar_to_kotlin_source(self):
616654
from1 = make_resource_file(
617655
self.project1,

0 commit comments

Comments
 (0)