Skip to content

Commit 1b983ca

Browse files
authored
Merge pull request #26 from matsoftware/analyze-submodules-part2
Analyze submodules - part 2
2 parents 4f874e9 + 7844d7f commit 1b983ca

File tree

11 files changed

+194
-97
lines changed

11 files changed

+194
-97
lines changed

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ matplotlib >= 2
22
adjustText < 1
33
pygraphviz == 1.5
44
pyfunctional == 1.2
5-
mergedeep == 1.3.0
65
dataclasses == 0.7.0

swift_code_metrics/_analyzer.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33

44
from ._helpers import AnalyzerHelpers
55
from ._parser import SwiftFileParser, SwiftFile
6-
from ._metrics import Framework
6+
from ._metrics import Framework, SubModule
77
from ._report import ReportProcessor
88
from functional import seq
99
from typing import List, Optional
10-
from mergedeep import merge
1110

1211

1312
class Inspector:
@@ -61,7 +60,7 @@ def __analyze_directory(self, directory: str, exclude_paths: List[str], tests_de
6160

6261
def __append_dependency(self, swift_file: 'SwiftFile'):
6362
framework = self.__get_or_create_framework(swift_file.framework_name)
64-
Inspector.__add_raw_files(framework=framework, swift_file=swift_file)
63+
Inspector.__populate_submodule(framework=framework, swift_file=swift_file)
6564
# This covers the scenario where a test framework might contain no tests
6665
framework.is_test_framework = swift_file.is_test
6766

@@ -72,12 +71,23 @@ def __append_dependency(self, swift_file: 'SwiftFile'):
7271
framework.append_import(imported_framework)
7372

7473
@staticmethod
75-
def __add_raw_files(framework: 'Framework', swift_file: 'SwiftFile'):
76-
paths = str(swift_file.path).split('/')
77-
ref_dict = swift_file
78-
for key in reversed(paths):
79-
ref_dict = {key: ref_dict}
80-
merge(framework.raw_files, ref_dict)
74+
def __populate_submodule(framework: 'Framework', swift_file: 'SwiftFile'):
75+
current_paths = str(swift_file.path).split('/')
76+
paths = list(reversed(current_paths))
77+
78+
submodule = framework.submodule
79+
while len(paths) > 1:
80+
path = paths.pop()
81+
submodules = [s for s in submodule.submodules]
82+
existing_submodule = seq(submodules).filter(lambda sm: sm.name == path)
83+
if len(list(existing_submodule)) > 0:
84+
submodule = existing_submodule.first()
85+
else:
86+
new_submodule = SubModule(name=path, files=[], submodules=[])
87+
submodule.submodules.append(new_submodule)
88+
submodule = new_submodule
89+
90+
submodule.files.append(swift_file)
8191

8292
def __process_shared_file(self, swift_file: 'SwiftFile', directory: str):
8393
if not swift_file.is_shared:

swift_code_metrics/_helpers.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,3 @@ class JSONReader:
271271
def read_json_file(path: str) -> Dict:
272272
with open(path, 'r') as fp:
273273
return json.load(fp)
274-
275-
276-
# Methods
277-
278-
def flatten_nested_dictionary_values(dictionary) -> List:
279-
for v in dictionary.values():
280-
if isinstance(v, dict):
281-
yield from flatten_nested_dictionary_values(v)
282-
else:
283-
yield v

swift_code_metrics/_metrics.py

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from ._helpers import AnalyzerHelpers, Log, ParsingHelpers, ReportingHelpers, flatten_nested_dictionary_values
1+
from ._helpers import AnalyzerHelpers, Log, ParsingHelpers, ReportingHelpers
22
from ._parser import SwiftFile
33
from dataclasses import dataclass
44
from functional import seq
@@ -340,8 +340,12 @@ def __from_sd(cls, sd: 'SyntheticData', n_o_i: int) -> 'FrameworkData':
340340
class Framework:
341341
def __init__(self, name: str, is_test_framework: bool = False):
342342
self.name = name
343-
self.raw_files = {}
344343
self.__total_imports = {}
344+
self.submodule = SubModule(
345+
name=self.name,
346+
files=[],
347+
submodules=[]
348+
)
345349
self.is_test_framework = is_test_framework
346350

347351
def __repr__(self):
@@ -365,18 +369,15 @@ def data(self) -> SyntheticData:
365369
The metrics data describing the framework
366370
:return: an instance of SyntheticData
367371
"""
368-
if self.number_of_files == 0:
369-
return SyntheticData()
370-
return seq([SyntheticData.from_swift_file(swift_file=sf) for sf in self.__raw_files_data()]) \
371-
.reduce(lambda sd1, sd2: sd1 + sd2)
372+
return self.submodule.data
372373

373374
@property
374375
def number_of_files(self) -> int:
375376
"""
376377
Number of files in the framework
377378
:return: The total number of files in this framework (int)
378379
"""
379-
return len(self.__raw_files_data())
380+
return self.submodule.n_of_files
380381

381382
@property
382383
def imports(self) -> Dict[str, int]:
@@ -413,10 +414,38 @@ def compact_name_description(self) -> str:
413414
def __filtered_imports(items: 'ItemsView') -> Dict[str, int]:
414415
return seq(items).filter(lambda f: f[0].name not in AnalyzerHelpers.APPLE_FRAMEWORKS).dict()
415416

416-
# Private
417417

418-
def __raw_files_data(self) -> List['SwiftFile']:
419-
return list(flatten_nested_dictionary_values(self.raw_files))
418+
@dataclass
419+
class SubModule:
420+
"""
421+
Representation of a submodule inside a Framework
422+
"""
423+
name: str
424+
files: List['SwiftFile']
425+
submodules: List['SubModule']
426+
427+
@property
428+
def n_of_files(self) -> int:
429+
sub_files = 0 if (len(self.submodules) == 0) else \
430+
seq([s.n_of_files for s in self.submodules]).reduce(lambda a, b: a + b)
431+
return len(self.files) + sub_files
432+
433+
@property
434+
def data(self) -> 'SyntheticData':
435+
root_module_files = [SyntheticData()] if (len(self.files) == 0) else \
436+
[SyntheticData.from_swift_file(swift_file=f) for f in self.files]
437+
submodules_files = SyntheticData() if (len(self.submodules) == 0) else \
438+
seq([s.data for s in self.submodules]).reduce(lambda a, b: a + b)
439+
return seq(root_module_files).reduce(lambda a, b: a + b) + submodules_files
440+
441+
@property
442+
def as_dict(self) -> Dict:
443+
return {
444+
self.name: {
445+
"n_of_files": self.n_of_files,
446+
"metric": self.data.as_dict
447+
}
448+
}
420449

421450

422451
@dataclass

swift_code_metrics/_report.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,17 @@ def __framework_analysis(framework: 'Framework', frameworks: List['Framework'])
4141
:param framework: The framework to analyze
4242
:return: The architectural analysis of the framework
4343
"""
44-
loc = framework.data.loc
45-
noc = framework.data.noc
46-
poc = Metrics.percentage_of_comments(framework.data.noc,
47-
framework.data.loc)
44+
framework_data = framework.data
45+
loc = framework_data.loc
46+
noc = framework_data.noc
47+
poc = Metrics.percentage_of_comments(framework_data.noc,
48+
framework_data.loc)
4849
analysis = Metrics.poc_analysis(poc)
49-
n_a = framework.data.number_of_interfaces
50-
n_c = framework.data.number_of_concrete_data_structures
51-
nom = framework.data.number_of_methods
50+
n_a = framework_data.number_of_interfaces
51+
n_c = framework_data.number_of_concrete_data_structures
52+
nom = framework_data.number_of_methods
5253
dependencies = Metrics.total_dependencies(framework)
53-
n_of_tests = framework.data.number_of_tests
54+
n_of_tests = framework_data.number_of_tests
5455
n_of_imports = framework.number_of_imports
5556

5657
# Non-test framework analysis

swift_code_metrics/tests/test_helper.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,6 @@ def test_helpers_funcs_property_modifier_extract_substring_with_pattern_expectTr
164164
def test_helpers_funcs_reduce_dictionary(self):
165165
self.assertEqual(3, _helpers.ParsingHelpers.reduce_dictionary({"one": 1, "two": 2}))
166166

167-
# Additional helpers
168-
169-
def test_flatten_nested_dictionary_values(self):
170-
nested_dictionary = {'group1': {'subGroup1': 1}, 'group2': 2, 'group3': {'subGroup3': {'subSubGroup3': 3}}}
171-
flattened_values = list(_helpers.flatten_nested_dictionary_values(nested_dictionary))
172-
self.assertEqual([1, 2, 3], flattened_values)
173-
174167

175168
if __name__ == '__main__':
176169
unittest.main()

swift_code_metrics/tests/test_metrics.py

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from swift_code_metrics._metrics import Framework, Dependency, Metrics, SyntheticData, FrameworkData
2+
from swift_code_metrics._metrics import Framework, Dependency, Metrics, SyntheticData, FrameworkData, SubModule
33
from swift_code_metrics._parser import SwiftFile
44
from functional import seq
55

@@ -17,15 +17,28 @@
1717
is_test=False
1818
)
1919

