slither.utils.martin

Robert "Uncle Bob" Martin - Agile software metrics https://en.wikipedia.org/wiki/Software_package_metrics

Efferent Coupling (Ce): Number of contracts that the contract depends on Afferent Coupling (Ca): Number of contracts that depend on a contract Instability (I): Ratio of efferent coupling to total coupling (Ce / (Ce + Ca)) Abstractness (A): Number of abstract contracts / total number of contracts Distance from the Main Sequence (D): abs(A + I - 1)

  1"""
  2    Robert "Uncle Bob" Martin - Agile software metrics
  3    https://en.wikipedia.org/wiki/Software_package_metrics
  4
  5    Efferent Coupling (Ce): Number of contracts that the contract depends on
  6    Afferent Coupling (Ca): Number of contracts that depend on a contract
  7    Instability (I): Ratio of efferent coupling to total coupling (Ce / (Ce + Ca))
  8    Abstractness (A): Number of abstract contracts / total number of contracts
  9    Distance from the Main Sequence (D):  abs(A + I - 1)
 10
 11"""
 12from typing import Tuple, List, Dict
 13from dataclasses import dataclass, field
 14from collections import OrderedDict
 15from slither.slithir.operations.high_level_call import HighLevelCall
 16from slither.core.declarations import Contract
 17from slither.utils.myprettytable import make_pretty_table, MyPrettyTable
 18
 19
 20@dataclass
 21class MartinContractMetrics:
 22    contract: Contract
 23    ca: int
 24    ce: int
 25    abstractness: float
 26    i: float = 0.0
 27    d: float = 0.0
 28
 29    def __post_init__(self) -> None:
 30        if self.ce + self.ca > 0:
 31            self.i = float(self.ce / (self.ce + self.ca))
 32            self.d = float(abs(self.i - self.abstractness))
 33
 34    def to_dict(self) -> Dict:
 35        return {
 36            "Dependents": self.ca,
 37            "Dependencies": self.ce,
 38            "Instability": f"{self.i:.2f}",
 39            "Distance from main sequence": f"{self.d:.2f}",
 40        }
 41
 42
 43@dataclass
 44class SectionInfo:
 45    """Class to hold the information for a section of the report."""
 46
 47    title: str
 48    pretty_table: MyPrettyTable
 49    txt: str
 50
 51
 52@dataclass
 53class MartinMetrics:
 54    contracts: List[Contract] = field(default_factory=list)
 55    abstractness: float = 0.0
 56    contract_metrics: OrderedDict = field(default_factory=OrderedDict)
 57    title: str = "Martin complexity metrics"
 58    full_text: str = ""
 59    core: SectionInfo = field(default=SectionInfo)
 60    CORE_KEYS = (
 61        "Dependents",
 62        "Dependencies",
 63        "Instability",
 64        "Distance from main sequence",
 65    )
 66    SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (("Core", "core", CORE_KEYS),)
 67
 68    def __post_init__(self) -> None:
 69        self.update_abstractness()
 70        self.update_coupling()
 71        self.update_reporting_sections()
 72
 73    def update_reporting_sections(self) -> None:
 74        # Create the table and text for each section.
 75        data = {
 76            contract.name: self.contract_metrics[contract.name].to_dict()
 77            for contract in self.contracts
 78        }
 79        for (title, attr, keys) in self.SECTIONS:
 80            pretty_table = make_pretty_table(["Contract", *keys], data, False)
 81            section_title = f"{self.title} ({title})"
 82            txt = f"\n\n{section_title}:\n"
 83            txt = "Martin agile software metrics\n"
 84            txt += "Efferent Coupling (Ce) - Number of contracts that a contract depends on\n"
 85            txt += "Afferent Coupling (Ca) - Number of contracts that depend on the contract\n"
 86            txt += (
 87                "Instability (I) - Ratio of efferent coupling to total coupling (Ce / (Ce + Ca))\n"
 88            )
 89            txt += "Abstractness (A) - Number of abstract contracts / total number of contracts\n"
 90            txt += "Distance from the Main Sequence (D) - abs(A + I - 1)\n"
 91            txt += "\n"
 92            txt += f"Abstractness (overall): {round(self.abstractness, 2)}\n"
 93            txt += f"{pretty_table}\n"
 94            self.full_text += txt
 95            setattr(
 96                self,
 97                attr,
 98                SectionInfo(title=section_title, pretty_table=pretty_table, txt=txt),
 99            )
