|
2 | 2 |
|
3 | 3 | #include <model/cppastnodemetrics.h> |
4 | 4 | #include <model/cppastnodemetrics-odb.hxx> |
| 5 | +#include <model/cppcohesionmetrics.h> |
| 6 | +#include <model/cppcohesionmetrics-odb.hxx> |
5 | 7 | #include <model/cppfilemetrics.h> |
6 | 8 | #include <model/cppfilemetrics-odb.hxx> |
7 | 9 |
|
|
12 | 14 |
|
13 | 15 | #include <util/filesystem.h> |
14 | 16 | #include <util/logutil.h> |
| 17 | +#include <util/filesystem.h> |
15 | 18 | #include <util/odbtransaction.h> |
16 | 19 |
|
17 | 20 | #include <memory> |
@@ -138,13 +141,116 @@ void CppMetricsParser::functionMcCabe() |
138 | 141 | }); |
139 | 142 | } |
140 | 143 |
|
| 144 | +void CppMetricsParser::lackOfCohesion() |
| 145 | +{ |
| 146 | + util::OdbTransaction {_ctx.db} ([&, this] |
| 147 | + { |
| 148 | + // Simplify some type names for readability. |
| 149 | + typedef std::uint64_t HashType; |
| 150 | + |
| 151 | + typedef odb::query<model::CohesionCppFieldView>::query_columns QField; |
| 152 | + const auto& QFieldTypeHash = QField::CppMemberType::typeHash; |
| 153 | + |
| 154 | + typedef odb::query<model::CohesionCppMethodView>::query_columns QMethod; |
| 155 | + const auto& QMethodTypeHash = QMethod::CppMemberType::typeHash; |
| 156 | + |
| 157 | + typedef odb::query<model::CohesionCppAstNodeView>::query_columns QNode; |
| 158 | + const auto& QNodeFilePath = QNode::File::path; |
| 159 | + const auto& QNodeRange = QNode::CppAstNode::location.range; |
| 160 | + |
| 161 | + // Calculate the cohesion metric for all types. |
| 162 | + for (const model::CohesionCppRecordView& type |
| 163 | + : _ctx.db->query<model::CohesionCppRecordView>()) |
| 164 | + { |
| 165 | + // Skip types that were included from external libraries. |
| 166 | + if (!cc::util::isRootedUnderAnyOf(_inputPaths, type.filePath)) |
| 167 | + continue; |
| 168 | + |
| 169 | + std::unordered_set<HashType> fieldHashes; |
| 170 | + // Query all fields of the current type. |
| 171 | + for (const model::CohesionCppFieldView& field |
| 172 | + : _ctx.db->query<model::CohesionCppFieldView>( |
| 173 | + QFieldTypeHash == type.entityHash |
| 174 | + )) |
| 175 | + { |
| 176 | + // Record these fields for later use. |
| 177 | + fieldHashes.insert(field.entityHash); |
| 178 | + } |
| 179 | + std::size_t fieldCount = fieldHashes.size(); |
| 180 | + |
| 181 | + std::size_t methodCount = 0; |
| 182 | + std::size_t totalCohesion = 0; |
| 183 | + // Query all methods of the current type. |
| 184 | + for (const model::CohesionCppMethodView& method |
| 185 | + : _ctx.db->query<model::CohesionCppMethodView>( |
| 186 | + QMethodTypeHash == type.entityHash |
| 187 | + )) |
| 188 | + { |
| 189 | + // Do not consider methods with no explicit bodies. |
| 190 | + const model::Position start(method.startLine, method.startColumn); |
| 191 | + const model::Position end(method.endLine, method.endColumn); |
| 192 | + if (start < end) |
| 193 | + { |
| 194 | + std::unordered_set<HashType> usedFields; |
| 195 | + |
| 196 | + // Query all AST nodes that use a variable for reading or writing... |
| 197 | + for (const model::CohesionCppAstNodeView& node |
| 198 | + : _ctx.db->query<model::CohesionCppAstNodeView>( |
| 199 | + // ... in the same file as the current method |
| 200 | + (QNodeFilePath == method.filePath && |
| 201 | + // ... within the textual scope of the current method's body. |
| 202 | + (QNodeRange.start.line >= start.line |
| 203 | + || (QNodeRange.start.line == start.line |
| 204 | + && QNodeRange.start.column >= start.column)) && |
| 205 | + (QNodeRange.end.line <= end.line |
| 206 | + || (QNodeRange.end.line == end.line |
| 207 | + && QNodeRange.end.column <= end.column))) |
| 208 | + )) |
| 209 | + { |
| 210 | + // If this AST node is a reference to a field of the type... |
| 211 | + if (fieldHashes.find(node.entityHash) != fieldHashes.end()) |
| 212 | + { |
| 213 | + // ... then mark it as used by this method. |
| 214 | + usedFields.insert(node.entityHash); |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + ++methodCount; |
| 219 | + totalCohesion += usedFields.size(); |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + // Calculate and record metrics. |
| 224 | + const double dF = fieldCount; |
| 225 | + const double dM = methodCount; |
| 226 | + const double dC = totalCohesion; |
| 227 | + const bool trivial = fieldCount == 0 || methodCount == 0; |
| 228 | + const bool singular = methodCount == 1; |
| 229 | + |
| 230 | + // Standard lack of cohesion (range: [0,1]) |
| 231 | + model::CppAstNodeMetrics lcm; |
| 232 | + lcm.astNodeId = type.astNodeId; |
| 233 | + lcm.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION; |
| 234 | + lcm.value = trivial ? 0.0 : |
| 235 | + (1.0 - dC / (dM * dF)); |
| 236 | + _ctx.db->persist(lcm); |
| 237 | + |
| 238 | + // Henderson-Sellers variant (range: [0,2]) |
| 239 | + model::CppAstNodeMetrics lcm_hs; |
| 240 | + lcm_hs.astNodeId = type.astNodeId; |
| 241 | + lcm_hs.type = model::CppAstNodeMetrics::Type::LACK_OF_COHESION_HS; |
| 242 | + lcm_hs.value = trivial ? 0.0 : singular ? NAN : |
| 243 | + ((dM - dC / dF) / (dM - 1.0)); |
| 244 | + _ctx.db->persist(lcm_hs); |
| 245 | + } |
| 246 | + }); |
| 247 | +} |
| 248 | + |
141 | 249 | bool CppMetricsParser::parse() |
142 | 250 | { |
143 | | - // Function parameter number metric. |
144 | 251 | functionParameters(); |
145 | | - // Function McCabe metric |
146 | 252 | functionMcCabe(); |
147 | | - |
| 253 | + lackOfCohesion(); |
148 | 254 | return true; |
149 | 255 | } |
150 | 256 |
|
|
0 commit comments