20+
example_file2 = SwiftFile(
21+
path='/my/path/class.swift',
22+
framework_name='Test',
23+
loc=1,
24+
imports=['Foundation', 'dep1', 'dep2'],
25+
interfaces=['prot1', 'prot2', 'prot3', 'prot4',
26+
'prot5', 'prot6', 'prot7', 'prot8'],
27+
structs=['struct1', 'struct2'],
28+
classes=['class1', 'class2'],
29+
methods=['meth1', 'meth2', 'meth3', 'testMethod'],
30+
n_of_comments=7,
31+
is_shared=False,
32+
is_test=False
33+
)
34+
2035

2136
class FrameworkTests(unittest.TestCase):
2237

2338
def setUp(self):
2439
self.frameworks = [Framework('BusinessLogic'), Framework('UIKit'), Framework('Other')]
2540
self.framework = Framework('AwesomeName')
26-
self.framework.raw_files['Group1'] = {}
27-
self.framework.raw_files['Group1']['File1'] = example_swiftfile
28-
self.framework.raw_files['Group1']['File2'] = example_swiftfile
41+
self.framework.submodule.files = [example_swiftfile, example_file2]
2942
seq(self.frameworks) \
3043
.for_each(lambda f: self.framework.append_import(f))
3144

@@ -119,7 +132,7 @@ def test_distance_main_sequence(self):
119132
is_shared=False,
120133
is_test=False
121134
)
122-
self.app_layer.raw_files['File'] = example_file
135+
self.app_layer.submodule.files.append(example_file)
123136