100
101    def update_abstractness(self) -> None:
102        abstract_contract_count = 0
103        for c in self.contracts:
104            if not c.is_fully_implemented:
105                abstract_contract_count += 1
106        self.abstractness = float(abstract_contract_count / len(self.contracts))
107
108    # pylint: disable=too-many-branches
109    def update_coupling(self) -> None:
110        dependencies = {}
111        for contract in self.contracts:
112            external_calls = []
113            for func in contract.functions:
114                high_level_calls = [
115                    ir
116                    for node in func.nodes
117                    for ir in node.irs_ssa
118                    if isinstance(ir, HighLevelCall)
119                ]
120                # convert irs to string with target function and contract name
121                # Get the target contract name for each high level call
122                new_external_calls = []
123                for high_level_call in high_level_calls:
124                    if isinstance(high_level_call.destination, Contract):
125                        new_external_call = high_level_call.destination.name
126                    elif isinstance(high_level_call.destination, str):
127                        new_external_call = high_level_call.destination
128                    elif not hasattr(high_level_call.destination, "type"):
129                        continue
130                    elif isinstance(high_level_call.destination.type, Contract):
131                        new_external_call = high_level_call.destination.type.name
132                    elif isinstance(high_level_call.destination.type, str):
133                        new_external_call = high_level_call.destination.type
134                    elif not hasattr(high_level_call.destination.type, "type"):
135                        continue
136                    elif isinstance(high_level_call.destination.type.type, Contract):
137                        new_external_call = high_level_call.destination.type.type.name
138                    elif isinstance(high_level_call.destination.type.type, str):
139                        new_external_call = high_level_call.destination.type.type
140                    else:
141                        continue
142                    new_external_calls.append(new_external_call)
143                external_calls.extend(new_external_calls)
144            dependencies[contract.name] = set(external_calls)
145        dependents = {}
146        for contract, deps in dependencies.items():
147            for dep in deps:
148                if dep not in dependents:
149                    dependents[dep] = set()
150                dependents[dep].add(contract)
151
152        for contract in self.contracts:
153            ce = len(dependencies.get(contract.name, []))
154            ca = len(dependents.get(contract.name, []))
155            self.contract_metrics[contract.name] = MartinContractMetrics(
156                contract, ca, ce, self.abstractness
157            )
class MartinContractMetrics:
22class MartinContractMetrics:
23    contract: Contract
24    ca: int
25    ce: int
26    abstractness: float
27    i: float = 0.0
28    d: float = 0.0
29
30    def __post_init__(self) -> None:
31        if self.ce + self.ca > 0:
32            self.i = float(self.ce / (self.ce + self.ca))
33            self.d = float(abs(self.i - self.abstractness))
34
35    def to_dict(self) -> Dict:
36        return {
37            "Dependents": self.ca,
38            "Dependencies": self.ce,
39            "Instability": f"{self.i:.2f}",
40            "Distance from main sequence": f"{self.d:.2f}",
41        }
MartinContractMetrics( contract: slither.core.declarations.contract.Contract, ca: int, ce: int, abstractness: float, i: float = 0.0, d: float = 0.0)
ca: int
ce: int
abstractness: float
i: float = 0.0
d: float = 0.0
def to_dict(self) -> Dict:
35    def to_dict(self) -> Dict:
36        return {
37            "Dependents": self.ca,
38            "Dependencies": self.ce,
39            "Instability": f"{self.i:.2f}",
40            "Distance from main sequence": f"{self.d:.2f}",
41        }
class SectionInfo:
45class SectionInfo:
46    """Class to hold the information for a section of the report."""
47
48    title: str
49    pretty_table: MyPrettyTable
50    txt: str

Class to hold the information for a section of the report.