124137
self.assertAlmostEqual(0.286,
125138
Metrics.distance_main_sequence(self.app_layer, self.frameworks),
@@ -138,23 +151,7 @@ def test_abstractness_no_concretes(self):
138151
self.assertEqual(0, Metrics.abstractness(self.foundation_kit))
139152

140153
def test_abstractness_concretes(self):
141-
142-
example_file = SwiftFile(
143-
path='/my/path/class.swift',
144-
framework_name='Test',
145-
loc=1,
146-
imports=['Foundation', 'dep1', 'dep2'],
147-
interfaces=['prot1', 'prot2', 'prot3', 'prot4',
148-
'prot5', 'prot6', 'prot7', 'prot8'],
149-
structs=['struct1', 'struct2'],
150-
classes=['class1', 'class2'],
151-
methods=['meth1', 'meth2', 'meth3', 'testMethod'],
152-
n_of_comments=7,
153-
is_shared=False,
154-
is_test=False
155-
)
156-
157-
self.foundation_kit.raw_files['File'] = example_file
154+
self.foundation_kit.submodule.files.append(example_file2)
158155
self.assertEqual(2, Metrics.abstractness(self.foundation_kit))
159156

160157
def test_fan_in_test_frameworks(self):
@@ -313,7 +310,7 @@ def test_init_swift_file(self):
313310
def test_append_framework(self):
314311
test_framework = Framework('Test')
315312
test_framework.append_import(Framework('Imported'))
316-
test_framework.raw_files['File'] = example_swiftfile
313+
test_framework.submodule.files.append(example_swiftfile)
317314

318315
self.framework_data.append_framework(test_framework)
319316
self.assertEqual(2, self.framework_data.loc)
@@ -350,5 +347,62 @@ def test_as_dict(self):
350347
self.assertEqual(expected_dict, self.framework_data.as_dict)
351348

352349

350+
class SubModuleTests(unittest.TestCase):
351+
352+
def setUp(self):
353+
self.submodule = SubModule(
354+
name="BusinessModule",
355+
files=[example_swiftfile],
356+
submodules=[
357+
SubModule(
358+
name="Helper",
359+
files=[example_file2],
360+
submodules=[]
361+
)
362+
]
363+
)
364+
365+
def test_n_of_files(self):
366+
self.assertEqual(2, self.submodule.n_of_files)
367+
368+
def test_data(self):
369+
data = SyntheticData(
370+
loc=2,
371+
noc=14,
372+
number_of_interfaces=11,
373+
number_of_concrete_data_structures=6,
374+
number_of_methods=8,
375+
number_of_tests=2
376+
)
377+
self.assertEqual(data, self.submodule.data)
378+
379+
def test_empty_data(self):
380+
data = SyntheticData(
381+
loc=0,
382+
noc=0,
383+
number_of_interfaces=0,
384+
number_of_concrete_data_structures=0,
385+
number_of_methods=0,
386+
number_of_tests=0
387+
)
388+
self.assertEqual(data, SubModule(name="", files=[], submodules=[]).data)
389+
390+
def test_dict_repr(self):
391+
self.assertEqual({
392+
"BusinessModule": {
393+
"n_of_files": 2,
394+
"metric": {
395+
"loc": 2,
396+
"n_a": 11,
397+
"n_c": 6,
398+
"noc": 14,
399+
"nom": 8,
400+
"not": 2,
401+
"poc": 87.5
402+
}
403+
}
404+
}, self.submodule.as_dict)
405+
406+
353407
if __name__ == '__main__':
354408
unittest.main()

swift_code_metrics/tests/test_resources/ExampleProject/SwiftCodeMetricsExample/Foundation/Foundation.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
E511F8282523A68600EE2AEC /* CommonTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E511F8272523A68600EE2AEC /* CommonTypes.swift */; };
1011
E576963D2117671600CADE76 /* FoundationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E57696332117671600CADE76 /* FoundationFramework.framework */; };
1112
E57696422117671600CADE76 /* FoundationFrameworkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E57696412117671600CADE76 /* FoundationFrameworkTests.swift */; };
1213
E576964E2117672A00CADE76 /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = E576964D2117672A00CADE76 /* Networking.swift */; };
@@ -27,6 +28,7 @@
2728
/* End PBXContainerItemProxy section */
2829

2930
/* Begin PBXFileReference section */
31+
E511F8272523A68600EE2AEC /* CommonTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonTypes.swift; sourceTree = "<group>"; };
3032
E57696332117671600CADE76 /* FoundationFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FoundationFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3133
E57696372117671600CADE76 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3234
E576963C2117671600CADE76 /* FoundationFrameworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FoundationFrameworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -66,6 +68,14 @@
6668
/* End PBXFrameworksBuildPhase section */
6769

6870
/* Begin PBXGroup section */
71+
E511F8262523A66900EE2AEC /* Interfaces */ = {
72+
isa = PBXGroup;
73+
children = (
74+
E511F8272523A68600EE2AEC /* CommonTypes.swift */,
75+
);
76+
path = Interfaces;
77+
sourceTree = "<group>";
78+
};
6979
E57696292117671600CADE76 = {
7080
isa = PBXGroup;
7181
children = (
@@ -92,6 +102,7 @@
92102
children = (
93103
E57696372117671600CADE76 /* Info.plist */,
94104
E576964D2117672A00CADE76 /* Networking.swift */,
105+
E511F8262523A66900EE2AEC /* Interfaces */,
95106
);
96107
path = FoundationFramework;
97108
sourceTree = "<group>";
@@ -272,6 +283,7 @@
272283
files = (
273284
E576964E2117672A00CADE76 /* Networking.swift in Sources */,
274285
E57696502117673500CADE76 /* Helpers.swift in Sources */,
286+
E511F8282523A68600EE2AEC /* CommonTypes.swift in Sources */,
275287
);
276288
runOnlyForDeploymentPostprocessing = 0;
277289
};

0 commit comments

Comments
 (0)