SectionInfo( title: str, pretty_table: slither.utils.myprettytable.MyPrettyTable, txt: str)
title: str
txt: str
class MartinMetrics:
 54class MartinMetrics:
 55    contracts: List[Contract] = field(default_factory=list)
 56    abstractness: float = 0.0
 57    contract_metrics: OrderedDict = field(default_factory=OrderedDict)
 58    title: str = "Martin complexity metrics"
 59    full_text: str = ""
 60    core: SectionInfo = field(default=SectionInfo)
 61    CORE_KEYS = (
 62        "Dependents",
 63        "Dependencies",
 64        "Instability",
 65        "Distance from main sequence",
 66    )
 67    SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (("Core", "core", CORE_KEYS),)
 68
 69    def __post_init__(self) -> None:
 70        self.update_abstractness()
 71        self.update_coupling()
 72        self.update_reporting_sections()
 73
 74    def update_reporting_sections(self) -> None:
 75        # Create the table and text for each section.
 76        data = {
 77            contract.name: self.contract_metrics[contract.name].to_dict()
 78            for contract in self.contracts
 79        }
 80        for (title, attr, keys) in self.SECTIONS:
 81            pretty_table = make_pretty_table(["Contract", *keys], data, False)
 82            section_title = f"{self.title} ({title})"
 83            txt = f"\n\n{section_title}:\n"
 84            txt = "Martin agile software metrics\n"
 85            txt += "Efferent Coupling (Ce) - Number of contracts that a contract depends on\n"
 86            txt += "Afferent Coupling (Ca) - Number of contracts that depend on the contract\n"
 87            txt += (
 88                "Instability (I) - Ratio of efferent coupling to total coupling (Ce / (Ce + Ca))\n"
 89            )
 90            txt += "Abstractness (A) - Number of abstract contracts / total number of contracts\n"
 91            txt += "Distance from the Main Sequence (D) - abs(A + I - 1)\n"
 92            txt += "\n"
 93            txt += f"Abstractness (overall): {round(self.abstractness, 2)}\n"
 94            txt += f"{pretty_table}\n"
 95            self.full_text += txt
 96            setattr(
 97                self,
 98                attr,
 99                SectionInfo(title=section_title, pretty_table=pretty_table, txt=txt),
100            )
101
102    def update_abstractness(self) -> None:
103        abstract_contract_count = 0
104        for c in self.contracts:
105            if not c.is_fully_implemented:
106                abstract_contract_count += 1
107        self.abstractness = float(abstract_contract_count / len(self.contracts))
108
109    # pylint: disable=too-many-branches
110    def update_coupling(self) -> None:
111        dependencies = {}
112        for contract in self.contracts:
113            external_calls = []
114            for func in contract.functions:
115                high_level_calls = [
116                    ir
117                    for node in func.nodes
118                    for ir in node.irs_ssa
119                    if isinstance(ir, HighLevelCall)
120                ]
121                # convert irs to string with target function and contract name
122                # Get the target contract name for each high level call
123                new_external_calls = []
124                for high_level_call in high_level_calls:
125                    if isinstance(high_level_call.destination, Contract):
126                        new_external_call = high_level_call.destination.name
127                    elif isinstance(high_level_call.destination, str):
128                        new_external_call = high_level_call.destination
129                    elif not hasattr(high_level_call.destination, "type"):
130                        continue
131                    elif isinstance(high_level_call.destination.type, Contract):
132                        new_external_call = high_level_call.destination.type.name
133                    elif isinstance(high_level_call.destination.type, str):
134                        new_external_call = high_level_call.destination.type
135                    elif not hasattr(high_level_call.destination.type, "type"):
136                        continue
137                    elif isinstance(high_level_call.destination.type.type, Contract):
138                        new_external_call = high_level_call.destination.type.type.name
139                    elif isinstance(high_level_call.destination.type.type, str):
140                        new_external_call = high_level_call.destination.type.type
141                    else:
142                        continue
143                    new_external_calls.append(new_external_call)
144                external_calls.extend(new_external_calls)
145            dependencies[contract.name] = set(external_calls)
146        dependents = {}
147        for contract, deps in dependencies.items():
148            for dep in deps:
149                if dep not in dependents:
150                    dependents[dep] = set()
151                dependents[dep].add(contract)
152
153        for contract in self.contracts:
154            ce = len(dependencies.get(contract.name, []))
155            ca = len(dependents.get(contract.name, []))
156            self.contract_metrics[contract.name] = MartinContractMetrics(
157                contract, ca, ce, self.abstractness
158            )
MartinMetrics( contracts: List[slither.core.declarations.contract.Contract] = <factory>, abstractness: float = 0.0, contract_metrics: collections.OrderedDict = <factory>, title: str = 'Martin complexity metrics', full_text: str = '', core: SectionInfo = <class 'SectionInfo'>, SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (('Core', 'core', ('Dependents', 'Dependencies', 'Instability', 'Distance from main sequence')),))
abstractness: float = 0.0
contract_metrics: collections.OrderedDict
title: str = 'Martin complexity metrics'
full_text: str = ''
core: SectionInfo = <class 'SectionInfo'>
CORE_KEYS = ('Dependents', 'Dependencies', 'Instability', 'Distance from main sequence')
SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = (('Core', 'core', ('Dependents', 'Dependencies', 'Instability', 'Distance from main sequence')),)
def update_reporting_sections(self) -> None:
 74    def update_reporting_sections(self) -> None:
 75        # Create the table and text for each section.
 76        data = {
 77            contract.name: self.contract_metrics[contract.name].to_dict()
 78            for contract in self.contracts
 79        }
 80        for (title, attr, keys) in self.SECTIONS:
 81            pretty_table = make_pretty_table(["Contract", *keys], data, False)
 82            section_title = f"{self.title} ({title})"
 83            txt = f"\n\n{section_title}:\n"
 84            txt = "Martin agile software metrics\n"
 85            txt += "Efferent Coupling (Ce) - Number of contracts that a contract depends on\n"
 86            txt += "Afferent Coupling (Ca) - Number of contracts that depend on the contract\n"
 87            txt += (
 88                "Instability (I) - Ratio of efferent coupling to total coupling (Ce / (Ce + Ca))\n"
 89            )
 90            txt += "Abstractness (A) - Number of abstract contracts / total number of contracts\n"
 91            txt += "Distance from the Main Sequence (D) - abs(A + I - 1)\n"
 92            txt += "\n"
 93            txt += f"Abstractness (overall): {round(self.abstractness, 2)}\n"
 94            txt += f"{pretty_table}\n"
 95            self.full_text += txt
 96            setattr(
 97                self,
 98                attr,
 99                SectionInfo(title=section_title, pretty_table=pretty_table, txt=txt),
100            )
def update_abstractness(self) -> None:
102    def update_abstractness(self) -> None:
103        abstract_contract_count = 0
104        for c in self.contracts:
105            if not c.is_fully_implemented:
106                abstract_contract_count += 1
107        self.abstractness = float(abstract_contract_count / len(self.contracts))
def update_coupling(self) -> None:
110    def update_coupling(self) -> None:
111        dependencies = {}
112        for contract in self.contracts:
113            external_calls = []
114            for func in contract.functions:
115                high_level_calls = [
116                    ir
117                    for node in func.nodes
118                    for ir in node.irs_ssa
119                    if isinstance(ir, HighLevelCall)
120                ]
121                # convert irs to string with target function and contract name
122                # Get the target contract name for each high level call
123                new_external_calls = []
124                for high_level_call in high_level_calls:
125                    if isinstance(high_level_call.destination, Contract):
126                        new_external_call = high_level_call.destination.name
127                    elif isinstance(high_level_call.destination, str):
128                        new_external_call = high_level_call.destination
129                    elif not hasattr(high_level_call.destination, "type"):
130                        continue
131                    elif isinstance(high_level_call.destination.type, Contract):
132                        new_external_call = high_level_call.destination.type.name
133                    elif isinstance(high_level_call.destination.type, str):
134                        new_external_call = high_level_call.destination.type
135                    elif not hasattr(high_level_call.destination.type, "type"):
136                        continue
137                    elif isinstance(high_level_call.destination.type.type, Contract):
138                        new_external_call = high_level_call.destination.type.type.name
139                    elif isinstance(high_level_call.destination.type.type, str):
140                        new_external_call = high_level_call.destination.type.type
141                    else:
142                        continue
143                    new_external_calls.append(new_external_call)
144                external_calls.extend(new_external_calls)
145            dependencies[contract.name] = set(external_calls)
146        dependents = {}
147        for contract, deps in dependencies.items():
148            for dep in deps:
149                if dep not in dependents:
150                    dependents[dep] = set()
151                dependents[dep].add(contract)
152
153        for contract in self.contracts:
154            ce = len(dependencies.get(contract.name, []))
155            ca = len(dependents.get(contract.name, []))
156            self.contract_metrics[contract.name] = MartinContractMetrics(
157                contract, ca, ce, self.abstractness
158            )