From db92f1678b83c2f7d976e88f25993a15eb9f329e Mon Sep 17 00:00:00 2001 From: Collin Barrett Date: Tue, 17 Apr 2018 19:46:56 -0500 Subject: [PATCH] add ndepend --- .gitignore | 15 +- FilterLists.ndproj | 11518 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 11529 insertions(+), 4 deletions(-) create mode 100644 FilterLists.ndproj diff --git a/.gitignore b/.gitignore index c217e628d..90214f87b 100644 --- a/.gitignore +++ b/.gitignore @@ -64,8 +64,10 @@ StyleCopReport.xml *.ilk *.meta *.obj +*.iobj *.pch *.pdb +*.ipdb *.pgc *.pgd *.rsp @@ -248,6 +250,7 @@ ServiceFabricBackup/ *.rdl.data *.bim.layout *.bim_*.settings +*.rptproj.rsuser # Microsoft Fakes FakesAssemblies/ @@ -259,9 +262,6 @@ FakesAssemblies/ .ntvs_analysis.dat node_modules/ -# TypeScript v1 declaration files -typings/ - # Visual Studio 6 build log *.plg @@ -322,9 +322,16 @@ ASALocalRun/ # MSBuild Binary and Structured Log *.binlog +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + # FilterLists Custom Ignores gs/Private.gs appsettings.development.json appsettings.production.json src/FilterLists.Agent/appsettings.json -src/FilterLists.Web/wwwroot/dist \ No newline at end of file +src/FilterLists.Web/wwwroot/dist +NDependOut/ \ No newline at end of file diff --git a/FilterLists.ndproj b/FilterLists.ndproj new file mode 100644 index 000000000..b0e0da3ba --- /dev/null +++ b/FilterLists.ndproj @@ -0,0 +1,11518 @@ + + + C:\Users\cb\Documents\GitHub\filterlists\NDependOut1 + + FilterLists.Api + FilterLists.Data + FilterLists.Services + FilterLists.Web + FilterLists.Agent + + + System.Runtime + System.Diagnostics.Debug + Microsoft.AspNetCore.Hosting.Abstractions + Microsoft.Extensions.Configuration.Abstractions + Microsoft.Extensions.DependencyInjection.Abstractions + Microsoft.AspNetCore.Http.Abstractions + Swashbuckle.AspNetCore.SwaggerUI + Microsoft.AspNetCore.Routing + Microsoft.AspNetCore.Mvc.Versioning + Microsoft.AspNetCore.Mvc.ViewFeatures + Microsoft.AspNetCore.Mvc.Core + Microsoft.AspNetCore.Mvc.Abstractions + System.Threading.Tasks + Microsoft.EntityFrameworkCore + Microsoft.EntityFrameworkCore.Relational + System.Linq.Expressions + Swashbuckle.AspNetCore.SwaggerGen + System.Collections + Microsoft.AspNetCore.Hosting + Microsoft.AspNetCore + Microsoft.ApplicationInsights.AspNetCore + Microsoft.AspNetCore.HttpOverrides + Swashbuckle.AspNetCore.Swagger + Microsoft.AspNetCore.StaticFiles + Microsoft.Extensions.Configuration.Binder + System.ComponentModel + Pomelo.EntityFrameworkCore.MySql + Microsoft.AspNetCore.Mvc + Microsoft.ApplicationInsights + Microsoft.AspNetCore.Mvc.Formatters.Json + Microsoft.Extensions.PlatformAbstractions + System.Runtime.Extensions + netstandard + Newtonsoft.Json + AutoMapper + AutoMapper.Extensions.Microsoft.DependencyInjection + Microsoft.AspNetCore.Diagnostics + Microsoft.AspNetCore.SpaServices + System.Diagnostics.DiagnosticSource + Microsoft.Extensions.DependencyInjection + Microsoft.Extensions.Configuration + System.IO.FileSystem + Microsoft.Extensions.Configuration.FileExtensions + Microsoft.Extensions.Configuration.Json + System.Console + + + C:\Users\cb\Documents\GitHub\filterlists\src\FilterLists.Api\bin\debug\netcoreapp2.0 + C:\Users\cb\Documents\GitHub\filterlists\src\FilterLists.Agent\bin\debug\netcoreapp2.0 + C:\Users\cb\Documents\GitHub\filterlists\src\FilterLists.Web\bin\debug\netcoreapp2.1 + C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.0.6 + C:\Program Files\dotnet\store\x64\netcoreapp2.0 + C:\Program Files\dotnet\sdk\NuGetFallbackFolder + C:\Users\cb\.nuget\packages + + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + 0 + 0 + $ManDay$ + 50 + USD + After + 18 + 240 + 8 + 5 + 10 + 20 + 50 + 1200000000 + 12000000000 + 72000000000 + 360000000000 + + + + + Quality Gates Evolution +from qg in QualityGates +let qgBaseline = qg.OlderVersion() +let relyOnDiff = qgBaseline == null +let evolution = relyOnDiff ? (TrendIcon?)null : + // When a quality gate relies on diff between now and baseline + // it is not executed against the baseline + qg.ValueDiff() == 0d ? + TrendIcon.Constant : + (qg.ValueDiff() > 0 ? + ( qg.MoreIsBad ? TrendIcon.RedUp: TrendIcon.GreenUp) : + (!qg.MoreIsBad ? TrendIcon.RedDown: TrendIcon.GreenDown)) +select new { qg, + Evolution = evolution, + + BaselineStatus = relyOnDiff? (QualityGateStatus?) null : qgBaseline.Status, + Status = qg.Status, + + BaselineValue = relyOnDiff? (null) : qgBaseline.ValueString, + Value = qg.ValueString, +} + +// +// Show quality gates evolution between baseline and now. +// +// When a quality gate relies on diff between now and baseline (like *New Debt since Baseline*) +// it is not executed against the baseline and as a consequence its evolution is not available. +// +// Double-click a quality gate for editing. +// ]]> + +failif value < 70% +warnif value < 80% +codeBase.PercentageCoverage + +// +// Code coverage is a measure used to describe the degree to which the source code of a program +// is tested by a particular test suite. A program with high code coverage, measured as a percentage, +// has had more of its source code executed during testing which suggests it has a lower chance of +// containing undetected software bugs compared to a program with low code coverage. +// +// Code coverage is certainly the most important quality code metric. But coverage is not enough +// the team needs to ensure that results are checked at test-time. These checks can be done both +// in test code, and in application code through assertions. The important part is that a test +// must fail explicitely when a check gets unvalidated during the test execution. +// +// This quality gate define a warn threshold (70%) and a fail threshold (80%). These are +// indicative thresholds and in practice the more the better. To achieve high coverage and +// low risk, make sure that new and refactored classes gets 100% covered by tests and that +// the application and test code contains as many checks/assertions as possible. +//]]> + +failif value < 70% +warnif value < 80% +let newMethods = Application.Methods.Where(m => m.WasAdded() && m.NbLinesOfCode > 0) +let locCovered = newMethods.Sum(m => m.NbLinesOfCodeCovered) +let loc = newMethods.Sum(m => m.NbLinesOfCode) +select 100d * locCovered / loc + +// +// *New Code* is defined as methods added since the baseline. +// +// To achieve high code coverage it is essential that new code gets properly +// tested and covered by tests. It is advised that all non-UI new classes gets +// 100% covered. +// +// Typically 90% of a class is easy to cover by tests and 10% is hard to reach +// through tests. It means that this 10% remaining is not easily testable, which +// means it is not well designed, which often means that this code is especially +// **error-prone**. This is the reason why it is important to reach 100% coverage +// for a class, to make sure that potentially *error-prone* code gets tested. +// +]]> + +failif value < 70% +warnif value < 80% +let newMethods = Application.Methods.Where(m => m.CodeWasChanged() && m.NbLinesOfCode > 0) +let locCovered = newMethods.Sum(m => m.NbLinesOfCodeCovered) +let loc = newMethods.Sum(m => m.NbLinesOfCode) +select 100d * locCovered / loc + +// +// *Refactored Code* is defined as methods where *code was changed* since the baseline. +// +// Comment changes and formatting changes are not considerd as refactoring. +// +// To achieve high code coverage it is essential that refactored code gets properly +// tested and covered by tests. It is advised that when refactoring a class +// or a method, it is important to also write tests to make sure it gets 100% covered. +// +// Typically 90% of a class is easy to cover by tests and 10% is hard to reach +// through tests. It means that this 10% remaining is not easily testable, which +// means it is not well designed, which often means that this code is especially +// **error-prone**. This is the reason why it is important to reach 100% coverage +// for a class, to make sure that potentially *error-prone* code gets tested. +// +]]> + +failif count > 0 issues +from i in Issues +where i.Severity == Severity.Blocker +select new { i, i.Severity, i.Debt, i.AnnualInterest } + +// +// An issue with the severity **Blocker** cannot move to production, it must be fixed. +// +// The severity of an issue is either defined explicitely in the rule source code, +// either inferred from the issue *annual interest* and thresholds defined in the +// NDepend Project Properties > Issue and Debt. +// + +]]> + +failif count > 10 issues +warnif count > 0 issues + +from i in Issues +where i.Severity == Severity.Critical +select new { i, i.Severity, i.Debt, i.AnnualInterest } + +// +// An issue with a severity level **Critical** shouldn't move to production. +// It still can for business imperative needs purposes, but at worst it must +// be fixed during the next iterations. +// +// The severity of an issue is either defined explicitely in the rule source code, +// either inferred from the issue *annual interest* and thresholds defined in the +// NDepend Project Properties > Issue and Debt. +//]]> + +failif count > 0 issues +from i in Issues +where i.Severity.EqualsAny(Severity.Blocker, Severity.Critical, Severity.High) && + // Count both the new issues and the issues that became at least Critical + (i.WasAdded() || i.OlderVersion().Severity < Severity.High) +select new { i, i.Severity, i.Debt, i.AnnualInterest } + + +// +// An issue with the severity **Blocker** cannot move to production, it must be fixed. +// +// An issue with a severity level **Critical** shouldn't move to production. +// It still can for business imperative needs purposes, but at worth it must be fixed +// during the next iterations. +// +// An issue with a severity level **High** should be fixed quickly, but can wait until +// the next scheduled interval. +// +// The severity of an issue is either defined explicitely in the rule source code, +// either inferred from the issue *annual interest* and thresholds defined in the +// NDepend Project Properties > Issue and Debt. +// +]]> + +failif count > 0 rules +from r in Rules where r.IsCritical && r.IsViolated() +select new { r, issues = r.Issues() } + +// +// The concept of critical rule is useful to pinpoint certain rules that +// should not be violated. +// +// A rule can be made critical just by checking the *Critical button* in the +// rule edition control and then saving the rule. +// +// This quality gate fails if any critical rule gets any violations. +// +// When no baseline is available, rules that rely on diff are not counted. +// If you observe that this quality gate count slightly decreases with no apparent reason, +// the reason is certainly that rules that rely on diff are not counted +// because the baseline is not defined. +//]]> + +failif value > 30% +warnif value > 20% +let timeToDev = codeBase.EffortToDevelop() +let debt = Issues.Sum(i => i.Debt) +select 100d * debt.ToManDay() / timeToDev.ToManDay() + +// +// % Debt total is defined as a percentage on: +// +// • the estimated total effort to develop the code base +// +// • and the the estimated total time to fix all issues (the Debt) +// +// Estimated total effort to develop the code base is inferred from +// # lines of code of the code base and from the +// *Estimated number of man-day to develop 1000 logicial lines of code* +// setting found in NDepend Project Properties > Issue and Debt. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +// +// This quality gates fails if the estimated debt is more than 30% +// of the estimated effort to develop the code base, and warns if the +// estimated debt is more than 20% of the estimated effort to develop +// the code base +// ]]> + +failif value > 50 man-days +warnif value > 30 man-days +Issues.Sum(i => i.Debt).ToManDay() + +// +// This Quality Gate is disabled per default because the fail and warn +// thresholds of unacceptable Debt in man-days can only depend on the +// project size, number of developers and overall context. +// +// However you can refer to the default Quality Gate **Percentage Debt**. +// +// The Debt is defined as the sum of estimated effort to fix all issues. +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +//]]> + +failif value > 2 man-days +warnif value > 0 man-days +let debt = Issues.Sum(i => i.Debt) +let debtInBaseline = IssuesInBaseline.Sum(i => i.Debt) +select (debt - debtInBaseline).ToManDay() + + +// +// This Quality Gate fails if the estimated effort to fix new or worsened +// issues (what is called the *New Debt since Baseline*) is higher +// than 2 man-days. +// +// This Quality Gate warns if this estimated effort is positive. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +//]]> + +failif count > 0 namespaces + +from n in Application.Namespaces +where n.DebtRating() != null && + n.DebtRating().Value.EqualsAny(DebtRating.E, DebtRating.D) +select new { + n, + debtRating = n.DebtRating(), + debtRatio = n.DebtRatio(), // % of debt from which DebtRating is inferred + devTimeInManDay = n.EffortToDevelop().ToDebt(), + debtInManDay = n.AllDebt(), + issues = n.AllIssues() +} + +// +// Forbid namespaces with a poor Debt Rating equals to **E** or **D**. +// +// The **Debt Rating** for a code element is estimated by the value of the **Debt Ratio** +// and from the various rating thresholds defined in this project *Debt Settings*. +// +// The **Debt Ratio** of a code element is a percentage of **Debt Amount** (in floating man-days) +// compared to the **estimated effort to develop the code element** (also in floating man-days). +// +// The **estimated effort to develop the code element** is inferred from the code elements +// number of lines of code, and from the project *Debt Settings* parameters +// *estimated number of man-days to develop 1000* **logical lines of code**. +// +// The **logical lines of code** corresponds to the number of debug breakpoints in a method +// and doesn't depend on code formatting nor comments. +// +// The Quality Gate can be modified to match assemblies, types or methods +// with a poor Debt Rating, instead of matching namespaces. +// ]]> + +failif value > 50 man-days +warnif value > 30 man-days +Issues.Sum(i => i.AnnualInterest).ToManDay() + + +// +// This Quality Gate is disabled per default because the fail and warn +// thresholds of unacceptable Annual-Interest in man-days can only depend +// on the project size, number of developers and overall context. +// +// However you can refer to the default Quality Gate +// **New Annual Interest since Baseline**. +// +// The Annual-Interest is defined as the sum of estimated annual cost +// in man-days, to leave all issues unfixed. +// +// Each rule can either provide a formula to compute the Annual-Interest +// per issue, or assign a **Severity** level for each issue. Some thresholds +// defined in *Project Properties > Issue and Debt > Annual Interest* are +// used to infer an Annual-Interest value from a Severity level. +// Annual Interest documentation: https://www.ndepend.com/docs/technical-debt#AnnualInterest +//]]> + +failif value > 2 man-days +warnif value > 0 man-days +let ai = Issues.Sum(i => i.AnnualInterest) +let aiInBaseline = IssuesInBaseline.Sum(i => i.AnnualInterest) +select (ai - aiInBaseline).ToManDay() + +// +// This Quality Gate fails if the estimated annual cost to leave all issues +// unfixed, increased from more than 2 man-days since the baseline. +// +// This Quality Gate warns if this estimated annual cost is positive. +// +// This estimated annual cost is named the **Annual-Interest**. +// +// Each rule can either provide a formula to compute the Annual-Interest +// per issue, or assign a **Severity** level for each issue. Some thresholds +// defined in *Project Properties > Issue and Debt > Annual Interest* are +// used to infer an Annual-Interest value from a Severity level. +// Annual Interest documentation: https://www.ndepend.com/docs/technical-debt#AnnualInterest +//]]> + + + Types Hot Spots +from t in JustMyCode.Types +where t.AllDebt() > Debt.Zero && + t.AllAnnualInterest() > AnnualInterest.Zero +orderby t.AllDebt().Value.TotalMinutes descending +select new { t, + Debt = t.AllDebt(), + Issues = t.AllIssues(), // AllIssues = {types issues} union {members issues} + AnnualInterest = t.AllAnnualInterest(), + BreakingPoint = t.AllBreakingPoint(), + t.NbLinesOfCode, + // t.PercentageCoverage, to uncomment if coverage data is imported + DebtRating = t.DebtRating(), + DebtRatio = t.DebtRatio() +} + +// +// This query lists **types with most Debt**, +// or in other words, types with issues that would need +// the largest effort to get fixed. +// +// Both issues on the type and its members are +// taken account. +// +// Since untested code often generates a lot of +// Debt, the type size and percentage coverage is shown +// (just uncomment *t.PercentageCoverage* in the query +// source code once you've imported the coverage data). +// +// The *Debt Rating* and *Debt Ratio* are also shown +// for informational purpose. +// +// -- +// +// The amount of *Debt* is not a measure to prioritize +// the effort to fix issues, it is an estimation of how far +// the team is from clean code that abides by the rules set. +// +// For each issue the *Annual Interest* estimates the annual +// cost to leave the issues unfixed. The *Severity* of an issue +// is estimated through thresholds from the *Annual Interest*. +// +// The **Debt Breaking Point** represents the duration +// from now when the estimated cost to leave the issue unfixed +// costs as much as the estimated effort to fix it. +// +// Hence the shorter the **Debt Breaking Point** +// the largest the **Return on Investment** for fixing +// the issue. The **Breaking Point is the right metric +// to prioritize issues fix**. +//]]> + Types to Fix Priority +from t in JustMyCode.Types +where t.AllBreakingPoint() > TimeSpan.Zero && + t.AllDebt().Value > 30.ToMinutes() +orderby t.AllBreakingPoint().TotalMinutes ascending +select new { t, + BreakingPoint = t.AllBreakingPoint(), + Debt = t.AllDebt(), + AnnualInterest = t.AllAnnualInterest(), + Issues = t.AllIssues(), + t.NbLinesOfCode, + // t.PercentageCoverage, to uncomment if coverage data is imported + DebtRating = t.DebtRating(), + DebtRatio = t.DebtRatio() +} + +// +// This query lists types per increasing +// **Debt Breaking Point**. +// +// For each issue the *Debt* estimates the +// effort to fix the issue, and the *Annual Interest* +// estimates the annual cost to leave the issue unfixed. +// The *Severity* of an issue is estimated through +// thresholds from the *Annual Interest* of the issue. +// +// The **Debt Breaking Point** represents the duration +// from now when the estimated cost to leave the issue unfixed +// costs as much as the estimated effort to fix it. +// +// Hence the shorter the **Debt Breaking Point** +// the largest the **Return on Investment** for fixing +// the issues. +// +// Often new and refactored types since baseline will be +// listed first, because issues on these types get a +// higher *Annual Interest* because it is important to +// focus first on new issues. +// +// +// -- +// +// Both issues on the type and its members are +// taken account. +// +// Only types with at least 30 minutes of Debt are listed +// to avoid parasiting the list with the numerous +// types with small *Debt*, on which the *Breaking Point* +// value makes less sense. +// +// The *Annual Interest* estimates the cost per year +// in man-days to leave these issues unfixed. +// +// Since untested code often generates a lot of +// Debt, the type size and percentage coverage is shown +// (just uncomment *t.PercentageCoverage* in the query +// source code once you've imported the coverage data). +// +// The *Debt Rating* and *Debt Ratio* are also shown +// for informational purpose. +//]]> + Issues to Fix Priority +from i in Issues +// Don't show first issues with BreakingPoint equals to zero. +orderby i.BreakingPoint != TimeSpan.Zero ? i.BreakingPoint : TimeSpan.MaxValue +select new { i, + Debt = i.Debt, + AnnualInterest = i.AnnualInterest, + BreakingPoint = i.BreakingPoint, + CodeElement = i.CodeElement +} + +// +// This query lists issues per increasing +// **Debt Breaking Point**. +// +// Double-click an issue to edit its rule and +// select the issue in the rule result. This way +// you can view all information concerning the issue. +// +// For each issue the *Debt* estimates the +// effort to fix the issue, and the *Annual Interest* +// estimates the annual cost to leave the issue unfixed. +// The *Severity* of an issue is estimated through +// thresholds from the *Annual Interest* of the issue. +// +// The **Debt Breaking Point** represents the duration +// from now when the estimated cost to leave the issue unfixed +// costs as much as the estimated effort to fix it. +// +// Hence the shorter the **Debt Breaking Point** +// the largest the **Return on Investment** for fixing +// the issue. +// +// Often issues on new and refactored code elements since +// baseline will be listed first, because such issues get a +// higher *Annual Interest* because it is important to +// focus first on new issues on recent code. +//]]> + Debt and Issues per Rule +from r in Rules +where r.IsViolated() +orderby r.Debt().Value descending +select new { + r, + Issues = r.Issues(), + Debt = r.Debt(), + AnnualInterest = r.AnnualInterest(), + BreakingPoint = r.BreakingPoint(), + Category = r.Category +} + +// +// This query lists violated rules with most *Debt* first. +// +// A rule violated has issues. For each issue the *Debt* +// estimates the effort to fix the issue. +// +// -- +// +// The amount of *Debt* is not a measure to prioritize +// the effort to fix issues, it is an estimation of how far +// the team is from clean code that abides by the rules set. +// +// For each issue the *Annual Interest* estimates the annual +// cost to leave the issues unfixed. The *Severity* of an issue +// is estimated through thresholds from the *Annual Interest*. +// +// The **Debt Breaking Point** represents the duration +// from now when the estimated cost to leave the issue unfixed +// costs as much as the estimated effort to fix it. +// +// Hence the shorter the **Debt Breaking Point** +// the largest the **Return on Investment** for fixing +// the issue. The **Breaking Point is the right metric +// to prioritize issues fix**. +// +// -- +// +// Notice that rules can be grouped in *Rule Category*. This +// way you'll see categories that generate most *Debt*. +// +// Typically the rules that generate most *Debt* are the +// ones related to *Code Coverage by Tests*, *Architecture* +// and *Code Smells*. +//]]> + New Debt and Issues per Rule +from r in Rules +where r.IsViolated() && r.IssuesAdded().Count() > 0 +orderby r.DebtDiff().Value descending +select new { + r, + IssuesAdded = r.IssuesAdded(), + IssuesFixed = r.IssuesFixed(), + Issues = r.Issues(), + Debt = r.Debt(), + DebtDiff = r.DebtDiff(), + Category = r.Category +} + +// +// This query lists violated rules that have new issues +// since baseline, with most **new Debt** first. +// +// A rule violated has issues. For each issue the *Debt* +// estimates the effort to fix the issue. +// +// -- +// +// New issues since the baseline are consequence of recent code +// refactoring sessions. They represent good opportunities +// of fix because the code recently refactored is fresh in +// the developers mind, which means fixing now costs less +// than fixing later. +// +// Fixing issues on recently touched code is also a good way +// to foster practices that will lead to higher code quality +// and maintainability, including writing unit-tests +// and avoiding unnecessary complex code. +// +// -- +// +// Notice that rules can be grouped in *Rule Category*. This +// way you'll see categories that generate most *Debt*. +// +// Typically the rules that generate most *Debt* are the +// ones related to *Code Coverage by Tests*, *Architecture* +// and *Code Smells*. +//]]> + Debt and Issues per Code Element +from elem in CodeElements +where elem.HasIssue() +orderby elem.Debt().Value descending +select new { + elem, + Issues = elem.Issues(), + Debt = elem.Debt(), + AnnualInterest = elem.AnnualInterest(), + BreakingPoint = elem.BreakingPoint() +} + +// +// This query lists code elements that have issues, +// with most *Debt* first. +// +// For each code element the *Debt* estimates +// the effort to fix the element issues. +// +// The amount of *Debt* is not a measure to prioritize +// the effort to fix issues, it is an estimation of how far +// the team is from clean code that abides by the rules set. +// +// For each element the *Annual Interest* estimates the annual +// cost to leave the elements issues unfixed. The *Severity* of an +// issue is estimated through thresholds from the *Annual Interest* +// of the issue. +// +// The **Debt Breaking Point** represents the duration +// from now when the estimated cost to leave the issues unfixed +// costs as much as the estimated effort to fix it. +// +// Hence the shorter the **Debt Breaking Point** +// the largest the **Return on Investment** for fixing +// the issue. The **Breaking Point is the right metric +// to prioritize issues fix**. +//]]> + New Debt and Issues per Code Element +from elem in CodeElements +where elem.HasIssue() && elem.IssuesAdded().Count() > 0 +orderby elem.DebtDiff().Value descending +select new { + elem, + IssuesAdded = elem.IssuesAdded(), + IssuesFixed = elem.IssuesFixed(), + Issues = elem.Issues(), + Debt = elem.Debt(), + DebtDiff = elem.DebtDiff() +} + // +// This query lists code elements that have new issues +// since baseline, with most **new Debt** first. +// +// For each code element the *Debt* estimates +// the effort to fix the element issues. +// +// New issues since the baseline are consequence of recent code +// refactoring sessions. They represent good opportunities +// of fix because the code recently refactored is fresh in +// the developers mind, which means fixing now costs less +// than fixing later. +// +// Fixing issues on recently touched code is also a good way +// to foster practices that will lead to higher code quality +// and maintainability, including writing unit-tests +// and avoiding unnecessary complex code. +// +]]> + + + Avoid types too big +warnif count > 0 from t in JustMyCode.Types where + + // First filter on type to optimize + t.NbLinesOfCode > 200 + // # IL Instructions is commented, because with LINQ syntax, a few lines of code can compile to hundreds of IL instructions. + // || t.NbILInstructions > 3000 + + // What matters is the # lines of code in JustMyCode + let locJustMyCode = t.MethodsAndContructors.Where(m => JustMyCode.Contains(m)).Sum(m => m.NbLinesOfCode) + where locJustMyCode > 200 + + let isStaticWithNoMutableState = (t.IsStatic && t.Fields.Any(f => !f.IsImmutable)) + let staticFactor = (isStaticWithNoMutableState ? 0.2 : 1) + + orderby locJustMyCode descending +select new { + t, + locJustMyCode, + t.NbILInstructions, + t.Methods, + t.Fields, + + Debt = (staticFactor*locJustMyCode.Linear(200, 1, 2000, 10)).ToHours().ToDebt(), + + // The annual interest varies linearly from interest for severity major for 300 loc + // to interest for severity critical for 2000 loc + AnnualInterest = staticFactor*(locJustMyCode.Linear( + 200, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 2000, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest() +} + +// +// This rule matches types with more than 200 lines of code. +// **Only lines of code in JustMyCode methods are taken account.** +// +// Types where *NbLinesOfCode > 200* are extremely complex +// to develop and maintain. +// See the definition of the NbLinesOfCode metric here +// https://www.ndepend.com/docs/code-metrics#NbLinesOfCode +// +// Maybe you are facing the **God Class** phenomenon: +// A **God Class** is a class that controls way too many other classes +// in the system and has grown beyond all logic to become +// *The Class That Does Everything*. +// + +// +// Types with many lines of code +// should be split in a group of smaller types. +// +// To refactor a *God Class* you'll need patience, +// and you might even need to recreate everything from scratch. +// Here are a few refactoring advices: +// +// • The logic in the *God Class* must be splitted in smaller classes. +// These smaller classes can eventually become private classes nested +// in the original *God Class*, whose instances objects become +// composed of instances of smaller nested classes. +// +// • Smaller classes partitioning should be driven by the multiple +// responsibilities handled by the *God Class*. To identify these +// responsibilities it often helps to look for subsets of methods +// strongly coupled with subsets of fields. +// +// • If the *God Class* contains way more logic than states, a good +// option can be to define one or several static classes that +// contains no static field but only pure static methods. A pure static +// method is a function that computes a result only from inputs +// parameters, it doesn't read nor assign any static or instance field. +// The main advantage of pure static methods is that they are easily +// testable. +// +// • Try to maintain the interface of the *God Class* at first +// and delegate calls to the new extracted classes. +// In the end the *God Class* should be a pure facade without its own logic. +// Then you can keep it for convenience or throw it away and +// start to use the new classes only. +// +// • Unit Tests can help: write tests for each method before extracting it +// to ensure you don't break functionality. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 1 hour for a 200 lines of code type, +// up to 10 hours for a type with 2.000 or more lines of code. +// +// In Debt and Interest computation, this rule takes account of the fact +// that static types with no mutable fields are just a collection of +// static methods that can be easily splitted and moved from one type +// to another. +//]]> + Avoid types with too many methods +warnif count > 0 from t in JustMyCode.Types + + // Optimization: Fast discard of non-relevant types + where t.Methods.Count() > 20 + + // Don't match these methods + let methods = t.Methods.Where( + m => !(m.IsGeneratedByCompiler || + m.IsConstructor || m.IsClassConstructor || + m.IsPropertyGetter || m.IsPropertySetter || + m.IsEventAdder || m.IsEventRemover)) + + where methods.Count() > 20 + orderby methods.Count() descending + + let isStaticWithNoMutableState = (t.IsStatic && t.Fields.Any(f => !f.IsImmutable)) + let staticFactor = (isStaticWithNoMutableState ? 0.2 : 1) + +select new { + t, + nbMethods = methods.Count(), + instanceMethods = methods.Where(m => !m.IsStatic), + staticMethods = methods.Where(m => m.IsStatic), + + t.NbLinesOfCode, + + Debt = (staticFactor*methods.Count().Linear(20, 1, 200, 10)).ToHours().ToDebt(), + + // The annual interest varies linearly from interest for severity major for 30 methods + // to interest for severity critical for 200 methods + AnnualInterest = (staticFactor*methods.Count().Linear( + 20, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 200, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest() +} + +// +// This rule matches types with more than 20 methods. +// Such type might be hard to understand and maintain. +// +// Notice that methods like constructors or property +// and event accessors are not taken account. +// +// Having many methods for a type might be a symptom +// of too many responsibilities implemented. +// +// Maybe you are facing the **God Class** phenomenon: +// A **God Class** is a class that controls way too many other classes +// in the system and has grown beyond all logic to become +// *The Class That Does Everything*. +// + +// +// To refactor properly a *God Class* please read *HowToFix advices* +// from the default rule **Types to Big**. +//// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 1 hour for a type with 20 methods, +// up to 10 hours for a type with 200 or more methods. +// +// In Debt and Interest computation, this rule takes account of the fact +// that static types with no mutable fields are just a collection of +// static methods that can be easily splitted and moved from one type +// to another. +//]]> + Avoid types with too many fields +warnif count > 0 from t in JustMyCode.Types + + // Optimization: Fast discard of non-relevant types + where !t.IsEnumeration && + t.Fields.Count() > 15 + + // Count instance fields and non-constant static fields + let fields = t.Fields.Where(f => + !f.IsGeneratedByCompiler && + !f.IsLiteral && + !(f.IsStatic && f.IsInitOnly) && + JustMyCode.Contains(f) ) + + where fields.Count() > 15 + + let methodsAssigningFields = fields.SelectMany(f => f.MethodsAssigningMe) + + orderby fields.Count() descending +select new { + t, + instanceFields = fields.Where(f => !f.IsStatic), + staticFields = fields.Where(f => f.IsStatic), +methodsAssigningFields , + + // See definition of Size of Instances metric here: + // https://www.ndepend.com/docs/code-metrics#SizeOfInst + t.SizeOfInst, + + Debt = fields.Count().Linear(15, 1, 200, 10).ToHours().ToDebt(), + + // The annual interest varies linearly from interest for severity major for 30 methods + // to interest for severity critical for 200 methods + AnnualInterest = fields.Count().Linear(15, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 200, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest() +} + +// +// This rule matches types with more than 15 fields. +// Such type might be hard to understand and maintain. +// +// Notice that constant fields and static-readonly fields are not counted. +// Enumerations types are not counted also. +// +// Having many fields for a type might be a symptom +// of too many responsibilities implemented. +// + +// +// To refactor such type and increase code quality and maintainability, +// certainly you'll have to group subsets of fields into smaller types +// and dispatch the logic implemented into the methods +// into these smaller types. +// +// More refactoring advices can be found in the default rule +// **Types to Big**, *HowToFix* section. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 1 hour for a type with 15 fields, +// to up to 10 hours for a type with 200 or more fields. +//]]> + Avoid methods too big, too complex +warnif count > 0 from m in JustMyCode.Methods where + m.ILNestingDepth > 2 && + (m.NbLinesOfCode > 35 || + m.CyclomaticComplexity > 20 || + m.ILCyclomaticComplexity > 60) + + let complexityScore = m.NbLinesOfCode/2 + m.CyclomaticComplexity + m.ILCyclomaticComplexity/3 + 3*m.ILNestingDepth + + orderby complexityScore descending, + m.CyclomaticComplexity descending, + m.ILCyclomaticComplexity descending, + m.ILNestingDepth descending +select new { + m, + m.NbLinesOfCode, + m.CyclomaticComplexity, + m.ILCyclomaticComplexity, + m.ILNestingDepth, + complexityScore, + + Debt = complexityScore.Linear(30, 40, 400, 8*60).ToMinutes().ToDebt(), + + // The annual interest varies linearly from interest for severity minor + // to interest for severity major + AnnualInterest = complexityScore .Linear(30, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 200, 2*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest() + +} + +// +// This rule matches methods where *ILNestingDepth* > 2 +// and (*NbLinesOfCode* > 35 +// or *CyclomaticComplexity* > 20 +// or *ILCyclomaticComplexity* > 60) +// Such method is typically hard to understand and maintain. +// +// Maybe you are facing the **God Method** phenomenon. +// A "God Method" is a method that does way too many processes in the system +// and has grown beyond all logic to become *The Method That Does Everything*. +// When need for new processes increases suddenly some programmers realize: +// why should I create a new method for each processe if I can only add an *if*. +// +// See the definition of the *CyclomaticComplexity* metric here: +// https://www.ndepend.com/docs/code-metrics#CC +// +// See the definition of the *ILCyclomaticComplexity* metric here: +// https://www.ndepend.com/docs/code-metrics#ILCC +// +// See the definition of the *ILNestingDepth* metric here: +// https://www.ndepend.com/docs/code-metrics#ILNestingDepth +// + +// +// A large and complex method should be split in smaller methods, +// or even one or several classes can be created for that. +// +// During this process it is important to question the scope of each +// variable local to the method. This can be an indication if +// such local variable will become an instance field of the newly created class(es). +// +// Large *switch…case* structures might be refactored through the help +// of a set of types that implement a common interface, the interface polymorphism +// playing the role of the *switch cases tests*. +// +// Unit Tests can help: write tests for each method before extracting it +// to ensure you don't break functionality. +// +// The estimated Debt, which means the effort to fix such issue, +// varies from 40 minutes to 8 hours, linearly from a weighted complexity score. +//]]> + Avoid methods with too many parameters +warnif count > 0 from m in JustMyCode.Methods where + m.NbParameters >= 7 + orderby m.NbParameters descending +select new { + m, + m.NbParameters, + + Debt = m.NbParameters.Linear(7, 1, 40, 6).ToHours().ToDebt(), + + // The annual interest varies linearly from interest for severity Medium for 7 parameters + // to interest for severity Critical for 40 parameters + AnnualInterest = m.NbParameters.Linear(7, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 40, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest() +} + +// +// This rule matches methods with 7 or more parameters. +// Such method is painful to call and might degrade performance. +// See the definition of the *NbParameters* metric here: +// https://www.ndepend.com/docs/code-metrics#NbParameters +// + +// +// More properties/fields can be added to the declaring type to +// handle numerous states. An alternative is to provide +// a class or a structure dedicated to handle arguments passing. +// For example see the class *System.Diagnostics.ProcessStartInfo* +// and the method *System.Diagnostics.Process.Start(ProcessStartInfo)*. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 1 hour for a method with 7 parameters, +// up to 6 hours for a methods with 40 or more parameters. +//]]> + Avoid methods with too many overloads +warnif count > 0 + +let lookup = JustMyCode.Methods.Where(m => + m.NbOverloads >= 6 && + !m.IsOperator && // Don't report operator overload + + // Don't match overloads due tu the visitor pattern, based on a naming convention. + !m.SimpleName.ToLower().StartsWithAny("visit", "dispatch") +).ToLookup(m => m.ParentType.FullName + "."+ m.SimpleName) + +from @group in lookup +let overloads = @group.ToArray() +orderby overloads.Length descending + +select new { + m = @group.First(), + overloads, + Debt = (3*overloads.Length).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Method overloading is the ability to create multiple methods of the same name +// with different implementations, and various set of parameters. +// +// This rule matches sets of methods with 6 overloads or more. +// +// Such method set might be a problem to maintain +// and provokes coupling higher than necessary. +// +// See the definition of the *NbOverloads* metric here +// https://www.ndepend.com/docs/code-metrics#NbOverloads +// + +// +// Typically the *too many overloads* phenomenon appears when an algorithm +// takes a various set of in-parameters. Each overload is presented as +// a facility to provide a various set of in-parameters. +// In such situation, the C# and VB.NET language feature named +// *Named and Optional arguments* should be used. +// +// The *too many overloads* phenomenon can also be a consequence of the usage +// of the **visitor design pattern** http://en.wikipedia.org/wiki/Visitor_pattern +// since a method named *Visit()* must be provided for each sub type. +// For this reason, the default version of this rule doesn't match overloads whose name +// start with "visit" or "dispatch" (case-unsensitive) to avoid match +// overload visitors, and you can adapt this rule to your own naming convention. +// +// Sometime *too many overloads* phenomenon is not the symptom of a problem, +// for example when a *numeric to something conversion* method applies to +// all numeric and nullable numeric types. +// +// The estimated Debt, which means the effort to fix such issue, +// is of 3 minutes per method overload. +//]]> + Avoid methods potentially poorly commented +warnif count > 0 from t in JustMyCode.Types where + // Entity Framework ModelSnapshot and DbContext and Migration have large uncommented methods. + !t.DeriveFrom("Microsoft.EntityFrameworkCore.Infrastructure.ModelSnapshot".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.EntityFrameworkCore.DbContext".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.EntityFrameworkCore.Migrations.Migration".AllowNoMatch()) + + +from m in t.Methods where + m.PercentageComment < 10 && + m.NbLinesOfCode > 20 && + JustMyCode.Contains(t) + + let nbLinesOfCodeNotCommented = m.NbLinesOfCode - m.NbLinesOfComment + + orderby nbLinesOfCodeNotCommented descending + +select new { + m, + m.PercentageComment, + m.NbLinesOfCode, + m.NbLinesOfComment, + nbLinesOfCodeNotCommented, + + Debt = nbLinesOfCodeNotCommented .Linear(20, 2, 200, 20).ToMinutes().ToDebt(), + + // The annual interest varies linearly from interest for severity major for 300 loc + // to interest for severity critical for 2000 loc + AnnualInterest = m.PercentageComment.Linear( + 0, 8 *(Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes), + 20, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest() +} + +// +// This rule matches methods with less than 10% of comment lines and that have +// at least 20 lines of code. Such method might need to be more commented. +// +// See the definitions of the *Comments metric* here: +// https://www.ndepend.com/docs/code-metrics#PercentageComment +// https://www.ndepend.com/docs/code-metrics#NbLinesOfComment +// +// Notice that only comments about the method implementation +// (comments in method body) are taken account. +// + +// +// Typically add more comment. But code commenting is subject to controversy. +// While poorly written and designed code would needs a lot of comment +// to be understood, clean code doesn't need that much comment, especially +// if variables and methods are properly named and convey enough information. +// Unit-Test code can also play the role of code commenting. +// +// However, even when writing clean and well-tested code, one will have +// to write **hacks** at a point, usually to circumvent some API limitations or bugs. +// A hack is a non-trivial piece of code, that doesn't make sense at first glance, +// and that took time and web research to be found. +// In such situation comments must absolutely be used to express the intention, +// the need for the hacks and the source where the solution has been found. +// +// The estimated Debt, which means the effort to comment such method, +// varies linearly from 2 minutes for 10 lines of code not commented, +// up to 20 minutes for 200 or more, lines of code not commented. +//]]> + Avoid types with poor cohesion +warnif count > 0 from t in JustMyCode.Types where + t.LCOM > 0.8 && + t.NbFields > 10 && + t.NbMethods >10 + + let poorCohesionScore = 1/(1.01 - t.LCOM) + orderby poorCohesionScore descending + + select new { + t, + t.LCOM, + t.NbMethods, + t.NbFields, + poorCohesionScore, + + Debt = poorCohesionScore.Linear(5, 5, 50, 4*60).ToMinutes().ToDebt(), + + // The annual interest varies linearly from interest for severity Medium for low poorCohesionScore + // to 4 times interest for severity High for high poorCohesionScore + AnnualInterest = poorCohesionScore.Linear(5, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 50, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest() + +} + +// +// This rule is based on the *LCOM code metric*, +// LCOM stands for **Lack Of Cohesion of Methods**. +// See the definition of the LCOM metric here +// https://www.ndepend.com/docs/code-metrics#LCOM +// +// The LCOM metric measures the fact that most methods are using most fields. +// A class is considered utterly cohesive (which is good) +// if all its methods use all its instance fields. +// +// Only types with enough methods and fields are taken account to avoid bias. +// The LCOM takes its values in the range [0-1]. +// +// This rule matches types with LCOM higher than 0.8. +// Such value generally pinpoints a **poorly cohesive class**. +// + +// +// To refactor a poorly cohesive type and increase code quality and maintainability, +// certainly you'll have to split the type into several smaller and more cohesive types +// that together, implement the same logic. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 5 minutes for a type with a low poorCohesionScore, +// up to 4 hours for a type with high poorCohesionScore. +//]]> + Avoid methods with too many local variables +warnif count > 0 from m in JustMyCode.Methods where + m.NbVariables > 15 + orderby m.NbVariables descending +select new { + m, + m.NbVariables, + + Debt = m.NbVariables.Linear(15, 1, 80, 6).ToHours().ToDebt(), + + // The annual interest varies linearly from interest for severity Medium for 15 variables + // to interest for severity Critical for 80 variables + AnnualInterest = m.NbVariables.Linear(15, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 80, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest() + +} + +// +// This rule matches methods with more than 15 variables. +// +// Methods where *NbVariables > 8* are hard to understand and maintain. +// Methods where *NbVariables > 15* are extremely complex and must be refactored. +// +// The number of variables is infered from the compiled IL code of the method. +// The C# and VB.NET compiler might introduce some hidden variables +// for language constructs like lambdas, so the default threshold of +// this rule is set to 15 to avoid matching false positives. +// + +// +// To refactor such method and increase code quality and maintainability, +// certainly you'll have to split the method into several smaller methods +// or even create one or several classes to implement the logic. +// +// During this process it is important to question the scope of each +// variable local to the method. This can be an indication if +// such local variable will become an instance field of the newly created class(es). +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 10 minutes for a method with 15 variables, +// up to 2 hours for a methods with 80 or more variables. +//]]> + + + From now, all types added should respect basic quality principles +warnif count > 0 from t in JustMyCode.Types where + +// Only match types added since Baseline. +// Uncomment this line to match also refactored types since Baseline. +// (t.WasAdded() || t.CodeWasChanged()) && + t.WasAdded() && + +// Eliminate interfaces, enumerations or types only with constant fields +// by making sure we are matching type with code. +t.NbLinesOfCode > 10 && + +// Optimization: Fast discard of non-relevant types +(t.Fields.Count() > 20 || t.Methods.Count() > 20) + +// Count instance fields and non-constant static fields +let fields = t.Fields.Where(f => + !f.IsLiteral && + !(f.IsStatic && f.IsInitOnly)) + +// Don't match these methods +let methods = t.Methods.Where( + m => !(m.IsConstructor || m.IsClassConstructor || + m.IsGeneratedByCompiler || + m.IsPropertyGetter || m.IsPropertySetter || + m.IsEventAdder || m.IsEventRemover)) + +where + +// Low Quality types Metrics' definitions are available here: +// https://www.ndepend.com/docs/code-metrics#MetricsOnTypes +( // Types with too many methods + fields.Count() > 20 || + + methods.Count() > 20 || + + // Complex Types that use more than 50 other types + t.NbTypesUsed > 50 +) +select new { + t, + t.NbLinesOfCode, + + instanceMethods = methods.Where(m => !m.IsStatic), + staticMethods = methods.Where(m => m.IsStatic), + + instanceFields = fields.Where(f => !f.IsStatic), + staticFields = fields.Where(f => f.IsStatic), + + t.TypesUsed, + + // Constant Debt estimation, since for such type rules in category "Code Smells" + // accurately estimate the Debt. + Debt = 10.ToMinutes().ToDebt(), + + // The Severity is higher for new types than for refactored types + AnnualInterest= (t.WasAdded() ? 3 : 1) * + Severity.High.AnnualInterestThreshold() +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// This rule operates only on types added since baseline. +// +// This rule can be easily modified to also match types refactored since baseline, +// that don't satisfy all quality criterions. +// +// Types matched by this rule not only have been recently added or refactored, +// but also somehow violate one or several basic quality principles, +// whether it has too many methods, +// it has too many fields, +// or is using too many types. +// Any of these criterions is often a symptom of a type with too many responsibilities. +// +// Notice that to count methods and fields, methods like constructors +// or property and event accessors are not taken account. +// Notice that constants fields and static-readonly fields are not counted. +// Enumerations types are not counted also. +// + +// +// To refactor such type and increase code quality and maintainability, +// certainly you'll have to split the type into several smaller types +// that together, implement the same logic. +// +// Issues of this rule have a constant 10 minutes Debt, because the Debt, +// which means the effort to fix such issue, is already estimated for issues +// of rules in the category **Code Smells**. +// +// However issues of this rule have a **High** severity, with even more +// interests for issues on new types since baseline, because the proper time +// to increase the quality of these types is **now**, before they get commited +// in the next production release. +//]]> + From now, all types added should be 100% covered by tests +warnif count > 0 from t in JustMyCode.Types where + +// Only match types added since Baseline. +// Uncomment this line to match also refactored types since Baseline. +// (t.WasAdded() || t.CodeWasChanged()) && + t.WasAdded() && + + // …that are not 100% covered by tests + t.PercentageCoverage < 100 + + let methodsCulprit = t.Methods.Where(m => m.PercentageCoverage < 100) + +select new { + t, + t.PercentageCoverage, + methodsCulprit, + t.NbLinesOfCode, + + // Constant Debt estimation, since for such type rules in category "Coverage" + // accurately estimate the untested code Debt. + Debt = 10.ToMinutes().ToDebt(), + + // The Severity is higher for new types than for refactored types + AnnualInterest= (t.WasAdded() ? 3 : 1) * + Severity.High.AnnualInterestThreshold() +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// This rule operates only on types added since baseline. +// +// This rule can be easily modified to also match types refactored since baseline, +// that are not 100% covered by tests. +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// Often covering 10% of remaining uncovered code of a class, +// requires as much work as covering the first 90%. +// For this reason, typically teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* +// which usually leads to *error prone code*. +// So it might be worth refactoring and making sure to cover the 10% remaining code +// because **most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// In this context, this rule warns when a type added or refactored since the baseline, +// is not fully covered by tests. +// + +// +// Write more unit-tests dedicated to cover code not covered yet. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +// +// You'll find code impossible to cover by unit-tests, like calls to *MessageBox.Show()*. +// An infrastructure must be defined to be able to *mock* such code at test-time. +// +// Issues of this rule have a constant 10 minutes Debt, because the Debt, +// which means the effort to write tests for the culprit type, is already +// estimated for issues in the category **Code Coverage**. +// +// However issues of this rule have a **High** severity, with even more +// interests for issues on new types since baseline, because the proper time +// to write tests for these types is **now**, before they get commited +// in the next production release. +//]]> + From now, all methods added should respect basic quality principles +warnif count > 0 from m in JustMyCode.Methods where + + // Only match methods added since Baseline. + // Uncomment this line to match also refactored methods since Baseline. + // (m.WasAdded() || m.CodeWasChanged()) && + m.WasAdded() && + + // Don't match async methods here to avoid + // false positives because of special compiler tricks. + !m.IsAsync && + +// Low Quality methods// Metrics' definitions +( m.NbLinesOfCode > 30 || // https://www.ndepend.com/docs/code-metrics#NbLinesOfCode + m.NbILInstructions > 200 || // https://www.ndepend.com/docs/code-metrics#NbILInstructions + m.CyclomaticComplexity > 20 || // https://www.ndepend.com/docs/code-metrics#CC + m.ILCyclomaticComplexity > 50 || // https://www.ndepend.com/docs/code-metrics#ILCC + m.ILNestingDepth > 4 || // https://www.ndepend.com/docs/code-metrics#ILNestingDepth + m.NbParameters > 5 || // https://www.ndepend.com/docs/code-metrics#NbParameters + m.NbVariables > 8 || // https://www.ndepend.com/docs/code-metrics#NbVariables + m.NbOverloads > 6 ) +select new { + m, + m.NbLinesOfCode, + m.NbILInstructions, + m.CyclomaticComplexity, + m.ILCyclomaticComplexity, + m.ILNestingDepth, + m.NbParameters, + m.NbVariables, + m.NbOverloads, // https://www.ndepend.com/docs/code-metrics#NbOverloads + + // Constant Debt estimation, since for such method rules in category "Code Smells" + // accurately estimate the Debt. + Debt = 5.ToMinutes().ToDebt(), + + // The Severity is higher for new methods than for refactored methods + AnnualInterest= (m.WasAdded() ? 3 : 1) * + Severity.High.AnnualInterestThreshold() +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// This rule operates only on methods added or refactored since the baseline. +// +// This rule can be easily modified to also match methods refactored since baseline, +// that don't satisfy all quality criterions. +// +// Methods matched by this rule not only have been recently added or refactored, +// but also somehow violate one or several basic quality principles, +// whether it is too large (too many *lines of code*), +// too complex (too many *if*, *switch case*, loops…) +// has too many variables, too many parameters +// or has too many overloads. +// + +// +// To refactor such method and increase code quality and maintainability, +// certainly you'll have to split the method into several smaller methods +// or even create one or several classes to implement the logic. +// +// During this process it is important to question the scope of each +// variable local to the method. This can be an indication if +// such local variable will become an instance field of the newly created class(es). +// +// Large *switch…case* structures might be refactored through the help +// of a set of types that implement a common interface, the interface polymorphism +// playing the role of the *switch cases tests*. +// +// Unit Tests can help: write tests for each method before extracting it +// to ensure you don't break functionality. +// +// Issues of this rule have a constant 5 minutes Debt, because the Debt, +// which means the effort to fix such issue, is already estimated for issues +// of rules in the category **Code Smells**. +// +// However issues of this rule have a **High** severity, with even more +// interests for issues on new methods since baseline, because the proper time +// to increase the quality of these methods is **now**, before they get commited +// in the next production release. +//]]> + Avoid decreasing code coverage by tests of types +warnif count > 0 +from t in JustMyCode.Types where + t.IsPresentInBothBuilds() && t.CoverageDataAvailable && t.OlderVersion().CoverageDataAvailable +let locDiff = (int)t.NbLinesOfCode.Value - (int)t.OlderVersion().NbLinesOfCode.Value +where locDiff >= 0 +let uncoveredLoc = (int)t.NbLinesOfCodeNotCovered.Value - ((int)t.OlderVersion().NbLinesOfCodeNotCovered.Value + locDiff) +where uncoveredLoc > 0 + +orderby uncoveredLoc descending + +select new { + t, + OldCoveragePercent = t.OlderVersion().PercentageCoverage, + NewCoveragePercent = t.PercentageCoverage, + OldLoc = t.OlderVersion().NbLinesOfCode, + NewLoc = t.NbLinesOfCode, + uncoveredLoc, + + Debt = uncoveredLoc.Linear(1, 15, 100, 3*60).ToMinutes().ToDebt(), + + // The annual interest varies linearly from interest for severity High for one line of code that is not covered by tests anymore + // to interest for severity Critical for 50 lines of code that are not covered by tests anymore + AnnualInterest = uncoveredLoc.Linear(1, Severity.High.AnnualInterestThreshold().Value.TotalMinutes, + 50, 2*Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes).ToMinutes().ToAnnualInterest() + + +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// This rule warns when the number of lines of a type covered by tests +// decreased since the baseline. In case the type faced some refactoring +// since the baseline, this loss in coverage is estimated only for types +// with more lines of code, where # lines of code covered now is lower +// than # lines of code covered in baseline + the extra number of +// lines of code. +// +// Such situation can mean that some tests have been removed +// but more often, this means that the type has been modified, +// and that changes haven't been covered properly by tests. +// +// To visualize changes in code, right-click a matched type and select: +// +// • Compare older and newer versions of source file +// +// • or Compare older and newer versions disassembled with Reflector +// + +// +// Write more unit-tests dedicated to cover changes in matched types +// not covered yet. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +// +// The estimated Debt, which means the effort to cover by test +// code that used to be covered, varies linearly 15 minutes to 3 hours, +// depending on the number of lines of code that are not covered by tests anymore. +// +// Severity of issues of this rule varies from **High** to **Critical** +// depending on the number of lines of code that are not covered by tests anymore. +// Because the loss in code coverage happened since the baseline, +// the severity is high because it is important to focus on these issues +// **now**, before such code gets released in production. +//]]> + Avoid making complex methods even more complex +warnif count > 0 + +let complexityScoreProc = new Func(m => + (m.CyclomaticComplexity + m.ILCyclomaticComplexity/3 + 5*m.ILNestingDepth).Value) + +from m in JustMyCode.Methods where + !m.IsAbstract && + m.IsPresentInBothBuilds() && + m.CodeWasChanged() && + m.OlderVersion().CyclomaticComplexity > 6 + +let complexityScore = complexityScoreProc(m) +let oldComplexityScore = complexityScoreProc(m.OlderVersion()) +where complexityScore > oldComplexityScore + +let complexityScoreDiff = complexityScoreProc(m) - complexityScoreProc(m.OlderVersion()) +orderby complexityScoreDiff descending + +select new { + m, + oldComplexityScore , + complexityScore , + diff= complexityScoreDiff, + + Debt = complexityScoreDiff.Linear(1, 15, 50, 60).ToMinutes().ToDebt(), + + // The annual interest varies linearly from interest for severity Medium for a tiny complexity increment + // to interest for severity critical for 2000 loc + AnnualInterest = complexityScoreDiff.Linear(1, Severity.High.AnnualInterestThreshold().Value.TotalMinutes, + 50, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest() + +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// The method complexity is measured through the code metric +// *Cyclomatic Complexity* defined here: +// https://www.ndepend.com/docs/code-metrics#CC +// +// This rule warns when a method already complex +// (i.e with *Cyclomatic Complexity* higher than 6) +// become even more complex since the baseline. +// +// This rule needs assemblies PDB files and source code +// to be available at analysis time, because the *Cyclomatic Complexity* +// is inferred from the source code and source code location +// is inferred from PDB files. See: +// https://www.ndepend.com/docs/ndepend-analysis-inputs-explanation +// +// To visualize changes in code, right-click a matched method and select: +// +// • Compare older and newer versions of source file +// +// • or Compare older and newer versions disassembled with Reflector +// + +// +// A large and complex method should be split in smaller methods, +// or even one or several classes can be created for that. +// +// During this process it is important to question the scope of each +// variable local to the method. This can be an indication if +// such local variable will become an instance field of the newly created class(es). +// +// Large *switch…case* structures might be refactored through the help +// of a set of types that implement a common interface, the interface polymorphism +// playing the role of the *switch cases tests*. +// +// Unit Tests can help: write tests for each method before extracting it +// to ensure you don't break functionality. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 15 to 60 minutes depending on the extra complexity added. +// +// Issues of this rule have a **High** severity, because it is important to focus +// on these issues **now**, before such code gets released in production. +//]]> + Avoid making large methods even larger + +warnif count > 0 +from m in JustMyCode.Methods where + !m.IsAbstract && + + // Eliminate constructors from match, since they get larger + // as soons as some fields initialization are added. + !m.IsConstructor && + !m.IsClassConstructor && + + // Filter just here for optimization + m.NbLinesOfCode > 15 && + + m.IsPresentInBothBuilds() && + m.CodeWasChanged() + +let oldLoc = m.OlderVersion().NbLinesOfCode +where oldLoc > 15 && m.NbLinesOfCode > oldLoc + +let diff = m.NbLinesOfCode - oldLoc +where diff > 0 +orderby diff descending + +select new { + m, + oldLoc, + newLoc = m.NbLinesOfCode, + diff, + + Debt = diff.Linear(1, 10, 100, 60).ToMinutes().ToDebt(), + + // The annual interest varies linearly from interest for severity Medium for a tiny complexity increment + // to interest for severity critical for 2000 loc + AnnualInterest = diff .Linear(1, Severity.High.AnnualInterestThreshold().Value.TotalMinutes, + 100, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest() + +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns when a method already large +// (i.e with more than 15 lines of code) +// become even larger since the baseline. +// +// The method size is measured through the code metric +// *# Lines of Code* defined here: +// https://www.ndepend.com/docs/code-metrics#NbLinesOfCode +// +// This rule needs assemblies PDB files +// to be available at analysis time, because the *# Lines of Code* +// is inferred from PDB files. See: +// https://www.ndepend.com/docs/ndepend-analysis-inputs-explanation +// +// To visualize changes in code, right-click a matched method and select: +// +// • Compare older and newer versions of source file +// +// • or Compare older and newer versions disassembled with Reflector +// + +// +// Usually too big methods should be split in smaller methods. +// +// But long methods with no branch conditions, that typically initialize some data, +// are not necessarily a problem to maintain, and might not need refactoring. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 5 to 20 minutes depending +// on the number of lines of code added. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 10 to 60 minutes depending on the extra complexity added. +// +// Issues of this rule have a **High** severity, because it is important to focus +// on these issues **now**, before such code gets released in production. +//]]> + Avoid adding methods to a type that already had many methods + +warnif count > 0 + +// Don't count constructors and methods generated by the compiler! +let getMethodsProc = new Func>( + t => t.Methods.Where(m => + !m.IsConstructor && !m.IsClassConstructor && + !m.IsGeneratedByCompiler).ToArray()) + + +from t in JustMyCode.Types where + + t.NbMethods > 30 && // Just here for optimization + + t.IsPresentInBothBuilds() + + // Optimization: fast discard of non-relevant types + where t.OlderVersion().NbMethods > 30 + + let oldMethods = getMethodsProc(t.OlderVersion()) + where oldMethods.Count > 30 + + let newMethods = getMethodsProc(t) + where newMethods.Count > oldMethods.Count + + let addedMethods = newMethods.Where(m => m.WasAdded()) + let removedMethods = oldMethods.Where(m => m.WasRemoved()) + + orderby addedMethods.Count() descending + +select new { + t, + nbOldMethods = oldMethods.Count, + nbNewMethods = newMethods.Count, + addedMethods, + removedMethods, + + Debt = (10*addedMethods.Count()).ToMinutes().ToDebt(), + AnnualInterest = addedMethods.Count().Linear( + 1, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 100, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest() +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// Types where number of methods is greater than 15 +// might be hard to understand and maintain. +// +// This rule lists types that already had more than 15 methods +// at the baseline time, and for which new methods have been added. +// +// Having many methods for a type might be a symptom +// of too many responsibilities implemented. +// +// Notice that constructors and methods generated by the compiler +// are not taken account. +// + +// +// To refactor such type and increase code quality and maintainability, +// certainly you'll have to split the type into several smaller types +// that together, implement the same logic. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 10 minutes per method added. +// +// Issues of this rule have a **High** severity, because it is important to focus +// on these issues **now**, before such code gets released in production. +//]]> + Avoid adding instance fields to a type that already had many instance fields + +warnif count > 0 + +let getFieldsProc = new Func>( + t => t.Fields.Where(f => + !f.IsLiteral && + !f.IsGeneratedByCompiler && + !f.IsStatic).ToArray()) + + +from t in JustMyCode.Types where + + !t.IsEnumeration && + t.IsPresentInBothBuilds() + + // Optimization: fast discard of non-relevant types + where t.OlderVersion().NbFields > 15 + + let oldFields = getFieldsProc(t.OlderVersion()) + where oldFields.Count > 15 + + let newFields = getFieldsProc(t) + where newFields.Count > oldFields.Count + + let addedFields = newFields.Where(f => f.WasAdded()) + let removedFields = oldFields.Where(f => f.WasRemoved()) + + orderby addedFields.Count() descending + +select new { + t, + nbOldFields = oldFields.Count, + nbNewFields = newFields.Count, + addedFields, + removedFields, + + Debt = (10*addedFields.Count()).ToMinutes().ToDebt(), + AnnualInterest = addedFields.Count().Linear( + 1, Severity.High.AnnualInterestThreshold().Value.TotalMinutes, + 100, 4*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)).ToMinutes().ToAnnualInterest() + +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// Types where number of fields is greater than 15 +// might be hard to understand and maintain. +// +// This rule lists types that already had more than 15 fields +// at the baseline time, and for which new fields have been added. +// +// Having many fields for a type might be a symptom +// of too many responsibilities implemented. +// +// Notice that *constants* fields and *static-readonly* fields are not taken account. +// Enumerations types are not taken account also. +// + +// +// To refactor such type and increase code quality and maintainability, +// certainly you'll have to group subsets of fields into smaller types +// and dispatch the logic implemented into the methods +// into these smaller types. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 10 minutes per field added. +// +// Issues of this rule have a **High** severity, because it is important to focus +// on these issues **now**, before such code gets released in production. +//]]> + Avoid transforming an immutable type into a mutable one + +warnif count > 0 +from t in Application.Types where + t.CodeWasChanged() && + t.OlderVersion().IsImmutable && + !t.IsImmutable && + // Don't take account of immutable types transformed into static types (not deemed as immutable) + !t.IsStatic + +let culpritFields = t.InstanceFields.Where(f => f.IsImmutable) +select new { + t, + culpritFields, + Debt = (10 + 10*culpritFields.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// A type is considered as *immutable* if its instance fields +// cannot be modified once an instance has been built by a constructor. +// +// Being immutable has several fortunate consequences for a type. +// For example its instance objects can be used concurrently +// from several threads without the need to synchronize accesses. +// +// Hence users of such type often rely on the fact that the type is immutable. +// If an immutable type becomes mutable, there are chances that this will break +// users code. +// +// This is why this rule warns about such immutable type that become mutable. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 2 minutes per instance field that became mutable. +// + +// +// If being immutable is an important property for a matched type, +// then the code must be refactored to preserve immutability. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 10 minutes plus 10 minutes per instance fields of +// the matched type that is now mutable. +// +// Issues of this rule have a **High** severity, because it is important to focus +// on these issues **now**, before such code gets released in production. +//]]> + + + Avoid interfaces too big +warnif count > 0 + +from i in JustMyCode.Types +where i.IsInterface && i.NbMethods >= 10 // Optimization First threshold + +// A get;set; property count as one method +let properties = i.Methods.Where(m => m.SimpleName.Length > 4 && (m.IsPropertyGetter || m.IsPropertySetter)) + .Distinct(m => m.SimpleName.Substring(4, m.SimpleName.Length -4)) + +// An event count as one method +let events = i.Methods.Where(m => (m.IsEventAdder|| m.IsEventRemover)) + .Distinct(m => m.SimpleName.Replace("add_","").Replace("remove_","")) + +let methods = i.Methods.Where(m => !m.IsPropertyGetter && !m.IsPropertySetter && !m.IsEventAdder && !m.IsEventRemover) +let methodsCount = methods.Count() + properties.Count() + events.Count() +where methodsCount >= 10 +let publicFactor = i.IsPubliclyVisible ? 1 : 0.5 +orderby methodsCount descending +select new { + i, + Methods= methods, + Properties = properties, + Events = events, + Debt = (publicFactor*methodsCount.Linear(10, 20, 100, 7*60)).ToMinutes().ToDebt(), + // The annual interest varies linearly from interest for severity Medium for an interface with 10 methods + // to interest for severity Critical for an interface with 100 methods and more + AnnualInterest = (publicFactor*methodsCount.Linear( + 10, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 100, Severity.Critical.AnnualInterestThreshold().Value.TotalMinutes)) + .ToMinutes().ToAnnualInterest() +} + + +// +// This rule matches interfaces with more than 10 methods. +// Interfaces are abstractions and are meant to simplify the code structure. +// An interface should represent a single responsibility. +// Making an interface too large, too complex, necessarily means +// that the interface has too many responsibilities. +// +// A property with getter or setter or both count as one method. +// An event count as one method. +// + +// +// Typically to fix such issue, the interface must be refactored +// in a grape of smaller *single-responsibility* interfaces. +// +// A classic example is a *ISession* large interface, responsible +// for holding states, run commands and offer various accesses +// and facilities. +// +// The classic problem for a large public interface is that it has +// many clients that consume it. As a consequence splitting it in +// smaller interfaces has an important impact and it is not always +// feasible. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from 20 minutes for an interface with 10 methods, +// up to 7 hours for an interface with 100 or more methods. +// The Debt is divided by two if the interface is not publicly +// visible, because in such situation only the current project is impacted +// by the refactoring. +// +]]> + Base class should not use derivatives +warnif count > 0 +from baseClass in JustMyCode.Types +where baseClass.IsClass && baseClass.NbChildren > 0 // <-- for optimization! +let derivedClassesUsed = baseClass.DerivedTypes.UsedBy(baseClass) + // Don't warn when a base class is using nested private derived class + .Where(derivedClass => + !(derivedClass.IsNested && + derivedClass.Visibility == Visibility.Private && + derivedClass.ParentType == baseClass)) +where derivedClassesUsed.Count() > 0 + +let derivedClassesMemberUsed = derivedClassesUsed.SelectMany(c => c.Members).UsedBy(baseClass) +orderby derivedClassesMemberUsed.Count() descending + +select new { + baseClass, + derivedClassesUsed, + derivedClassesMemberUsed, + + Debt = 3*(derivedClassesUsed.Count()+derivedClassesMemberUsed.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// In *Object-Oriented Programming*, the **open/closed principle** states: +// *software entities (components, classes, methods, etc.) should be open +// for extension, but closed for modification*. +// http://en.wikipedia.org/wiki/Open/closed_principle +// +// Hence a base class should be designed properly to make it easy to derive from, +// this is *extension*. But creating a new derived class, or modifying an +// existing one, shouldn't provoke any *modification* in the base class. +// And if a base class is using some derivative classes somehow, there +// are good chances that such *modification* will be needed. +// +// Extending the base class is not anymore a simple operation, +// this is not good design. +// +// Note that this rule doesn't warn when a base class is using a derived class +// that is nested in the base class and declared as private. In such situation +// we consider that the derived class is an encapsulated implementation +// detail of the base class. +// + +// +// Understand the need for using derivatives, +// then imagine a new design, and then refactor. +// +// Typically an algorithm in the base class needs to access something +// from derived classes. You can try to encapsulate this access behind +// an abstract or a virtual method. +// +// If you see in the base class some conditions on *typeof(DerivedClass)* +// not only *urgent refactoring* is needed. Such condition can easily +// be replaced through an abstract or a virtual method. +// +// Sometime you'll see a base class that creates instance of some derived classes. +// In such situation, certainly using the *factory method pattern* +// http://en.wikipedia.org/wiki/Factory_method_pattern +// or the *abstract factory pattern* +// http://en.wikipedia.org/wiki/Abstract_factory_pattern +// will improve the design. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 3 minutes per derived class used by the base class + +// 3 minutes per member of a derived class used by the base class. +//]]> + Class shouldn't be too deep in inheritance tree + +warnif count > 0 from t in JustMyCode.Types +where t.IsClass +let baseClasses = t.BaseClasses.ExceptThirdParty() +where baseClasses.Count() >= 3 +orderby baseClasses.Count() descending + +select new { + t, + baseClasses, + // The metric value DepthOfInheritance takes account + // of third-party base classessee its definition here: + // https://www.ndepend.com/docs/code-metrics#DIT + t.DepthOfInheritance, + Debt = (baseClasses.Count() -2)*3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about classes having 3 or more base classes. +// Notice that third-party base classes are not counted +// because this rule is about your code design, not +// third-party libraries consumed design. +// +// *In theory*, there is nothing wrong having a *long inheritance chain*, +// if the modelization has been well thought out, +// if each base class is a well-designed refinement of the domain. +// +// *In practice*, modeling properly a domain demands a lot of effort +// and experience and more often than not, a *long inheritance chain* +// is a sign of confused design, that will be hard to work with and maintain. +// + +// +// In *Object-Oriented Programming*, a well-known motto is +// **Favor Composition over Inheritance**. +// +// This is because *inheritance* comes with pitfalls. +// In general, the implementation of a derived class is very bound up with +// the base class implementation. Also a base class exposes implementation +// details to its derived classes, that's why it's often said that +// inheritance breaks encapsulation. +// +// On the other hands, *Composition* favors binding with interfaces +// over binding with implementations. Hence, not only the encapsulation +// is preserved, but the design is clearer, because interfaces make it explicit +// and less coupled. +// +// Hence, to break a *long inheritance chain*, *Composition* is often +// a powerful way to enhance the design of the refactored underlying logic. +// +// You can also read: +// http://en.wikipedia.org/wiki/Composition_over_inheritance and +// http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance +// +// The estimated Debt, which means the effort to fix such issue, +// depends linearly upon the depth of inheritance. +//]]> + Class with no descendant should be sealed if possible +warnif count > 0 from t in JustMyCode.Types where + t.IsClass && + t.NbChildren ==0 && + !t.IsSealed && + !t.IsStatic && + !t.IsPubliclyVisible // You might want to comment this condition + // if you are developing an application, + // instead of developing a library + // with public classes that are intended to be + // sub-classed by your clients. + orderby t.NbLinesOfCode descending +select new { + t, + t.NbLinesOfCode, + Debt = 30.ToSeconds().ToDebt(), + Severity = Severity.Medium +} + +// +// If a *non-static* class isn't declared with the keyword *sealed*, +// it means that it can be subclassed everywhere the *non-sealed* +// class is visible. +// +// Making a class a *base class* requires significant design effort. +// Subclassing a *non-sealed* class, not initially designed +// to be subclassed, will lead to unanticipated design issue. +// +// Most classes are *non-sealed* because developers don't care about +// the keyword *sealed*, not because the primary intention was to write +// a class that can be subclassed. +// +// There are minor performance gain in declaring a class as *sealed*. +// But the real benefit of doing so, is actually to **express the +// intention**: *this class has not be designed to be a base class, +// hence it is not allowed to subclass it*. +// +// Notice that by default this rule doesn't match *public* class +// to avoid matching classes that are intended to be sub-classed by +// third-party code using your library. +// If you are developing an application and not a library, +// just uncomment the clause *!t.IsPubliclyVisible*. +// + +// +// For each matched class, take the time to assess if it is really +// meant to be subclassed. Certainly most matched class will end up +// being declared as *sealed*. +//]]> + Overrides of Method() should call base.Method() +warnif count > 0 +from t in Types // Take account of third-party base classes also + +// Bother only classes with descendant +where t.IsClass && t.NbChildren > 0 + +from mBase in t.InstanceMethods +where mBase.IsVirtual && + !mBase.IsThirdParty && + !mBase.IsAbstract && + !mBase.IsExplicitInterfaceImpl && + !mBase.IsPropertyGetter && + !mBase.IsPropertySetter && + !mBase.IsIndexerGetter && + !mBase.IsIndexerSetter +from mOverride in mBase.OverridesDirectDerived +where !mOverride.IsUsing(mBase) && + JustMyCode.Contains(mOverride) // Don't warn on generated code +select new { + mOverride, + shouldCall = mBase, + definedInBaseClass = mBase.ParentType, + + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Typically overrides of a base method, should **refine** or **complete** +// the behavior of the base method. If the base method is not called, +// the base behavior is not refined but it is *replaced*. +// +// Violations of this rule are a sign of *design flaw*, +// especially if the actual design provides valid reasons +// that advocates that the base behavior must be replaced and not refined. +// + +// +// You should investigate if *inheritance* is the right choice +// to bind the base class implementation with the derived classes +// implementations. Does presenting the method with polymorphic +// behavior through an interface, would be a better design choice? +// +// In such situation, often using the design pattern **template method** +// http://en.wikipedia.org/wiki/Template_method_pattern might help +// improving the design. +//]]> + Do not hide base class methods +warnif count > 0 + +// Define a lookup table indexing methods by their name including parameters signature. +let lookup = Methods.Where(m => !m.IsConstructor && !m.IsStatic && !m.IsGeneratedByCompiler) + .ToLookup(m1 => m1.Name) + +from t in Application.Types +where !t.IsStatic && t.IsClass && + // Discard classes deriving directly from System.Object + t.DepthOfInheritance > 1 +where t.BaseClasses.Any() + +// For each methods not overriding any methods (new slot), +// let's check if it hides by name some methods defined in base classes. +from m in t.InstanceMethods +where m.IsNewSlot && !m.IsExplicitInterfaceImpl && !m.IsGeneratedByCompiler + +// Notice how lookup is used to quickly retrieve methods with same name as m. +// This makes the query 10 times faster than iterating each base methods to check their name. +let baseMethodsHidden = lookup[m.Name].Where(m1 => m1 != m && t.DeriveFrom(m1.ParentType)) + +where baseMethodsHidden.Count() > 0 +select new { + m, + baseMethodsHidden, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// Method hiding is when a base class has a non-virtual method *M()*, +// and a derived class has also a method *M()* with the same signature. +// In such situation, calling *base.M()* does something different +// than calling *derived.M()*. +// +// Notice that this is not *polymorphic* behavior. With *polymorphic* +// behavior, calling both *base.M()* and *derived.M()* on an instance +// object of *derived*, invoke the same implementation. +// +// This situation should be avoided because it obviously leads to confusion. +// This rule warns about all method hiding cases in the code base. +// + +// +// To fix a violation of this rule, remove or rename the method, +// or change the parameter signature so that the method does +// not hide the base method. +// +// However *method hiding is for those times when you need to have two +// things to have the same name but different behavior*. This is a very +// rare situations, described here: +// http://blogs.msdn.com/b/ericlippert/archive/2008/05/21/method-hiding-apologia.aspx +//]]> + A stateless class or structure might be turned into a static type + +warnif count > 0 + +let testAttributes = ThirdParty.Types + .Where(t => t.IsAttributeClass && t.SimpleName.Contains("Test")).ToArray() + +from t in JustMyCode.Types where + !t.IsStatic && + !t.IsGeneric && + t.InstanceFields.Count() == 0 && + t.SimpleName != "Program" && // Don't warn on Program classes generated by designers + + // Don't match: + // --> types that implement some interfaces. + t.NbInterfacesImplemented == 0 && + + // --> or classes that have sub-classes children. + t.NbChildren == 0 && + + // --> or classes that have a base class + ((t.IsClass && t.DepthOfDeriveFrom("System.Object".AllowNoMatch()) == 1) || + t.IsStructure) && + + // Don't match test classes + !testAttributes.Any(tAttr => t.HasAttribute(tAttr)) + +let methodsUsingMe = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t)) + +select new { + t, + methodsUsingMe, + Debt = (1 + methodsUsingMe.Count()).ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule matches classes and structures that are not static, nor generic, +// that doesn't have any instance fields, that doesn't implement any interface +// nor has a base class (different than *System.Object*). +// +// Such class or structure is a *stateless* collection of *pure* functions, +// that doesn't act on any *this* object data. Such collection of *pure* functions +// is better hosted in a **static class**. Doing so simplifies the client code +// that doesn't have to create an object anymore to invoke the *pure* functions. +// + +// +// Declare all methods as *static* and transform the class or structure +// into a *static* class. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Non-static classes should be instantiated or turned to static +warnif count > 0 + +let testAttributes = Types + .Where(t => t.IsAttributeClass && t.SimpleName.Contains("Test")).ToArray() + +from t in JustMyCode.Types +where t.IsClass && + //!t.IsPublic && // if you are developing a framework, + // you might not want to match public classes + !t.IsStatic && + !t.IsAbstract && + !t.IsAttributeClass && // Attributes class are never seen as instantiated + t.SimpleName != "Program" && // Don't warn on Program classes generated by designers + + // Don't suggest to turn to static, classes that implement interfaces + t.InterfacesImplemented.Count() == 0 && + + !t.DeriveFrom("System.MarshalByRefObject".AllowNoMatch()) && // Types instantiated through remoting infrastructure + + // XML serialized type might never be seen as instantiated. + !t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + + // Serialized type might never be seen as instantiated. + !t.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) && + !t.IsUsing("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) && + + // ASP.NET Core ViewModel and Repository + !t.SimpleName.EndsWithAny("Model","Repository") && + !t.IsUsing("System.ComponentModel.DataAnnotations".AllowNoMatch().MatchNamespace()) && + + // ASP.NET Classes that are instantiated by the ASP.NET infrastructure. + !t.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWithAny("System.Web", "Microsoft.AspNetCore")) && + !(t.Constructors.Count() == 1 && t.Constructors.Single().Name.Contains("(IHostingEnvironment)")) && + + // Entity Framework ModelSnapshot and DbContext and Migration + !t.DeriveFrom("Microsoft.EntityFrameworkCore.Infrastructure.ModelSnapshot".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.EntityFrameworkCore.DbContext".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.EntityFrameworkCore.Migrations.Migration".AllowNoMatch()) && + + // Don't match test classes + !testAttributes.Any(tAttr => t.HasAttribute(tAttr)) + +// find the first constructor of t called +let ctorCalled = t.Constructors.FirstOrDefault(ctor => ctor.NbMethodsCallingMe > 0) + +// match t if none of its constructors is called. +where ctorCalled == null +select new { + t, + t.Visibility, + Debt = 2.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// Notice that classes only instantiated through reflection, like plug-in root classes +// are matched by this rules. + +// +// If the constructors of a class are never called, the class is +// never instantiated, and should be defined as a *static class*. +// +// However this rule doesn't match instantiation through reflection. +// As a consequence, plug-in root classes, instantiated through reflection +// via *IoC frameworks*, can be *false positives* for this rule. +// +// This rule doesn't match also classes instantiated by the ASP.NET +// infrastructure, ASP.NET view model classes +// and Entity Framework ModelSnapshot, DbContext and Migration classes. +// +// Notice that by default this rule matches also *public* class. +// If you are developing a framework with classes that are intended +// to be instantiated by your clients, just uncomment the line +// *!t.IsPublic*. +// + +// +// First it is important to investigate why the class is never instantiated. +// If the reason is *the class hosts only static methods* then the class +// can be safely declared as *static*. +// +// Others reasons like, *the class is meant to be instantiated via reflection*, +// or *is meant to be instantiated only by client code* should lead to +// adapt this rule code to avoid these matches. +//]]> + Methods should be declared static if possible +warnif count > 0 + +let testAttributes = ThirdParty.Types + .Where(t => t.IsAttributeClass && + ( t.SimpleName.Contains("Test") || + t.SimpleName.Contains("Fact") || + t.SimpleName.Contains("SetUp") || + t.SimpleName.Contains("TearDown") ) + ).ToArray() + +from t in JustMyCode.Types.Where(t => + !t.IsStatic && !t.IsInterface && + !t.IsEnumeration && !t.IsDelegate && + !t.IsGeneratedByCompiler && + // Don't advise to declare Global ASP.NET or ApiController methods as static + !t.DeriveFrom("System.Web.HttpApplication".AllowNoMatch()) && + !t.DeriveFrom("System.Web.Http.ApiController".AllowNoMatch()) && + !t.DeriveFrom("Microsoft.AspNetCore.Mvc.Controller".AllowNoMatch())) + +let methodsThatCanBeMadeStatic = + from m in t.InstanceMethods + + // An instance method can be turned to static if it is not virtual, + // not using the this reference and also, not using + // any of its class or base classes instance fields or instance methods. + where !m.IsAbstract && !m.IsVirtual && + !m.AccessThis && !m.IsExplicitInterfaceImpl && + !m.IsProtected && // Protected method access doesn't match well with static methods + + // Don't warn about not yet implemented methods. + !m.CreateA("System.NotImplementedException".AllowNoMatch()) && + + // Optimization: Using FirstOrDefault() avoid to check all members, + // as soon as one member is found + // we know the method m cannot be made static. + m.MembersUsed.FirstOrDefault( + mUsed => !mUsed.IsStatic && + (mUsed.ParentType == t || + t.DeriveFrom(mUsed.ParentType)) + ) == null + + // Don't match test methods + && !testAttributes.Any(tAttr => m.HasAttribute(tAttr)) + select m + +from m in methodsThatCanBeMadeStatic +let staticFieldsUsed = m.ParentType.StaticFields.UsedBy(m).Where(f => !f.IsGeneratedByCompiler) +let methodsCallingMe = m.MethodsCallingMe + +// All callers of the method must be in JustMyCode, +// else having a method declared as static would break the call from the code generated +// like when a WPF Connect() method is binding a method to an event. +where methodsCallingMe.All(m1 => JustMyCode.Contains(m1)) + +select new { + m, + staticFieldsUsed, + methodsCallingMe, + Debt = (1 + methodsCallingMe.Count())*30.ToSeconds().ToDebt(), + Severity = Severity.Medium +} + +// +// When an instance method can be *safely* declared as static you should declare it as static. +// +// Whenever you write a method, you fulfill a contract in a given scope. +// The narrower the scope is, the smaller the chance is that you write a bug. +// +// When a method is static, you can't access non-static members; hence, your scope is +// narrower. So, if you don't need and will never need (even in subclasses) instance +// fields to fulfill your contract, why give access to these fields to your method? +// Declaring the method static in this case will let the compiler check that you +// don't use members that you do not intend to use. +// +// Declaring a method as static if possible is also good practice because clients can +// tell from the method signature that calling the method can't alter the object's state. +// +// Doing so, is also a micro performance optimization, since a static method is a +// bit cheaper to invoke than an instance method, because the *this* reference* +// doesn't need anymore to be passed. +// +// Notice that if a matched method is a handler, bound to an event through code +// generated by a designer, declaring it as static might break the designer +// generated code, if the generated code use the *this* invocation syntax, +// (like *this.Method()*). +// + +// +// Declare matched methods as static. +// +// Since such method doesn't use any instance fields and methods of its type and +// base-types, you should consider if it makes sense, to move such a method +// to a static utility class. +//]]> + Constructor should not call a virtual method +warnif count > 0 + +from t in JustMyCode.Types where + t.IsClass && + !t.IsGeneratedByCompiler && + !t.IsSealed + +from ctor in t.Constructors +let virtualMethodsCalled = + from mCalled in ctor.MethodsCalled + where mCalled.IsVirtual && !mCalled.IsFinal && + // Only take care of just-my-code virtual methods called + JustMyCode.Contains(mCalled) && + ( mCalled.ParentType == t || + (t.DeriveFrom(mCalled.ParentType) && + // Don't accept Object methods since they can be called + // from another reference than the 'this' reference. + mCalled.ParentType.FullName != "System.Object") + ) + select mCalled +where virtualMethodsCalled.Count() > 0 + +select new { + ctor , + virtualMethodsCalled, + // If there is no derived type, it might be + // an opportunity to mark t as sealed. + t.DerivedTypes, + Debt = ((virtualMethodsCalled.Count())*6).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule matches constructors of a non-sealed class that call one or +// several virtual methods. +// +// When an object written in C# is constructed, what happens is that constructors +// run in order from the base class to the most derived class. +// +// Also objects do not change type as they are constructed, but start out as +// the most derived type, with the method table being for the most derived type. +// This means that virtual method calls always run on the most derived type, +// even when calls are made from the constructor. +// +// When you combine these two facts you are left with the problem that if you +// make a virtual method call in a constructor, and it is not the most derived +// type in its inheritance hierarchy, then it will be called on a class whose +// constructor has not been run, and therefore may not be in a suitable state +// to have that method called. +// +// Hence this situation makes the class *fragile to derive from*. +// + +// +// Violations reported can be solved by re-designing object initialisation +// or by declaring the parent class as *sealed*, if possible. +// +]]> + Avoid the Singleton pattern +warnif count > 0 +from t in Application.Types +where !t.IsStatic && !t.IsAbstract && (t.IsClass || t.IsStructure) + +// All ctors of a singleton are private +where t.Constructors.Where(ctor => !ctor.IsPrivate).Count() == 0 + +// A singleton contains one or several static fields of its parent type, +// or of an interface implented by its parent type, +// to reference the unique instance +let staticFieldInstances = t.StaticFields.WithFieldTypeIn(t.InterfacesImplemented.Concat(t)) +where staticFieldInstances.Count() == 1 + +let staticFieldInstance = staticFieldInstances.Single() +let methodsUsingField = staticFieldInstance.MethodsUsingMe +let methodsUsingField2 = methodsUsingField.Concat(methodsUsingField.SelectMany(m => m.MethodsCallingMe)) + +select new { + t, + staticFieldInstance, + methodsUsingField2, + Debt = (3*methodsUsingField2.Count()).ToMinutes().ToDebt(), + AnnualInterest = (10+methodsUsingField2.Count()).ToMinutes().ToAnnualInterest() +} + +// +// The *singleton pattern* consists in enforcing that a class has just +// a single instance: http://en.wikipedia.org/wiki/Singleton_pattern +// At first glance, this pattern looks appealing, it is simple to implement, +// it adresses a common situation, and as a consequence it is widely used. +// +// However, we discourage you from using singleton classes because experience +// shows that **singleton often results in less testable and less maintainable code**. +// Singleton is *by-design*, not testable. Each unit test should use their own objects +// while singleton forces multiple unit-tests to use the same instance object. +// +// Also the singleton static *GetInstance()* method allows *magic* access to that +// single object and its state from wherever developers want! This potentially +// attractive facility unfortunatly ends up into *unorganized*/*messy* code that +// will require effort to be refactored. +// +// More details available in these discussions: +// http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/ +// http://adamschepis.com/blog/2011/05/02/im-adam-and-im-a-recovering-singleton-addict/ +// + +// +// This rule matches *the classic syntax of singletons*, where one +// static field hold the single instance of the parent class. We underline that +// *the problem is this particular syntax*, that plays against testability. +// The problem is not the fact that a single instance of the class lives +// at runtime. +// +// Hence to fix matches fo this rule, creates the single instance +// at the startup of the program, and pass it to all classes and methods +// that need to access it. +// +// If multiple singletons are identified, they actually form together a +// *program execution context*. Such context can be unified in a unique +// singleton context. Doing so will make it easier to propagate the +// context across the various program units. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 3 minutes per method relying on the singleton. +// It is not rare that hundreds of methods rely on the singleton +// and that it takes hours to get rid of a singleton, refactoring +// the way just explained above. +// +// The severity of each singleton issue is **Critical** because as +// explained, using a the singleton pattern can really prevent the +// whole program to be testable. +//]]> + Don't assign static fields from instance methods + +warnif count > 0 +from f in Application.Fields where + f.IsStatic && + !f.IsLiteral && + !f.IsInitOnly && + !f.IsGeneratedByCompiler && + // Contract API define such a insideContractEvaluation static field + f.Name != "insideContractEvaluation" +let assignedBy = f.MethodsAssigningMe.Where(m => !m.IsStatic) +where assignedBy .Count() > 0 +select new { + f, + assignedBy, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Assigning static fields from instance methods leads to +// poorly maintainable and non-thread-safe code. +// +// More discussion on the topic can be found here: +// http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/ +// + +// +// If the *static* field is just assigned once in the program +// lifetime, make sure to declare it as *readonly* and assign +// it inline, or from the static constructor. +// +// In *Object-Oriented-Programming* the natural artifact +// to hold states that can be modified is **instance fields**. +// +// Hence to fix violations of this rule, make sure to +// hold assignable states through *instance* fields, not +// through *static* fields. +//]]> + Avoid empty interfaces +warnif count > 0 from t in JustMyCode.Types where + t.IsInterface && + t.NbMethods == 0 && + !t.InterfacesImplemented.Any() +select new { + t, + t.TypesThatImplementMe, + Debt = (10 + 3*t.TypesThatImplementMe.Count()).ToMinutes().ToDebt(), + Severity = t.TypesThatImplementMe.Any() ? Severity.Medium : Severity.Low +} + +// +// Interfaces define members that provide a behavior or usage contract. +// The functionality that is described by the interface +// can be adopted by any type, regardless of where the type +// appears in the inheritance hierarchy. +// A type implements an interface by providing implementations +// for the members of the interface. +// An empty interface does not define any members. +// Therefore, it does not define a contract that can be implemented. +// +// If your design includes empty interfaces that types +// are expected to implement, you are probably using an interface +// as a marker or a way to identify a group of types. +// If this identification will occur at run time, +// the correct way to accomplish this is to use a custom attribute. +// Use the presence or absence of the attribute, +// or the properties of the attribute, to identify the target types. +// If the identification must occur at compile time, +// then it is acceptable to use an empty interface. +// +// Note that if an interface is empty but implements at least one +// other interface, it won't be matched by this rule. +// Such interface can be considered as not empty, +// since implementing it means that sub-interfaces members +// must be implemented. +// + +// +// Remove the interface or add members to it. +// If the empty interface is being used to label a set of types, +// replace the interface with a custom attribute. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 10 minutes to discard an empty interface plus +// 3 minutes per type implementing an empty interface. +//]]> + Avoid types initialization cycles +warnif count > 0 + +// Types initialization cycle can only happen between types of an assembly. +from assembly in Application.Assemblies + +let cctorSuspects = assembly.ChildMethods.Where( + m => m.IsClassConstructor && + // Optimization: types involved in a type cycle necessarily don't have type level. + m.ParentType.Level == null) + +where cctorSuspects.Count() > 1 +let typesSuspects = cctorSuspects.ParentTypes().ToHashSet() + +// +// dicoTmp associates to each type suspect T, a set of types from typesSuspects +// that contains at least a method or a field used directly or indirectly by the cctor of T. +// +let dicoTmp = cctorSuspects.ToDictionary( + cctor => cctor.ParentType, + cctor => ((IMember)cctor).ToEnumerable().FillIterative( + members => from m in members + from mUsed in (m is IMethod) ? (m as IMethod).MembersUsed : new IMember[0] + where mUsed.ParentAssembly == assembly + select mUsed) + .DefinitionDomain + .Select(m => m.ParentType) // Don't need .Distinct() here, because of ToHashSet() below. + .Except(cctor.ParentType) + .Intersect(typesSuspects) + .ToHashSet() +) + +// +// dico associates to each type suspect T, the set of types initialized (directly or indirectly) +// by the initialization of T. This second step is needed, because if a cctor of a type T1 +// calls a member of a type T2, not only the cctor of T1 triggers the initialization of T2, +// but also it triggers the initialization of all types that are initialized by T2 initialization. +// +let dico = typesSuspects.Where(t => dicoTmp[t].Count() > 0).ToDictionary( + typeSuspect => typeSuspect, + typeSuspect => typeSuspect.ToEnumerable().FillIterative( + types => from t in types + from tUsed in dicoTmp[t] + select tUsed) + .DefinitionDomain + .Except(typeSuspect) + .ToHashSet() +) + + +// +// Now that dico is prepared, detect the cctor cycles +// +from t in dico.Keys + + // Thanks to the work done to build dico, it is now pretty easy + // to spot types involved in an initialization cyle with t! + let usersAndUseds = from tTmp in dico[t] + where dico.ContainsKey(tTmp) && dico[tTmp].Contains(t) + select tTmp + where usersAndUseds.Count() > 0 + + // Here we've found type(s) both using and used by the suspect type. + // A cycle involving the type t is found! + // v2017.3.2: don't call Append() as an extension method else ambiguous syntax error + // with the new extension method in .NET Fx v4.7.1 / .NET Standard 2.0: System.Linq.Enumerable.Append() + let typeInitCycle = ExtensionMethodsEnumerable.Append(usersAndUseds,t) + + + // Compute methodsCalled and fieldsUsed, useful to explore + // how a cctor involved in a type initialization cycle, triggers other type initialization. + let methodsCalledDepth = assembly.ChildMethods.DepthOfIsUsedBy(t.ClassConstructor) + let fieldsUsedDepth = assembly.ChildFields.DepthOfIsUsedBy(t.ClassConstructor) + + let methodsCalled = methodsCalledDepth.DefinitionDomain.OrderBy(m => methodsCalledDepth[m]).ToArray() + let fieldsUsed = fieldsUsedDepth.DefinitionDomain.OrderBy(f => fieldsUsedDepth[f]).ToArray() + +// Use the tick box to: Group cctors methods By parent types +select new { + t.ClassConstructor, + cctorsCycle= typeInitCycle.Select(tTmp => tTmp.ClassConstructor), + + // methodsCalled and fieldsUsed are members used directly and indirectly by the cctor. + // Export these members to the dependency graph (right click the cell Export/Append … to the Graph) + // and see how the cctor trigger the initialization of other types + methodsCalled, + fieldsUsed, + Debt = (20+10*typeInitCycle.Count()).ToMinutes().ToDebt(), + Severity = Severity.Critical +} + +// +// The *class constructor* (also called *static constructor*, and named *cctor* in IL code) +// of a type, if any, is executed by the CLR at runtime, the first time the type is used. +// A *cctor* doesn't need to be explicitly declared in C# or VB.NET, to exist in compiled IL code. +// Having a static field inline initialization is enough to have +// the *cctor* implicitly declared in the parent class or structure. +// +// If the *cctor* of a type *t1* is using the type *t2* and if the *cctor* of *t2* is using *t1*, +// some type initialization unexpected and hard-to-diagnose buggy behavior can occur. +// Such a cyclic chain of initialization is not necessarily limited to two types +// and can embrace *N* types in the general case. +// More information on types initialization cycles can be found here: +// http://codeblog.jonskeet.uk/2012/04/07/type-initializer-circular-dependencies/ +// +// The present code rule enumerates types initialization cycles. +// Some *false positives* can appear if some lambda expressions are defined +// in *cctors* or in methods called by *cctors*. In such situation, this rule +// considers these lambda expressions as executed at type initialization time, +// while it is not necessarily the case. +// + +// +// Types initialization cycles create confusion and unexpected behaviors. +// If several states hold by several classes must be initialized during the first +// access of any of those classes, a better design option is to create a dedicated +// class whose responsibility is to initialize and hold all these states. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 20 minutes per cycle plus 10 minutes per type class constructor +// involved in the cycle. +//]]> + + + Avoid custom delegates +warnif count > 0 +from t in JustMyCode.Types where t.IsDelegate + +let invokeMethod = (from m in t.Methods where m.SimpleName == "Invoke" select m).Single() +let signature1 = invokeMethod.Name.Substring( + invokeMethod.SimpleName.Length, + invokeMethod.Name.Length - invokeMethod.SimpleName.Length) + +// 'ref' and 'out' parameters cannot be supported +where !signature1.Contains("&") + +let signature2 = signature1.Replace("(","<").Replace(")",">") +let signature3 = signature2 == "<>" ? "" : signature2 +let resultTypeName = invokeMethod.ReturnType == null ? "????" : + invokeMethod.ReturnType.FullName == "System.Void" ? "" : + invokeMethod.ReturnType.Name +let replaceWith = + resultTypeName == "Boolean" && invokeMethod.NbParameters == 1 ? + "Predicate" + signature3 : resultTypeName == "" ? + "Action" + signature3 : invokeMethod.NbParameters ==0 ? + "Func<" + resultTypeName + ">" : + "Func" + signature3.Replace(">", "," + resultTypeName + ">") + +let methodsUser = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t)) + +select new { + t, + replaceWith, + methodsUser, + Debt = (5 + 3*methodsUser.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// Generic delegates sould be preferred over custom delegates. +// Generic delegates are: +// +// • *Action<…>* to represent any method with *void* return type. +// +// • *Func<…>* to represent any method with a return type. The last +// generic argument is the return type of the prototyped methods. +// +// • *Predicate* to represent any method that takes an instance +// of *T* and that returns a *boolean*. +// +// • Expression<…> that represents function definitions that can be +// compiled and subsequently invoked at runtime but can also be +// serialized and passed to remote processes. +// +// Thanks to generic delegates, not only the code using these custom +// delegates will become clearer, but you'll be relieved from the +// maintenance of these delegate types. +// +// Notice that delegates that are consumed by *DllImport* extern methods +// must not be converted, else this could provoke marshalling issues. +// + +// +// Remove custom delegates and replace them with generic +// delegates shown in the **replaceWith** column. +// +// The estimated Debt, which means the effort to fix such issue, +// is 5 minutes per custom delegates plus 3 minutes per method +// using such custom delegate. +//]]> + Types with disposable instance fields must be disposable + +warnif count > 0 + +// Several IDisposable types can be found if several .NET profiles are referenced. +let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable") +where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable + +from t in Application.Types.Except( + Application.Types.ThatImplementAny(iDisposables) + // Don't match ASP.NET types like Page, MasterPage or Control + .Union(Application.Types.Where(t => t.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWith("System.Web.UI"))))) +where !t.IsGeneratedByCompiler + +let instanceFieldsDisposable = + t.InstanceFields.Where(f => f.FieldType != null && + f.FieldType.InterfacesImplemented.Intersect(iDisposables).Any()) + +where instanceFieldsDisposable.Any() + +// Don't warn for types that implement the dispose pattern with a Dispose(bool) method. +where t.Methods.FirstOrDefault(m => m.Name == "Dispose(Boolean)") == null + +select new { + t, + instanceFieldsDisposable, + Debt = (5 + 2*instanceFieldsDisposable.Count()).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns when a class declares and implements an instance field that +// is a *System.IDisposable* type and the class does not implement *IDisposable*. +// +// A class implements the *IDisposable* interface to dispose of unmanaged resources +// that it owns. An instance field that is an *IDisposable* type indicates that +// the field owns an unmanaged resource. A class that declares an *IDisposable* +// field indirectly owns an unmanaged resource and should implement the +// *IDisposable* interface. If the class does not directly own any unmanaged +// resources, it should not implement a finalizer. +// +// This rules might report false positive in case the lifetime of the disposable +// objects referenced, is longer than the lifetime of the object that hold the +// disposable references. +// + +// +// To fix a violation of this rule, implement *IDisposable* and from the +// *IDisposable.Dispose()* method call the *Dispose()* method of the field(s). +// +// The estimated Debt, which means the effort to fix such issue, +// is 5 minutes per type matched plus 3 minutes per disposable instance field. +//]]> + Disposable types with unmanaged resources should declare finalizer + +// This default rule is disabled by default, +// see in the rule description (below) why. +// warnif count > 0 + +// Several IDisposable type can be found if several .NET Fx are referenced. +let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable") +where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable + +let disposableTypes = Application.Types.ThatImplementAny(iDisposables) +let unmanagedResourcesFields = disposableTypes.ChildFields().Where(f => + !f.IsStatic && + f.FieldType != null && + f.FieldType.FullName.EqualsAny( + "System.IntPtr", + "System.UIntPtr", + "System.Runtime.InteropServices.HandleRef")).ToHashSet() +let disposableTypesWithUnmanagedResource = unmanagedResourcesFields.ParentTypes() + +from t in disposableTypesWithUnmanagedResource +where !t.HasFinalizer +let unmanagedResourcesTypeFields = unmanagedResourcesFields.Intersect(t.InstanceFields) +select new { + t, + unmanagedResourcesTypeFields, + //Debt = 10.ToMinutes().ToDebt(), + //Severity = Severity.Critical +} + +// +//A type that implements *System.IDisposable*, +//and has fields that suggest the use of unmanaged resources, +//does not implement a finalizer as described by *Object.Finalize()*. +//A violation of this rule is reported +//if the disposable type contains fields of the following types: +// +// • *System.IntPtr* +// +// • *System.UIntPtr* +// +// • *System.Runtime.InteropServices.HandleRef* +// +// Notice that this default rule is disabled by default, +// because it typically reports *false positive* for classes +// that just hold some references to managed resources, +// without the responsibility to dispose them. +// +// To enable this rule just uncomment *warnif count > 0*. +// + +// +//To fix a violation of this rule, +//implement a finalizer that calls your *Dispose()* method. +//]]> + Methods that create disposable object(s) and that don't call Dispose() + +// Uncomment this to transform this code query into a code rule. +// warnif count > 0 + +// Several IDisposable types can be found if several .NET Fx are referenced. +let iDisposables = ThirdParty.Types.WithFullName("System.IDisposable") +where iDisposables.Any() // in case the code base doesn't use at all System.IDisposable + +// Build sequences of disposableTypes and disposeMethods +let disposableTypes = Types.ThatImplementAny(iDisposables).Concat(iDisposables) +let disposeMethods = disposableTypes.ChildMethods().WithName("Dispose()").ToHashSet() + + +// -> You can refine this code query by assigning to disposableTypesToLookAfter something like: +// disposableTypes.WithFullNameIn("Namespace.TypeName1", "Namespace.TypeName2", ...) +let disposableTypesToLookAfter = disposableTypes + + +// -> You can refine this code query by assigning to methodsToLookAfter something like: +// Application.Assemblies.WithNameLike("Asm").ChildMethods() +let methodsToLookAfter = Application.Methods + + +// Enumerate methods that create any disposable type, without calling Dispose() +from m in methodsToLookAfter.ThatCreateAny(disposableTypesToLookAfter ) + +where !m.MethodsCalled.Intersect(disposeMethods).Any() +select new { + m, + disposableObjectsCreated = disposableTypes.Where(t => m.CreateA(t)), + m.MethodsCalled, + //Debt = 10.ToMinutes().ToDebt(), + //Severity = Severity.Low +} + +// +// This code query enumerates methods that create one or several disposable object(s), +// without calling any Dispose() method. +// +// This code query is not a code rule because it is acceptable to do so, +// as long as disposable objects are disposed somewhere else. +// +// This code query is designed to be be easily refactored +// to look after only specific disposable types, or specific caller methods. +// +// You can then refactor this code query to adapt it to your needs and transform it into a code rule. +//]]> + Classes that are candidate to be turned into structures +warnif count > 0 +from t in JustMyCode.Types where + t.IsClass && + !t.IsGeneratedByCompiler && + !t.IsStatic && + !t.IsGeneric && + + t.NbChildren == 0 && // Must not have children + + t.Constructors.All(c => c.NbParameters > 0) && // Must not have parameterless ctor, struct cannot have custom parameterless ctor + + t.IsImmutable && // Structures should be immutable type. + + // Must have fields and all fields must be of value-type + t.InstanceFields.Count() > 0 && + t.InstanceFields.All(f => f.FieldType != null) && + t.InstanceFields.All(f => f.FieldType.IsStructure || f.FieldType.IsEnumeration) && + + // Must not implement interfaces to avoid boxing mismatch + // when structures implements interfaces. + t.InterfacesImplemented.Count() == 0 && + + // Must derive directly from System.Object + t.DepthOfDeriveFrom("System.Object".AllowNoMatch()) == 1 && + + // Must not be a serializable class because a structure should be immutable + // and serialized types are mutable. + !t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) && + !t.IsUsing("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !t.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) && + !t.IsUsing("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) && + + // ASP.NET Core ViewModel and Repository + !t.SimpleName.EndsWithAny("Model","Repository") && + !t.IsUsing("System.ComponentModel.DataAnnotations".AllowNoMatch().MatchNamespace()) && + + // Must not be an entry point class like 'Program' + !t.Methods.Any(m => m.IsEntryPoint) + + // && t.IsSealed <-- You might want to add this condition + // to restraint the set. + // && !t.IsPubliclyVisible <-- You might want to add this condition if + // you are developping a framework with classes + // that are intended to be sub-classed by + // your clients. + let methodsUser = t.TypesUsingMe.ChildMethods().Where(m => m.IsUsing(t)) + +select new { + t, + t.SizeOfInst, + t.InstanceFields, + methodsUser, + Debt = (5 + 1*methodsUser.Count()).ToMinutes().ToDebt(), + Severity = Severity.Info +} + +// +// *Int32*, *Double*, *Char* or *Boolean* are structures and not classes. +// Structures are particularly suited to implement **lightweight values**. +// Hence a class is candidate to be turned into a structure +// when its instances are *lightweight values*. +// +// This is a matter of *performance*. It is expected that a program +// works with plenty of *short lived lightweight values*. +// In such situation, the advantage of using *struct* instead of +// *class*, (in other words, the advantage of using *values* instead +// of *objects*), is that *values* are not managed by the garbage collector. +// This means that values are cheaper to deal with. +// +// This rule matches classes that looks like being *lightweight values*. +// The characterization of such class is: +// +// • It has instance fields. +// +// • All instance fields are typed with value-types (primitive, structure or enumeration) +// +// • It is immutable (the value of its instance fields cannot be modified once the constructor ended). +// +// • It implements no interfaces. +// +// • It has no parameterless construtor. +// +// • It is not generic. +// +// • It has no derived classes. +// +// • It derives directly from *System.Object*. +// +// • ASP.NETCore ViewModel (its name ends with *Model*) and Repository +// +// This rule doesn't take account if instances of matched +// classes are numerous *short-lived* objects. +// These criterions are just indications. Only you can decide if it is +// *performance wise* to transform a class into a structure. +// +// A related case-study of using *class* or *struct* for *Tuple<…>* generic +// types can be found here: +// http://stackoverflow.com/questions/2410710/why-is-the-new-tuple-type-in-net-4-0-a-reference-type-class-and-not-a-value-t +// + +// +// Just use the keyword *struct* instead of the keyword *class*. +// +// **CAUTION:** Before applying this rule, make sure to understand +// the **deep implications** of transforming a class into a structure. +// http://msdn.microsoft.com/en-us/library/aa664471(v=vs.71).aspx +// +// The estimated Debt, which means the effort to fix such issue, +// is 5 minutes per class matched plus one minute per method +// using such class transformed into a structure. +//]]> + Avoid namespaces with few types +warnif count > 0 + +// Common infrastructure namespace names not matched by the rule. +// Complete this list to your need. +let infraNamespaceNames = new HashSet() { + "Services","Exceptions","Logging", + "Identity", "Migrations", "Controllers", + "Specifications", "Interfaces", "Components", + "Bus", "Models", "EventHandlers", "Mappings", + "ViewModels", "ViewComponents", "Notifications", + "Configurations", "Extensions", "Events", + "Context", "Data", "EventSourcing", "Repository", + "IoC", "Manage", "Commands", "CommandHandlers", + "Validations", "EventStoreSQL", "Authorization", + "Formatters", "Xml", "Json" +} + +from n in JustMyCode.Namespaces +where n.Name.Length > 0 // Don't match anonymous namespaces + && !infraNamespaceNames.Contains(n.SimpleName) // Don't match common infrastructure namespaces + && n.Name != n.ParentAssembly.Name // Don't warn on namespace named as assembly to avoid warning on new VS projects +let types = n.ChildTypes.Where(t => !t.IsGeneratedByCompiler).ToArray() +where + types.Length > 0 && // Don't match namespaces that contain only types GeneratedByCompiler + types.Length < 5 && + // Only match namespaces that have all types in JustMyCode + types.All(t => JustMyCode.Contains(t)) + orderby types.Length ascending +select new { + n, + types, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule warns about namespaces other than the global namespace +// that contain less than five types. +// +// Make sure that each of your namespaces has a logical organization +// and that a valid reason exists to put types in a sparsely +// populated namespace. +// +// Namespaces should contain types that are used together in most +// scenarios. When their applications are mutually exclusive, +// types should be located in separate namespaces. For example, +// the *System.Web.UI* namespace contains types that are used +// in Web applications, and the *System.Windows.Forms* namespace +// contains types that are used in Windows-based applications. +// Even though both namespaces have types that control aspects +// of the user interface, these types are not designed for +// use in the same application. Therefore, they are located in +// separate namespaces. +// +// Careful namespace organization can also be helpful because +// it increases the discoverability of a feature. By examining the +// namespace hierarchy, library consumers should be able to locate +// the types that implement a feature. +// +// Notice that this rule source code contains a list of common +// infrastructure namespace names that you can complete. +// Namespaces with ending name component in this list are not matched. +// + +// +// To fix a violation of this rule, try to combine namespaces +// that contain just a few types into a single namespace. +// +]]> + Nested types should not be visible +warnif count > 0 from t in JustMyCode.Types where + t.IsNested && + !t.IsGeneratedByCompiler && + !t.IsPrivate +let typesUser = t.TypesUsingMe.Where(t1 => t1 != t.ParentType && t1.ParentType != t.ParentType) +select new { + t, + t.Visibility, + typesUser, + Debt = (2 + 4*typesUser.Count()).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about nested types not declared as private. +// +// A nested type is a type declared within the scope of another +// type. Nested types are useful for encapsulating private +// implementation details of the containing type. Used +// for this purpose, nested types should not be externally visible. +// +// Do not use externally visible nested types for logical +// grouping or to avoid name collisions; instead use namespaces. +// +// Nested types include the notion of member accessibility, +// which some programmers do not understand clearly. +// +// Protected types can be used in subclasses and nested types +// in advanced customization scenarios. +// + +// +// If you do not intend the nested type to be externally visible, +// change the type's accessibility. +// +// Otherwise, remove the nested type from its parent and make it +// *non-nested*. +// +// If the purpose of the nesting is to group some nested types, +// use a namespace to create the hierarchy instead. +// +// The estimated Debt, which means the effort to fix such issue, +// is 2 minutes per nested type plus 4 minutes per outter type +// using such nesting type. +//]]> + Declare types in namespaces +warnif count > 0 from n in Application.Namespaces where + // If an anonymous namespace can be found, + // it means that it contains types outside of namespaces. + n.Name == "" + + // Eliminate anonymous namespaces that contains + // only notmycode types (like generated types). + let childTypes = n.ChildTypes.Where(t => JustMyCode.Contains(t)) + where childTypes.Count() > 0 +select new { + n, + childTypes, + n.NbLinesOfCode, + Debt = 2*childTypes.Count().ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Types are declared within namespaces to prevent name collisions, +// and as a way of organizing related types in an object hierarchy. +// +// Types outside any named namespace are in a *global +// namespace* that cannot be referenced in code. +// +// The *global namespace* has no name, hence it is qualified as +// being the *anonymous namespace*. +// +// This rule warns about *anonymous namespaces*. +// + +// +// To fix a violation of this rule, +// declare all types of all anonymous +// namespaces in some named namespaces. +//]]> + Empty static constructor can be discarded +warnif count > 0 from m in JustMyCode.Methods where + m.IsClassConstructor && + m.NbLinesOfCode == 0 +select new { + m, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// The *class constructor* (also called *static constructor*, and named *cctor* in IL code) +// of a type, if any, is executed by the CLR at runtime, just before the first time the type is used. +// +// This rule warns about the declarations of *static constructors* +// that don't contain any lines of code. Such *cctors* are useless +// and can be safely removed. +// + +// +// Remove matched empty *static constructors*. +//]]> + Instances size shouldn't be too big +warnif count > 0 from t in JustMyCode.Types where + t.SizeOfInst > 128 && + + // You might want to restrict this rule only on structure, since the cost of copying instance data at each method call might be prohibitive + // t.IsStructure && + + // Discard types that represent WPF, WindowsForm and ASP.NET forms and controls and EntityFramwork classes. + t.BaseClasses.All(bc => !bc.ParentNamespace.Name.StartsWithAny( + "System.Windows", "System.Web.UI", "System.Web", "Microsoft.EntityFramework", + // Discard types related to these namespaces that typically require large instances size + "System.ComponentModel", "System.Xml", + // Just add more component vendors here if needed + "DevExpress")) + + orderby t.SizeOfInst descending +select new { + t, + t.SizeOfInst, + t.InstanceFields, + t.BaseClasses, + Debt = t.SizeOfInst.Linear(128,10, 2048, 120).ToMinutes().ToDebt(), + + // The annual interest varies linearly from interests for severity Medium for 64 bytes per instance + // to twice interests for severity High for 2048 bytes per instance + AnnualInterest = (t.SizeOfInst.Linear(128, Severity.Medium.AnnualInterestThreshold().Value.TotalMinutes, + 2048, 2*(Severity.High.AnnualInterestThreshold().Value.TotalMinutes)) + )*(t.IsStructure ? 10 : 1) // Multiply interest by 10 for structures + .ToMinutes().ToAnnualInterest() + +} + +// +// Types where *SizeOfInst > 128* might degrade performance +// if many instances are created at runtime. +// They can also be hard to maintain. +// +// Notice that a class with a large *SizeOfInst* value +// doesn't necessarily have a lot of instance fields. +// It might derive from a class with a large *SizeOfInst* value. +// +// This query doesn't match types that represent WPF +// and WindowsForm forms and controls nor Entity Framework +// special classes. +// +// Some other namespaces like *System.ComponentModel* or *System.Xml* +// have base classes that typically imply large instances size +// so this rule doesn't match these situations. +// +// This rule doesn't match custom *DevExpress* component +// and it is easy to modify this rule ro append other component vendors +// to avoid false positives. +// +// See the definition of the *SizeOfInst* metric here +// https://www.ndepend.com/docs/code-metrics#SizeOfInst +// + +// +// A type with a large *SizeOfInst* value hold *directly* +// a lot of data. Typically, you can group this data into +// smaller types that can then be composed. +// +// The estimated Debt, which means the effort to fix such issue, +// varies linearly from severity **Medium** for 128 bytes per instance +// to twice interests for severity **High** for 2048 bytes per instance. +// +// The estimated annual interest of issues of this rule is 10 times higher +// for structures, because large structures have a significant performance cost. +// Indeed, each time such structure *value* is passed as a method parameter +// it gets copied to a new local variable +// (note that the word *value* is more appropriate than the word *instance* for structures). +// For this reason, such structure should be declared as class. +//]]> + Attribute classes should be sealed +warnif count > 0 from t in Application.Types where + t.IsAttributeClass && + !t.IsSealed && + !t.IsAbstract && + t.IsPublic +select new { + t, + t.NbLinesOfCode, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// The .NET Framework class library provides methods +// for retrieving custom attributes. By default, +// these methods search the attribute inheritance +// hierarchy; for example +// *System.Attribute.GetCustomAttribute()* +// searches for the specified attribute type, or any +// attribute type that extends the specified attribute +// type. +// +// Sealing the attribute eliminates the search +// through the inheritance hierarchy, and can improve +// performance. +// + +// +// To fix a violation of this rule, seal the attribute +// type or make it abstract. +//]]> + Don't use obsolete types, methods or fields +warnif count > 0 +let obsoleteTypes = Types.Where(t => t.IsObsolete) +let obsoleteMethods = Methods.Where(m => m.IsObsolete).ToHashSet() +let obsoleteFields = Fields.Where(f => f.IsObsolete) + +from m in JustMyCode.Methods.UsingAny(obsoleteTypes).Union( + JustMyCode.Methods.UsingAny(obsoleteMethods)).Union( + JustMyCode.Methods.UsingAny(obsoleteFields)) +let obsoleteTypesUsed = obsoleteTypes.UsedBy(m) + +// Optimization: MethodsCalled + Intersect() is faster than using obsoleteMethods.UsedBy() +let obsoleteMethodsUsed = m.MethodsCalled.Intersect(obsoleteMethods) +let obsoleteFieldsUsed = obsoleteFields.UsedBy(m) + +let obsoleteUsage = obsoleteTypesUsed.Cast().Concat(obsoleteMethodsUsed).Concat(obsoleteFieldsUsed) + +select new { + m, + obsoleteUsage, + Debt = (5*obsoleteUsage.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The attribute *System.ObsoleteAttribute* is used to tag +// types, methods or fields of an API that clients shouldn't +// use because these code elements will be removed sooner +// or later. +// +// This rule warns about methods that use a type, a method +// or a field, tagged with *System.ObsoleteAttribute*. +// + +// +// Typically when a code element is tagged with +// *System.ObsoleteAttribute*, a *workaround message* +// is provided to clients. +// +// This *workaround message* will tell you what to do +// to avoid using the obsolete code element. +// +// The estimated Debt, which means the effort to fix such issue, +// is 5 minutes per type, method or field used. +// +// Issues of this rule have a severity **High** +// because it is important to not rely anymore on obsolete code. +//]]> + Do implement methods that throw NotImplementedException +warnif count > 0 +from m in Application.Methods +where m.CreateA("System.NotImplementedException".AllowNoMatch()) +select new { + m, + m.NbLinesOfCode, + Debt = (m.NbLinesOfCode == 1 ? 10 : 3).ToMinutes().ToDebt(), + Severity = m.NbLinesOfCode == 1 ? Severity.High : Severity.Medium +} + +// +// The exception *NotImplementedException* is used to declare +// a method *stub* that can be invoked, and defer the +// development of the method implementation. +// +// This exception is especially useful when doing **TDD** +// (*Test Driven Development*) when tests are written first. +// This way tests fail until the implementation is written. +// +// Hence using *NotImplementedException* is a *temporary* +// facility, and before releasing, will come a time when +// this exception shouldn't be used anywhere in code. +// +// *NotImplementedException* should not be used permanently +// to mean something like *this method should be overriden* +// or *this implementation doesn't support this facility*. +// Artefact like *abstract method* or *abstract class* should +// be used instead, to favor a *compile time* error over a +// *run-time* error. +// +// This rule warns about method still using +// *NotImplementedException*. +// + +// +// Investigate why *NotImplementedException* is still +// thrown. +// +// Such issue has a **High** severity if the method code +// consists only in throwing *NotImplementedException*. +// Such situation means either that the method should be +// implemented, either that what should be a *compile time* +// error is a *run-time* error *by-design*, +// and this is not good design. Sometime this situation +// also pinpoints a method stub that can be safely removed. +// +// If *NotImplementedException* is thrown from a method +// with significant logic, the severity is considered as +// **Medium**, because often the fix consists in throwing +// another exception type, like **InvalidOperationException**. +//]]> + Override equals and operator equals on value types +warnif count > 0 +from t in JustMyCode.Types where + t.IsStructure && + t.InstanceFields.Count() > 0 +let equalsMethod = t.InstanceMethods.Where(m0 => m0.Name == "Equals(Object)").SingleOrDefault() +where equalsMethod == null +select new { + t, + t.InstanceFields, + Debt = (15 + 2*t.InstanceFields.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// For value types, the inherited implementation of *Equals()* uses +// the Reflection library, and compares the contents of all instances +// fields. Reflection is computationally expensive, and comparing +// every field for equality might be unnecessary. +// +// If you expect users to compare or sort instances, or use them +// as hash table keys, your value type should implement *Equals()*. +// In C# and VB.NET, you should also provide an implementation of +// *GetHashCode()* and of the equality and inequality operators. +// + +// +// To fix a violation of this rule, provide an implementation +// of *Equals()* and *GetHashCode()* and implement the equality +// and inequality operators. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 15 minutes plus 2 minutes per instance field. +//]]> + Boxing/unboxing should be avoided +warnif count > 0 from m in JustMyCode.Methods where + m.IsUsingBoxing || + m.IsUsingUnboxing +select new { + m, + m.NbLinesOfCode, + m.IsUsingBoxing, + m.IsUsingUnboxing, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// *Boxing* is the process of converting a value type to the type +// *object* or to any interface type implemented by this value +// type. When the CLR boxes a value type, it wraps the value +// inside a *System.Object* and stores it on the managed heap. +// +// *Unboxing* extracts the value type from the object. Boxing +// is implicit; unboxing is explicit. +// +// The concept of boxing and unboxing underlies the C# unified +// view of the type system in which a value of any type can +// be treated as an object. More about *boxing* and *unboxing* +// here: https://msdn.microsoft.com/en-us/library/yz2be5wk.aspx +// +// **This rule has been disabled by default** to avoid noise in +// issues found by the NDepend default rule set. If boxing/unboxing +// is important to your team, just re-activate this rule. +// + +// +// Thanks to .NET generic, and especially thanks to +// generic collections, *boxing* and *unboxing* should +// be rarely used. Hence in most situations the code can +// be refactored to avoid relying on *boxing* and *unboxing*. +// See for example: +// http://stackoverflow.com/questions/4403055/boxing-unboxing-and-generics +// +// With a performance profiler, indentify methods that consume +// a lot of CPU time. If such method uses *boxing* or +// *unboxing*, especially in a **loop**, make sure to refactor it. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + + + Avoid namespaces mutually dependent +warnif count > 0 + +// Optimization: restraint application assemblies set +// If some namespaces are mutually dependent +// - They must be declared in the same assembly +// - The parent assembly must ContainsNamespaceDependencyCycle +from assembly in Application.Assemblies.Where(a => a.ContainsNamespaceDependencyCycle != null && a.ContainsNamespaceDependencyCycle.Value) + +// hashset is used to avoid reporting both A <-> B and B <-> A +let hashset = new HashSet() + +// Optimization: restraint namespaces set +// If a namespace doesn't have a Level value, it must be in a dependency cycle +// or it must be using directly or indirectly a dependency cycle. +let namespacesSuspect = assembly.ChildNamespaces.Where(n => n.Level == null) + +from nA in namespacesSuspect + +// Select namespaces mutually dependent with nA +let unused = hashset.Add(nA) // Populate hashset +let namespacesMutuallyDependentWith_nA = nA.NamespacesUsed.Using(nA) + .Except(hashset) // <-- avoid reporting both A <-> B and B <-> A +where namespacesMutuallyDependentWith_nA.Count() > 0 + +from nB in namespacesMutuallyDependentWith_nA + +// nA and nB are mutually dependent +// Infer which one is low level and which one is high level, +// for that we need to compute the coupling from A to B +// and from B to A in terms of number of types and methods +// usages involved in the coupling. +let typesOfBUsedByA = nB.ChildTypes.UsedBy(nA).ToArray() // Enumerate once +let couplingA2B = + typesOfBUsedByA.Sum(t => t.TypesUsingMe.Count(t1 => t1.ParentNamespace == nA)) + + typesOfBUsedByA.ChildMethods().Sum(m => m.MethodsCallingMe.Count(m1 => m1.ParentNamespace == nA)) + +let typesOfAUsedByB = nA.ChildTypes.UsedBy(nB).ToArray() // Enumerate once +let couplingB2A = + typesOfAUsedByB.Sum(t => t.TypesUsingMe.Count(t1 => t1.ParentNamespace == nB)) + + typesOfAUsedByB.ChildMethods().Sum(m => m.MethodsCallingMe.Count(m1 => m1.ParentNamespace == nB)) + +// The lowLevelNamespace is inferred from the fact that +// [coupling lowLevel -> highLevel] is lower than [coupling highLevel -> lowLevel] +let lowLevelNamespace = (couplingA2B < couplingB2A) ? nA : nB +let highLevelNamespace = (lowLevelNamespace == nA) ? nB : nA + +let highLevelTypesUsed = (lowLevelNamespace == nA) ? typesOfBUsedByA : typesOfAUsedByB +let lowLevelTypesUser = lowLevelNamespace.ChildTypes.UsingAny(highLevelTypesUsed) + +let lowLevelTypesMethodsUser = lowLevelTypesUser.Cast() + .Concat(lowLevelTypesUser.ChildMethods().Using(highLevelNamespace)) + .ToArray() // Enumerate once + +// Make the rule works also when lines of code is not available +let lowLevelNamespaceLoc = lowLevelNamespace.NbLinesOfCode ?? (lowLevelNamespace.NbILInstructions / 7) +let highLevelNamespaceLoc = highLevelNamespace.NbLinesOfCode ?? (highLevelNamespace.NbILInstructions / 7) + +let annualInterestPerIssue = + // Such issue has at least a Severity.Medium, never a Severity.Low + Math.Max(Severity.Medium.AnnualInterestThreshold().Value.TotalSeconds, + ((3600 + lowLevelNamespaceLoc + highLevelNamespaceLoc) / Math.Max(1,lowLevelTypesMethodsUser.Length)).Value) + .ToSeconds().ToAnnualInterest() + +// Select in details types and methods involved in the coupling lowLevelNamespace using highLevelNamespace +from tmCulprit in lowLevelTypesMethodsUser +let used = (tmCulprit.IsType ? tmCulprit.AsType.TypesUsed.Where(t => t.ParentNamespace == highLevelNamespace) : + tmCulprit.AsMethod.MembersUsed.Where(m => m.ParentNamespace == highLevelNamespace)) + .ToArray() // Enumerate once + +select new { + tmCulprit, + shouldntUse = used, + becauseNamespace = lowLevelNamespace, + shouldntUseNamespace = highLevelNamespace, + Debt = used.Length.Linear(1, 15, 10, 60).ToMinutes().ToDebt(), + AnnualInterest = annualInterestPerIssue + +} +// +// This rule lists types and methods from a low-level namespace +// that use types and methods from higher-level namespace. +// +// The pair of low and high level namespaces is made of two +// namespaces that use each other. +// +// For each pair of namespaces, to infer which one is low-level +// and which one is high-level, the rule computes the two coupling +// [from A to B] and [from B to A] in terms of number of types, +// methods and fields involved in the coupling. Typically +// the coupling from low-level to high-level namespace is significantly +// lower than the other legitimate coupling. +// +// Following this rule is useful to avoid **namespaces dependency +// cycles**. This will get the code architecture close to a +// *layered architecture*, where *low-level* code is not allowed +// to use *high-level* code. +// +// In other words, abiding by this rule will help significantly +// getting rid of what is often called **spagetthi code: +// Entangled code that is not properly layered and structured**. +// +// More on this in our white books relative to partitioning code. +// https://www.ndepend.com/docs/white-books +// + +// +// Refactor the code to make sure that **the low-level namespace +// doesn't use the high-level namespace**. +// +// The rule lists in detail which low-level types and methods +// shouldn't use which high-level types and methods. The refactoring +// patterns that help getting rid of each listed dependency include: +// +// • Moving one or several types from the *low-level* namespaces +// to the *high-level* one, or do the opposite. +// +// • Use *Inversion of Control (IoC)*: +// http://en.wikipedia.org/wiki/Inversion_of_control +// This consists in creating new interfaces in the +// *low-level* namespace, implemented by classes +// in the *high-level* namespace. This way *low-level* +// code can consume *high-level* code through interfaces, +// without using directly *high-level* implementations. +// Interfaces can be passed to *low-level* code through +// the *high-level* namespace code, or through even +// higher-level code. In related documentations +// you can see these interfaces named as *callbacks*, +// and the overall pattern is also known as +// *Dependency Injection (DI)*: +// http://en.wikipedia.org/wiki/Dependency_injection +// +// That rule might not be applicable for frameworks +// that present public namespaces mutually dependent. +// In such situation the cost to break the API can be +// higher than the cost to let the code entangled. +// +// - +// +// The estimated **Debt**, which means the effort to fix such issue +// to make sure that the first namespace doesn't rely anymore +// on the second one, depends on the number of types and methods used. +// +// Because both namespace are now forming a *super-component* +// that cannot be partitioned in smaller components, the cost to +// unfix each issue is proportional to the size of this super-component. +// As a consequence, the estimated **Annual Interest**, which means +// the annual cost to let both namespaces mutually dependend, is equal +// to an hour plus a number of minutes proportional to the size +// (in lines of code) of both namespaces. The obtained *Annual Interest* +// value is then divided by the number of detailled issues listed. +// +// Often the estimated *Annual Interest* for each listed issue +// is higher than the *Debt*, which means that leaving such issue +// unfixed for a year costs more than taking the time to fix issue once. +// +// -- +// +// To explore the coupling between the two namespaces mutually +// dependent: +// +// 1) from the *becauseNamespace right-click menu* choose +// *Copy to Matrix Columns* to export this low-level namespace +// to the horizontal header of the dependency matrix. +// +// 2) from the *shouldntUseNamespace right-click menu* choose +// *Copy to Matrix Rows* to export this high-level namespace to +// the vertical header of the dependency matrix. +// +// 3) double-click the black matrix cell (it is black because of +// the mutual dependency). +// +// 4) in the matrix command bar, click the button: +// *Remove empty Row(s) and Column(s)*. +// +// At this point, the dependency matrix shows types involved +// into the coupling. +// +// • Blue cells represent types from low-level namespace using types +// from high-level namespace +// +// • Green cells represent types from high-level namespace using +// types from low-level namespace +// +// • Black cells represent types from low-level and high-level +// namespaces that use each other. +// +// There are more green cells than blue and black cells because +// green cell represents correct coupling from high-level to low-level. +// **The goal is to eliminate incorrect dependencies represented by +// blue and black cells.** +// + ]]> + Avoid namespaces dependency cycles +warnif count > 0 + +// Optimization: restraint application assemblies set +// If some namespaces are mutually dependent +// - They must be declared in the same assembly +// - The parent assembly must ContainsNamespaceDependencyCycle +from assembly in Application.Assemblies + .Where(a => a.ContainsNamespaceDependencyCycle != null && + a.ContainsNamespaceDependencyCycle.Value) + +// Optimization: restraint namespaces set +// A namespace involved in a cycle necessarily have a null Level. +let namespacesSuspect = assembly.ChildNamespaces.Where(n => n.Level == null) + +// hashset is used to avoid iterating again on namespaces already caught in a cycle. +let hashset = new HashSet() + + +from suspect in namespacesSuspect + // By commenting in this line, the query matches all namespaces involved in a cycle. + where !hashset.Contains(suspect) + + // Define 2 code metrics + // • Namespaces depth of is using indirectly the suspect namespace. + // • Namespaces depth of is used by the suspect namespace indirectly. + // Note: for direct usage the depth is equal to 1. + let namespacesUserDepth = namespacesSuspect.DepthOfIsUsing(suspect) + let namespacesUsedDepth = namespacesSuspect.DepthOfIsUsedBy(suspect) + + // Select namespaces that are both using and used by namespaceSuspect + let usersAndUsed = from n in namespacesSuspect where + namespacesUserDepth[n] > 0 && + namespacesUsedDepth[n] > 0 + select n + + where usersAndUsed.Count() > 0 + + // Here we've found namespace(s) both using and used by the suspect namespace. + // A cycle involving the suspect namespace is found! + // v2017.3.2: don't call Append() as an extension method else ambiguous syntax error + // with the new extension method in .NET Fx v4.7.1 / .NET Standard 2.0: System.Linq.Enumerable.Append() + let cycle = ExtensionMethodsEnumerable.Append(usersAndUsed,suspect) + + // Fill hashset with namespaces in the cycle. + // .ToArray() is needed to force the iterating process. + let unused1 = (from n in cycle let unused2 = hashset.Add(n) select n).ToArray() + +select new { + suspect, + cycle, + Debt = 120.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule lists all *application namespace dependency cycles*. +// Each row shows a different cycle, indexed with one of the namespace entangled +// in the cycle. +// +// To browse a cycle on the dependency graph or the dependency matrix, right click +// a cycle cell and export the matched namespaces to the dependency graph or matrix. +// +// In the matrix, dependency cycles are represented with red squares and black cells. +// To easily browse dependency cycles, the dependency matrix comes with an option: +// *Display Direct and Indirect Dependencies* +// +// Read our white books relative to partitioning code, +// to know more about namespaces dependency cycles, and why avoiding them +// is a *simple yet efficient* solution to clean the architecture of a code base. +// https://www.ndepend.com/docs/white-books +// + +// +// Removing first pairs of *mutually dependent namespaces* will eliminate +// most *namespaces dependency cycles*. This is why it is recommended +// focusing on matches of the default rule +// **Avoid namespaces mutually dependent** before dealing +// with the present rule. +// +// Once solving all *mutually dependent namespaces*, remaining cycles +// matched by the present rule necessarily involve 3 or more namespaces +// like in: *A is using B is using C is using A*. +// Such cycle can be broken by identifying which namespace should +// be at the *lower-level*. For example if B should be at the +// *lower-level*, then it means C should be at the *higher-level* +// and to break the cycle, you just have to remove the dependency +// from B to C, with a pattern described in the *HowToFix* section +// of the rule *Avoid namespaces mutually dependent*. +// +// The estimated Debt, which means the effort to fix such issue, +// doesn't depend on the cycle length. First because fixing the rule +// **Avoid namespaces mutually dependent** will fix most cycle reported +// here, second because even a long cycle can be broken by removing +// a few dependency. +//]]> + Avoid partitioning the code base through many small library Assemblies +warnif count > 10 +from a in Application.Assemblies where + ( a.NbLinesOfCode < 1000 || + a.NbILInstructions < 7000 ) && + a.FilePath.FileExtension.ToLower() == ".dll" +select new { + a, + a.NbLinesOfCode, + a.NbILInstructions, + Debt = 40.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Each .NET Assembly compiled represents one or several physical file(s). +// Having too many library .NET Assemblies is a symptom of +// considering **physical** .NET Assemblies as **logical** components. +// +// We advise having less, and bigger, .NET Assemblies +// and using the concept of namespaces to define logical components. +// Benefits are: +// +// • Faster compilation time. +// +// • Faster startup time for your program. +// +// • Easier deployment thanks to less files to manage. +// +// • If you are developing a Framework, +// less .NET assemblies to reference and manage for your clients. +// + +// +// Consider using the *physical* concept of assemblies for physical needs +// only. +// +// Our white book about **Partitioning code base through .NET assemblies +// and Visual Studio projects** explains in details valid and invalid +// reasons to use assemblies. +// Download it here: +// https://www.ndepend.com/Res/NDependWhiteBook_Assembly.pdf +//]]> + UI layer shouldn't use directly DB types + +warnif count > 0 + +// UI layer is made of types using a UI framework +let uiTypes = Application.Types.UsingAny(Assemblies.WithNameIn("PresentationFramework", "System.Windows", "System.Windows.Forms", "System.Web")) + +// You can easily customize this part to define what are DB types. +let dbTypes = ThirdParty.Assemblies.WithNameIn("System.Data", "EntityFramework", "NHibernate").ChildTypes() + // Ideally even DataSet and associated, usage should be forbidden from UI layer: + // http://stackoverflow.com/questions/1708690/is-list-better-than-dataset-for-ui-layer-in-asp-net + .Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow")) + +from uiType in uiTypes.UsingAny(dbTypes) +let dbTypesUsed = dbTypes.Intersect(uiType.TypesUsed) + +let dbTypesAndMembersUsed = dbTypesUsed.Union(dbTypesUsed.ChildMembers().UsedBy(uiType)) + +// Per defaut this rule estimates a technical debt +// proportional to the coupling between the UI and DB types. +let couplingPerUIType = 2 + + uiType.Methods.UsingAny(dbTypesUsed).Count() + + dbTypesAndMembersUsed.Count() + +select new { + uiType, + dbTypesAndMembersUsed, + Debt = (4 * couplingPerUIType).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is more a *sample rule to adapt to your need*, +// than a rigid rule you should abide by. It shows how to +// define code layers in a rule and how to be warned about +// layers dependencies violations. +// +// This rule first defines the UI layer and the DB framework +// layer. Second it checks if any UI layer type is using +// directly any DB framework layer type. +// +// • The **DB framework layer** is defined as the set of *third-party* +// types in the framework *ADO.NET*, *EntityFramework*, +// *NHibernate* types, that the application is consuming. +// It is easy to append and suppress any DB framework. +// +// • The UI layer (**User Interface Layer**) is defined as the +// set of types that use *WPF*, *Windows Form*, *ASP.NET*. +// +// *UI using directly DB frameworks* is generally considered +// as *poor design* because DB frameworks accesses should be +// a concept hidden to UI, encapsulated into a **dedicated +// Data Access Layer (DAL)**. +// +// Notice that per defaut this rule estimates a technical debt +// proportional to the coupling between the UI and DB types. +// + +// +// This rule lists precisely which UI type uses which +// DB framework type. Instead of fixing matches one by one, +// first imagine how DB framework accesses could be +// encapsulated into a dedicated layer. +// +]]> + UI layer shouldn't use directly DAL layer +warnif count > 0 + +// UI layer is made of types using a UI framework +let uiTypes = Application.Types.UsingAny(Assemblies.WithNameIn("PresentationFramework", "System.Windows", "System.Windows.Forms", "System.Web")) + +// Exclude commonly used DataSet and associated, from ADO.Net types +// You can easily customize this part to define what are DB types. +let dbTypes = ThirdParty.Assemblies.WithNameIn("System.Data", "EntityFramework", "NHibernate").ChildTypes() + .Except(ThirdParty.Types.WithNameIn("DataSet", "DataTable", "DataRow")) + +// DAL layer is made of types using a DB framework +// .ToHashSet() results to faster execution of dalTypes.Intersect(uiType.TypesIUse). +let dalTypes = Application.Types.UsingAny(dbTypes).ToHashSet() + +from uiType in uiTypes.UsingAny(dalTypes) +let dalTypesUsed = dalTypes.Intersect(uiType.TypesUsed) + +let dalTypesAndMembersUsed = dalTypesUsed.Union(dalTypesUsed.ChildMembers().UsedBy(uiType)) + +// Per defaut this rule estimates a technical debt +// proportional to the coupling between the UI with the DAL layer. +let couplingPerUIType = 2 + + uiType.Methods.UsingAny(dalTypesUsed).Count() + + dalTypesAndMembersUsed.Count() + +select new { + uiType, + // if dalTypesUsed is empty, it means that the uiType is part of the DAL + dalTypesAndMembersUsed, + Debt = (4 * couplingPerUIType).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is more a *sample rule to adapt to your need*, +// than a rigid rule you should abide by. It shows how to +// define code layers in a rule and how to be warned about +// layers dependencies violations. +// +// This rule first defines the UI layer and the DAL layer. +// Second it checks if any UI layer type is using directly +// any DAL layer type. +// +// • The DB layer (the DAL, **Data Access Layer**) is defined as +// the set of types of the application that use *ADO.NET*, +// *EntityFramework*, *NHibernate* types. It is easy to append +// and suppress any DB framework. +// +// • The UI layer (**User Interface Layer**) is defined as the +// set of types that use *WPF*, *Windows Form*, *ASP.NET*. +// +// *UI using directly DAL* is generally considered as *poor +// design* because DAL accesses should be a concept +// hidden to UI, encapsulated into an **intermediary domain +// logic**. +// +// Notice that per defaut this rule estimates a technical debt +// proportional to the coupling between the UI with the DAL layer. +// + +// +// This rule lists precisely which UI type uses which DAL type. +// +// More about this particular design topic here: +// http://www.kenneth-truyers.net/2013/05/12/the-n-layer-myth-and-basic-dependency-injection/ +// +]]> + Assemblies with poor cohesion (RelationalCohesion) +warnif count > 0 from a in Application.Assemblies + +// Build the types list on which we want to check cohesion +// This is the assembly 'a' type, minus enumeration +// and types generated by the compiler. +let types = a.ChildTypes.Where( + t => !t.IsGeneratedByCompiler && + !t.IsEnumeration && + JustMyCode.Contains(t)) + // Absolutly need ToHashet() to have fast Intersect() calls below. + .ToHashSet() + +// Relational Cohesion metrics is relevant only if there are enough types +where types.LongCount()> 20 + +// R is the total number of relationship between types of the assemblies. +let R = types.Sum(t => t.TypesUsed.Intersect(types).Count()) + +// Relational Cohesion formula +let relationalCohesion = (double)R / types.Count +where + + (relationalCohesion < 1.5 || + relationalCohesion > 4.0) +select new { + a, + a.ChildTypes, + relationalCohesion, + a.RelationalCohesion, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule computes the *Relational Cohesion* metric for +// the application assemblies, and warns about wrong values. +// +// The *Relational Cohesion* for an assembly, is the total number +// of relationship between types of the assemblies, divided +// by the number of types. In other words it is the average +// number of types in the assembly used by a type in the assembly. +// +// As classes inside an assembly should be strongly related, +// the cohesion should be high. On the other hand, a value +// which is too high may indicate over-coupling. A good range +// for *Relational Cohesion* is **1.5 to 4.0**. +// +// Notice that assemblies with less than 20 types are ignored. +// + +// +// Matches of this present rule might reveal either assemblies +// with specific coding constraints (like code generated that +// have particular structure) either issues in design. +// +// In the second case, large refactoring can be planned +// not to respect this rule in particular, but to increase +// the overall design and code maintainability. +// +// The severity of issues of this rule is **Low** because +// the code metric *Relational Cohesion* is an information +// about the code structure state but **is not actionable**, +// it doesn't tell precisely what to do obtain a better score. +// +// Fixing actionable issues of others **Architecture** and +// **Code Smells** default rules will necessarily increase +// the *Relational Cohesion* scores. +//]]> + Namespaces with poor cohesion (RelationalCohesion) +warnif count > 0 from n in Application.Namespaces + +// Build the types list on which we want to check cohesion +// This is the namespace children types, minus enumerations +// and types generated by the compiler. +let types = n.ChildTypes.Where( + t => JustMyCode.Contains(t) && !t.IsEnumeration) + // Absolutly need ToHashet() to have fast Intersect() calls below. + .ToHashSet() + +// Relational Cohesion metrics is relevant only if there are enough types +where types.LongCount() > 20 + +// R is the total number of relationship between types of the namespaces. +let R = types.Sum(t => t.TypesUsed.Intersect(types).Count()) + +// Relational Cohesion formula +let relationalCohesion = (double)R / types.Count +where + (relationalCohesion < 1.5 || + relationalCohesion > 4.0) +select new { + n, + n.ChildTypes, + relationalCohesion, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule computes the *Relational Cohesion* metric for +// the application namespaces, and warns about wrong values. +// +// The *Relational Cohesion* for a namespace, is the total number +// of relationship between types of the namespaces, divided +// by the number of types. In other words it is the average +// number of types in the namespace used by a type in the namespace. +// +// As classes inside a namespace should be strongly related, +// the cohesion should be high. On the other hand, a value +// which is too high may indicate over-coupling. A good range +// for *Relational Cohesion* is **1.5 to 4.0**. +// +// Notice that namespaces with less than 20 types are ignored. +// + +// +// Matches of this present rule might reveal either namespaces +// with specific coding constraints (like code generated that +// have particular structure) either issues in design. +// +// In the second case, refactoring sessions can be planned +// to increase the overall design and code maintainability. +// +// You can get an overview of class coupling for a +// matched namespace by exporting the *ChildTypes* to the graph. +// (Right click the *ChildTypes* cells) +// +// The severity of issues of this rule is **Low** because +// the code metric *Relational Cohesion* is an information +// about the code structure state but **is not actionable**, +// it doesn't tell precisely what to do obtain a better score. +// +// Fixing actionable issues of others **Architecture** and +// **Code Smells** default rules will necessarily increase +// the *Relational Cohesion* scores. +//]]> + Assemblies that don't satisfy the Abstractness/Instability principle +warnif count > 0 from a in Application.Assemblies + where a.NormDistFromMainSeq > 0.7 + orderby a.NormDistFromMainSeq descending +select new { + a, + a.NormDistFromMainSeq, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// The **Abstractness versus Instability Diagram** that is shown in the NDepend +// report helps to assess which assemblies are **potentially painful to maintain** +// (i.e concrete and stable) and which assemblies are **potentially useless** +// (i.e abstract and instable). +// +// • **Abstractness**: If an assembly contains many abstract types +// (i.e interfaces and abstract classes) and few concrete types, +// it is considered as abstract. +// +// • **Stability**: An assembly is considered stable if its types +// are used by a lot of types from other assemblies. In this context +// stable means *painful to modify*. +// +// From these metrics, we define the *perpendicular normalized distance of +// an assembly from the idealized line* **A + I = 1** (called *main sequence*). +// This metric is an indicator of the assembly's balance between abstractness +// and stability. We precise that the word *normalized* means that the range +// of values is [0.0 … 1.0]. +// +// This rule warns about assemblies with a *normalized distance* greater than +// than 0.7. +// +// This rules use the default code metric on assembly +// *Normalized Distance from the Main Sequence* explained here: +// https://www.ndepend.com/docs/code-metrics#DitFromMainSeq +// +// These concepts have been originally introduced by *Robert C. Martin* +// in 1994 in this paper: http://www.objectmentor.com/resources/articles/oodmetrc.pdf +// + +// +// Violations of this rule indicate assemblies with an improper +// *abstractness / stability* balance. +// +// • Either the assembly is *potentially painful to maintain* (i.e is massively +// used and contains mostly concrete types). This can be fixed by creating +// abstractions to avoid too high coupling with concrete implementations. +// +// • Either the assembly is *potentially useless* (i.e contains mostly +// abstractions and is not used enough). In such situation, the design +// must be reviewed to see if it can be enhanced. +// +// The severity of issues of this rule is **Low** because +// the *Abstractness/Instability principle* is an information +// about the code structure state but **is not actionable**, +// it doesn't tell precisely what to do obtain a better score. +// +// Fixing actionable issues of others **Architecture** and +// **Code Smells** default rules will necessarily push +// the *Abstractness/Instability principle* scores in the +// right direction. +//]]> + Higher cohesion - lower coupling +// warnif count > 0 +let abstractNamespaces = JustMyCode.Namespaces.Where( + n => n.ChildTypes.Where(t => !t.IsInterface && !t.IsEnumeration && !t.IsDelegate).Count() == 0 +).ToHashSet() + +let concreteNamespaces = JustMyCode.Namespaces.Except(abstractNamespaces).ToHashSet() + +from n in concreteNamespaces +let namespacesUsed = n.NamespacesUsed.ExceptThirdParty() +let concreteNamespacesUsed = namespacesUsed.Except(abstractNamespaces) +let abstractNamespacesUsed = namespacesUsed.Except(concreteNamespaces) +orderby concreteNamespacesUsed.Count() descending +select new { + n, + concreteNamespacesUsed , + abstractNamespacesUsed, + // Debt = 50.ToMinutes().ToDebt(), + // Severity = Severity.High +} + +// +// It is deemed as a good software architecture practice to clearly separate +// *abstract* namespaces that contain only abstractions (interfaces, enumerations, delegates) +// from *concrete* namespaces, that contain classes and structures. +// +// Typically, the more concrete namespaces rely on abstract namespaces *only*, +// the more **Decoupled** is the architecture, and the more **Cohesive** are +// classes inside concrete namespaces. +// +// The present code query defines sets of abstract and concrete namespaces +// and show for each concrete namespaces, which concrete and abstract namespaces +// are used. +// + +// +// This query can be transformed into a code rule, depending if you wish to +// constraint your code structure *coupling / cohesion* ratio. +//]]> + Example of custom rule to check for dependency +warnif count > 0 from a in Assemblies +where +a.IsUsing("Foo1.Foo2".AllowNoMatch().MatchNamespace()) && +(a.Name == @"Foo3") +select new { + a, + a.NbLinesOfCode, + // Debt and Severity / Annual Interest can be modified to estimate well the + // effort to fix the issue and the annual cost to leave the issue unfixed. + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} +// the assembly Foo3 +// shouldn't use directly +// the namespace Foo3.Foo4 +// because (TODO insert your reason) + +// +// This rule is a **sample rule** that shows how to +// check if a particular dependency exists or not, +// from a code element **A** to a code element **B**, +// **A** and **B** being an *Assembly*, a *Namespace*, a *Type*, +// a *Method* or a *Field*, **A** and **B** being not +// necessarily of same kind (i.e two Assemblies or +// two Namespaces…). +// +// Such rule can be generated: +// +// • by right clicking the cell in the *Dependency Matrix* +// with **B** in row and **A** in column, +// +// • or by right-clicking the concerned arrow in the *Dependency +// Graph* from **A** to **B**, +// +// and in both cases, click the menu +// **Generate a code rule that warns if this dependency exists** +// +// The generated rule will look like this one. +// It is now up to you to adapt this rule to check exactly +// your needs. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + + + API Breaking Changes: Types + +warnif count > 0 from t in codeBase.OlderVersion().Application.Types +where t.IsPubliclyVisible && + + // The type has been removed, it was not tagged as obsolete + // and its parent assembly hasn't been removed … + ( ( t.WasRemoved() && + !t.ParentAssembly.WasRemoved() && + !t.IsObsolete) || + + // … or the type is not publicly visible anymore + !t.WasRemoved() && !t.NewerVersion().IsPubliclyVisible) + +select new { + t, + NewVisibility = + (t.WasRemoved() ? " " : + t.NewerVersion().Visibility.ToString()), + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns if a type publicly visible in the *baseline*, +// is not publicly visible anymore or if it has been removed. +// Clients code using such type will be broken. +// + +// +// Make sure that public types that used to be presented to +// clients, still remain public now, and in the future. +// +// If a public type must really be removed, you can tag it +// with *System.ObsoleteAttribute* with a *workaround message* +// during a few public releases, until it gets removed definitely. +// Notice that this rule doesn't match types removed that were +// tagged as obsolete. +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consumers of the API. +//]]> + API Breaking Changes: Methods +warnif count > 0 from m in codeBase.OlderVersion().Application.Methods +where m.IsPubliclyVisible && + + // The method has been removed, it was not tagged as obsolete + // and its parent type hasn't been removed … + ( ( m.WasRemoved() && + !m.ParentType.WasRemoved() && + !m.IsObsolete ) + + // … or the method is not publicly visible anymore + || (!m.WasRemoved() && !m.NewerVersion().IsPubliclyVisible) + + // … or the method return type has changed + || (!m.WasRemoved() && m.ReturnType != null && m.NewerVersion().ReturnType != null + && m.ReturnType.FullName != m.NewerVersion().ReturnType.FullName) + ) + +//-------------------------------------- +// Handle special case: if between two versions a regular property becomes +// an auto-property (or vice-versa) the property getter/setter method have +// a different value for IMethod.IsGeneratedByCompiler +// since auto-property getter/setter are marked as generated by the compiler. +// +// If a method IsGeneratedByCompiler value changes between two versions, +// NDepend doesn't pair the newer/older occurences of the method. +// +// Hence in such situation, a public method is seen as added +// and a public method is seen as removed, but the API is not broken! +// The equivalentMethod-check below avoids reporting such +// API Breaking Change false-positive. +let equivalentMethod = m.WasRemoved() && m.ParentType.IsPresentInBothBuilds() ? + m.ParentType.NewerVersion().Methods + .FirstOrDefault(m1 => + m1.IsPubliclyVisible && + m1.Name == m.Name && + m1.IsGeneratedByCompiler != m.IsGeneratedByCompiler && + (m1.ReturnType == null || m.ReturnType == null || m1.ReturnType.FullName == m.ReturnType.FullName) + ) + : null +where equivalentMethod == null +//-------------------------------------- + + +select new { + m, + NewVisibility = + (m.WasRemoved() ? " " : + m.NewerVersion().Visibility.ToString()), + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns if a method publicly visible in the *baseline*, +// is not publicly visible anymore or if it has been removed. +// Clients code using such method will be broken. +// + +// +// Make sure that public methods that used to be presented to +// clients, still remain public now, and in the future. +// +// If a public method must really be removed, you can tag it +// with *System.ObsoleteAttribute* with a *workaround message* +// during a few public releases, until it gets removed definitely. +// Notice that this rule doesn't match methods removed that were +// tagged as obsolete. +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consumers of the API. +// +]]> + API Breaking Changes: Fields +warnif count > 0 from f in codeBase.OlderVersion().Application.Fields +where f.IsPubliclyVisible && + + // The field has been removed, it was not tagged as obsolete + // and its parent type hasn't been removed … + ( ( f.WasRemoved() && + !f.ParentType.WasRemoved() && + !f.IsObsolete) + + // … or the field is not publicly visible anymore + || !f.WasRemoved() && !f.NewerVersion().IsPubliclyVisible) + + // … or the field type has changed + || (!f.WasRemoved() && f.FieldType != null && f.NewerVersion().FieldType != null + && f.FieldType.FullName != f.NewerVersion().FieldType.FullName) + +select new { + f, + NewVisibility = + (f.WasRemoved() ? " " : + f.NewerVersion().Visibility.ToString()), + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns if a field publicly visible in the *baseline*, +// is not publicly visible anymore or if it has been removed. +// Clients code using such field will be broken. +// + +// +// Make sure that public fields that used to be presented to +// clients, still remain public now, and in the future. +// +// If a public field must really be removed, you can tag it +// with *System.ObsoleteAttribute* with a *workaround message* +// during a few public releases, until it gets removed definitely. +// Notice that this rule doesn't match fields removed that were +// tagged as obsolete. +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consumers of the API. +//]]> + API Breaking Changes: Interfaces and Abstract Classes +warnif count > 0 from tNewer in Application.Types where + (tNewer.IsInterface || tNewer.IsClass && tNewer.IsAbstract) && + tNewer.IsPubliclyVisible && + tNewer.IsPresentInBothBuilds() + +let tOlder = tNewer.OlderVersion() where tOlder.IsPubliclyVisible + +let methodsRemoved = tOlder.Methods.Where(m => m.IsAbstract && m.WasRemoved()) +let methodsAdded = tNewer.Methods.Where(m => m.IsAbstract && m.WasAdded()) + +where methodsAdded.Count() > 0 || methodsRemoved.Count() > 0 +select new { + tNewer, + methodsAdded, + methodsRemoved, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns if a publicly visible interface or abstract class +// has been changed and contains new abstract methods or +// if some abstract methods have been removed. +// +// Clients code that implement such interface or derive from +// such abstract class will be broken. +// + +// +// Make sure that the public contracts of interfaces and abstract classes +// that used to be presented to clients, remain stable now, and in the future. +// +// If a public contract must really be changed, you can tag +// abstract methods that will be removed with *System.ObsoleteAttribute* +// with a *workaround message* during a few public releases, until it gets +// removed definitely. +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consummers of the API. +// The severity is not set to **Critical** because an interface +// is not necessarily meant to be implemented by the consummer +// of the API. +//]]> + Broken serializable types +warnif count > 0 + +from t in Application.Types where + + // Collect types tagged with SerializableAttribute + t.HasAttribute("System.SerializableAttribute".AllowNoMatch()) && + !t.IsDelegate && + t.IsPresentInBothBuilds() && + t.HasAttribute(t) + + // Find newer and older versions of NonSerializedAttribute + let newNonSerializedAttribute = ThirdParty.Types.WithFullName("System.NonSerializedAttribute").SingleOrDefault() + let oldNonSerializedAttribute = newNonSerializedAttribute == null ? null : newNonSerializedAttribute.OlderVersion() + + // Find added or removed fields not marked with NonSerializedAttribute + let addedInstanceField = from f in t.InstanceFields where + f.WasAdded() && + (newNonSerializedAttribute == null || !f.HasAttribute(newNonSerializedAttribute)) + select f + let removedInstanceField = from f in t.OlderVersion().InstanceFields where + f.WasRemoved() && + (oldNonSerializedAttribute == null || !f.HasAttribute(oldNonSerializedAttribute)) + select f + where addedInstanceField.Count() > 0 || removedInstanceField.Count() > 0 + +select new { + t, + addedInstanceField, + removedInstanceField, + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.Critical +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule warns about breaking changes in types tagged with +// *SerializableAttribute*. +// +// To do so, this rule searches for serializable type with serializable +// instance fields added or removed. Notice that it doesn't take account +// of fields tagged with *NonSerializedAttribute*. +// +// From http://msdn.microsoft.com/library/system.serializableattribute.aspx : +// "All the public and private fields in a type that are marked by the +// *SerializableAttribute* are serialized by default, unless the type +// implements the *ISerializable* interface to override the serialization process. +// The default serialization process excludes fields that are marked +// with the *NonSerializedAttribute* attribute." +// + +// +// Make sure that the serialization process of serializable types remains +// stable now, and in the future. +// +// Else you'll have to deal with **Version Tolerant Serialization** +// that is explained here: +// https://msdn.microsoft.com/en-us/library/ms229752(v=vs.110).aspx +// +// Issues of this rule have a severity equal to **High** +// because an API Breaking change can provoque significant +// friction with consummers of the API. +//]]> + Avoid changing enumerations Flags status +warnif count > 0 + +let oldFlags = codeBase.OlderVersion().ThirdParty.Types.WithFullName("System.FlagsAttribute").FirstOrDefault() +let newFlags = ThirdParty.Types.WithFullName("System.FlagsAttribute").FirstOrDefault() +where oldFlags != null && newFlags != null + +from t in Application.Types where + t.IsEnumeration && + t.IsPresentInBothBuilds() +let hasFlagsAttributeNow = t.HasAttribute(newFlags) +let usedToHaveFlagsAttribute = t.OlderVersion().HasAttribute(oldFlags) +where hasFlagsAttributeNow != usedToHaveFlagsAttribute +select new { + t, + hasFlagsAttributeNow, + usedToHaveFlagsAttribute, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule matches enumeration types that used to be tagged +// with *FlagsAttribute* in the *baseline*, and not anymore. +// It also matches the opposite, enumeration types that are now +// tagged with *FlagsAttribute*, and were not tagged in the *baseline*. +// +// Being tagged with *FlagsAttribute* is a strong property for an enumeration. +// Not so much in terms of *behavior* (only the *enum.ToString()* method +// behavior changes when an enumeration is tagged with *FlagsAttribute*) +// but in terms of *meaning*: is the enumeration a **range of values** +// or a **range of flags**? +// +// As a consequence, changing the *FlagsAttribute*s status of an enumeration can +// have significant impact for its clients. +// + +// +// Make sure the *FlagsAttribute* status of each enumeration remains stable +// now, and in the future. +//]]> + API: New publicly visible types +from t in Application.Types +where t.IsPubliclyVisible && + + // The type has been removed and its parent assembly hasn't been removed … + ( (t.WasAdded() && !t.ParentAssembly.WasAdded()) || + + // … or the type existed but was not publicly visible + !t.WasAdded() && !t.OlderVersion().IsPubliclyVisible) + +select new { + t, + OldVisibility = + (t.WasAdded() ? " " : + t.OlderVersion().Visibility.ToString()), +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists types that are new in the public +// surface of the analyzed assemblies. +//]]> + API: New publicly visible methods +from m in Application.Methods +where m.IsPubliclyVisible && + + // The method has been removed and its parent assembly hasn't been removed … + ( (m.WasAdded() && !m.ParentType.WasAdded()) || + + // … or the method existed but was not publicly visible + !m.WasAdded() && !m.OlderVersion().IsPubliclyVisible) + +//-------------------------------------- +// Handle special case: if between two versions a regular property becomes +// an auto-property (or vice-versa) the property getter/setter method have +// a different value for IMethod.IsGeneratedByCompiler +// since auto-property getter/setter are marked as generated by the compiler. +// +// If a method IsGeneratedByCompiler value changes between two versions, +// NDepend doesn't pair the newer/older occurences of the method. +// +// Hence in such situation, a public method is seen as added +// and a public method is seen as removed, but the API is not broken! +// The equivalentMethod-check below avoids reporting such +// API Breaking Change false-positive. +let equivalentMethod = m.WasAdded() && m.ParentType.IsPresentInBothBuilds() ? + m.ParentType.OlderVersion().Methods + .FirstOrDefault(m1 => + m1.IsPubliclyVisible && + m1.Name == m.Name && + m1.IsGeneratedByCompiler != m.IsGeneratedByCompiler) + : null +where equivalentMethod == null +//-------------------------------------- + +select new { + m, + OldVisibility = + (m.WasAdded() ? " " : + m.OlderVersion().Visibility.ToString()) +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists methods that are new in the public +// surface of the analyzed assemblies. +// +]]> + API: New publicly visible fields +from f in Application.Fields +where f.IsPubliclyVisible && + + // The method has been removed and its parent assembly hasn'f been removed … + ( (f.WasAdded() && !f.ParentType.WasAdded()) || + + // … or the t existed but was not publicly visible + !f.WasAdded() && !f.OlderVersion().IsPubliclyVisible) + +select new { + f, + OldVisibility = + (f.WasAdded() ? " " : + f.OlderVersion().Visibility.ToString()) +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists fields that are new in the public +// surface of the analyzed assemblies. +//]]> + + + Code should be tested +warnif count > 0 + +// This lambda infers a factor in the range [0,1] from a sequence of distinct types used, +// based on how many of these types are actually concrete (i.e are not interfaces or enumeration). +// Primitive types (int, bool...) are eliminated from the sequence of types used +// by filtering types declared in the namespace System. +let abstractionUsageFactorFormula = new Func,double>(typesUsed => + typesUsed.Count(t => t.ParentNamespace.Name != "System" && !t.IsInterface && !t.IsEnumeration) + / (1 + typesUsed.Count(t => t.ParentNamespace.Name != "System"))) + + +from method in Application.Methods + where !method.IsExcludedFromCoverage && method.NbLinesOfCode >= 0 && method.PercentageCoverage < 100 + + // Factor in case method is partially covered + let uncoverageFactor = ((100 - method.PercentageCoverage) / 100).Value + + // Complexity factor + let complexityFactor = ((method.CyclomaticComplexity ?? method.ILCyclomaticComplexity) + method.ILNestingDepth).Linear(0, 0.1, 10, 1).Value + + // Not my code is often generated code and is in general easier to get tested since test can be generated as well. + let justMyCodeFactor = (JustMyCode.Contains(method) ? 1 : 0.4) + + // abstractionUsageFactor reflects the fact that code that relies on interfaces + // is easier to test that code that relies on concrete classes. + let abstractionUsageFactor = 0.7 + 0.3 *abstractionUsageFactorFormula(method.MembersUsed.Select(m => m.ParentType).Distinct()) + + // The usageFactor depends on the method 'rank' that is a value + // indicating if the method is often used or not + let usageFactor = (method.Rank / (method.Rank + 4)).Value + + // It is more complicated to write tests for non publicly visible methods + let visibilityFactor = method.Visibility.EqualsAny(Visibility.Public, Visibility.Internal) ? 1 : + method.Visibility != Visibility.Private ? 1.1 : 1.2 + + // Is is more complicated to write tests for methods that read mutable static fields + // whose changing state is shared across tests executions. + let staticFieldUsageFactor = method.ReadsMutableTypeState ? 1.3 : 1.0 + + + // Both "effort to write tests" and "annual cost to not test" for a method + // is determined by several factors in the range [0,1] that multiplies the effortToDevelop + let effortToDevelopInMinutes = method.EffortToDevelop().Value.TotalMinutes + + let effortToWriteTests = Math.Max(2, // Minimum 2 minutes per method not tested + effortToDevelopInMinutes * + uncoverageFactor * + complexityFactor * + justMyCodeFactor * + abstractionUsageFactor * + visibilityFactor * + staticFieldUsageFactor).ToMinutes().ToDebt() + + let annualCostToNotFix = Math.Max(2, // Minimum 2 minutes per method not tested + effortToDevelopInMinutes * + usageFactor * + uncoverageFactor * + justMyCodeFactor).ToMinutes().ToAnnualInterest() + + orderby annualCostToNotFix.Value descending + +select new { + method, + method.PercentageCoverage, + method.NbLinesOfCode, + method.NbLinesOfCodeNotCovered, + method.CyclomaticComplexity, + Debt = effortToWriteTests, + AnnualInterest = annualCostToNotFix, + + // BreakingPoint = effortToWriteTests.BreakingPoint(annualCostToNotFix), + + // Uncomment the line below to tinker with various factors + // uncoverageFactor, complexityFactor , justMyCodeFactor , abstractionUsageFactor, visibilityFactor, staticFieldUsageFactor +} + + +// +// This rule lists methods not covered at all by test +// or partially covered by tests. +// +// For each match, the rules estimates the **technical debt**, i.e +// the effort to write unit and integration tests for the method. +// The estimation is based on the effort to develop the code element +// multiplied by factors in the range ]0,1.3] based on +// +// • the method code size and complexity +// +// • the actual percentage coverage +// +// • the abstractness of types used, because relying on classes instead of +// interfaces makes the code more difficult to test +// +// • the method visibility because testing private or protected +// methods is more difficult than testing public and internal ones +// +// • the fields used by the method, because is is more complicated to +// write tests for methods that read mutable static fields whose changing +// state is shared across tests executions. +// +// • whether the method is considered *JustMyCode* or not because *NotMyCode* +// is often generated easier to get tested since tests can be generated as well. +// +// This rule is necessarily a large source of technical debt, since +// the code left untested is by definition part of the technical debt. +// +// This rule also estimates the **annual interest**, i.e the annual cost +// to let the code uncovered, based on the effort to develop the +// code element, multiplied by factors based on usage of the code element. +// + +// +// Write unit tests to test and cover the methods and their parent classes +// matched by this rule. +//]]> + New Methods should be tested +warnif count > 0 +from m in Application.Methods where + m.NbLinesOfCode > 0 && + m.PercentageCoverage < 30 && + m.WasAdded() + orderby m.NbLinesOfCode descending, + m.NbLinesOfCodeNotCovered , + m.PercentageCoverage +select new { + m, + m.PercentageCoverage, + m.NbLinesOfCode, + m.NbLinesOfCodeNotCovered, + + // Simplistic Debt estimation, because the effort to write tests for a method not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// This rule operates only on methods added or refactored since the baseline. +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// It is important to write code mostly covered by tests +// to achieve *maintainable* and *non-error-prone* code. +// +// In real-world, many code bases are poorly covered by tests. +// However it is not practicable to stop the development for months +// to refactor and write tests to achieve high code coverage ratio. +// +// Hence it is recommended that each time a method (or a type) gets added, +// the developer takes the time to write associated unit-tests to cover it. +// +// Doing so will help to increase significantly the maintainability of the code base. +// You'll notice that quickly, refactoring will also be driven by testability, +// and as a consequence, the overall code structure and design will increase as well. +// +// Issues of this rule have a **High** severity because they reflect +// an actual trend to not care about writing tests on refactored code. +// + +// +// Write unit-tests to cover the code of most methods and classes added. +//]]> + Methods refactored should be tested +warnif count > 0 +from m in Application.Methods where + m.PercentageCoverage < 30 && + m.CodeWasChanged() + orderby m.NbLinesOfCode descending, + m.NbLinesOfCodeNotCovered , + m.PercentageCoverage +select new { + m, + m.PercentageCoverage, + m.NbLinesOfCode, + m.NbLinesOfCodeNotCovered, + + // Simplistic Debt estimation, because the effort to write tests for a method not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// This rule operates only on methods added or refactored since the baseline. +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// It is important to write code mostly covered by tests +// to achieve *maintainable* and *non-error-prone* code. +// +// In real-world, many code bases are poorly covered by tests. +// However it is not practicable to stop the development for months +// to refactor and write tests to achieve high code coverage ratio. +// +// Hence it is recommended that each time a method (or a type) gets refactored, +// the developer takes the time to write associated unit-tests to cover it. +// +// Doing so will help to increase significantly the maintainability of the code base. +// You'll notice that quickly, refactoring will also be driven by testability, +// and as a consequence, the overall code structure and design will increase as well. +// +// Issues of this rule have a **High** severity because they reflect +// an actual trend to not care about writing tests on refactored code. +// + +// +// Write unit-tests to cover the code of most methods and classes refactored. +//]]> + Assemblies Namespaces and Types should be tested +warnif count > 0 +from elem in CodeElementParents +where elem.NbLinesOfCode > 0 && + elem.PercentageCoverage == 0 && + elem.Parent.PercentageCoverage != 0 +orderby elem.NbLinesOfCode descending +select new { + elem, + elem.NbLinesOfCodeNotCovered, + Debt = 4.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule lists assemblies, namespaces and types that are not +// covered at all by unit tests. +// +// If a parent is matched its children are not matched. For example +// if a namespace is matched, its child types are not matched. +// +// This rule goal is not to collide with the **Code should be tested** +// rule that lists uncovered code for each method and infer the effort +// to write unit tests (the *Debt*) and the annual cost to let the code +// untested (the Annual Interest). +// +// This rule goal is to inform of large code elements left untested. +// As a consequence the *Debt* per issue is only 4 minutes and the +// severity of the issues is *Low*. +// +// +// +// Write unit and integration tests to cover, even partially, +// code elements matched by this rule. +// +// Then use issues of the rules **Code should be tested**, +// **New Methods should be tested** and +// **Methods refactored should be tested** +// to write more tests where it matters most, and eventually +// refactor some code to make it more testable. +//]]> + Types almost 100% tested should be 100% tested +warnif count > 0 +from t in Application.Types where + t.PercentageCoverage >= 95 && + t.PercentageCoverage <= 99 && + !t.IsGeneratedByCompiler + + let methodsCulprit = t.Methods.Where(m => m.PercentageCoverage < 100) + + orderby t.NbLinesOfCode descending , + t.NbLinesOfCodeNotCovered , + t.PercentageCoverage +select new { + t, + t.PercentageCoverage, + t.NbLinesOfCode, + t.NbLinesOfCodeNotCovered, + methodsCulprit, + + // Simplistic Debt estimation, because the effort to write tests for a type not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = t.NbLinesOfCodeNotCovered.Linear(1,2, 20,20).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// Often covering the few percents of remaining uncovered code of a class, +// requires as much work as covering the first 90%. +// For this reason, often teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* +// which usually leads to *error prone code*. +// So it might be worth refactoring and making sure to cover the few uncovered lines of code +// **because most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// Issues of this rule have a **High** severity because as explained, +// such situation is *bug-prone*. +// + +// +// Write more unit-tests dedicated to cover code not covered yet. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +//]]> + Namespaces almost 100% tested should be 100% tested +warnif count > 0 +from n in Application.Namespaces where + n.PercentageCoverage >= 95 && + n.PercentageCoverage <= 99 + + let methodsCulprit = n.ChildMethods.Where(m => m.PercentageCoverage < 100) + + orderby n.NbLinesOfCode descending , + n.NbLinesOfCodeNotCovered , + n.PercentageCoverage +select new { + n, + n.PercentageCoverage, + n.NbLinesOfCode, + n.NbLinesOfCodeNotCovered, + methodsCulprit, + + // Simplistic Debt estimation, because the effort to write tests for a type not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = n.NbLinesOfCodeNotCovered.Linear(1,2, 50,60).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// Often covering the few percents of remaining uncovered code of +// one or several classes in a namespace +// requires as much work as covering the first 90%. +// For this reason, often teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* +// which usually leads to *error prone code*. +// So it might be worth refactoring and making sure to cover the few uncovered lines of code +// **because most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// Issues of this rule have a **High** severity because as explained, +// such situation is *bug-prone*. +// + +// +// Write more unit-tests dedicated to cover code not covered yet in the namespace. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +//]]> + Types that used to be 100% covered by tests should still be 100% covered + +warnif count > 0 +from t in JustMyCode.Types where + t.IsPresentInBothBuilds() && + t.OlderVersion().PercentageCoverage == 100 && + t.PercentageCoverage < 100 + +from m in t .MethodsAndContructors where + m.NbLinesOfCode> 0 && + m.PercentageCoverage < 100 && + !m.IsExcludedFromCoverage + +select new { + m, + m.PercentageCoverage, + + // Simplistic Debt estimation, because the effort to write tests for a method not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = t.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// Often covering 10% of remaining uncovered code of a class, +// requires as much work as covering the first 90%. +// For this reason, typically teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* +// which usually leads to *error prone code*. +// So it might be worth refactoring and making sure to cover the 10% remaining code +// **because most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// In this context, this rule warns when a type fully covered by tests is now only partially covered. +// +// Issues of this rule have a **High** severity because often, +// a type that used to be 100% and is not covered anymore +// is a bug-prone situation that should be carefully handled. +// + +// +// Write more unit-tests dedicated to cover code not covered anymore. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +// +// You'll find code impossible to cover by unit-tests, like calls to *MessageBox.Show()*. +// An infrastructure must be defined to be able to *mock* such code at test-time. +//]]> + Types tagged with FullCoveredAttribute should be 100% covered +warnif count > 0 +from t in Application.Types where + t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) && + t.PercentageCoverage < 100 + +from m in t .MethodsAndContructors where + m.NbLinesOfCode> 0 && + m.PercentageCoverage < 100 && + !m.IsExcludedFromCoverage + +select new { + m, + m.PercentageCoverage, + m.NbLinesOfCodeNotCovered, + m.NbLinesOfCode, + + // Simplistic Debt estimation, because the effort to write tests for a method not 100% tested + // is already estimated properly with the rule "Code should be tested". + Debt = m.NbLinesOfCodeNotCovered.Linear(1,2, 10,10).ToMinutes().ToDebt(), + + Severity = Severity.High +} + +// +// This rule lists methods partially covered by tests, of types tagged with +// **FullCoveredAttribute**. +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// By using a **FullCoveredAttribute**, you can express in source code the intention +// that a class is 100% covered by tests, and should remain 100% covered in the future. +// If you don't want to link *NDepend.API.dll*, +// you can use your own attribute and adapt the source code of this rule. +// +// Benefits of using a **FullCoveredAttribute** are twofold: +// Not only the intention is expressed in source code, +// but it is also continuously checked by the present rule. +// +// Often covering 10% of remaining uncovered code of a class, +// requires as much work as covering the first 90%. +// For this reason, often teams estimate that 90% coverage is enough. +// However *untestable code* usually means *poorly written code* which usually means *error prone code*. +// So it might be worth refactoring and making sure to cover the 10% remaining code +// **because most tricky bugs might come from this small portion of hard-to-test code**. +// +// Not all classes should be 100% covered by tests (like UI code can be hard to test) +// but you should make sure that most of the logic of your application +// is defined in some *easy-to-test classes*, 100% covered by tests. +// +// Issues of this rule have a **High** severity because often, +// a type that used to be 100% and is not covered anymore +// is a bug-prone situation that should be carefully handled. +// + +// +// Write more unit-tests dedicated to cover code of matched classes not covered yet. +// If you find some *hard-to-test code*, it is certainly a sign that this code +// is not *well designed* and hence, needs refactoring. +//]]> + Types 100% covered should be tagged with FullCoveredAttribute + +warnif count > 0 from t in JustMyCode.Types where + !t.HasAttribute ("NDepend.Attributes.FullCoveredAttribute".AllowNoMatch()) && + t.PercentageCoverage == 100 && + !t.IsGeneratedByCompiler +select new { + t, + t.NbLinesOfCode, + Debt = 3.ToMinutes().ToDebt(), // It is fast to add such attribute to a type. + Severity = Severity.Low +} + +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// By using a **FullCoveredAttribute**, you can express in source code the intention +// that a class is 100% covered by tests, and should remain 100% covered in the future. +// +// Benefits of using a **FullCoveredAttribute** are twofold: +// Not only the intention is expressed in source code, +// but it is also continuously checked by the present rule. +// +// Issues of this rule have an **Low** severity because they don't reflect +// a problem, but provide an advice for potential improvement. +// + +// +// Just tag types 100% covered by tests with the **FullCoveredAttribute** +// that can be found in *NDepend.API.dll*, +// or by an attribute of yours defined in your own code +// (in which case this rule must be adapted). +//]]> + Methods should have a low C.R.A.P score + +warnif count > 0 +from m in JustMyCode.Methods + +// Don't match too short methods +where m.NbLinesOfCode > 10 && m.CoverageDataAvailable + +let CC = m.CyclomaticComplexity +let uncov = (100 - m.PercentageCoverage) / 100f +let CRAP = (double)(CC * CC * uncov * uncov * uncov) + CC +where CRAP != null && CRAP > 30 +orderby CRAP descending, m.NbLinesOfCode descending +select new { + m, + CRAP, + CC, + m.PercentageCoverage, m.NbLinesOfCode, + + // CRAP score equals 30 => 10 minutes debt + // CRAP score equals 3000 => 3 hours to write tests + Debt = CRAP.Linear(30,10, 3000, 3*60).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is executed only if some code coverage data is imported +// from some code coverage files. +// +// So far this rule is disabled because other code coverage rules +// assess properly code coverage issues. +// +// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric +// that helps in pinpointing overly both complex and untested code. +// Is has been first defined here: +// http://www.artima.com/weblogs/viewpost.jsp?thread=215899 +// +// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)** +// +// • where *CC(m)* is the *cyclomatic complexity* of the method *m* +// +// • and *cov(m)* is the *percentage coverage* by tests of the method *m* +// +// Matched methods cumulates two highly *error prone* code smells: +// +// • A complex method, difficult to develop and maintain. +// +// • Non 100% covered code, difficult to refactor without introducing any regression bug. +// +// The higher the CRAP score, the more painful to maintain and error prone is the method. +// +// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors. +// +// Notice that no amount of testing will keep methods with a Cyclomatic Complexity +// higher than 30, out of CRAP territory. +// +// Notice that this rule doesn't match too short method +// with less than 10 lines of code. +// + +// +// In such situation, it is recommended to both refactor the complex method logic +// into several smaller and less complex methods +// (that might belong to some new types especially created), +// and also write unit-tests to full cover the refactored logic. +// +// You'll find code impossible to cover by unit-tests, like calls to *MessageBox.Show()*. +// An infrastructure must be defined to be able to *mock* such code at test-time. +//]]> + Test Methods + +let testAttr = ThirdParty.Types.WithNameIn("FactAttribute", "TestAttribute", "TestCaseAttribute") +let testMethods = Methods.TaggedWithAnyAttributes(testAttr) +from m in testMethods +select m + +// +// We advise to not include test assemblies in code analyzed by NDepend. +// We estimate that it is acceptable and practical to lower the quality gate of test code, +// because the important measures for tests are: +// +// • The coverage ratio, +// +// • And the amount of logic results asserted: This includes both +// assertions in test code, and assertions in code covered by tests, +// like *Code Contract* assertions and *Debug.Assert(…)* assertions. +// +// But if you wish to enforce the quality of test code, you'll need to +// consider test assemblies in your list of application assemblies +// analyzed by NDepend. +// +// In such situation, this code query lists tests methods and you can +// reuse this code in custom rules. +//]]> + Methods directly called by test Methods + +let testAttr = ThirdParty.Types.WithNameIn("FactAttribute", "TestAttribute", "TestCaseAttribute") +let testMethods = Methods.TaggedWithAnyAttributes(testAttr).ToHashSet() + +// --- Uncomment this line if your test methods are in dedicated test assemblies --- +//let testAssemblies = testMethods.ParentAssemblies().ToHashSet() + +from m in Application.Methods.UsedByAny(testMethods) + +// --- Uncomment this line if your test methods are in dedicated test assemblies --- +//where !testAssemblies.Contains(m.ParentAssembly) + +select new { m , + calledByTests = m.MethodsCallingMe.Intersect(testMethods ), + // --- Uncomment this line if your project import some coverage data --- + // m.PercentageCoverage +} + + +// +// This query lists all methods directly called by tests methods. +// Overrides of virtual and abstract methods, called through polymorphism, are not listed. +// Methods solely invoked through a delegate are not listed. +// Methods solely invoked through reflection are not listed. +// +// We advise to not include test assemblies in code analyzed by NDepend. +// We estimate that it is acceptable and practical to lower the quality gate of test code, +// because the important measures for tests are: +// +// • The coverage ratio, +// +// • And the amount of logic results asserted: This includes both +// assertions in test code, and assertions in code covered by tests, +// like *Code Contract* assertions and *Debug.Assert(…)* assertions. +// +// But if you wish to run this code query, +// you'll need to consider test assemblies in your list of +// application assemblies analyzed by NDepend. +//]]> + Methods directly and indirectly called by test Methods + +let testAttr = from t in ThirdParty.Types.WithNameIn("FactAttribute", "TestAttribute", "TestCaseAttribute") select t +let testMethods = Methods.TaggedWithAnyAttributes(testAttr) + +// --- Uncomment this line if your test methods are in dedicated test assemblies --- +// let testAssemblies = testMethods.ParentAssemblies().ToHashSet() + +let depthOfCalledByTest = Application.Methods.DepthOfIsUsedByAny(testMethods) +from pair in depthOfCalledByTest +where pair.Value > 0 +orderby pair.Value ascending +// --- Uncomment this line if your test methods are in dedicated test assemblies --- +//&& !testAssemblies.Contains(pair.CodeElement.ParentAssembly) + +select new { + method = pair.CodeElement, + // (depthOfCalledByTests == 1) means that the method is directly called by tests + // (depthOfCalledByTests == 2) means that the method is directly called by a method directly called by tests + // … + depthOfCalledByTests = pair.Value, + nbLinesOfCode = pair.CodeElement.NbLinesOfCode, + // --- Uncomment this line if your project import some coverage data --- + // m.PercentageCoverage +} + +// +// This query lists all methods *directly or indirectly* called by tests methods. +// *Indirectly* called by a test means that a test method calls a method, that calls a method… +// From this recursion, a code metric named *depthOfCalledByTests* is inferred, +// The value *1* means directly called by test, +// the value *2* means called by a method that is called by a test… +// +// Overrides of virtual and abstract methods, called through polymorphism, are not listed. +// Methods solely invoked through a delegate are not listed. +// Methods solely invoked through reflection are not listed. +// +// We advise to not include test assemblies in code analyzed by NDepend. +// We estimate that it is acceptable and practical to lower the quality gate of test code, +// because the important measures for tests are: +// +// • The coverage ratio, +// +// • And the amount of logic results asserted: This includes both +// assertions in test code, and assertions in code covered by tests, +// like *Code Contract* assertions and *Debug.Assert(…)* assertions. +// +// But if you wish to run this code query, +// you'll need to consider test assemblies in your list of +// application assemblies analyzed by NDepend. +//]]> + + + Potentially Dead Types + +warnif count > 0 +// Filter procedure for types that should'nt be considered as dead +let canTypeBeConsideredAsDeadProc = new Func( + t => !t.IsPublic && // Public types might be used by client applications of your assemblies. + t.Name != "Program" && + !t.IsGeneratedByCompiler && + + // If you don't want to link NDepend.API.dll, you can use your own IsNotDeadCodeAttribute + // and adapt the source code of this rule. + !t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + + // Exclude static types that define only const fields + // because they cannot be seen as used in IL code. + !(t.IsStatic && t.NbMethods == 0 && !t.Fields.Where(f => !f.IsLiteral).Any()) && + + // Entity Framework ModelSnapshot classes are used ony by the EF infrastructure. + !t.DeriveFrom("Microsoft.EntityFrameworkCore.Infrastructure.ModelSnapshot".AllowNoMatch())) + +// Select types unused +let typesUnused = + from t in JustMyCode.Types where + t.NbTypesUsingMe == 0 && canTypeBeConsideredAsDeadProc(t) + select t + +// Dead types = types used only by unused types (recursive) +let deadTypesMetric = typesUnused.FillIterative( +types => from t in codeBase.Application.Types.UsedByAny(types).Except(types) + where canTypeBeConsideredAsDeadProc(t) && + t.TypesUsingMe.Intersect(types).Count() == t.NbTypesUsingMe + select t) + +from t in deadTypesMetric.DefinitionDomain +select new { + t, + depth = deadTypesMetric[t], + t.TypesUsingMe, + Debt = 15.ToMinutes().ToDebt(), + AnnualInterest = (10 + (t.NbLinesOfCode ?? 1)).ToMinutes().ToAnnualInterest() +} + +// +// This rule lists *potentially* **dead types**. +// A dead type is a type that can be removed +// because it is never used by the program. +// +// This rule lists not only types not used anywhere in code, +// but also types used only by types not used anywhere in code. +// This is why this rule comes with a column *TypesusingMe* and +// this is why there is a code metric named *depth*: +// +// • A *depth* value of *0* means the type is not used. +// +// • A *depth* value of *1* means the type is used only by types not used. +// +// • etc… +// +// By reading the source code of this rule, you'll see that by default, +// *public* types are not matched, because such type might not be used +// by the analyzed code, but still be used by client code, not analyzed by NDepend. +// This default behavior can be easily changed. +// +// Note that this rule doesn't match Entity Framework ModelSnapshot classes +// that are used ony by the EF infrastructure. +// + +// +// *Static analysis* cannot provide an *exact* list of dead types, +// because there are several ways to use a type *dynamically* (like through reflection). +// +// For each type matched by this query, first investigate if the type is used somehow +// (like through reflection). +// If the type is really never used, it is important to remove it +// to avoid maintaining useless code. +// If you estimate the code of the type might be used in the future, +// at least comment it, and provide an explanatory comment about the future intentions. +// +// If a type is used somehow, +// but still is matched by this rule, you can tag it with the attribute +// **IsNotDeadCodeAttribute** found in *NDepend.API.dll* to avoid matching the type again. +// You can also provide your own attribute for this need, +// but then you'll need to adapt this code rule. +// +// Issues of this rule have a **Debt** equal to 15 minutes because it only +// takes a short while to investigate if a type can be safely discarded. +// The **Annual Interest** of issues of this rule, the annual cost to not +// fix such issue, is proportional to the type #lines of code, because +// the bigger the type is, the more it slows down maintenance. +//]]> + Potentially Dead Methods + +warnif count > 0 +// Filter procedure for methods that should'nt be considered as dead +let canMethodBeConsideredAsDeadProc = new Func( + m => !m.IsPubliclyVisible && // Public methods might be used by client applications of your assemblies. + !m.IsEntryPoint && // Main() method is not used by-design. + !m.IsExplicitInterfaceImpl && // The IL code never explicitly calls explicit interface methods implementation. + !m.IsClassConstructor && // The IL code never explicitly calls class constructors. + !m.IsFinalizer && // The IL code never explicitly calls finalizers. + !m.IsVirtual && // Only check for non virtual method that are not seen as used in IL. + !(m.IsConstructor && // Don't take account of protected ctor that might be call by a derived ctors. + m.IsProtected) && + !m.IsEventAdder && // The IL code never explicitly calls events adder/remover. + !m.IsEventRemover && + !m.IsGeneratedByCompiler && + !m.ParentType.IsDelegate && + + // Don't consider Global ASP.NET methods as unused + !m.ParentType.DeriveFrom("System.Web.HttpApplication".AllowNoMatch()) && + + // Methods tagged with these two attributes are called by the serialization infrastructure. + !m.HasAttribute("System.Runtime.Serialization.OnSerializingAttribute".AllowNoMatch()) && + !m.HasAttribute("System.Runtime.Serialization.OnDeserializedAttribute".AllowNoMatch()) && + + // If you don't want to link NDepend.API.dll, you can use your own IsNotDeadCodeAttribute + // and adapt the source code of this rule. + !m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + + // Don't consider public getters/setters + // of classes that implement INotifyPropertyChanged + // as dead code. + !(m.IsPublic && (m.IsPropertyGetter || m.IsPropertySetter) && + m.ParentType.Implement("System.ComponentModel.INotifyPropertyChanged".AllowNoMatch())) + ) + +// Get methods unused +let methodsUnused = + from m in JustMyCode.Methods where + m.NbMethodsCallingMe == 0 && + canMethodBeConsideredAsDeadProc(m) + select m + +// Dead methods = methods used only by unused methods (recursive) +let deadMethodsMetric = methodsUnused.FillIterative( + methods => // Unique loop, just to let a chance to build the hashset. + from o in (new object()).ToEnumerable() + // Use a hashet to make Intersect calls much faster! + let hashset = methods.ToHashSet() + from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods) + where canMethodBeConsideredAsDeadProc(m) && + // Select methods called only by methods already considered as dead + hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe + select m) + +from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain) +let depth = deadMethodsMetric[m] +select new { + m, + depth, + m.MethodsCallingMe, + Debt = (10 + 3*depth).ToMinutes().ToDebt(), + AnnualInterest = (8 + (m.NbLinesOfCode ?? 1)).ToMinutes().ToAnnualInterest() +} + +// +// This rule lists *potentially* **dead methods**. +// A dead method is a method that can be removed +// because it is never called by the program. +// +// This rule lists not only methods not called anywhere in code, +// but also methods called only by methods not called anywhere in code. +// This is why this rule comes with a column *MethodsCallingMe* and +// this is why there is a code metric named *depth*: +// +// • A *depth* value of *0* means the method is not called. +// +// • A *depth* value of *1* means the method is called only by methods not called. +// +// • etc… +// +// By reading the source code of this rule, you'll see that by default, +// *public* methods are not matched, because such method might not be called +// by the analyzed code, but still be called by client code, not analyzed by NDepend. +// This default behavior can be easily changed. +// + +// +// *Static analysis* cannot provide an *exact* list of dead methods, +// because there are several ways to invoke a method *dynamically* (like through reflection). +// +// For each method matched by this query, first investigate if the method is invoked somehow +// (like through reflection). +// If the method is really never invoked, it is important to remove it +// to avoid maintaining useless code. +// If you estimate the code of the method might be used in the future, +// at least comment it, and provide an explanatory comment about the future intentions. +// +// If a method is invoked somehow, +// but still is matched by this rule, you can tag it with the attribute +// **IsNotDeadCodeAttribute** found in *NDepend.API.dll* to avoid matching the method again. +// You can also provide your own attribute for this need, +// but then you'll need to adapt this code rule. +// +// Issues of this rule have a **Debt** equal to 10 minutes because it only +// takes a short while to investigate if a method can be safely discarded. +// On top of these 10 minutes, the depth of usage of such method adds up +// 3 minutes per unity because dead method only called by dead code +// takes a bit more time to be investigated. +// +// The **Annual Interest** of issues of this rule, the annual cost to not +// fix such issue, is proportional to the type #lines of code, because +// the bigger the method is, the more it slows down maintenance. +//]]> + Potentially Dead Fields +warnif count > 0 +from f in JustMyCode.Fields where + f.NbMethodsUsingMe == 0 && + !f.IsPublic && // Although not recommended, public fields might be used by client applications of your assemblies. + !f.IsLiteral && // The IL code never explicitly uses literal fields. + !f.IsEnumValue && // The IL code never explicitly uses enumeration value. + f.Name != "value__" && // Field named 'value__' are relative to enumerations and the IL code never explicitly uses them. + !f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + !f.IsGeneratedByCompiler + // If you don't want to link NDepend.API.dll, you can use your own IsNotDeadCodeAttribute + // and adapt the source code of this rule. +select new { + f, + Debt = 10.ToMinutes().ToDebt(), + AnnualInterest = 8.ToMinutes().ToAnnualInterest() +} + +// +// This rule lists *potentially* **dead fields**. +// A dead field is a field that can be removed +// because it is never used by the program. +// +// By reading the source code of this rule, you'll see that by default, +// *public* fields are not matched, because such field might not be used +// by the analyzed code, but still be used by client code, not analyzed by NDepend. +// This default behavior can be easily changed. +// Some others default rules in the *Visibility* group, warn about public fields. +// +// More restrictions are applied by this rule because of some *by-design* limitations. +// NDepend mostly analyzes compiled IL code, and the information that +// an enumeration value or a literal constant (which are fields) is used +// is lost in IL code. Hence by default this rule won't match such field. +// + +// +// *Static analysis* cannot provide an *exact* list of dead fields, +// because there are several ways to assign or read a field *dynamically* +// (like through reflection). +// +// For each field matched by this query, first investigate +// if the field is used somehow (like through reflection). +// If the field is really never used, it is important to remove it +// to avoid maintaining a useless code element. +// +// If a field is used somehow, +// but still is matched by this rule, you can tag it with the attribute +// **IsNotDeadCodeAttribute** found in *NDepend.API.dll* +// to avoid matching the field again. +// You can also provide your own attribute for this need, +// but then you'll need to adapt this code rule. +// +// Issues of this rule have a **Debt** equal to 10 minutes because it only +// takes a short while to investigate if a method can be safely discarded. +// The **Annual Interest** of issues of this rule, the annual cost to not +// fix such issue, is set by default to 8 minutes per unused field matched. +//]]> + Wrong usage of IsNotDeadCodeAttribute +warnif count > 0 + +let tAttr = Types.WithFullName("NDepend.Attributes.IsNotDeadCodeAttribute").FirstOrDefault() +where tAttr != null + +// Get types that do a wrong usage of IsNotDeadCodeAttribute +let types = from t in Application.Types where + t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + + ( // types used don't need to be tagged with IsNotDeadCodeAttribute! + t.TypesUsingMe.Count(t1 => + !t.NestedTypes.Contains(t1) && + !t1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) ) > 0 || + + // Static types that define only const fields cannot be seen as used in IL code. + // They don't need to be tagged with IsNotDeadCodeAttribute. + (t.IsStatic && t.NbMethods == 0 && !t.Fields.Where(f => !f.IsLiteral).Any()) + ) + select t + +// Get methods that do a wrong usage of IsNotDeadCodeAttribute +let methods = from m in Application.Methods where + m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + m.MethodsCallingMe.Count(m1 => !m1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch())) > 0 + select m + +// Get fields that do a wrong usage of IsNotDeadCodeAttribute +let fields = from f in Application.Fields where + f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + f.MethodsUsingMe.Count(m1 => !m1.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch())) > 0 + select f + +from member in types.Cast().Concat(methods).Concat(fields) +select new { + member, + Debt = 4.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// The attribute **NDepend.Attributes.IsNotDeadCodeAttribute** +// is defined in *NDepend.API.dll*. This attribute is used +// to mean that a code element is not used directly, but is used +// somehow, like through reflection. +// +// This attribute is used in the dead code rules, +// *Potentially dead Types*, *Potentially dead Methods* +// and *Potentially dead Fields*. +// If you don't want to link *NDepend.API.dll*, you can use +// your own *IsNotDeadCodeAttribute* and adapt the source code of +// this rule, and the source code of the *dead code* rules. +// +// In this context, this code rule matches code elements +// (types, methods, fields) that are tagged with this attribute, +// but still used directly somewhere in the code. +// + +// +// Just remove *IsNotDeadCodeAttribute* tagging of +// types, methods and fields matched by this rule +// because this tag is not useful anymore. +//]]> + + + Methods that could have a lower visibility +warnif count > 0 from m in JustMyCode.Methods where + m.Visibility != m.OptimalVisibility && + + !m.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + !m.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + // If you don't want to link NDepend.API.dll, you can use your own attributes + // and adapt the source code of this rule. + + // methods of serialized type must remain public. + !m.ParentType.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) && + !m.ParentType.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + + // Eliminate public methods visible outside of their assembly + // because the rule cannot know if the developer left the method public + // intentionally or not. + !m.IsPubliclyVisible && + + // Avoid matching public methods declared in a non-public type, + // that could have the visibility internal, because + // such situation is caught by the rule 'Avoid public methods not publicly visible'. + !(m.Visibility == Visibility.Public && + m.ParentType.Visibility != Visibility.Public && + m.OptimalVisibility == Visibility.Internal) && + + // Eliminate default constructor from the result. + // Whatever the visibility of the declaring class, + // default constructors are public and introduce noise + // in the current rule. + !( m.IsConstructor && m.IsPublic && m.NbParameters == 0) && + + // Don't advise to reduce visibility of property getters/setters + // of classes that implement INotifyPropertyChanged + !((m.IsPropertyGetter || m.IsPropertySetter) && + m.ParentType.Implement("System.ComponentModel.INotifyPropertyChanged".AllowNoMatch())) && + + // Don't decrease the visibility of Main() methods. + !m.IsEntryPoint + +select new { + m, + m.Visibility , + CouldBeDeclared = m.OptimalVisibility, + m.MethodsCallingMe, + + Debt = 30.ToSeconds().ToDebt(), // It is fast to change the method visibility + Severity = Severity.Medium +} + +// +// This rule warns about methods that can be declared with a lower visibility +// without provoking any compilation error. +// For example *private* is a visibility lower than *internal* +// which is lower than *public*. +// +// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**. +// The scope from which methods can be called is then reduced to a minimum. +// +// By default, this rule doesn't match publicly visible methods that could have a +// lower visibility because it cannot know if the developer left the method public +// intentionally or not. Public methods matched are declared in non-public types. +// +// By default this rule doesn't match methods with the visibility *public* +// that could be *internal*, declared in a type that is not *public* +// (internal, or nested private for example) because +// this situation is caught by the rule *Avoid public methods not publicly visible*. +// +// Notice that methods tagged with one of the attribute +// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or +// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll* +// are not matched. If you don't want to link *NDepend.API.dll* but still +// wish to rely on this facility, you can declare these attributes in your code. +// + +// +// Declare each matched method with the specified *optimal visibility* +// in the *CouldBeDeclared* rule result column. +// +// By default, this rule matches *public methods*. If you are publishing an API +// many public methods matched should remain public. In such situation, +// you can opt for the *coarse solution* to this problem by adding in the +// rule source code *&& !m.IsPubliclyVisible* or you can prefer the +// *finer solution* by tagging each concerned method with +// *CannotDecreaseVisibilityAttribute*. +//]]> + Types that could have a lower visibility +warnif count > 0 from t in JustMyCode.Types where + + t.Visibility != t.OptimalVisibility && + + // If you don't want to link NDepend.API.dll, you can use your own attributes + // and adapt the source code of this rule. + !t.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + !t.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + + // XML serialized type must remain public. + !t.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) && + + // Eliminate public types visible outside of their assembly + // because the rule cannot know if the developer left the type public + // intentionally or not. + !t.IsPubliclyVisible && + + // Static types that define only const fields cannot be seen as used in IL code. + // They don't have to be tagged with CannotDecreaseVisibilityAttribute. + !( t.IsStatic && + !t.Methods.Any(m => !m.IsClassConstructor) && + !t.Fields.Any(f => !f.IsLiteral && !(f.IsStatic && f.IsInitOnly))) && + + // A type used by an interface that has the same visibility + // cannot have its visibility decreased, else a compilation error occurs! + !t.TypesUsingMe.Any(tUser => + tUser.IsInterface && + tUser.Visibility == t.Visibility) && + + // Don't change the visibility of a type that contain an entry point method. + !t.Methods.Any(m =>m.IsEntryPoint) + +select new { + t, + t.Visibility , + CouldBeDeclared = t.OptimalVisibility, + t.TypesUsingMe, + + Debt = 30.ToSeconds().ToDebt(), // It is fast to change the method visibility + Severity = Severity.Medium +} + +// +// This rule warns about types that can be declared with a lower visibility +// without provoking any compilation error. +// For example *private* is a visibility lower than *internal* +// which is lower than *public*. +// +// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**. +// The scope from which types can be consumed is then reduced to a minimum. +// +// By default, this rule doesn't match publicly visible types that could have +// a lower visibility because it cannot know if the developer left the type public +// intentionally or not. Public types matched are nested in non-public types. +// +// Notice that types tagged with one of the attribute +// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or +// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll* +// are not matched. If you don't want to link *NDepend.API.dll* but still +// wish to rely on this facility, you can declare these attributes in your code. +// + +// +// Declare each matched type with the specified *optimal visibility* +// in the *CouldBeDeclared* rule result column. +// +// By default, this rule matches *public types*. If you are publishing an API +// many public types matched should remain public. In such situation, +// you can opt for the *coarse solution* to this problem by adding in the +// rule source code *&& !m.IsPubliclyVisible* or you can prefer the +// *finer solution* by tagging each concerned type with +// *CannotDecreaseVisibilityAttribute*. +//]]> + Fields that could have a lower visibility +warnif count > 0 from f in JustMyCode.Fields where + f.Visibility != f.OptimalVisibility && + !f.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + !f.HasAttribute("NDepend.Attributes.IsNotDeadCodeAttribute".AllowNoMatch()) && + // If you don't want to link NDepend.API.dll, you can use your own attributes + // and adapt the source code of this rule. + + // XML serialized fields must remain public. + !f.HasAttribute("System.Xml.Serialization.XmlElementAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Xml.Serialization.XmlArrayAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Xml.Serialization.XmlArrayItemAttribute".AllowNoMatch()) && + + // Don't check for serialized fields visibility + !f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) && + + // Eliminate public fields visible outside of their assembly + // because the rule cannot know if the developer left the field public + // intentionally or not. + !f.IsPubliclyVisible + +select new { + f, + f.Visibility , + CouldBeDeclared = f.OptimalVisibility, + f.MethodsUsingMe, + + Debt = 30.ToSeconds().ToDebt(), // It is fast to change the field visibility + Severity = Severity.Medium +} + +// +// This rule warns about fields that can be declared with a lower visibility +// without provoking any compilation error. +// For example *private* is a visibility lower than *internal* +// which is lower than *public*. +// +// **Narrowing visibility** is a good practice because doing so **promotes encapsulation**. +// The scope from which fields can be consumed is then reduced to a minimum. +// +// By default, this rule doesn't match publicly visible fields that could have a +// lower visibility because it cannot know if the developer left the field public +// intentionally or not. Public fields matched are declared in non-public types. +// +// Notice that fields tagged with one of the attribute +// *NDepend.Attributes.CannotDecreaseVisibilityAttribute* or +// *NDepend.Attributes.IsNotDeadCodeAttribute*, found in *NDepend.API.dll* +// are not matched. If you don't want to link *NDepend.API.dll* but still +// wish to rely on this facility, you can declare these attributes in your code. +// + +// +// Declare each matched field with the specified *optimal visibility* +// in the *CouldBeDeclared* rule result column. +// +// By default, this rule matches *public fields*. If you are publishing an API +// some public fields matched should remain public. In such situation, +// you can opt for the *coarse solution* to this problem by adding in the +// rule source code *&& !m.IsPubliclyVisible* or you can prefer the +// *finer solution* by tagging eah concerned field with +// *CannotDecreaseVisibilityAttribute*. +//]]> + Types that could be declared as private, nested in a parent type + +warnif count > 0 +from t in JustMyCode.Types +where !t.IsGeneratedByCompiler && + !t.IsNested && + !t.IsPubliclyVisible && + !t.IsEnumeration && + // Only one type user… + t.TypesUsingMe.Count() == 1 + +let couldBeNestedIn = t.TypesUsingMe.Single() +where !couldBeNestedIn.IsGeneratedByCompiler && + !couldBeNestedIn.IsInterface && // Cannot nest a type in an interface + // …declared in the same namespace + couldBeNestedIn.ParentNamespace == t.ParentNamespace && + + // Don't advise to move a base class + // or an interface into one of its child type. + !couldBeNestedIn.DeriveFrom(t) && + !couldBeNestedIn.Implement(t) + + // Require that t doesn't contain any extension method. + // Types with extension methods cannot be nested. +where t.Methods.All(m => !m.IsExtensionMethod) + +select new { + t, + couldBeNestedIn, + Debt = 3.ToMinutes().ToDebt(), // It is fast to nest a type into another one + Severity = Severity.Low // This rule proposes advices, not potential problems +} + +// +// This rule matches types that can be potentially +// *nested* and declared *private* into another type. +// +// The conditions for a type to be potentially nested +// into a *parent type* are: +// +// • the *parent type* is the only type consuming it, +// +// • the type and the *parent type* are declared in the same namespace. +// +// Declaring a type as private into a parent type **promotes encapsulation**. +// The scope from which the type can be consumed is then reduced to a minimum. +// +// This rule doesn't match classes with extension methods +// because such class cannot be nested in another type. +// + +// +// Nest each matched *type* into the specified *parent type* and +// declare it as private. +// +// However *nested private types* are hardly testable. Hence this rule +// might not be applied to types consumed directly by tests. +//]]> + Avoid publicly visible constant fields +warnif count > 0 +from f in JustMyCode.Fields +where f.IsLiteral && + f.IsPubliclyVisible && + !f.IsEnumValue +select new { + f, + + Debt = 30.ToSeconds().ToDebt(), // It is fast to update field declaration + Severity = Severity.Medium +} + +// +// This rule warns about constant fields that are visible outside their +// parent assembly. Such field, when used from outside its parent assembly, +// has its constant value *hard-coded* into the client assembly. +// Hence, when changing the field's value, it is *mandatory* to recompile +// all assemblies that consume the field, else the program will run +// with different constant values in-memory. Certainly in such situation +// bugs are lurking. +// + +// +// Declare matched fields as **static readonly** instead of **constant**. +// This way, the field value is *safely changeable* without the need to +// recompile client assemblies. +// +// Notice that enumeration value fields suffer from the same *potential +// pitfall*. But enumeration values cannot be declared as +// *static readonly* hence the rule comes with the condition +// **&& !f.IsEnumValue** to avoid matching these. Unless you decide +// to banish public enumerations, just let the rule *as is*. +//]]> + Fields should be declared as private +warnif count > 0 from f in JustMyCode.Fields where + !f.IsPrivate && + + // These conditions filter cases where fields + // doesn't represent state that should be encapsulated. + !f.IsGeneratedByCompiler && + !f.IsSpecialName && + !f.IsInitOnly && + !f.IsLiteral && + !f.IsEnumValue && + !f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) + +// A non-private field assigned from outside its class, +// usually leads to complicated field state management. +let outsideMethodsAssigningMe = + f.MethodsAssigningMe.Where(m => m.ParentType != f.ParentType) + +select new { + f, + f.Visibility, + outsideMethodsAssigningMe, + + Debt = (60+20*outsideMethodsAssigningMe.Count()).ToSeconds().ToDebt(), + // The cost to leave such issue unfixed is higher if the field is publicly visible! + AnnualInterest = Severity.Medium.AnnualInterestThreshold() * (f.IsPubliclyVisible ? 3 : 1) +} + +// +// This rule matches **non-private and mutable fields**. +// *Mutable* means that the field value can be modified. +// Typically mutable fields are *non-constant*, +// *non-readonly* fields. +// +// Fields should be considered as **implementation details** +// and as a consequence they should be declared as private. +// +// If something goes wrong with a *non-private field*, +// the culprit can be anywhere, and so in order to track down +// the bug, you may have to look at quite a lot of code. +// +// A private field, by contrast, can only be assigned from +// inside the same class, so if something goes wrong with that, +// there is usually only one source file to look at. +// +// Issues of this rule are fast to get fixed, and they have +// a debt proportional to the number of methods assigning +// the field. +// + +// +// Declare a matched mutable field as *private*, or declare it +// as *readonly*. +// +// If code outside the type needs to access the field +// you can encapsulate the field accesses in a read-write property. +// At least with a read-write property you can set a debug breakpoint +// on the property setter, which makes easier to track write-accesses +// in case of problem. +// +]]> + Constructors of abstract classes should be declared as protected or private +warnif count > 0 +from t in Application.Types where + t.IsClass && + t.IsAbstract +let ctors = t.Constructors.Where(c => !c.IsProtected && !c.IsPrivate) +where ctors.Count() > 0 +select new { + t, + ctors, + + Debt = 30.ToSeconds().ToDebt(), + Severity = Severity.Medium +} + +// +// Constructors of abstract classes can only be called from derived +// classes. +// +// Because a public constructor is creating instances of its class, +// and because it is forbidden to create instances of an *abstract* class, +// an abstract class with a public constructor is wrong design. +// +// Notice that when the constructor of an abstract class is private, +// it means that derived classes must be nested in the abstract class. +// + +// +// To fix a violation of this rule, +// either declare the constructor as *protected*, +// or do not declare the type as *abstract*. +//]]> + Avoid public methods not publicly visible + +warnif count > 0 +from m in JustMyCode.Methods where + !m.IsPubliclyVisible && m.IsPublic && + + // Eliminate virtual methods + !m.IsVirtual && + // Eliminate interface and delegate types + !m.ParentType.IsInterface && + !m.ParentType.IsDelegate && + // Eliminate default constructors + !(m.IsConstructor && m.NbParameters == 0) && + // Eliminate operators that must be declared public + !m.IsOperator && + // Eliminate methods generated by compiler, except auto-property getter/setter + (!m.IsGeneratedByCompiler || m.IsPropertyGetter || m.IsPropertySetter) && + + // Don't advise to reduce visibility of property getters/setters + // of classes that implement INotifyPropertyChanged + !((m.IsPropertyGetter || m.IsPropertySetter) && + m.ParentType.Implement("System.ComponentModel.INotifyPropertyChanged".AllowNoMatch())) + +let calledOutsideParentType = + m.MethodsCallingMe.FirstOrDefault(mCaller => mCaller.ParentType != m.ParentType) != null + +select new { + m, + parentTypeVisibility = m.ParentType.Visibility, + declareMethodAs = (Visibility) (calledOutsideParentType ? Visibility.Internal : Visibility.Private), + methodsCaller = m.MethodsCallingMe, + + Debt = 30.ToSeconds().ToDebt(), + Severity = Severity.Low +} + +// +// This rule warns about methods declared as *public* +// whose parent type is not declared as *public*. +// +// In such situation *public* means, *can be accessed +// from anywhere my parent type is visible*. Some +// developers think this is an elegant language construct, +// some others find it misleading. +// +// This rule can be deactivated if you don't agree with it. +// Read the whole debate here: +// http://ericlippert.com/2014/09/15/internal-or-public/ +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +// + +// +// Declare the method as *internal* if it is used outside of +// its type, else declare it as *private*. +//]]> + Event handler methods should be declared as private or protected +warnif count > 0 +from m in Application.Methods where + !(m.IsPrivate || m.IsProtected) && + !m.IsGeneratedByCompiler && + + // A method is considered as an event handler if… + m.NbParameters == 2 && // … it has two parameters … + m.Name.Contains("Object") && // … of types Object … + m.Name.Contains("EventArgs") && // … and EventArgs + + // Discard special cases + !m.ParentType.IsDelegate && + !m.IsGeneratedByCompiler + +select new { + m, + m.Visibility, + Debt = 2.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Think of a event handler like for example *OnClickButton()*. +// Typically such method must be declared as *private* +// and shouldn't be called in other context than event firing. +// +// Such method can also be declared as *protected* because +// some designers such as the ASP.NET designer, generates such method +// as *protected* to let a chance to sub-classes to call it. +// + +// +// If you have the need that event handler method should be called +// from another class, then find a code structure that more +// closely matches the concept of what you're trying to do. +// Certainly you don't want the other class to click a button; you +// want the other class to do something that clicking a button +// also do. +//]]> + Wrong usage of CannotDecreaseVisibilityAttribute +warnif count > 0 + +let tAttr = Types.WithFullName("NDepend.Attributes.CannotDecreaseVisibilityAttribute").FirstOrDefault() +where tAttr != null + +// Get types that do a wrong usage of CannotDecreaseVisibilityAttribute +let types = from t in Application.Types where + t.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + ( t.Visibility == t.OptimalVisibility || + + // Static types that define only const fields cannot be seen as used in IL code. + // They don't need to be tagged with CannotDecreaseVisibilityAttribute. + (t.IsStatic && t.NbMethods == 0 && !t.Fields.Any(f => !f.IsLiteral)) + ) + select t + +// Get methods that do a wrong usage of CannotDecreaseVisibilityAttribute +let methods = from m in Application.Methods where + m.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + m.Visibility == m.OptimalVisibility + select m + +// Get fields that do a wrong usage of CannotDecreaseVisibilityAttribute +let fields = from f in Application.Fields where + f.HasAttribute("NDepend.Attributes.CannotDecreaseVisibilityAttribute".AllowNoMatch()) && + f.Visibility == f.OptimalVisibility + select f + +from member in types.Cast().Concat(methods).Concat(fields) +select new { + member, + Debt = 30.ToSeconds().ToDebt(), + Severity = Severity.Low +} + +// +// The attribute **NDepend.Attributes.CannotDecreaseVisibilityAttribute** +// is defined in *NDepend.API.dll*. If you don't want to reference +// *NDepend.API.dll* you can declare it in your code. +// +// Usage of this attribute means that a code element visibility is not +// optimal (it can be lowered like for example from *public* to *internal*) +// but shouldn’t be modified anyway. Typical reasons to do so include: +// +// • Public code elements consumed through reflection, through a mocking +// framework, through XML or binary serialization, through designer, +// COM interfaces… +// +// • Non-private code element invoked by test code, that would be difficult +// to reach from test if it was declared as *private*. +// +// In such situation *CannotDecreaseVisibilityAttribute* is used to avoid +// that default rules about not-optimal visibility warn. Using this +// attribute can be seen as an extra burden, but it can also be seen as +// an opportunity to express in code: **Don't change the visibility else +// something will be broken** +// +// In this context, this code rule matches code elements +// (types, methods, fields) that are tagged with this attribute, +// but still already have an optimal visibility. +// + +// +// Just remove *CannotDecreaseVisibilityAttribute* tagging of +// types, methods and fields matched by this rule +// because this tag is not useful anymore. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Methods that should be declared as 'public' in C#, 'Public' in VB.NET +from m in Application.Methods where + m.ShouldBePublic +let usedInAssemblies = m.MethodsCallingMe.ParentAssemblies().Except(m.ParentAssembly) +select new { + m, + m.ParentAssembly, + usedInAssemblies, + m.MethodsCallingMe +} + +// +// This code query lists methods that *should* be declared +// as *public*. Such method is actually declared as *internal* +// and is consumed from outside its parent assembly +// thanks to the attribute +// *System.Runtime.CompilerServices.InternalsVisibleToAttribute*. +// +// This query relies on the property +// *NDepend.CodeModel.IMember.ShouldBePublic* +// https://www.ndepend.com/api/webframe.html?NDepend.API~NDepend.CodeModel.IMember~ShouldBePublic.html +// +// This is just a code query, it is not intended to advise +// you to declare the method as *public*, but to inform you +// that the code actually relies on the peculiar behavior +// of the attribute *InternalsVisibleToAttribute*. +//]]> + + + Fields should be marked as ReadOnly when possible +warnif count > 0 +from f in JustMyCode.Fields where + f.IsImmutable && + !f.IsInitOnly && // The condition IsInitOnly matches fields that + // are marked with the C# readonly keyword + // (ReadOnly in VB.NET). + !f.IsGeneratedByCompiler && + !f.IsEventDelegateObject && + !f.ParentType.IsEnumeration && + !f.IsLiteral && + + // Don't warn if a method using the field is also calling a method that has 'ref' and 'out' parameters. + // This could lead to false positive. A field used in a 'ref' or 'out' parameter cannot be set as read-only. + f.MethodsUsingMe.SelectMany(m => m.MethodsCalled).FirstOrDefault(m => m.Name.Contains("&")) == null + +select new { + f, + f.MethodsReadingMeButNotAssigningMe, + f.MethodsAssigningMe, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about instance and static fields that +// can be declared as **readonly**. +// +// This source code of this rule is based on the conditon +// *IField.IsImmutable*. +// https://www.ndepend.com/api/webframe.html?NDepend.API~NDepend.CodeModel.IField~IsImmutable.html +// +// A field that matches the condition *IsImmutable* +// is a field that is assigned only by constructors +// of its class. +// +// For an *instance field*, this means its value +// will remain constant through the lifetime +// of the object. +// +// For a *static field*, this means its value will +// remain constant through the lifetime of the +// program. +// + +// +// Declare the field with the C# *readonly* keyword +// (*ReadOnly* in VB.NET). This way the intention +// that the field value shouldn't change is made +// explicit. +//]]> + Avoid non-readonly static fields +warnif count > 0 +from f in Application.Fields +where f.IsStatic && + !f.IsEnumValue && + !f.IsGeneratedByCompiler && + !f.IsLiteral && + !f.IsInitOnly + +let methodAssigningField = f.MethodsAssigningMe + +select new { + f, + methodAssigningField, + Debt = (2+8*methodAssigningField.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about static fields that are not +// declared as read-only. +// +// In *Object-Oriented-Programming* the natural artifact +// to hold states that can be modified is **instance fields**. +// Such mutable static fields create *confusion* about +// the expected state at runtime and impairs the code +// testability since the same mutable state is re-used for +// each test. +// +// More discussion on the topic can be found here: +// http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/ +// + +// +// If the *static* field is just assigned once in the program +// lifetime, make sure to declare it as *readonly* and assign +// it inline, or from the static constructor. +// +// Else if methods other than the static constructor need to +// assign the state hold by the static field, refactoring must +// occur to ensure that this state is hold through an instance +// field. +//]]> + Avoid static fields with a mutable field type +warnif count > 0 +from f in Application.Fields +where f.IsStatic && + !f.IsEnumValue && + !f.IsGeneratedByCompiler && + !f.IsLiteral + +let fieldType = f.FieldType +where fieldType != null && + !fieldType.IsThirdParty && + !fieldType.IsInterface && + !fieldType.IsImmutable + +select new { + f, + mutableFieldType = fieldType , + isFieldImmutable = f.IsImmutable ? "Immutable" : "Mutable", + isFieldReadOnly = f.IsInitOnly ? "ReadOnly" : "Not ReadOnly", + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about static fields whose field type +// is mutable. In such case the static field is +// holding a state that can be modified. +// +// In *Object-Oriented-Programming* the natural artifact +// to hold states that can be modified is **instance fields**. +// Hence such static fields create *confusion* about +// the expected state at runtime. +// +// More discussion on the topic can be found here: +// http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/ +// + +// +// To fix violations of this rule, make sure to +// hold mutable states through objects that are passed +// **explicitly** everywhere they need to be consumed, in +// opposition to mutable object hold by a static field that +// makes it modifiable from a bit everywhere in the program. +//]]> + Structures should be immutable +warnif count > 0 from t in JustMyCode.Types where + t.IsStructure && + !t.IsImmutable + +let mutableFields = t.Fields.Where(f => !f.IsImmutable) + +select new { + t, + t.NbLinesOfCode, + mutableFields, + Debt = (3+2*mutableFields.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// An object is immutable if its state doesn’t change once the +// object has been created. Consequently, a structure or a class +// is immutable if its instances fields are only assigned inline +// or from constructor(s). +// +// But for structure it is a bit different. **Structures are value +// types** which means instances of structures are copied when they are +// passed around (like through a method argument). +// +// So if you change a copy you are changing only that copy, not the +// original and not any other copies which might be around. Such +// situation is very different than what happen with instances of +// classes. Hence developers are not used to work with modified values +// and doing so introduces *confusion* and is *error-prone*. +// + +// +// Make sure matched structures are immutable. This way, all +// automatic copies of an original instance, resulting from being +// *passed by value* will hold the same values and there will be +// no surprises. +// +// If your structure is immutable then if you want to change +// a value, you have to consciously do it by creating a new instance +// of the structure with the modified data. +//]]> + Property Getters should be immutable +warnif count > 0 from m in Application.Methods where + !m.IsGeneratedByCompiler && + m.IsPropertyGetter && + ( ( !m.IsStatic && m.ChangesObjectState) || + ( m.IsStatic && m.ChangesTypeState) ) + +let propertyName = m.SimpleName.Substring(4,m.SimpleName.Length-4) +let setterSimpleName = "set_" + propertyName + + +let fieldsAssigned = m.FieldsAssigned.Where(f => + + // Don't count field that have a name similar to the property name + // to avoid matching lazy initialization situations. + !(propertyName.Length >= 4 && + f.SimpleName.Length >= 4 && + // Don't count the first 3 characters of the field name, + // to avoid special field name formatting like 'm_X' or '_x' + propertyName.EndsWith(f.SimpleName.Substring(3, f.SimpleName.Length -3))) + && + + // Don't count field that are assigned only by the property getter and the related property setter. + f.MethodsAssigningMe.Any(m1 => m1 != m && + !(m1.IsPropertySetter && m1.SimpleName == setterSimpleName))) +where fieldsAssigned.Any() +let otherMethodsAssigningSameFields = fieldsAssigned.SelectMany(f => f.MethodsAssigningMe.Where(m1 => m1 != m)) + +select new { + m, + m.NbLinesOfCode, + fieldsAssigned, + otherMethodsAssigningSameFields, + Debt = (2 + 5*fieldsAssigned.Count() + 5*otherMethodsAssigningSameFields.Count() ).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// It is not expected that a state gets modified when +// accessing a property getter. Hence doing so create +// confusion and property getters should be pure methods, +// they shouldn't assign any field. +// +// This rule doesn't match property getters that assign a field +// not assigned by any other methods than the getter itself +// and the corresponding property setter. Hence this rule avoids +// matching *lazy initialization at first access* of a state. +// In such situation the getter assigns a field at first access +// and from the client point of view, lazy initialization +// is an invisible implementation detail. +// +// A field assigned by a property with a name similar to the +// property name are not count either, also to avoid matching +// *lazy initialization at first access* situations. +// + +// +// Make sure that matched property getters don't assign any +// field. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 2 minutes plus 5 minutes per field assigned and +// 5 minutes per other method assigning such field. +//]]> + A field must not be assigned from outside its parent hierarchy types + +warnif count > 0 +from f in JustMyCode.Fields.Where(f => + (f.IsInternal || f.IsPublic) && + !f.IsGeneratedByCompiler && + !f.IsImmutable && + !f.IsEnumValue) + +let methodsAssignerOutsideOfMyType = f.MethodsAssigningMe.Where( + m =>!m.IsGeneratedByCompiler && + m.ParentType != f.ParentType && + !m.ParentType.DeriveFrom(f.ParentType) ) + +where methodsAssignerOutsideOfMyType.Any() + +select new { + f, + methodsAssignerOutsideOfMyType, + Debt = (5*methodsAssignerOutsideOfMyType.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is related to the rule *Fields should be declared as +// private*. It matches any **public or internal, mutable field** +// that is assigned from outside its parent class and subclasses. +// +// Fields should be considered as **implementation details** +// and as a consequence they should be declared as private. +// +// If something goes wrong with a *non-private field*, +// the culprit can be anywhere, and so in order to track down +// the bug, you may have to look at quite a lot of code. +// +// A private field, by contrast, can only be assigned from +// inside the same class, so if something goes wrong with that, +// there is usually only one source file to look at. +// + +// +// Matched fields must be declared as *protected* and even better +// as *private*. +// +// Alternatively, if the field can reference immutable states, +// it can remain visible from the outside, but then must be +// declared as *readonly*. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 5 minutes per method outside the parent hierarchy +// that assigns the matched field. +//]]> + Don't assign a field from many methods + +warnif count > 0 +from f in JustMyCode.Fields where + !f.IsEnumValue && + !f.IsImmutable && + !f.IsInitOnly && + !f.IsGeneratedByCompiler && + !f.IsEventDelegateObject + +let methodsAssigningMe = f.MethodsAssigningMe.Where(m => !m.IsConstructor) + +// The threshold 4 is arbitrary and it should avoid matching too many fields. +// Threshold is even lower for static fields because this reveals situations even more complex. +where methodsAssigningMe.Count() >= (!f.IsStatic ? 4 : 2) + +select new { + f, + methodsAssigningMe, + f.MethodsReadingMeButNotAssigningMe, + f.MethodsUsingMe, + Debt = (4+(f.IsStatic ? 10 : 5)).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// A field assigned from many methods is a symptom of **bug-prone code**. +// Notice that: +// +// • For an instance field, constructor(s) of its class that assign the field are not counted. +// +// • For a static field, the class constructor that assigns the field is not counted. +// +// The default threshold for *instance fields* is equal to *4 or more than 4 methods +// assigning the instance field*. Such situation makes harder to anticipate the +// field state at runtime. The code is then complicated to read, hard to debug +// and hard to maintain. Hard-to-solve bugs due to corrupted state are often the +// consequence of fields *anarchically assigned*. +// +// The situation is even more complicated if the field is *static*. +// Indeed, such situation potentially involves global random accesses from +// various parts of the application. This is why this rule provides a lower +// threshold equals to *2 or more than 2 methods assigning the static field*. +// +// If the object containing such field is meant to be used from multiple threads, +// there are **alarming chances** that the code is unmaintainable and bugged. +// When multiple threads are involved, the rule of thumb is to use immutable objects. +// +// If the field type is a reference type (interfaces, classes, strings, delegates) +// corrupted state might result in a *NullReferenceException*. +// If the field type is a value type (number, boolean, structure) +// corrupted state might result in wrong result not even signaled by an exception +// thrown. +// + +// +// There is no straight advice to refactor the number of methods responsible +// for assigning a field. Sometime the situation is simple enough, like when +// a field that hold an indentation state is assigned by many writer methods. +// Such situation only requires to define two methods *IndentPlus()/IndentMinus()* +// that assign the field, called from all writers methods. +// +// Sometime the solution involves rethinking and then rewriting +// a complex algorithm. Such field can sometime become just a variable accessed +// locally by a method or a *closure*. Sometime, just rethinking the life-time +// and the role of the parent object allows the field to become immutable +// (i.e assigned only by the constructor). +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 4 minutes plus 5 minutes per method assigning the instance field +// or 10 minutes per method assigning the static field. +//]]> + Do not declare read only mutable reference types +warnif count > 0 +from f in JustMyCode.Fields where + f.IsInitOnly && + !f.ParentType.IsPrivate && + !f.IsPrivate && + f.FieldType != null && + f.FieldType.IsClass && + !f.FieldType.IsThirdParty && + !f.FieldType.IsImmutable +select new { + f, + f.FieldType, + FieldVisibility = f.Visibility, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule is violated when a *public* or *internal* +// type contains a *public* or *internal* read-only field +// whose field type is a mutable reference type. +// +// This situation gives the false impression that the +// value can't change, when actually it's only the field +// value that can't change, but the object state +// can still change. +// + +// +// To fix a violation of this rule, +// replace the field type with an immutable type, +// or declare the field as *private*. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Array fields should not be read only +warnif count > 0 +from f in Application.Fields where + f.IsInitOnly && + f.IsPubliclyVisible && + f.FieldType != null && + f.FieldType.FullName == "System.Array" +select new { + f, + FieldVisibility = f.Visibility, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This rule is violated when a publicly visible field +// that holds an array, is declared read-only. +// +// This situation represents a *security vulnerability*. +// Because the field is read-only it cannot be changed to refer +// to a different array. However, the elements of the array +// that are stored in a read-only field can be changed. +// Code that makes decisions or performs operations that are +// based on the elements of a read-only array that can be publicly +// accessed might contain an exploitable security vulnerability. +// + +// +// To fix the security vulnerability that is identified by +// this rule do not rely on the contents of a read-only array +// that can be publicly accessed. It is strongly recommended +// that you use one of the following procedures: +// +// • Replace the array with a strongly typed collection +// that cannot be changed. See for example: +// *System.Collections.Generic.IReadOnlyList* ; +// *System.Collections.Generic.IReadOnlyCollection* ; +// *System.Collections.ReadOnlyCollectionBase* +// +// • Or replace the public field with a method that returns a clone +// of a private array. Because your code does not rely on +// the clone, there is no danger if the elements are modified. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Types tagged with ImmutableAttribute must be immutable +warnif count > 0 +from t in Application.Types where + t.HasAttribute ("NDepend.Attributes.ImmutableAttribute".AllowNoMatch()) && + !t.IsImmutable +let culpritFields = t.Fields.Where(f => !f.IsStatic && !f.IsImmutable) +select new { + t, + culpritFields, + Debt = (5+10*culpritFields.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// An object is immutable if its state doesn’t change once the +// object has been created. Consequently, a structure or a class +// is immutable if its instances fields are only assigned inline +// or from constructor(s). +// +// An attribute **NDepend.Attributes.ImmutableAttribute** can be +// used to express in code that a type is immutable. In such +// situation, the present code rule checks continuously that the +// type remains immutable whatever the modification done. +// +// This rule warns when a type that is tagged with +// *ImmutableAttribute* is actually not immutable anymore. +// +// Notice that *FullCoveredAttribute* is defined in *NDepend.API.dll* +// and if you don't want to link this assembly, you can create your +// own *FullCoveredAttribute* and adapt the rule. +// + +// +// First understand which modification broke the type immutability. +// The list of *culpritFields* provided in this rule result can help. +// Then try to refactor the type to bring it back to immutability. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 5 minutes plus 10 minutes per culprit field. +//]]> + Types immutable should be tagged with ImmutableAttribute + +// warnif count > 0 <-- not a code rule per default + +from t in Application.Types where + !t.HasAttribute ("NDepend.Attributes.ImmutableAttribute".AllowNoMatch()) && + t.IsImmutable +select new { + t, + t.NbLinesOfCode, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// An object is immutable if its state doesn’t change once the +// object has been created. Consequently, a structure or a class +// is immutable if its instances fields are only assigned inline +// or from constructor(s). +// +// This code query lists immutable type that are not tagged with +// an **ImmutableAttribute**. By using such attribute, you can express +// in source code the intention that a class is immutable, and +// should remain immutable in the future. Benefits of using +// an **ImmutableAttribute** are twofold: +// +// • Not only the intention is expressed in source code, +// +// • but it is also continuously checked by the rule +// *Types tagged with ImmutableAttribute must be immutable*. +// + +// +// Just tag types matched by this code query with **ImmutableAttribute** +// that can be found in *NDepend.API.dll*, +// or by an attribute of yours defined in your own code +// (in which case this code query must be adapted). +//]]> + Methods tagged with PureAttribute must be pure + +warnif count > 0 +from m in Application.Methods where + ( m.HasAttribute ("NDepend.Attributes.PureAttribute".AllowNoMatch()) || + m.HasAttribute ("System.Diagnostics.Contract.PureAttribute".AllowNoMatch()) ) && + ( m.ChangesObjectState || m.ChangesTypeState ) && + m.NbLinesOfCode > 0 + +let fieldsAssigned = m.FieldsAssigned + +select new { + m, + m.NbLinesOfCode, + fieldsAssigned, + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// A method is pure if its execution doesn’t change +// the value of any instance or static field. +// A pure method is just a **function** that output +// a result from inputs. +// Pure methods naturally simplify code by **limiting +// side-effects**. +// +// An attribute **PureAttribute** can be +// used to express in code that a method is pure. In such +// situation, the present code rule checks continuously that the +// method remains pure whatever the modification done. +// +// This rule warns when a method that is tagged with +// *PureAttribute* is actually not pure anymore. +// +// Notice that *NDepend.Attributes.PureAttribute* is defined +// in *NDepend.API.dll* and if you don't want to link this +// assembly, you can also use +// *System.Diagnostics.Contract.PureAttribute* +// or create your own *PureAttribute* and adapt the rule. +// +// Notice that *System.Diagnostics.Contract.PureAttribute* is +// taken account by the compiler only when the VS project has +// Microsoft Code Contract enabled. +// + +// +// First understand which modification broke the method purity. +// Then refactor the method to bring it back to purity. +//]]> + Pure methods should be tagged with PureAttribute + +// warnif count > 0 <-- not a code rule per default + +from m in Application.Methods where + !m.IsGeneratedByCompiler && + !m.HasAttribute ("NDepend.Attributes.PureAttribute".AllowNoMatch()) && + !m.HasAttribute ("System.Diagnostics.Contract.PureAttribute".AllowNoMatch()) && + !m.ChangesObjectState && !m.ChangesTypeState && + m.NbLinesOfCode > 0 +select new { + m, + m.NbLinesOfCode, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// A method is pure if its execution doesn’t change +// the value of any instance or static field. +// Pure methods naturally simplify code by **limiting +// side-effects**. +// +// This code query lists pure methods that are not tagged with +// a **PureAttribute**. By using such attribute, you can express +// in source code the intention that a method is pure, and +// should remain pure in the future. Benefits of using +// a **PureAttribute** are twofold: +// +// • Not only the intention is expressed in source code, +// +// • but it is also continuously checked by the rule +// *Methods tagged with PureAttribute must be pure*. +// +// This code query is not by default defined as a code rule +// because certainly many of the methods of the code base +// are matched. Hence fixing all matches and then +// maintaining the rule unviolated might require a lot of +// work. This may *counter-balance* such rule benefits. +// + +// +// Just tag methods matched by this code query with +// *NDepend.Attributes.PureAttribute* +// that can be found in *NDepend.API.dll*, +// or with *System.Diagnostics.Contract.PureAttribute*, +// or with an attribute of yours defined in your own code +// (in which case this code query must be adapted). +// +// Notice that *System.Diagnostics.Contract.PureAttribute* is +// taken account by the compiler only when the VS project has +// Microsoft Code Contract enabled. +//]]> + + + Instance fields naming convention +warnif count > 0 from f in JustMyCode.Fields where + !( + // Instance field name starting with a lower-case letter + (f.Name.Length >= 1 && char.IsLetter(f.Name[0]) && char.IsLower(f.Name[0])) || + + // Instance field name starting with "_" followed with a lower-case letter + (f.Name.Length >= 2 && f.Name.StartsWith("_") && char.IsLetter(f.Name[1]) && char.IsLower(f.Name[1])) || + + // Instance field name starting with "m_" followed with an upper-case letter + (f.Name.Length >= 3 && f.Name.StartsWith("m_") && char.IsLetter(f.Name[2]) && char.IsUpper(f.Name[2])) + + ) && + !f.IsStatic && + !f.IsLiteral && + !f.IsGeneratedByCompiler && + !f.IsSpecialName && + !f.IsEventDelegateObject && + + // Don't check naming convention on serializable fields + !f.HasAttribute("System.Xml.Serialization.XmlAttributeAttribute".AllowNoMatch()) && + !f.HasAttribute("System.Runtime.Serialization.DataMemberAttribute".AllowNoMatch()) && + + // Don't warn if a method using the field is also calling a method that has 'ref' and 'out' parameters. + // This could lead to false positive. A field used in a 'ref' or 'out' parameter cannot be set as read-only. + f.MethodsUsingMe.SelectMany(m => m.MethodsCalled).FirstOrDefault(m => m.Name.Contains("&")) == null + +select new { + f, + f.SizeOfInst, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// By default the presents rule supports the 3 most used naming +// conventions for instance fields: +// +// • Instance field name starting with a lower-case letter +// +// • Instance field name starting with "_" followed with a lower-case letter +// +// • Instance field name starting with "m_" followed with an upper-case letter +// +// The rule can be easily adapted to your own company naming convention. +// +// In terms of behavior, a *static field* is something completely different +// than an *instance field*, so it is interesting to differentiate them at +// a glance through a naming convetion. +// +// This is why it is advised to use a specific naming convention for instance +// field like name that starts with **m_**. +// +// Related discussion: +// http://codebetter.com/patricksmacchia/2013/09/04/on-hungarian-notation-for-instance-vs-static-fields-naming/ +// + +// +// Once the rule has been adapted to your own naming convention +// make sure to name all matched instance fields adequately. +//]]> + Static fields naming convention +warnif count > 0 from f in JustMyCode.Fields where + !( + // Static field name starting with an upper-case letter + (f.Name.Length >= 1 && char.IsLetter(f.Name[0]) && char.IsUpper(f.Name[0])) || + + // Static field name starting with "_" followed with an upper-case letter + (f.Name.Length >= 2 && f.Name.StartsWith("_") && char.IsLetter(f.Name[1]) && char.IsUpper(f.Name[1])) || + + // Static field name starting with "s_" followed with an upper-case letter + (f.Name.Length >= 3 && f.Name.StartsWith("s_") && char.IsLetter(f.Name[2]) && char.IsUpper(f.Name[2])) + + ) && + f.IsStatic && + !f.IsLiteral && + !f.IsGeneratedByCompiler && + !f.IsSpecialName && + !f.IsEventDelegateObject +select new { + f, + f.SizeOfInst, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// By default the presents rule supports the 3 most used naming +// conventions for static fields: +// +// • Static field name starting with an upper-case letter +// +// • Static field name starting with "_" followed with an upper-case letter +// +// • Static field name starting with "s_" followed with an upper-case letter +// +// The rule can be easily adapted to your own company naming convention. +// +// In terms of behavior, a *static field* is something completely different +// than an *instance field*, so it is interesting to differentiate them at +// a glance through a naming convetion. +// +// This is why it is advised to use a specific naming convention for static +// field like name that starts with **s_**. +// +// Related discussion: +// http://codebetter.com/patricksmacchia/2013/09/04/on-hungarian-notation-for-instance-vs-static-fields-naming/ +// + +// +// Once the rule has been adapted to your own naming convention +// make sure to name all matched static fields adequately. +//]]> + Interface name should begin with a 'I' + +// Don't query all Application.Types because some interface generated might not start with an 'I' +// like for example when adding a WSDL reference to a project. +warnif count > 0 from t in JustMyCode.Types where + t.IsInterface && + // Don't apply this rule for COM interfaces. + !t.HasAttribute("System.Runtime.InteropServices.ComVisibleAttribute".AllowNoMatch()) + +// Discard outter type(s) name prefix for nested types +let name = !t.IsNested ? + t.Name : + t.Name.Substring(t.Name.LastIndexOf('+') + 1, t.Name.Length - t.Name.LastIndexOf('+') - 1) + +where name[0] != 'I' +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// In the .NET world, interfaces names are commonly prefixed +// with an upper case **I**. This rule warns about interfaces +// whose names don't follow this convention. Because this +// naming convention is widely used and accepted, we +// recommend abiding by this rule. +// +// Typically COM interfaces names don't follow this rule. +// Hence this code rule doesn't take care of interfaces tagged +// with *ComVisibleAttribute*. +// + +// +// Make sure that matched interfaces names are prefixed with +// an upper **I**. +//]]> + Abstract base class should be suffixed with 'Base' + +warnif count > 0 from t in Application.Types where + t.IsAbstract && + t.IsClass && + + t.BaseClass != null && + t.BaseClass.FullName == "System.Object" && + + ((!t.IsGeneric && !t.NameLike (@"Base$")) || + ( t.IsGeneric && !t.NameLike (@"Base<"))) +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about *abstract classes* whose names are not +// suffixed with **Base**. It is a common practice in the .NET +// world to suffix base classes names with **Base**. +// +// Notice that this rule doesn't match abstract classes that +// are in a middle of a hierarchy chain. +// In other words, only base classes that derive directly +// from *System.Object* are matched. +// + +// +// Suffix the names of matched base classes with **Base**. +//]]> + Exception class name should be suffixed with 'Exception' +warnif count > 0 from t in Application.Types where + t.IsExceptionClass && + + // We use SimpleName, because in case of generic Exception type + // SimpleName suppresses the generic suffix (like ). + !t.SimpleNameLike(@"Exception$") && + !t.SimpleNameLike(@"ExceptionBase$") // Allow the second suffix Base + // for base exception classes. +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about *exception classes* whose names are not +// suffixed with **Exception**. It is a common practice in the .NET +// world to suffix exception classes names with **Exception**. +// +// For exception base classes, the suffix **ExceptionBase** +// is also accepted. +// + +// +// Suffix the names of matched exception classes with **Exception**. +//]]> + Attribute class name should be suffixed with 'Attribute' +warnif count > 0 from t in Application.Types where + t.IsAttributeClass && + !t.NameLike (@"Attribute$") +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about *attribute classes* whose names are not +// suffixed with **Attribute**. It is a common practice in the .NET +// world to suffix attribute classes names with **Attribute**. +// + +// +// Suffix the names of matched attribute classes with **Attribute**. +//]]> + Types name should begin with an Upper character +warnif count > 0 +let isAspNetApp = ThirdParty.Assemblies.WithName("System.Web").Any() +from t in JustMyCode.Types where + // The name of a type should begin with an Upper letter. + !t.SimpleNameLike (@"^[A-Z]") && + + // Except if it is generated by compiler. + !t.IsSpecialName && + !t.IsGeneratedByCompiler && + + // Special default ASP.NET type named "_Default" + !(isAspNetApp && t.SimpleName == "_Default") + +select new { + t, + // We show the type simple name + // that doesn't include the parent type name + // for nested types. + t.SimpleName, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about *types* whose names don't start +// with an Upper character. It is a common practice in the .NET +// world to use **Pascal Casing Style** to name types. +// +// **Pascal Casing Style** : The first letter in the identifier +// and the first letter of each subsequent concatenated word +// are capitalized. For example: *BackColor* +// + +// +// *Pascal Case* the names of matched types. +//]]> + Methods name should begin with an Upper character +warnif count > 0 +from m in JustMyCode.Methods where + !m.NameLike (@"^[A-Z]") && + !m.IsSpecialName && + !m.IsGeneratedByCompiler +select new { + m, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about *methods* whose names don't start +// with an Upper character. It is a common practice in the .NET +// world to use **Pascal Casing Style** to name methods. +// +// **Pascal Casing Style** : The first letter in the identifier +// and the first letter of each subsequent concatenated word +// are capitalized. For example: *ComputeSize* +// + +// +// *Pascal Case* the names of matched methods. +//]]> + Do not name enum values 'Reserved' +warnif count > 0 +from f in Application.Fields where + f.IsEnumValue && + f.NameLike (@"Reserved") +select new { + f, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule assumes that an enumeration member +// with a name that contains **"Reserved"** +// is not currently used but is a placeholder to +// be renamed or removed in a future version. +// Renaming or removing a member is a breaking +// change. You should not expect users to ignore +// a member just because its name contains +// **"Reserved"** nor can you rely on users to read or +// abide by documentation. Furthermore, because +// reserved members appear in object browsers +// and smart integrated development environments, +// they can cause confusion as to which members +// are actually being used. +// +// Instead of using a reserved member, add a +// new member to the enumeration in the future +// version. +// +// In most cases, the addition of the new +// member is not a breaking change, as long as the +// addition does not cause the values of the +// original members to change. +// + +// +// To fix a violation of this rule, remove or +// rename the member. +//]]> + Avoid types with name too long +warnif count > 0 +from t in JustMyCode.Types +where !t.IsGeneratedByCompiler + +where t.SimpleName.Length > 40 +orderby t.SimpleName.Length descending +select new { + t, + t.SimpleName, + NameLength = t.SimpleName.Length, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Types with a name too long tend to decrease code readability. +// This might also be an indication that a type is doing too much. +// +// This rule matches types with names with more than 40 characters. +// + +// +// To fix a violation of this rule, rename the type with a shortest name +// or eventually split the type in several more fine-grained types. +//]]> + Avoid methods with name too long +warnif count > 0 + +// First get test method for which we allow long names +let testAttr = ThirdParty.Types.WithNameIn("FactAttribute", "TestAttribute", "TestCaseAttribute") +let testMethods = Methods.TaggedWithAnyAttributes(testAttr).ToHashSet() + +from m in JustMyCode.Methods where + + // Explicit Interface Implementation methods are + // discarded because their names are prefixed + // with the interface name. + !m.IsExplicitInterfaceImpl && + !m.IsGeneratedByCompiler && + ((!m.IsSpecialName && m.SimpleName.Length > 40) || + // Property getter/setter are prefixed with "get_" "set_" of length 4. + ( m.IsSpecialName && m.SimpleName.Length - 4 > 40)) && + + // Don't match test methods + !testMethods.Contains(m) && + !m.SimpleName.Contains("Test") && + !m.ParentType.SimpleName.Contains("Test") && + !m.ParentNamespace.Name.Contains("Test") + +orderby m.SimpleName.Length descending + +select new { + m, + m.SimpleName, + NameLength = m.SimpleName.Length - (m.IsSpecialName ? 4 : 0), + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Methods with a name too long tend to decrease code readability. +// This might also be an indication that a method is doing too much. +// +// This rule matches methods with names with more than 40 characters. +// +// However it is considered as a good practice to name unit tests +// in such a way with a very expressive name, hence this rule doens't match +// methods tagged with *FactAttribute*, *TestAttribute* and *TestCaseAttribute*. +// + +// +// To fix a violation of this rule, rename the method with a shortest name +// that equally conveys the behavior of the method. +// Or eventually split the method into several smaller methods. +//]]> + Avoid fields with name too long +warnif count > 0 from f in JustMyCode.Fields where + !f.IsGeneratedByCompiler && + f.Name.Length > 40 +orderby f.Name descending +select new { + f, + NameLength = f.Name.Length, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Fields with a name too long tend to decrease code readability. +// +// This rule matches fields with names with more than 40 characters. +// + +// +// To fix a violation of this rule, rename the field with a shortest name +// that equally conveys the same information. +//]]> + Avoid having different types with same name +warnif count > 0 + +// Special type names for which multiple types with such name are allowed. +let nonReportedTypeName = new [] { "Program", "NamespaceDoc", "Initial", "Startup", "SwaggerConfig" } + +// This rule matches also collisions between +// application and third-party types sharing a same name. +let groups = JustMyCode.Types.Union(ThirdParty.Types) + // Discard nested types, whose name is + // prefixed with the parent type name. + .Where(t => !t.IsNested && !nonReportedTypeName.Contains(t.Name)) + + // Group types by name. + .GroupBy(t => t.Name) + +from @group in groups + where @group.Count() > 1 + + // Let's see if types with the same name are declared + // in different namespaces. + // (t.FullName is {namespaceName}.{typeName} ) + let groupsFullName = @group.GroupBy(t => t.FullName) + where groupsFullName.Count() > 1 + + // If several types with same name are declared in different namespaces + // eliminate the case where all types are declared in third-party assemblies. + let types= groupsFullName.SelectMany(g => g) + where types.Any(t => !t.IsThirdParty) + // Uncomment this line, to only gets naming collision involving + // both application and third-party types. + // && types.Any(t => t.IsThirdParty) + +orderby types.Count() descending + +select new { + // Order types by parent namespace and assembly name,this way we always get the same type across sessions. + // Without this astute, the same issue would be seen as added/removed when the first type choosen in the group + // was not always the same, a situation that actually happens. + // Also make sure that the chosen type is not a third-party one. + t = types.OrderBy(t => (t.IsThirdParty ? "1" : "0") + t.ParentNamespace.Name + t.ParentAssembly.Name).First(), + + // In the 'types' column, make sure to group matched types + // by parent assemblies and parent namespaces, to get a result + // more readable. + types, + + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about multiple types with same name, +// that are defined in different *application* or +// *third-party* namespaces or assemblies. +// +// Such practice create confusion and also naming collision +// in source files that use different types with same name. +// + +// +// To fix a violation of this rule, rename concerned types. +//]]> + Avoid prefixing type name with parent namespace name +warnif count > 0 + +from n in JustMyCode.Namespaces +where n.Name.Length > 0 + +from t in n.ChildTypes +where + JustMyCode.Contains(t) && // Don't warn about generated code + !t.IsGeneratedByCompiler && + !t.IsNested && + t.Name.IndexOf(n.SimpleName) == 0 && + + // The type name is equal to namespace name or the type name contains another + // word that starts with an upper-case letter after the namespace name. + // This way we avoid matching false-positive where namespace name is "Stat" and type name is "Statistic". + (t.Name.Length == n.SimpleName.Length || char.IsUpper(t.Name[n.SimpleName.Length])) +select new { + t, + namespaceName = n.SimpleName, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about situations where the parent namespace name +// is used as the prefix of a contained type. +// +// For example a type named "RuntimeEnvironment" +// declared in a namespace named "Foo.Runtime" +// should be named "Environment". +// +// Such situation creates naming redundancy with no readability gain. +// + +// +// To fix a violation of this rule, remove the prefix from the type name. +//]]> + Avoid naming types and namespaces with the same identifier +warnif count > 0 +let hashsetShortNames = Namespaces.Where(n => n.Name.Length > 0).Select(n => n.SimpleName).ToHashSet() + +from t in JustMyCode.Types +where hashsetShortNames.Contains(t.Name) +select new { + t, + namespaces = Namespaces.Where(n => n.SimpleName == t.Name), + Debt = 12.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns when a type and a namespace have the same name. +// +// For example when a type is named *Environment* +// and a namespace is named *Foo.Environment*. +// +// Such situation provokes tedious compiler resolution collision, +// and makes the code less readable because concepts are not +// concisely identified. +// + +// +// To fix a violation of this rule, renamed the concerned type or namespace. +//]]> + Don't call your method Dispose +warnif count > 0 +from m in JustMyCode.Methods.WithSimpleName("Dispose") +where !m.ParentType.Implement("System.IDisposable".AllowNoMatch()) + && m.OverriddensBase.Count() == 0 // Can't change the name of an override + && !m.IsNewSlot // new slot also means override of an intreface method +select new { + m, + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// In .NET programming, the identifier *Dispose* should be kept +// only for implementations of *System.IDisposable*. +// +// This rule warns when a method is named *Dispose()*, +// but the parent type doesn't implement *System.IDisposable*. +// + +// +// To fix a violation of this rule, +// either make the parent type implements *System.IDisposable*, +// or rename the *Dispose()* method with another identifier like: +// *Close() Terminate() Finish() Quit() Exit() Unlock() ShutDown()*… +//]]> + Methods prefixed with 'Try' should return a boolean +warnif count > 0 +from m in Application.Methods where + m.SimpleNameLike("^Try") && + m.ReturnType != null && + m.ReturnType.FullName != "System.Boolean" +select new { + m, + m.ReturnType, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// When a method has a name prefixed with **Try**, it is expected that +// it returns a *boolean*, that reflects the method execution status, +// *success* or *failure*. +// +// Such method usually returns a result through an *out parameter*. +// For example: *System.Int32.TryParse(int,out string):bool* +// + +// +// To fix a violation of this rule, +// Rename the method, or transform it into an operation that can fail. +//]]> + Properties and fields that represent a collection of items should be named Items. +warnif count > 0 + +let collectionTypes = Types.Where(t => + t.Implement("System.Collections.Generic.IEnumerable".AllowNoMatch()) && + t.IsGeneric && + t.FullName != "System.Collections.Generic.IDictionary" && + !t.Implement("System.Collections.Generic.IDictionary".AllowNoMatch()) && + t.FullName != "System.Collections.Generic.IReadOnlyDictionary" && + !t.Implement("System.Collections.Generic.IReadOnlyDictionary".AllowNoMatch())).ToHashSet() + +let properties = from m in JustMyCode.Methods +where (m.IsPropertyGetter || m.IsPropertySetter) && + collectionTypes.Contains(m.ReturnType) +select new { Member = (IMember)m, Type = m.ReturnType } + +let fields = from f in JustMyCode.Fields +where collectionTypes.Contains(f.FieldType) +select new { Member = (IMember)f, Type = f.FieldType } + +from pair in properties.Concat(fields) +let identifier = pair.Member.SimpleName +where !identifier.EndsWith("s") && + !identifier.Contains('<') // Remove potential generated fields like backing fields, and also potential generated properties accessors + +let identifierRefined = identifier.Replace("get_", "").Replace("set_", "") +let words = identifierRefined.GetWords() +where !words.Any(word => + word.EndsWith("s") || // Don't warn if any word ends with an s + word == "Empty") // Don't warn if any word in the identifier is Empty + +select new { pair.Member, pair.Type } + +// +// A good practice to make the code more readable and more predictable +// is to name properties and fields typed with a collection of *items* +// with the plural form of *Items*. +// +// Depending on the domain of your application, a proper identfier could be +// *NewDirectories*, *Words*, *Values*, *UpdatedDates*. +// +// Also this rule doesn't warn when any word in the identifier ends with an *s*. +// This way identifiers like **TasksToRun**, **KeysDisabled**, **VersionsSupported**, +// **ChildrenFilesPath**, **DatedValuesDescending**, **ApplicationNodesChanged** +// or **ListOfElementsInResult** are valid and won't be seen as violations. +// +// Moreover this rule won't warn for a field or property with an identifier +// that contain the word **Empty**. +// This is a common pattern to define an immutable and empty collection instance +// shared. +// +// Before inspecting properties and fields, this rule gathers +// application and third-party collection types that might be returned +// by a property or a field. To do so this rule searches types that implement +// *IEnumerable* except: +// +// - Non generic types: Often a non generic type is not seen as a collection. +// For example *System.String* implements *IEnumerable*, but a string +// is rarely named as a collection of characters. In others words, +// we have much more strings in our program named like *FirstName* +// than named like *EndingCharacters*. +// +// - Dictionaries types: A dictionary is more than a collection of pairs, +// it is a mapping from one domain to another. A common practice is to suffix +// the name of a dictionary with *Map* *Table* or *Dictionary*, +// although often dictionaries names satify this rule with names like +// *GuidsToPersons* or *PersonsByNames*. +// + +// +// Just rename the fields and properties accordingly, +// by making plural the word in the identifier +// that describes best the *items* in the collection. +// +// For example: +// +// - **ListOfDir** can be renamed **Directories**. +// +// - **Children** can be renamed **ChildrenItems** +// +// - **QueueForCache** can be renamed **QueueOfItemsForCache** +//]]> + DDD ubiquitous language check +warnif count > 0 + +// Update to your core domain namespace(s) +let coreDomainNamespaces = Application.Namespaces.WithNameLike("TrainTrain.Domain") + +// Update your vocabulary list +let vocabulary = new [] { +"Train", "Coach", "Coaches", "Seat", "Seats", +"Reservation", "Fulfilled", "Booking", "Book", "Reserve", "Confirm" +}.ToHashSet() + +let technicalWords = new [] { "get", "set", "Get", "Set", "Add" }.ToHashSet() +let multiWordsVocabulary = vocabulary.Where(w => w.GetWords().Length >= 2) + +// Append multi-words words in vocabulary +let multiWordsWords = multiWordsVocabulary.SelectMany(w => w.GetWords()) +let vocabulary2 = vocabulary.Concat(multiWordsWords).ToHashSet() +let vocabulary3 = vocabulary2.Concat(technicalWords).ToHashSet() + +from ce in coreDomainNamespaces.ChildTypesAndMembers() +let tokens = ce.SimpleName.GetWords().Select(w => w.FirstCharToUpper()).ToArray() + +where !(ce.IsMethod && ce.AsMethod.IsConstructor) && // No vocabulary in ctor + !(ce.IsField && ce.AsField.IsGeneratedByCompiler) && // Remove compiler generated backing fields, their vocabulary is in property + !(vocabulary3.Any(vocable => tokens.Contains(vocable))) + +let wordsNotInVocabulary = tokens.Where(w => !(vocabulary2.Contains(w) && string.IsNullOrEmpty(w))).ToArray() + +select new { ce, + wordsNotInVocabulary = string.Join(", ",wordsNotInVocabulary) +} + +// +// The language used in identifiers of classes, methods and fields of the **core domain**, +// should be based on the **Domain Model**. +// This constraint is known as **ubiquitous language** in **Domain Driven Design (DDD)** +// and it reflects the need to be rigorous with naming, +// since software doesn't cope well with ambiguity. +// +// This rule is disabled per default +// because its source code needs to be customized to work, +// both with the **core domain** namespace name +// (that contains classes and types to be checked), +// and with the list of domain language terms. +// +// If a term needs to be used both with singular and plural forms, +// both forms need to be mentioned, +// like **Seat** and **Seats** for example. Notice that this default rule is related with +// the other default rule *Collection properties and fields names should end with s* +// defined in the *Naming Convention* group. +// +// This rule implementation relies on the NDepend API +// **ExtensionMethodsString.GetWords(this string identifier)** +// extension method that extracts terms +// from classes, methods and fields identifiers in a smart way. +// + +// +// For each violation, this rule provides the list of **words not in vocabulary**. +// +// To fix a violation of this rule, either rename the concerned code element +// or update the domain language terms list defined in this rule source code, +// with the missing term(s). +// +]]> + + + Avoid referencing source file out of Visual Studio project directory +warnif count > 0 + +from a in Application.Assemblies +where a.VisualStudioProjectFilePath != null +let vsProjDirPathLower = a.VisualStudioProjectFilePath.ParentDirectoryPath.ToString().ToLower() + +from t in a.ChildTypes +where JustMyCode.Contains(t) && t.SourceFileDeclAvailable + +from decl in t.SourceDecls +let sourceFilePathLower = decl.SourceFile.FilePath.ToString().ToLower() +where sourceFilePathLower.IndexOf(vsProjDirPathLower) != 0 +select new { + t, + sourceFilePathLower, + projectFilePath = a.VisualStudioProjectFilePath.ToString(), + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// A source file located outside of the VS project directory can be added through: +// *> Add > Existing Items… > Add As Link* +// +// Doing so can be used to share types definitions across several assemblies. +// This provokes type duplication at binary level. +// Hence maintainability is degraded and subtle versioning bug can appear. +// +// This rule matches types whose source files are not declared under the +// directory that contains the related Visual Studio project file, or under +// any sub-directory of this directory. +// +// This practice can be tolerated for certain types shared across executable assemblies. +// Such type can be responsible for startup related concerns, +// such as registering custom assembly resolving handlers or +// checking the .NET Framework version before loading any custom library. +// + +// +// To fix a violation of this rule, prefer referencing from a VS project +// only source files defined in sub-directories of the VS project file location. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Avoid duplicating a type definition across assemblies +warnif count > 0 + +let groups = Application.Types + .Where(t => !t.IsGeneratedByCompiler && + // Types created by the test infrastructure + t.FullName != "AutoGeneratedProgram") + .GroupBy(t => t.FullName) +from @group in groups +where @group.Count() > 1 + +// Tricky: This rule is executed on both current snapshot and baseline snapshot (if any). +// Taking t as @group.First() could return any type in the group, and potentially +// it can return different types for current and baseline snapshot. +// As a result issues wouldn't corresponds (since types are different) and the user +// would see issues of this rule as a couple of issues added and removed. +// Ordering types by parent assembly name and then taking the first type, discards this risk. +let types = @group.OrderBy(t => t.ParentAssembly.Name) + +select new { + t = types.First(), + // In the 'types' column, make sure to group matched types by parent assemblies. + typesDefs = types.ToArray(), + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// A source file located outside of the VS project directory can be added through: +// *> Add > Existing Items… > Add As Link* +// +// This rule warns about using this feature to share code across several assemblies. +// This provokes type duplication at binary level. +// Hence maintainability is degraded and subtle versioning bug can appear. +// +// This practice can be tolerated for certain types shared across executable assemblies. +// Such type can be responsible for startup related concerns, +// such as registering custom assembly resolving handlers or +// checking the .NET Framework version before loading any custom library. +// + +// +// To fix a violation of this rule, prefer sharing types through DLLs. +//]]> + Avoid defining multiple types in a source file +warnif count > 0 + +// Build a lookup indexed by source files, values being a sequence of types defined in the source file. +let lookup = JustMyCode.Types + // When a source file is referenced by several assemblies, + // type(s) contained in the source file are seen as distinct types, both by the CLR and by NDepend. + // This Distinct clause based on type full-name (type name prefixed with namespace) + // avoids matching the multiple versions of such type. + .Distinct(t => t.FullName) + .Where(t => t.SourceFileDeclAvailable && + // except enumerations, nested types and types generated by compilers! + !t.IsEnumeration && + !t.IsNested && + !t.IsGeneratedByCompiler) + // We use multi-key, since a type can be declared in multiple source files. + .ToMultiKeyLookup(t => t.SourceDecls.Select(d => d.SourceFile)) + +from @group in lookup where @group.Count() > 1 + let sourceFile = @group.Key + + // CQLinq doesn't let indexing result with sourceFile + // so we choose a typeIndex in types, + // preferably the type that has the file name. + let typeWithSourceFileName = @group.FirstOrDefault(t => t.SimpleName == sourceFile.FileNameWithoutExtension) + let typeIndex = typeWithSourceFileName ?? @group.First() + +select new { + typeIndex, + TypesInSourceFile = @group as IEnumerable, + SourceFilePathString = sourceFile.FilePathString, + Debt = 3.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// Defining multiple types in a single source file decreases code readability, +// because developers are used to see all types in a namespace, +// when expanding a folder in the *Visual Studio Solution Explorer*. +// Also doing so, leads to source files with too many lines. +// +// Each match of this rule is a source file that contains several types +// definitions, indexed by one of those types, preferably the one with +// the same name than the source file name without file extension, if any. +// + +// +// To fix a violation of this rule, create a source file for each type. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Namespace name should correspond to file location +warnif count > 0 + +from a in Application.Assemblies +let assemblyName = a.Name + +from n in a.ChildNamespaces +let namespaceName = n.Name + +// Build the dirShouldContain string +// and then we check which source file path contains dirShouldContain or not +let dirShouldContain = ( + // namespaceName starts with assembly name => gets only components after the assembly name + namespaceName.StartsWith(assemblyName) + ? namespaceName.Substring(assemblyName.Length) + + // namespaceName contains assembly name => gets only components after the assembly name + : namespaceName.Contains("." + assemblyName) + ? namespaceName.Substring(n.Name.IndexOf("." + assemblyName)) + + // namespaceName has more than one part => gets only components after the first part + : namespaceName.Contains(".") + ? namespaceName.Substring(n.Name.IndexOf(".")) + : namespaceName) + + // Replace dots by spaces in namespace name + .Replace('.', ' ') + +where dirShouldContain.Length > 0 + +// Look at source file decl of JustMyCode type's declared in the namespace 'n' +from t in n.ChildTypes +where JustMyCode.Contains(t) && t.SourceFileDeclAvailable + +let sourceDeclConcerned = (from decl in t.SourceDecls + let sourceFilePath = decl.SourceFile.FilePath.ToString() + // Replace dots and path separators by spaces in source files names + where !sourceFilePath.Replace('.',' ').Replace('\\',' ').Contains(dirShouldContain) + select sourceFilePath).ToArray() +where sourceDeclConcerned.Length > 0 + +let justACaseSensitiveIssue = sourceDeclConcerned[0].ToLower().Replace('.',' ').Replace('\\',' ').Contains(dirShouldContain.ToLower()) + +select new { + t, + dirShouldContain, + sourceFilePath = sourceDeclConcerned[0], + nbSourceDeclConcerned = sourceDeclConcerned.Length, + justACaseSensitiveIssue , + Debt = (justACaseSensitiveIssue ? + 1f + sourceDeclConcerned.Length/2f : + 2f + sourceDeclConcerned.Length).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// For a solid code structure and organization, +// do mirror the namespaces hierarchy and the directories hierarchy containing source files. +// +// Doing so is a widely accepted convention, and not respecting this convention +// will lead to less maintainable and less browsable source code. +// +// This rule matches all types in such source file, whose location doesn't correspond +// to the type parent namespace. If a source file contains several such types (that +// are not necessarily in the same namespace) each type will result in a violation. +// +// If a type is declared in several such source files, the value for the column +// *nbSourceDeclConcerned* in the result, is greater than 1. +// The technical-debt per issue is proportional to *nbSourceDeclConcerned*. +// +// Notice that namespaces and directories names comparison is **case-sensitive**. +// A boolean *justACaseSensitiveIssue* indicates if it is just a case-sensitive issue, +// in which case the technical-debt is divided by two. +// + +// +// To fix a violation of this rule, make sure that the type parent namespace and +// the directory sub-paths that contains the type source file, are mirrored. +// +// Make sure to first check the boolean *justACaseSensitiveIssue*, in which case +// the issue is easier to fix. +// +]]> + Types with source files stored in the same directory, should be declared in the same namespace +warnif count > 0 + +// Group JustMyCode types in a lookup +// where groups are keyed with directories that contain the types' source file(s). +// Note that a type can be contained in several groups +// if it is declared in several source files stored in different directories. +let lookup = JustMyCode.Types.Where(t => t.SourceFileDeclAvailable) + .ToMultiKeyLookup( + t => t.SourceDecls.Select( + decl => decl.SourceFile.FilePath.ParentDirectoryPath).Distinct() + ) + +from groupOfTypes in lookup +let parentNamespaces = groupOfTypes.ParentNamespaces() + +// Select group of types (with source files stored in the same directory) … +// … but contained in several namespaces +where parentNamespaces.Count() > 1 + +// mainNamespaces is the namespace that contains many types +// declared in the directory groupOfTypes .key +let mainNamespace = groupOfTypes + .ToLookup(t => t.ParentNamespace) + .OrderByDescending(g => g.Count()).First().Key + +// Select types with source files stored in the same directory, +// but contained in namespaces different than mainNamespace. +let typesOutOfMainNamespace = groupOfTypes + .Where(t => t.ParentNamespace != mainNamespace && + t.ParentAssembly == mainNamespace.ParentAssembly) + + // Filter types declared on several source files that contain generated methods + // because typically such type contains one or several partial definitions generated. + // These partially generated types would be false positive for the present rule. + .Where(t => t.SourceDecls.Count() == 1 || + t.Methods.Count(m => JustMyCode.Contains(m)) == 0) +where typesOutOfMainNamespace.Count() > 0 + +let typesInMainNamespace = groupOfTypes.Where(t => t.ParentNamespace == mainNamespace) + +select new { + mainNamespace, + typesOutOfMainNamespace, + typesInMainNamespace, + Debt = (2+5*typesOutOfMainNamespace.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// For a solid code structure and organization, do mirror the namespaces +// hierarchy and the directories hierarchy containing source files. +// +// Doing so is a widely accepted convention, and not respecting this convention +// will lead to less maintainable and less browsable code. +// +// Respecting this convention means that types with source files stored in the same directory, +// should be declared in the same namespace. +// +// For each directory that contains several source files, where most types are declared +// in a namespace (what we call the **main namespace**) and a few types are declared +// out of the *main namespace*, this code rule matches: +// +// • The *main namespace* +// +// • **typesOutOfMainNamespace**: Types declared in source files in the *main namespace*'s directory +// but that are not in the *main namespace*. +// +// • *typesInMainNamespace*: And for informational purposes, types declared in source files in the +// *main namespace*'s directory, and that are in the *main namespace*. +// + +// +// Violations of this rule are types in the *typesOutOfMainNamespace* column. +// Typically such type … +// +// • … is contained in the wrong namespace but its source file is stored in the right directory. +// In such situation the type should be contained in *main namespace*. +// +// • … is contained in the right namespace but its source file is stored in the wrong directory +// In such situation the source file of the type must be moved to the proper parent namespace directory. +// +// • … is declared in multiple source files, stored in different directories. +// In such situation it is preferable that all source files are stored in a single directory. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 2 minutes plus 5 minutes per type in *typesOutOfMainNamespace*. +//]]> + Types declared in the same namespace, should have their source files stored in the same directory + +warnif count > 0 +from @namespace in Application.Namespaces + +// Group types of @namespace in a lookup +// where groups are keyed with directories that contain the types' source file(s). +// Note that a type can be contained in several groups +// if it is declared in several source files stored in different directories. +let lookup = @namespace.ChildTypes.Where( + t => t.SourceFileDeclAvailable && + JustMyCode.Contains(t) && + // Don't match ASP.NET application types declared in Global.asax file + // that typically are in the root directory of the VS project. + t.SourceDecls.First().SourceFile.FileNameWithoutExtension.ToLower() != "global.asax") + .ToMultiKeyLookup( + t => t.SourceDecls.Select( + decl => decl.SourceFile.FilePath.ParentDirectoryPath).Distinct() + ) + +// Are types of @namespaces declared in more than one directory? +where lookup.Count > 1 + +// Infer the main folder, preferably the one that has the same name as the namespace. +let dirs = lookup.Select(types => types.Key) +let mainDirNullable = dirs.Where(d => d.DirectoryName == @namespace.SimpleName).FirstOrDefault() +let mainDir = mainDirNullable ?? dirs.First() + +// Types declared out of mainDir, are types in group of types declared in a directory different than mainDir! +let typesDeclaredOutOfMainDir = + lookup.Where(types => types.Key != mainDir) + .SelectMany(types => types) + + // Filter types declared on several source files that contain generated methods + // because typically such type contains one or several partial definitions generated. + // These partially generated types would be false positive for the present rule. + .Where(t => t.SourceDecls.Count() == 1 || + t.Methods.Count(m => JustMyCode.Contains(m)) == 0) + +where typesDeclaredOutOfMainDir.Count() > 0 + +let typesDeclaredInMainDir = + lookup.Where(types => types.Key == mainDir) + .SelectMany(types => types) + +select new { + @namespace, + typesDeclaredOutOfMainDir, + mainDir = mainDir.ToString(), + typesDeclaredInMainDir, + Debt = (2+5*typesDeclaredOutOfMainDir.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// For a solid code structure and organization, +// do mirror the namespaces hierarchy and the directories hierarchy containing source files. +// +// Doing so is a widely accepted convention, and not respecting this convention +// will lead to less maintainable and less browsable code. +// +// Respecting this convention means that types declared in the same namespace, +// should have their source files stored in the same directory. +// +// For each namespace that contains types whose source files +// are declared in several directories, infer the **main directory**, +// the directory that naturally hosts source files of types, +// preferably the directory whose name corresponds with the namespace +// name. In this context, this code rule matches: +// +// • The namespace +// +// • **typesDeclaredOutOfMainDir**: types in the namespace whose source files +// are stored out of the *main directory*. +// +// • The *main directory* +// +// • *typesDeclaredInMainDir*: for informational purposes, types declared +// in the namespace, whose source files are stored in the *main directory*. +// + +// +// Violations of this rule are types in the **typesDeclaredOutOfMainDir** column. +// Typically such type… +// +// • … is contained in the wrong namespace but its source file is stored in the right directory. +// In such situation the type should be contained in the namespace corresponding to +// the parent directory. +// +// • … is contained in the right namespace but its source file is stored in the wrong directory. +// In such situation the source file of the type must be moved to the *main directory*. +// +// • … is declared in multiple source files, stored in different directories. +// In such situation it is preferable that all source files are stored in a single directory. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 2 minutes plus 5 minutes per type in *typesDeclaredOutOfMainDir*. +//]]> + + + + Mark ISerializable types with SerializableAttribute +warnif count > 0 + +from t in Application.Types where + t.IsPublic && + !t.IsDelegate && + !t.IsExceptionClass && // Don't match exceptions, since the Exception class + // implements ISerializable, this would generate + // too many false positives. + t.Implement ("System.Runtime.Serialization.ISerializable".AllowNoMatch()) && + !t.HasAttribute ("System.SerializableAttribute".AllowNoMatch()) + +select new { + t, + t.NbLinesOfCode, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// To be recognized by the CLR as serializable, +// types must be marked with the *SerializableAttribute* +// attribute even if the type uses a custom +// serialization routine through implementation of +// the *ISerializable* interface. +// +// This rule matches types that implement *ISerializable* and +// that are not tagged with *SerializableAttribute*. +// + +// +// To fix a violation of this rule, tag the matched type +// with *SerializableAttribute* . +//]]> + Mark assemblies with CLSCompliant (deprecated) +warnif count > 0 from a in Application.Assemblies where + !a.HasAttribute ("System.CLSCompliantAttribute".AllowNoMatch()) +select new { + a, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule has been deprecated and, as a consequence, it is disabled by default. +// Feel free to re-enable it if it makes sense in your dev environment. +// +// The *Common Language Specification* (CLS) defines naming restrictions, +// data types, and rules to which assemblies must conform if they are to +// be used across programming languages. Good design dictates that all +// assemblies explicitly indicate CLS compliance with **CLSCompliantAttribute**. +// If the attribute is not present on an assembly, the assembly is not compliant. +// +// Notice that it is possible for a CLS-compliant assembly to contain types or +// type members that are not compliant. +// +// This rule matches assemblies that are not tagged with +// **System.CLSCompliantAttribute**. +// + +// +// To fix a violation of this rule, tag the assembly with *CLSCompliantAttribute*. +// +// Instead of marking the whole assembly as non-compliant, you should determine +// which type or type members are not compliant and mark these elements as such. +// If possible, you should provide a CLS-compliant alternative for non-compliant +// members so that the widest possible audience can access all the functionality +// of your assembly. +//]]> + Mark assemblies with ComVisible (deprecated) +warnif count > 0 from a in Application.Assemblies where + !a.HasAttribute ("System.Runtime.InteropServices.ComVisibleAttribute".AllowNoMatch()) +select new { + a, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule has been deprecated and, as a consequence, it is disabled by default. +// Feel free to re-enable it if it makes sense in your dev environment. +// +// The **ComVisibleAttribute** attribute determines how COM clients access +// managed code. Good design dictates that assemblies explicitly indicate +// COM visibility. COM visibility can be set for a whole assembly and then +// overridden for individual types and type members. If the attribute is not +// present, the contents of the assembly are visible to COM clients. +// +// This rule matches assemblies that are not tagged with +// **System.Runtime.InteropServices.ComVisibleAttribute**. +// + +// +// To fix a violation of this rule, tag the assembly with *ComVisibleAttribute*. +// +// If you do not want the assembly to be visible to COM clients, set the +// attribute value to **false**. +//]]> + Mark attributes with AttributeUsageAttribute +warnif count > 0 +from t in JustMyCode.Types where + t.DeriveFrom ("System.Attribute".AllowNoMatch()) && + !t.HasAttribute ("System.AttributeUsageAttribute".AllowNoMatch()) +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// When you define a custom attribute, mark it by using **AttributeUsageAttribute** +// to indicate where in the source code the custom attribute can be applied. The +// meaning and intended usage of an attribute will determine its valid locations +// in code. For example, you might define an attribute that identifies the person +// who is responsible for maintaining and enhancing each type in a library, and +// that responsibility is always assigned at the type level. In this case, compilers +// should enable the attribute on classes, enumerations, and interfaces, but should +// not enable it on methods, events, or properties. Organizational policies and +// procedures would dictate whether the attribute should be enabled on assemblies. +// +// The **System.AttributeTargets** enumeration defines the targets that you can +// specify for a custom attribute. If you omit *AttributeUsageAttribute*, your +// custom attribute will be valid for all targets, as defined by the **All** value of +// *AttributeTargets* enumeration. +// +// This rule matches attribute classes that are not tagged with +// **System.AttributeUsageAttribute**. +// + +// +// To fix a violation of this rule, specify targets for the attribute by using +// *AttributeUsageAttribute* with the proper *AttributeTargets* values. +//]]> + Remove calls to GC.Collect() +warnif count > 0 + +let gcCollectMethods = ThirdParty.Methods.WithFullNameWildcardMatch( + "System.GC.Collect(*)").ToHashSet() + +from m in Application.Methods.UsingAny(gcCollectMethods) +select new { + m, + gcCollectMethodCalled = m.MethodsCalled.Intersect(gcCollectMethods), + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// It is preferable to avoid calling **GC.Collect()** +// explicitly in order to avoid some performance pitfall. +// +// More in information on this here: +// http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx +// +// This rule matches application methods that call an +// overload of the method *GC.Collect()*. +// + +// +// Remove matched calls to *GC.Collect()*. +//]]> + Don't call GC.Collect() without calling GC.WaitForPendingFinalizers() +warnif count > 0 + +let gcCollectMethods = ThirdParty.Methods.WithFullNameWildcardMatch( + "System.GC.Collect(*)").ToHashSet() + +from m in Application.Methods.UsingAny(gcCollectMethods) where + !m.IsUsing ("System.GC.WaitForPendingFinalizers()".AllowNoMatch()) +select new { + m, + gcCollectMethodCalled = m.MethodsCalled.Intersect(gcCollectMethods), + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// It is preferable to avoid calling **GC.Collect()** +// explicitly in order to avoid some performance +// pitfall. This situation is checked through the +// default rules: *Remove calls to GC.Collect()* +// +// But if you wish to call *GC.Collect()* anyway, +// you must do it this way: +// +// GC.Collect(); +// +// GC.WaitForPendingFinalizers(); +// +// GC.Collect(); +// +// To make sure that finalizer got executed, and +// object with finalizer got cleaned properly. +// +// This rule matches application methods that call an +// overload of the method *GC.Collect()*, without calling +// *GC.WaitForPendingFinalizers()*. +// + +// +// To fix a violation of this rule, if you really +// need to call *GC.Collect()*, make sure to call +// *GC.WaitForPendingFinalizers()* properly. +//]]> + Enum Storage should be Int32 +warnif count > 0 from f in JustMyCode.Fields where + f.ParentType.IsEnumeration && + f.Name == @"value__" && + f.FieldType != null && + f.FieldType.FullName != "System.Int32" && + !f.IsThirdParty +select new { + f, + f.SizeOfInst, + f.FieldType, + Debt = 7.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// An enumeration is a value type that defines a set of related named constants. +// By default, the **System.Int32** data type is used to store the constant value. +// +// Even though you can change this underlying type, it is not necessary or +// recommended for most scenarios. Note that *no significant performance gain* is +// achieved by using a data type that is smaller than *Int32*. If you cannot use +// the default data type, you should use one of the Common Language System +// (CLS)-compliant integral types, *Byte*, *Int16*, *Int32*, or *Int64* to make +// sure that all values of the enumeration can be represented in CLS-compliant +// programming languages. +// +// This rule matches enumerations whose underlying type used to store +// values is not *System.Int32*. +// + +// +// To fix a violation of this rule, unless size or compatibility issues exist, +// use *Int32*. For situations where *Int32* is not large enough to hold the values, +// use *Int64*. If backward compatibility requires a smaller data type, use +// *Byte* or *Int16*. +//]]> + Do not raise too general exception types + +warnif count > 0 + +let tooGeneralExceptionTypes = ThirdParty.Types.WithFullNameIn( + "System.Exception", + "System.ApplicationException", + "System.SystemException") + +from m in JustMyCode.Methods.ThatCreateAny(tooGeneralExceptionTypes) +// Make sure we don't match constructor of exception types +// that actually instantiate System.Exception. +where !m.IsConstructor || tooGeneralExceptionTypes.All(t => !m.ParentType.DeriveFrom(t)) +let exceptionsCreated = tooGeneralExceptionTypes.Where(t => m.IsUsing(t)) +select new { + m, + exceptionsCreated, + Debt = (15 + 5*exceptionsCreated.Count()).ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The following exception types are too general +// to provide sufficient information to the user: +// +// • System.Exception +// +// • System.ApplicationException +// +// • System.SystemException +// +// If you throw such a general exception type in a library or framework, +// it forces consumers to catch all exceptions, +// including unknown exceptions that they do not know how to handle. +// +// This rule matches methods that create an instance of +// such general exception class. +// + +// +// To fix a violation of this rule, change the type of the thrown exception +// to either a more derived type that already exists in the framework, +// or create your own type that derives from *System.Exception*. +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 15 minutes per method matched, plus 5 minutes per too general +// exception types instantiated by the method. +//]]> + Do not raise reserved exception types +warnif count > 0 + +let reservedExceptions = ThirdParty.Types.WithFullNameIn( + "System.NullReferenceException", + "System.ExecutionEngineException", + "System.IndexOutOfRangeException", + "System.OutOfMemoryException", + "System.StackOverflowException", + "System.InvalidProgramException", + "System.AccessViolationException", + "System.CannotUnloadAppDomainException", + "System.BadImageFormatException", + "System.DataMisalignedException") + +from m in Application.Methods.ThatCreateAny(reservedExceptions) +let reservedExceptionsCreated = reservedExceptions.Where(t => m.IsUsing(t)) +select new { + m, + reservedExceptionsCreated, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The following exception types are reserved +// and should be thrown only by the Common Language Runtime: +// +// • System.ExecutionEngineException +// +// • System.IndexOutOfRangeException +// +// • System.NullReferenceException +// +// • System.OutOfMemoryException +// +// • System.StackOverflowException +// +// • System.InvalidProgramException +// +// • System.AccessViolationException +// +// • System.CannotUnloadAppDomainException +// +// • System.BadImageFormatException +// +// • System.DataMisalignedException +// +// Do not throw an exception of such reserved type. +// +// This rule matches methods that create an instance of +// such reserved exception class. +// + +// +// To fix a violation of this rule, change the type of the +// thrown exception to a specific type that is not one of +// the reserved types. +// +// Concerning the particular case of a method throwing +// *System.NullReferenceException*, often the fix will be either +// to throw instead *System.ArgumentNullException*, either to +// use a contract (through MS Code Contracts API or *Debug.Assert()*) +// to signify that a null reference at that point can only be +// the consequence of a bug. +// +// More generally the idea of using a contract instead of throwing +// an exception in case of *corrupted state / bug consequence* detected +// is a powerful idea. It replaces a behavior (throwing exception) +// with a declarative assertion that basically means: at that point a bug +// somehow provoqued the detected corrupted state and continuing +// any processing from now is potentially harmful. The process should be +// shutdown and the circonstances of the failure should be reported +// as a bug to the product team. +//]]> + Use integral or string argument for indexers +warnif count > 0 +from m in Application.Methods where + m.IsIndexerGetter && + !( (m.Name == @"get_Item(String)") || + m.NameLike (@"get_Item\(Int") || + m.NameLike (@"get_Item\(Byte") || + m.NameLike (@"get_Item\(SByte") ) +select new { + m, + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// Indexers, that is, indexed properties, should use *integer* or *string* +// types for the index. These types are typically used for indexing data +// structures and increase the usability of the library. Use of the *Object* +// type should be restricted to those cases where the specific *integer* or +// *string* type cannot be specified at design time. If the design requires +// other types for the index, reconsider whether the type represents a +// logical data store. If it does not represent a logical data store, +// use a method. +// +// This rule matches indexer getter methods that whose index type +// is not *string*, *int*, *byte* or *sbyte*. +// + +// +// To fix a violation of this rule, change the index to an *integer* or *string* +// type, or use a method instead of the indexer. +//]]> + Uri fields should be of type System.Uri +warnif count > 0 from f in Application.Fields where + (f.NameLike (@"Uri$") || + f.NameLike (@"Url$")) && + f.FieldType != null && + f.FieldType.FullName != "System.Uri" +select new { + f, + f.FieldType, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// A field with the name ending with *'Uri'* or *'Url'* is deemed +// to represent a *Uniform Resource Identifier or Locator*. +// Such field should be of type **System.Uri**. +// +// This rule matches fields with the name ending with *'Uri'* or +// *'Url'* that are not typed with *System.Uri*. +// + +// +// Rename the field, or change the field type to *System.Uri*. +// +// By default issues of this rule have a **Low** severity +// because they reflect more an advice than a problem. +//]]> + Types should not extend System.ApplicationException +warnif count > 0 from t in Application.Types where + t.DeriveFrom("System.ApplicationException".AllowNoMatch()) +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// At .NET Framework version 1 time, it was +// recommended to derive new exceptions from +// *ApplicationException*. +// +// The recommendation has changed and new +// exceptions should derive from **System.Exception** +// or one of its subclasses in the *System* namespace. +// +// This rule matches application exception classes +// that derive from *ApplicationException*. +// + +// +// Make sure that matched exception types, +// derive from **System.Exception** or one of its +// subclasses in the *System* namespace. +//]]> + + + Collection properties should be read only +warnif count > 0 + +// First find collectionTypes +let collectionInterfaces = ThirdParty.Types.WithFullNameIn( + "System.Collections.ICollection", + "System.Collections.Generic.ICollection") +where collectionInterfaces.Count() > 0 +let collectionTypes = Types.ThatImplementAny(collectionInterfaces) + .Union(collectionInterfaces) + .ToHashSet() + +// Then find all property setters that have an associated +// getter that returns a collection type. +from propGetter in JustMyCode.Methods.Where( + m => m.IsPropertyGetter && + m.ReturnType != null && + collectionTypes.Contains(m.ReturnType)) + +let propSetter = propGetter.ParentType.Methods.WithSimpleName( + propGetter.SimpleName.Replace("get_","set_") + ).FirstOrDefault() + +where propSetter != null && + !propSetter.IsPrivate && // Ignore private setters since this is private implementation detail + !propSetter.ParentType.IsPrivate && // Ignore setters of private types + + // Ignore properties of serializable types + !propSetter.ParentType.HasAttribute("System.Runtime.Serialization.DataContractAttribute".AllowNoMatch()) && + !propSetter.ParentType.HasAttribute("System.Xml.Serialization.XmlRootAttribute".AllowNoMatch()) + +select new { + propSetter, + CollectionType = propGetter.ReturnType, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// A writable collection property allows a user to replace the collection with +// a completely different collection. A read-only property stops the collection +// from being replaced but still allows the individual members to be set. If +// replacing the collection is a goal, the preferred *design pattern* is to include +// a method to remove all the elements from the collection and a method to +// re-populate the collection. See the *Clear()* and *AddRange()* methods of the +// *System.Collections.Generic.List* class for an example of this pattern. +// +// Both binary and XML serialization support read-only properties that are +// collections. The *System.Xml.Serialization.XmlSerializer* class has specific +// requirements for types that implement *ICollection* and *System.Collections.IEnumerable* +// in order to be serializable. +// +// This rule matches property setter methods that assign a collection object. +// + +// +// To fix a violation of this rule, make the property read-only and, if +// the design requires it, add methods to clear and re-populate the collection. +//]]> + Don't use .NET 1.x HashTable and ArrayList (deprecated) +warnif count > 0 +let forbiddenTypes = ThirdParty.Types.WithFullNameIn( + "System.Collections.HashTable", + "System.Collections.ArrayList", + "System.Collections.Queue", + "System.Collections.Stack", + "System.Collections.SortedList") +where forbiddenTypes.Count() > 0 +from m in Application.Methods.ThatCreateAny(forbiddenTypes) +let forbiddenTypesUsed = m.MethodsCalled.Where(m1 => m1.IsConstructor && forbiddenTypes.Contains(m1.ParentType)).ParentTypes() +select new { + m, + forbiddenTypesUsed, + Debt = 15.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule has been deprecated and, as a consequence, it is disabled by default. +// Feel free to re-enable it if it makes sense in your dev environment. +// +// This rule warns about application methods that use a non-generic +// collection class, including **ArrayList**, **HashTable**, **Queue**, +// **Stack** or **SortedList**. +// + +// +// **List** should be preferred over **ArrayList**. +// It is generic hence you get strongly typed elements. +// Also, it is faster with *T* as a value types since it avoids boxing. +// +// For the same reasons: +// +// • **Dictionary** should be prevered over **HashTable**. +// +// • **Queue** should be prevered over **Queue**. +// +// • **Stack** should be prevered over **Stack**. +// +// • **SortedDictionary** or **SortedList** should be prevered over **SortedList**. +// +// You can be forced to use *non generic* collections +// because you are using third party code that requires +// working with these classes or because you are +// coding with .NET 1.x, but nowadays this situation should +// question about using newer updates of .NET. +// .NET 1.x is an immature platform conpared to newer .NET +// updates. +//]]> + Caution with List.Contains() + +// warnif count > 0 // This query is n ot a rule per default + +let containsMethods = ThirdParty.Methods.WithFullNameIn( + "System.Collections.Generic.List.Contains(T)", + "System.Collections.Generic.IList.Contains(T)", + "System.Collections.ArrayList.Contains(Object)") + +from m in Application.Methods.UsingAny(containsMethods) +select new { + m, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This code query matches calls to *List.Contains()* method. +// +// The cost of checking if a list contains an object is proportional +// to the size of the list. In other words it is a *O(N)* operation. +// For large lists and/or frequent calls to *Contains()*, prefer using +// the *System.Collections.Generic.HashSet* class +// where calls to *Contains()* take a constant +// time (*O(0)* operation). +// +// This code query is not a code rule, because more often than not, +// calling *O(N) Contains()* is not a mistake. This code query +// aims at pointing out this potential performance pitfall. +//]]> + Prefer return collection abstraction instead of implementation + +// warnif count > 0 // This query is n ot a rule per default + +let implTypes = ThirdParty.Types.WithFullNameIn( + "System.Collections.Generic.List", + "System.Collections.Generic.HashSet", + "System.Collections.Generic.Dictionary") + +from m in Application.Methods.WithReturnTypeIn(implTypes) +select new { + m, + m.ReturnType, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Low +} + +// +// This code query matches methods that return a +// collection implementation, such as *List* +// *HashSet* or *Dictionary*. +// +// Most often than not, clients of a method don't +// need to know the exact implementation of the +// collection returned. It is preferable to return +// a collection interface such as *IList*, +// *ICollection*, *IEnumerable* or +// *IDictionary*. +// +// Using the collection interface instead of the +// implementation shouldn't applies to all cases, +// hence this code query is not code rule. +//]]> + + + P/Invokes should be static and not be publicly visible +warnif count > 0 from m in Application.Methods where + !m.IsThirdParty && + (m.HasAttribute ("System.Runtime.InteropServices.DllImportAttribute".AllowNoMatch())) && + ( m.IsPubliclyVisible || + !m.IsStatic) +select new { + m, + m.Visibility, + m.IsStatic, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// Methods that are marked with the **DllImportAttribute** attribute +// (or methods that are defined by using the **Declare** keyword in Visual Basic) +// use **Platform Invocation Services** to access unmanaged code. +// +// Such methods should not be exposed. By keeping these methods *private* or *internal*, +// you make sure that your library cannot be used to breach security by allowing +// callers access to unmanaged APIs that they could not call otherwise. +// +// This rule matches methods tagged with *DllImportAttribute* attribute +// that are declared as *public* or declared as *non-static*. +// + +// +// To fix a violation of this rule, change the access level of the method +// and/or declare it as static. +//]]> + Move P/Invokes to NativeMethods class +warnif count > 0 from m in Application.Methods where + m.HasAttribute ("System.Runtime.InteropServices.DllImportAttribute".AllowNoMatch()) && + m.ParentType.SimpleName != "NativeMethods" +select new { + m, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// **Platform Invocation methods**, such as those that are marked by using the +// **System.Runtime.InteropServices.DllImportAttribute** attribute, or methods +// that are defined by using the **Declare** keyword in Visual Basic, access +// unmanaged code. These methods should be in one of the following classes: +// +// • **NativeMethods** - This class does not suppress stack walks for unmanaged +// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute* +// must not be applied to this class.) This class is for methods that can be +// used anywhere because a stack walk will be performed. +// +// • **SafeNativeMethods** - This class suppresses stack walks for unmanaged +// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute* +//is applied to this class.) This class is for methods that are safe for anyone +// to call. Callers of these methods are not required to perform a full security +// review to make sure that the usage is secure because the methods are harmless +// for any caller. +// +// • **UnsafeNativeMethods** - This class suppresses stack walks for unmanaged +// code permission. (*System.Security.SuppressUnmanagedCodeSecurityAttribute* +// is applied to this class.) This class is for methods that are potentially +// dangerous. Any caller of these methods must perform a full security review +// to make sure that the usage is secure because no stack walk will be performed. +// +// These classes are declared as *static internal*. The methods in these +// classes are *static* and *internal*. +// +// This rule matches *P/Invoke* methods not declared in such *NativeMethods* +// class. +// + +// +// To fix a violation of this rule, move the method to the appropriate +// **NativeMethods** class. For most applications, moving P/Invokes to a new +// class that is named **NativeMethods** is enough. +//]]> + NativeMethods class should be static and internal +warnif count > 0 from t in Application.Types.WithNameIn( + @"NativeMethods", "SafeNativeMethods", "UnsafeNativeMethods") where + t.IsPublic || !t.IsStatic +select new { + t, + t.Visibility, + t.IsStatic, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// In the description of the default rule *Move P/Invokes to NativeMethods class* +// it is explained that *NativeMethods* classes that host *P/Invoke* methods, +// should be declared as *static* and *internal*. +// +// This code rule warns about *NativeMethods* classes that are not declared +// *static* and *internal*. +// + +// +// Matched *NativeMethods* classes must be declared as *static* and *internal*. +//]]> + + + Don't create threads explicitly +warnif count > 0 from m in Application.Methods where + m.CreateA ("System.Threading.Thread".AllowNoMatch()) +select new { + m, + Debt = 60.ToMinutes().ToDebt(), + Severity = Severity.Critical +} + +// +// This code rule warns about methods that create *threads* explicitly +// by creating an instance of the class *System.Threading.Thread*. +// +// Prefer using the thread pool instead of creating manually your +// own threads. Threads are costly objects. They take approximately +// 200,000 cycles to create and about 100,000 cycles to destroy. +// By default they reserve 1 Mega Bytes of virtual memory for its +// stack and use 2,000-8,000 cycles for each context switch. +// +// As a consequence, it is preferable to let the thread pool +// recycle threads. +// + +// +// Instead of creating explicitly threads, use the **Task Parralel +// Library** *(TPL)* that relies on the CLR thread pool. +// +// Introduction to TPL: https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx +// +// TPL and the CLR v4 thread pool: +// http://www.danielmoth.com/Blog/New-And-Improved-CLR-4-Thread-Pool-Engine.aspx +// +// By default issues of this rule have a **Critical** severity +// because creating threads can have severe consequences. +//]]> + Don't use dangerous threading methods +warnif count > 0 + +let wrongMethods = ThirdParty.Methods.WithFullNameIn( + + "System.Threading.Thread.Abort()", + "System.Threading.Thread.Abort(Object)", + + "System.Threading.Thread.Sleep(Int32)", + + "System.Threading.Thread.Suspend()", + "System.Threading.Thread.Resume()") + +from m in Application.Methods.UsingAny(wrongMethods) +select new { + m, + suppressCallsTo = m.MethodsCalled.Intersect(wrongMethods), + Debt = 40.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns about using the methods +// *Abort()*, *Sleep()*, *Suspend()* or *Resume()* +// declared by the *Thread* class. +// +// • Usage of *Thread.Abort()* is dangerous. +// More information on this here: +// http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation +// +// • Usage of *Thread.Sleep()* is a sign of +// flawed design. More information on this here: +// http://msmvps.com/blogs/peterritchie/archive/2007/04/26/thread-sleep-is-a-sign-of-a-poorly-designed-program.aspx +// +// • *Suspend()* and *Resume()* are dangerous threading methods, marked as obsolete. +// More information on workaround here: +// http://stackoverflow.com/questions/382173/what-are-alternative-ways-to-suspend-and-resume-a-thread +// + +// +// Suppress calls to *Thread* methods exposed in the +// *suppressCallsTo* column in the rule result. +// +// Use instead facilities offered by the **Task Parralel +// Library** *(TPL)* : +// https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx +//]]> + Monitor TryEnter/Exit must be both called within the same method +warnif count > 0 + +let enterMethods = ThirdParty.Methods.WithFullNameWildcardMatchIn( + "System.Threading.Monitor.Enter(*", + "System.Threading.Monitor.TryEnter(*") + +from m in Application.Methods.UsingAny(enterMethods) +where + !m.IsUsing ("System.Threading.Monitor.Exit(Object)".AllowNoMatch()) +select new { + m, + enterMethodsCalled = m.MethodsCalled.Intersect(enterMethods), + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns when **System.Threading.Monitor** *Enter()* +// (or *TryEnter()*) and *Exit() methods are not called within +// the same method. +// +// Doing so makes the code *less readable*, because it gets harder +// to locate when **critical sections** begin and end. +// +// Also, you expose yourself to complex and error-prone scenarios. +// + +// +// Refactor matched methods to make sure that *Monitor critical +// sections* begin and end within the same method. Basics scenarios +// can be handled through the C# **lock** keyword. Using explicitly +// the class *Monitor* should be left for advanced situations, +// that require calls to methods like *Wait()* and *Pulse()*. +// +// More information on using the *Monitor* class can be found here: +// http://www.codeproject.com/Articles/13453/Practical-NET-and-C-Chapter +//]]> + ReaderWriterLock AcquireLock/ReleaseLock must be both called within the same method +warnif count > 0 + +let acquireLockMethods = ThirdParty.Methods.WithFullNameWildcardMatch( + "System.Threading.ReaderWriterLock.Acquire*Lock(*") + +let releaseLockMethods = ThirdParty.Methods.WithFullNameWildcardMatch( + "System.Threading.ReaderWriterLock.Release*Lock(*") + +from m in Application.Methods.UsingAny(acquireLockMethods) + .Except(Application.Methods.UsingAny(releaseLockMethods)) +select new { + m, + acquireLockMethods = m.MethodsCalled.Intersect(acquireLockMethods), + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns when **System.Threading.ReaderWriterLock** +// acquire and release, reader or writer locks methods are not called +// within the same method. +// +// Doing so makes the code *less readable*, because it gets harder +// to locate when **critical sections** begin and end. +// +// Also, you expose yourself to complex and error-prone scenarios. +// + +// +// Refactor matched methods to make sure that *ReaderWriterLock +// read or write critical sections* begin and end within the +// same method. +//]]> + Don't tag instance fields with ThreadStaticAttribute +warnif count > 0 +from f in Application.Fields +where !f.IsStatic && + f.HasAttribute ("System.ThreadStaticAttribute".AllowNoMatch()) +select new { + f, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule warns when the attribute **System.ThreadStaticAttribute** +// is tagging *instance* fields. As explained in documentation, this attribute +// is designed to tag only *static* fields. +// https://msdn.microsoft.com/en-us/library/system.threadstaticattribute +// + +// +// Refactor the code to make sure that all fields tagged with +// *ThreadStaticAttribute* are *static*. +//]]> + Method non-synchronized that read mutable states +from m in Application.Methods where + (m.ReadsMutableObjectState || m.ReadsMutableTypeState) && + !m.IsUsing ("System.Threading.Monitor".AllowNoMatch()) && + !m.IsUsing ("System.Threading.ReaderWriterLock".AllowNoMatch()) +select new { + m, + mutableFieldsUsed = m.FieldsUsed.Where(f => !f.IsImmutable) +} + +// +// Mutable object states are instance fields that +// can be modified through the lifetime of the object. +// +// Mutable type states are static fields that can be +// modified through the lifetime of the program. +// +// This query lists methods that read mutable state +// without synchronizing access. In the case of +// multi-threaded program, doing so can lead to +// state corruption. +// +// This code query is not a code rule because more often +// than not, a match of this query is not an issue. +//]]> + + + Method should not return concrete XmlNode +warnif count > 0 + +let concreteXmlTypes = ThirdParty.Types.ThatDeriveFromAny( + ThirdParty.Types.WithFullName("System.Xml.XmlNode")) + +from m in Application.Methods.WithReturnTypeIn(concreteXmlTypes) +select new { + m, + m.ReturnType, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns about method whose return type is +// **System.Xml.XmlNode** or any type derived from *XmlNode*. +// +// *XmlNode* implements the interface **System.Xml.Xpath.IXPathNavigable**. +// In most situation, returning this interface instead of the concrete +// type is a better *design* choice that will abstract client code +// from implementation details. +// + +// +// To fix a violation of this rule, change the concrete returned type +// to the suggested interface *IXPathNavigable* and refactor clients +// code if possible. +//]]> + Types should not extend System.Xml.XmlDocument +warnif count > 0 from t in Application.Types where + t.DeriveFrom("System.Xml.XmlDocument".AllowNoMatch()) +select new { + t, + Debt = 20.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule warns aboud subclasses of **System.Xml.XmlDocument**. +// +// Do not create a subclass of *XmlDocument* if you want to +// create an XML view of an underlying object model or data source. +// + +// +// Instead of subclassing *XmlDocument*, you can use the interface +// **System.Xml.XPath.IXPathNavigable** implemented by the class +// *XmlDocument*. +// +// An alternative of using *XmlDocument*, is to use +// **System.Xml.Linq.XDocument**, aka **LINQ2XML**. +// More information on this can be found here: +// http://stackoverflow.com/questions/1542073/xdocument-or-xmldocument +//]]> + + + Float and Date Parsing must be culture aware +warnif count > 0 + +let cultureUnawareMethods = + (from m in ThirdParty.Types.WithFullNameIn( + "System.DateTime", + "System.Single", + "System.Double", + "System.Decimal").ChildMethods() + where m.NbParameters > 0 && + (m.SimpleName.EqualsAny( + "Parse", "TryParse", "ToString")) && + !m.Name.Contains("IFormatProvider") + select m).ToHashSet() + +from m in Application.Methods.UsingAny(cultureUnawareMethods) +let cultureUnawareMethodsCalled = m.MethodsCalled.Intersect(cultureUnawareMethods) +select new { + m, + shouldntCall = cultureUnawareMethodsCalled, + Debt = (5 + 3*cultureUnawareMethodsCalled.Count()).ToMinutes().ToDebt(), + Severity = 5*cultureUnawareMethodsCalled.Count().ToMinutes().ToAnnualInterest() +} + +// +// Globalization is the design and development of applications that support +// localized user interfaces and regional data for users in multiple cultures. +// +// This rule warns about the usage of *non-globalized overloads* of +// the methods **Parse()**, **TryParse()** and **ToString()**, +// of the types **DateTime**, **float**, **double** and **decimal**. +// This is the symptom that your application is *at least partially* +// not globalized. +// +// *Non-globalized overloads* of these methods are the overloads +// that don't take a parameter of type **IFormatProvider**. +// + +// +// Globalize your applicaton and make sure to use the globalized overloads +// of these methods. In the column **MethodsCallingMe** of this rule result +// are listed the methods of your application that call the +// *non-globalized overloads*. +// +// More information on **Creating Globally Aware Applications** here: +// https://msdn.microsoft.com/en-us/library/cc853414(VS.95).aspx +// +// The estimated Debt, which means the effort to fix such issue, +// is equal to 5 minutes per application method calling at least one +// non-culture aware method called, plus 3 minutes per non-culture aware +// method called. +//]]> + + + Mark assemblies with assembly version +warnif count > 0 from a in Application.Assemblies where + !a.HasAttribute ("System.Reflection.AssemblyVersionAttribute".AllowNoMatch()) +select new { + a, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// The identity of an assembly is composed of the following information: +// +// • Assembly name +// +// • Version number +// +// • Culture +// +// • Public key (for strong-named assemblies). +// +// The .NET Framework uses the version number to uniquely identify an +// assembly, and to bind to types in strong-named assemblies. The +// version number is used together with version and publisher policy. +// By default, applications run only with the assembly version with +// which they were built. +// +// This rule matches assemblies that are not tagged with +// **System.Reflection.AssemblyVersionAttribute**. +// + +// +// To fix a violation of this rule, add a version number to the assembly +// by using the *System.Reflection.AssemblyVersionAttribute* attribute. +//]]> + Assemblies should have the same version +warnif count > 0 +let versionsLookup = Application.Assemblies.ToLookup(a => a.Version, a=> a) +let mostRepresentedVersion = versionsLookup.OrderByDescending(v => v.Count()).First().Key +from v in versionsLookup +where v.Key != mostRepresentedVersion +from a in v.ToArray() +select new { + a , + version = v.Key, + mostRepresentedVersion, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule reports application assemblies that have a version different +// than the version shared by most of application assemblies. +// +// Before fixing these issues, double check if there is a valid reason +// for dealing with more than one assembly version number. +// Typically this happens when the analyzed code base is made of assemblies +// that are not *compiled, developed or deployed* together. +// + +// +// If all assemblies of your application should have the same version number, +// just use the attribute **System.Reflection.AssemblyVersion** in a source +// file shared by the assemblies. +// +// Typically this source file is generated by a dedicated *MSBuild* task +// like this one http://www.msbuildextensionpack.com/help/4.0.5.0/html/d6c3b5e8-00d4-c826-1a73-3cfe637f3827.htm. +// +// Here you can find interesting assemblies versioning advices. +// http://stackoverflow.com/a/3905443/27194 +// +// By default issues of this rule have a severity set to **major** since +// unproper assemblies versioning can lead to complicated deployment problem. +//]]> + + + Public methods returning a reference needs a contract to ensure that a non-null reference is returned +warnif count > 0 +let ensureMethods = Application.Methods.WithFullName( + "System.Diagnostics.Contracts.__ContractsRuntime.Ensures(Boolean,String,String)") + +from ensureMethod in ensureMethods +from m in ensureMethod.ParentAssembly.ChildMethods where + m.IsPubliclyVisible && + !m.IsAbstract && + m.ReturnType != null && + // Identify that the return type is a reference type + (m.ReturnType.IsClass || m.ReturnType.IsInterface) && + !m.IsUsing(ensureMethod) && + + // Don't match method not implemented yet! + !m.CreateA("System.NotImplementedException".AllowNoMatch()) + +select new { + m, + ReturnTypeReference = m.ReturnType, + Debt = (5+3*m.MethodsCallingMe.Count()).ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// **Code Contracts** are useful to decrease ambiguity between callers and callees. +// Not ensuring that a reference returned by a method is *non-null* leaves ambiguity +// for the caller. This rule matches methods returning an instance of a reference type +// (class or interface) that doesn't use a **Contract.Ensure()** method. +// +// *Contract.Ensure()* is defined in the **Microsoft Code Contracts for .NET** +// library, and is typically used to write a code contract on returned reference: +// *Contract.Ensures(Contract.Result() != null, "returned reference is not null");* +// https://visualstudiogallery.msdn.microsoft.com/1ec7db13-3363-46c9-851f-1ce455f66970 +// + +// +// Use *Microsoft Code Contracts for .NET* on the public surface of your API, +// to remove most ambiguity presented to your client. Most of such ambiguities +// are about *null* or *not null* references. +// +// Don't use *null* reference if you need to define a method that might not +// return a result. Use instead the **TryXXX()** pattern exposed for example +// in the *System.Int32.TryParse()* method. +// +// The estimated Debt, which means the effort to fix such issue, is equal +// to 5 minutes per public application method that might return a null reference +// plus 3 minutes per method calling such method. +//]]> + + + + Discard generated Assemblies from JustMyCode +notmycode +from a in Application.Assemblies where +// Assemblies generated for Xsl IL compilation for example are tagged with this attribute +a.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) +select a + +// +// This code query is prefixed with **notmycode**. +// This means that all application assemblies matched by this +// code query are removed from the *code base view* **JustMyCode.Assemblies**. +// It also means that all *namespaces*, *types*, *methods* and +// *fields* contained in a matched assembly are removed from +// the code base view *JustMyCode*. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// So far this query only matches application assemblies tagged +// with *System.CodeDom.Compiler.GeneratedCodeAttribute*. +// Make sure to make this query richer to discard your generated +// assemblies from the NDepend rules results. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *assemblies*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + Discard generated Namespaces from JustMyCode +notmycode + +// First gather assemblies written with VB.NET +let vbnetAssemblies = Application.Assemblies.Where( + a => a.SourceDecls.Any(decl => decl.SourceFile.FileNameExtension.ToLower() == ".vb")) + +// Then find the My namespace and its child namespaces. +let vbnetMyNamespaces = vbnetAssemblies.ChildNamespaces().Where( + n => n.SimpleName == "My" || + n.ParentNamespaces.Any(nParent => nParent.SimpleName == "My")) + +from n in vbnetMyNamespaces +select n + +// +// This code query is prefixed with **notmycode**. +// This means that all application namespaces matched by this +// code query are removed from the *code base view* **JustMyCode.Namespaces**. +// It also means that all *types*, *methods* and *fields* contained in a +// matched namespace are removed from the code base view *JustMyCode*. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// So far this query matches the **My** namespaces generated +// by the VB.NET compiler. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *namespaces*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + Discard generated Types from JustMyCode +notmycode + +// Define some sets to quickly test EntityFramework generated types +let efContexts = Application.Types.Where(t => + t.DeriveFrom("System.Data.Entity.DbContext".AllowNoMatch()) && + t.SourceDecls.Count(sd => sd.SourceFile.FileName.ToLower().EndsWithAny(".context.cs", ".context.vb")) == 1).ToHashSet() +let efEntities = Application.Types.UsedByAny(efContexts).ToHashSet() +let efMigrations = Application.Types.Where(t => t.DeriveFrom("System.Data.Entity.Migrations.DbMigration".AllowNoMatch())).ToHashSet() + +from t in Application.Types where + + // Don't consider anonymous types as JustMyCode + // C# and VB.NET anonymous types generated by the compiler satisfies these conditions + (t.IsGeneratedByCompiler && + t.ParentNamespace.Name.Length == 0 && + t.SimpleNameLike("AnonymousType")) || + + // Resources, Settings, or typed DataSet generated types for example, are tagged with this attribute + t.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) || + + // This attribute identifies a type or member that is not part of the user code for an application. + t.HasAttribute ("System.Diagnostics.DebuggerNonUserCodeAttribute".AllowNoMatch()) || + + // Delegate types are always generated + t.IsDelegate || + + // Discard ASP.NET page types generated by aspnet_compiler.exe + // See: https://www.ndepend.com/FAQ.aspx#ASPNET + t.ParentNamespace.Name.EqualsAny("ASP", "__ASP") || + + // Discard ASP.NET special types + (t.SimpleName.EqualsAny("Startup","BundleConfig","RouteConfig") && + ThirdParty.Assemblies.WithNameWildcardMatchIn("System.Web*", "Microsoft.AspNetCore*").Any()) || + + // Discard DataSet classes and their nested types + (t.DeriveFrom("System.Data.DataSet".AllowNoMatch()) && + t.HasAttribute("System.ComponentModel.DesignerCategoryAttribute".AllowNoMatch())) || + (t.IsNested && t.ParentType != null && + t.ParentType.DeriveFrom("System.Data.DataSet".AllowNoMatch()) && + t.ParentType.HasAttribute("System.ComponentModel.DesignerCategoryAttribute".AllowNoMatch())) || + + // Discard DataSet TableAdapterManager classes their nested types + (t.SimpleName == "TableAdapterManager" && + t.DeriveFrom("System.ComponentModel.Component".AllowNoMatch())) || + (t.IsNested && t.ParentType != null && + t.ParentType.SimpleName == "TableAdapterManager" && + t.ParentType.DeriveFrom("System.ComponentModel.Component".AllowNoMatch())) || + + // Discard Xamarin form generated types that contain the method LoadDataTemplate() + (t.IsNested && + t.SimpleName.StartsWith("") && + t.Methods.Count(m => m.SimpleName == "LoadDataTemplate") == 1 && + t.ParentAssembly.AssembliesUsed.Count(a => a.Name.StartsWith("Xamarin")) > 0) || + + // Discard Xamarin Resource types + (t.IsNested && + t.ParentType != null && + t.ParentType.Name == "Resource" && + t.ParentAssembly.AssembliesUsed.Count(a => a.Name.StartsWith("Xamarin")) > 0) || + + // Discard Entity Framework generated DB context types and entities types used by DB context types! + efContexts.Contains(t) || + efEntities.Contains(t) || + efMigrations.Contains(t) || + + // Discard types generated for code contract + t.FullName.StartsWith("System.Diagnostics.Contracts.__ContractsRuntime") || + t.FullName == "System.Diagnostics.Contracts.RuntimeContractsAttribute" || + + // Discard all types declared in a folder path containing the word "generated" + (t.SourceFileDeclAvailable && + t.SourceDecls.All(s => s.SourceFile.FilePath.ParentDirectoryPath.ToString().ToLower().Contains("generated"))) || + + // Types created by the test infrastructure + t.FullName == "AutoGeneratedProgram" + +select t + +// +// This code query is prefixed with **notmycode**. +// This means that all application types matched by this +// code query are removed from the *code base view* **JustMyCode.Types**. +// It also means that all *methods* and *fields* contained in a +// matched type are removed from the code base view *JustMyCode*. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// So far this query matches several well-identified generated +// types, like the ones tagged with *System.CodeDom.Compiler.GeneratedCodeAttribute*. +// Make sure to make this query richer to discard your generated +// types from the NDepend rules results. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *types*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + Discard generated and designer Methods from JustMyCode +notmycode + +// +// First define source files paths to discard +// +from a in Application.Assemblies +where a.SourceFileDeclAvailable +let asmSourceFilesPaths = a.SourceDecls.Select(s => s.SourceFile.FilePath) + +let sourceFilesPathsToDiscard = ( + from filePath in asmSourceFilesPaths + let filePathLower= filePath.ToString().ToLower() + where + filePathLower.EndsWithAny( + ".g.cs", // Popular pattern to name generated files. + ".g.vb", + ".generated.cs", + ".generated.vb") || + filePathLower.EndsWithAny( + ".xaml", // notmycode WPF xaml code + ".designer.cs", // notmycode C# Windows Forms designer code + ".designer.vb") // notmycode VB.NET Windows Forms designer code + || + // notmycode methods in source files in a directory containing generated + filePathLower.Contains("generated") + select filePath +).ToHashSet() + +// +// Second: discard methods in sourceFilesPathsToDiscard +// +from m in a.ChildMethods +where (m.SourceFileDeclAvailable && + sourceFilesPathsToDiscard.Contains(m.SourceDecls.First().SourceFile.FilePath)) || + // Generated methods might be tagged with this attribute + m.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) || + + // This attributes identifies a type or member that is not part of the user code for an application. + m.HasAttribute ("System.Diagnostics.DebuggerNonUserCodeAttribute".AllowNoMatch()) || + + // Event adder/remover methods generated by the compiler. + ((m.IsEventAdder || m.IsEventRemover) && !m.SourceFileDeclAvailable) || + + // Default/implicit constructor generated by the compiler on class and structures that don't have constructor + (m.IsConstructor && + m.NbParameters == 0 && + (m.IsPublic || (m.IsProtected && m.ParentType.IsAbstract)) && + !m.SourceFileDeclAvailable) + +select new { m, m.NbLinesOfCode } + +// +// This code query is prefixed with **notmycode**. +// This means that all application methods matched by this +// code query are removed from the *code base view* **JustMyCode.Methods**. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// So far this query matches several well-identified generated +// methods, like the ones tagged with *System.CodeDom.Compiler.GeneratedCodeAttribute*, +// or the ones declared in a source file suffixed with *.designer.cs*. +// Make sure to make this query richer to discard your generated +// methods from the NDepend rules results. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *methods*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + Discard generated Fields from JustMyCode +notmycode + +// Define WindowsForm fields defined as fields assigned by the InitializeComponent() WindowsForm methods. +let winFormInitializeComponentsMethods = + Application.Methods.WithName("InitializeComponent()") + .Where(m => m.ParentType.BaseClass != null && m.ParentType.BaseClass.ParentNamespace.Name == "System.Windows.Forms") +let winFormFields = winFormInitializeComponentsMethods.SelectMany(m => m.FieldsAssigned).ToHashSet() + +from f in Application.Fields where + + // Discard WindowsForm fields + winFormFields.Contains(f) || + + // Eliminate "components" generated in Windows Form Control context + f.Name == "components" && f.ParentType.DeriveFrom("System.Windows.Forms.Control".AllowNoMatch()) || + + // Eliminate XAML generated fields + // IComponentConnector is XAML specific and is automatically implemented for every Window, Page and UserControl. + f.ParentType.Implement( "System.Windows.Markup.IComponentConnector".AllowNoMatch()) || // WPF IComponentConnector + f.ParentType.Implement("Windows.UI.Xaml.Markup.IComponentConnector".AllowNoMatch()) || // UWP IComponentConnector + + f.HasAttribute ("System.CodeDom.Compiler.GeneratedCodeAttribute".AllowNoMatch()) || + + // Property backing fields generated by the compiler + (f.IsGeneratedByCompiler && !f.IsEventDelegateObject) || + + // Match fields generated by the ASP.NET infrastructure + // in System.Web.UI classes like Page, Control, MasterPage... + (f.FieldType != null && + f.ParentType.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWith("System.Web.UI")) && + (f.FieldType.ParentNamespace.Name.StartsWith("System.Web.UI") || + f.FieldType.BaseClasses.Any(bc => bc.ParentNamespace.Name.StartsWith("System.Web.UI")) + )) || + + // Match fields named 'mappingSource' for DataContext classes + (f.Name == "mappingSource" && f.ParentType.DeriveFrom("System.Data.Linq.DataContext".AllowNoMatch())) + +select f + +// +// This code query is prefixed with **notmycode**. +// This means that all application fields matched by this +// code query are removed from the *code base view* **JustMyCode.Fields**. +// The code base view *JustMyCode* is used by most default code queries +// and rules. +// +// This query matches application fields tagged +// with *System.CodeDom.Compiler.GeneratedCodeAttribute*, and +// *Windows Form*, *WPF* and *UWP* fields generated by the designer. +// Make sure to make this query richer to discard your generated +// fields from the NDepend rules results. +// +// *notmycode* queries are executed before running others +// queries and rules. Also modifying a *notmycode* query +// provokes re-run of queries and rules that rely +// on the *JustMyCode* code base view. +// +// Several *notmycode* queries can be written to match *fields*, +// in which case this results in cumulative effect. +// +// Online documentation: +// https://www.ndepend.com/docs/cqlinq-syntax#NotMyCode +//]]> + JustMyCode code elements +from elem in JustMyCode.CodeElements +select new { + elem, + loc = elem.IsMethod? elem.AsMethod.NbLinesOfCode : null +} + +// +// This code query enumerates all +// *assemblies*, *namespaces*, *types*, *methods* and *fields* +// in your application, that are considered as being your code. +// +// This means concretely that the *ICodeBaseView* **JustMyCode** +// only shows these code elements. This code base view is used by +// many default code rule to avoid being warned on code elements +// that you don't consider as your code - typically the code +// elements generated by a tool. +// +// These code elements are the ones that are not matched +// by any quere prefixed with **notmycode**. +//]]> + NotMyCode code elements +from elem in Application.CodeElements.Where(element => !JustMyCode.Contains(element)) +select new { + elem, + loc = elem.IsMethod? elem.AsMethod.NbLinesOfCode : null +} + +// +// This code query enumerates all +// *assemblies*, *namespaces*, *types*, *methods* and *fields* +// in your application, that are considered as not being your code. +// +// This means concretely that the *ICodeBaseView* **JustMyCode** +// hide these code elements. This code base view is used by +// many default code rules to avoid being warned on code elements +// that you don't consider as your code - typically the code +// elements generated by a tool. +// +// These code elements are the ones matched by queries prefixed with +// **notmycode**. +//]]> + + + + +from issue in Issues +where issue.WasAdded() +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }]]> + +from issue in Issues +where issue.WasFixed() +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity }]]> + +from issue in Issues +where !issue.WasAdded() && + (issue.DebtDiff() > Debt.Zero || issue.AnnualInterestDiff() > AnnualInterest.Zero) +select new { + issue, + issue.Debt, debtDiff = issue.DebtDiff(), + issue.AnnualInterest, annualInterestDiff = issue.AnnualInterestDiff(), + issue.Severity +} + +// +// An issue is considered worsened if its *debt* increased since the baseline. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +// +]]> + +from issue in Issues +where issue.Severity == Severity.Blocker +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// An issue with the severity **Blocker** cannot move to production, it must be fixed. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > Issue and Debt. +//]]> + +from issue in Issues +where issue.Severity == Severity.Critical +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// An issue with a severity level **Critical** shouldn't move to production. +// It still can for business imperative needs purposes, but at worth it must be fixed during the next iterations. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > Issue and Debt. +//]]> + +from issue in Issues +where issue.Severity == Severity.High +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// An issue with a severity level **High** should be fixed quickly, but can wait until the next scheduled interval. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > issue and Debt. +//]]> + +from issue in Issues +where issue.Severity == Severity.Medium +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// An issue with a severity level **Medium** is a warning that if not fixed, won't have a significant impact on development. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > issue and Debt. +//]]> + +from issue in Issues +where issue.Severity == Severity.Low +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// The severity level **Low** is used by issues that have a zero, or close to zero, +// value for **Annual Interest**. +// +// Issues with a **Low** or **Medium** severity level represents small improvements, +// ways to make the code looks more elegant. +// +// The **Broken Window Theory** https://en.wikipedia.org/wiki/Broken_windows_theory states that: +// +// *"Consider a building with a few broken windows. +// If the windows are not repaired, the tendency is for vandals to break a few more windows. +// Eventually, they may even break into the building, and if it's unoccupied, perhaps become +// squatters or light fires inside."* +// + +// Issues with a *Low* or *Medium* severity level represents the *broken windows* of a code base. +// If they are not fixed, the tendency is for developers to not care for living +// in an elegant code, which will result in extra-maintenance-cost in the long term. +// +// The severity of an issue is inferred from the issue *annual interest* +// and thresholds defined in the NDepend Project Properties > issue and Debt. +//]]> + +from issue in Issues +where issue.Severity.EqualsAny(Severity.Blocker, Severity.Critical, Severity.High) +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// The number of issues with a severity Blocker, Critical or High. +// +// An issue with the severity **Blocker** cannot move to production, it must be fixed. +// +// An issue with a severity level **Critical** shouldn't move to production. +// It still can for business imperative needs purposes, but at worth it must be fixed during the next iterations. +// +// An issue with a severity level **High** should be fixed quickly, but can wait until the next scheduled interval. +//]]> + +from issue in Issues +select new { issue, issue.Debt, issue.AnnualInterest, issue.Severity } + +// +// The number of issues no matter the issue severity. +//]]> + + + +from rule in Rules +select new { + rule, + issues = rule.Issues(), + debt = rule.Debt(), + annualInterest = rule.AnnualInterest(), + maxSeverity = rule.IsViolated() && rule.Issues().Any() ? + (Severity?)rule.Issues().Max(i => i.Severity) : null +} + +// +// This trend metric counts the number of active rules. +// This count includes violated and not violated rules. +// This count includes critical and non critical rules. +// +// When no baseline is available, rules that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that rules that rely on diff are not counted +// because the baseline is not defined. +//]]> + +from rule in Rules +where rule.IsViolated() +select new { + rule, + issues = rule.Issues(), + debt = rule.Debt(), + annualInterest = rule.AnnualInterest(), + maxSeverity = rule.IsViolated() && rule.Issues().Any() ? + (Severity?)rule.Issues().Max(i => i.Severity) : null +} + +// +// This trend metric counts the number of active rules that are violated. +// This count includes critical and non critical rules. +// +// When no baseline is available, rules that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that rules that rely on diff are not counted +// because the baseline is not defined. +//]]> + +from rule in Rules +where rule.IsViolated() && rule.IsCritical +select new { + rule, + issues = rule.Issues(), + debt = rule.Debt(), + annualInterest = rule.AnnualInterest(), + maxSeverity = rule.IsViolated() && rule.Issues().Any() ? + (Severity?)rule.Issues().Max(i => i.Severity) : null +} + +// +// This trend metric counts the number of critical active rules that are violated. +// +// The concept of critical rule is useful to pinpoint certain rules that should not be violated. +// +// When no baseline is available, rules that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that rules that rely on diff are not counted +// because the baseline is not defined. +//]]> + + + +from qualityGate in QualityGates +select new { + qualityGate , + qualityGate.ValueString, + qualityGate.Status, +} + +// +// This trend metric counts the number of active quality gates, +// no matter the gate status (Pass, Warn, Fail). +// +// When no baseline is available, quality gates that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that quality gates that rely on diff are not counted +// because the baseline is not defined. +//]]> + +from qualityGate in QualityGates +where qualityGate.Warn +select new { + qualityGate , + qualityGate.ValueString, +} + +// +// This trend metric counts the number of active quality gates that warns. +// +// When no baseline is available, quality gates that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that quality gates that rely on diff are not counted +// because the baseline is not defined. +//]]> + +from qualityGate in QualityGates +where qualityGate.Fail +select new { + qualityGate , + qualityGate.ValueString, +} + +// +// This trend metric counts the number of active quality gates that fails. +// +// When no baseline is available, quality gates that rely on diff are not counted. +// If you observe that this count slightly decreases with no apparent reason, +// the reason is certainly that quality gates that rely on diff are not counted +// because the baseline is not defined. +//]]> + + + +let timeToDev = codeBase.EffortToDevelop() +let debt = Issues.Sum(i => i.Debt) +select 100d * debt.ToManDay() / timeToDev.ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Infer a percentage from: +// +// • the estimated total time to develop the code base +// +// • and the the estimated total time to fix all issues (the Debt). +// +// Estimated total time to develop the code base is inferred from +// # lines of code of the code base and from the +// *Estimated number of man-day to develop 1000 logicial lines of code* +// setting found in NDepend Project Properties > Issue and Debt. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +// ]]> + +Issues.Sum(i => i.Debt).ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +//]]> + +let debt = Issues.Sum(i => i.Debt) +let debtInBaseline = IssuesInBaseline.Sum(i => i.Debt) +select (debt - debtInBaseline).ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Debt added (or fixed if negative) since baseline. +// +// Debt documentation: https://www.ndepend.com/docs/technical-debt#Debt +//]]> + +Issues.Sum(i => i.AnnualInterest).ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Annual Interest documentation: https://www.ndepend.com/docs/technical-debt#AnnualInterest +//]]> + +let ai = Issues.Sum(i => i.AnnualInterest) +let aiInBaseline = IssuesInBaseline.Sum(i => i.AnnualInterest) +select (ai - aiInBaseline).ToManDay() + +// +// This Trend Metric name is suffixed with (Metric) +// to avoid query name collision with the Quality Gate with same name. +// +// Annual Interest added (or fixed if negative) since baseline. +// +// Annual Interest documentation: https://www.ndepend.com/docs/technical-debt#AnnualInterest +//]]> + +(Issues.Sum(i =>i.Debt).BreakingPoint(Issues.Sum(i =>i.AnnualInterest))).TotalYears() + +// +// The **breaking point** of a set of issues is the **debt** divided by the **annual interest**. +// +// The *debt* is the estimated cost-to-fix the issues. +// +// The *annual interest* is the estimated cost-to-**not**-fix the issues, per year. +// +// Hence the *breaking point* is the point in time from now, when not fixing the issues cost as much as fixing the issue. +// +// Breaking Point documentation: https://www.ndepend.com/docs/technical-debt#BreakingPoint +// ]]> + +let issues = Issues.Where(i => i.Severity.EqualsAny(Severity.Blocker, Severity.Critical, Severity.High)) +select (issues.Sum(i =>i.Debt).BreakingPoint(issues.Sum(i =>i.AnnualInterest))).TotalYears() + +// +// The **breaking point** of a set of issues is the **debt** divided by the **annual interest**. +// +// The *debt* is the estimated cost-to-fix the issues. +// +// The *annual interest* is the estimated cost-to-**not**-fix the issues, per year. +// +// Hence the *breaking point* is the point in time from now, when not fixing the issues cost as much as fixing the issue. +// +// Breaking Point documentation: https://www.ndepend.com/docs/technical-debt#BreakingPoint +// ]]> + + + +codeBase.NbLinesOfCode]]> + +JustMyCode.Methods.Sum(m => m.NbLinesOfCode) + +// JustMyCode is defined by code queries prefixed with 'notmycode' +// in the group 'Defining JustMyCode'. +]]> + +Application.Methods.Except(JustMyCode.Methods).Sum(m => m.NbLinesOfCode) + +// JustMyCode is defined by code queries prefixed with 'notmycode' +// in the group 'Defining JustMyCode'. +]]> + +from a in Application.Assemblies +let nbLocAdded = !a.IsPresentInBothBuilds() + ? a.NbLinesOfCode + : (a.NbLinesOfCode != null && a.OlderVersion().NbLinesOfCode != null) + ? a.NbLinesOfCode - (int)a.OlderVersion().NbLinesOfCode + : 0 +select (double?)nbLocAdded + + +// A value is computed by this Trend Metric query +// only if a Baseline for Comparison is provided. +// See Project Properties > Analysis > Baseline for Comparison +]]> + +Application.Assemblies.SelectMany( + a => a.SourceDecls.Select(sd => sd.SourceFile.FilePathString.ToLower())) +.Distinct() +.Count() + +// +// This trend metric counts the number of source files. +// +// If a value 0 is obtained, it means that at analysis time, +// assemblies PDB files were not available. +// https://www.ndepend.com/docs/ndepend-analysis-inputs-explanation +// +// So far source files cannot be matched by a code query. +// However editing the query "Application Types" and then +// *Group by source file declarations* will list source files +// with types source declarations. +//]]> + +codeBase.NbILInstructions +]]> + +Application.Methods.Except(JustMyCode.Methods).Sum(m => m.NbILInstructions) + +// JustMyCode is defined by code queries prefixed with 'notmycode' +// in the group 'Defining JustMyCode'. +]]> + +codeBase.NbLinesOfComment + +// +// This trend metric returns the number of lines of comment +// counted in application source files. +// +// So far commenting information is only extracted from C# source code +// and VB.NET support is planned. +//]]> + +codeBase.PercentageComment + +// +// This trend metric returns the percentage of comment +// compared to the number of **logical**lines of code. +// +// So far commenting information is only extracted from C# source code +// and VB.NET support is planned. +//]]> + +from a in Application.Assemblies +select new { + a, + Debt = a.AllDebt(), + Issues = a.AllIssues() +} + +// +// This trend metric query counts all application assemblies. +// For each assembly it shows the estimated **all** technical-debt and **all** issues. +// **All** means debt and issues of the assembly and of its child namespaces, types and members. +//]]> + +from n in Application.Namespaces +select new { + n, + Debt = n.AllDebt(), + Issues = n.AllIssues() +} + +// +// This trend metric query counts all application namespaces. +// For each namespace it shows the estimated **all** technical-debt and **all** issues. +// **All** means debt and issues of the namespace and of its child types and members. +//]]> + +from t in Application.Types.Where(t => !t.IsGeneratedByCompiler) +select new { + t, + Debt = t.AllDebt(), + Issues = t.AllIssues() +} + +// +// This trend metric query counts all application types non-generated by compiler. +// For each type it shows the estimated **all** technical-debt and **all** issues. +// **All** means debt and issues of the type and of its child members. +//]]> + +Application.Types.Where(t => t.IsPubliclyVisible && !t.IsGeneratedByCompiler)]]> + +Application.Types.Where(t => t.IsClass && !t.IsGeneratedByCompiler)]]> + +Application.Types.Where(t => t.IsClass && t.IsAbstract && !t.IsGeneratedByCompiler)]]> + +Application.Types.Where(t => t.IsInterface)]]> + +Application.Types.Where(t => t.IsStructure && !t.IsGeneratedByCompiler)]]> + +from m in Application.Methods.Where(m => !m.IsGeneratedByCompiler) +select new { + m, + Debt = m.Debt(), + Issues = m.Issues() +} + +// +// This trend metric query counts all application methods non-generated by compiler. +// For each method it shows the estimated technical-debt and the issues. +//]]> + +Application.Methods.Where(m => m.IsAbstract)]]> + +Application.Methods.Where(m => !m.IsAbstract && !m.IsGeneratedByCompiler)]]> + +from f in Application.Fields.Where(f => + !f.IsEnumValue && + !f.ParentType.IsEnumeration && + !f.IsGeneratedByCompiler && + !f.IsLiteral) +select new { + f, + Debt = f.AllDebt(), + Issues = f.AllIssues() +} + +// +// This trend metric query counts all application fields non-generated by compiler +// that are not enumeration values nor constant values. +// For each field it shows the estimated technical-debt and the issues. +//]]> + + + +JustMyCode.Methods + .Max(m => m.NbLinesOfCode) + +// Here is the code query to get the (JustMyCode) method with largest # Lines of Code +// JustMyCode.Methods.OrderByDescending(m => m.NbLinesOfCode).Take(1).Select(m => new {m, m.NbLinesOfCode})]]> + +Application.Methods.Where(m => m.NbLinesOfCode > 0) + .Average(m => m.NbLinesOfCode)]]> + +Application.Methods.Where(m => m.NbLinesOfCode >= 3) + .Average(m => m.NbLinesOfCode)]]> + +JustMyCode.Types + .Max(t => t.NbLinesOfCode) + +// Here is the code query to get the (JustMyCode) type with largest # Lines of Code +// JustMyCode.Types.OrderByDescending(t => t.NbLinesOfCode).Take(1).Select(t => new {t, t.NbLinesOfCode})]]> + +Application.Types.Where(t => t.NbLinesOfCode > 0) + .Average(t => t.NbLinesOfCode)]]> + +Application.Methods + .Max(m => m.CyclomaticComplexity) + +// Here is the code query to get the most complex method, according to Cyclomatic Complexity +// Application.Methods.OrderByDescending(m => m.CyclomaticComplexity).Take(1).Select(m => new {m, m.CyclomaticComplexity})]]> + +Application.Methods.Where(m => m.NbLinesOfCode> 0) + .Average(m => m.CyclomaticComplexity)]]> + +Application.Methods + .Max(m => m.ILCyclomaticComplexity) + +// Here is the code query to get the most complex method, according to Cyclomatic Complexity computed from IL code. +// Application.Methods.OrderByDescending(m => m.ILCyclomaticComplexity).Take(1).Select(m => new {m, m.CyclomaticComplexity})]]> + +Application.Methods.Where(m => m.NbILInstructions> 0) + .Average(m => m.ILCyclomaticComplexity)]]> + +Application.Methods + .Max(m => m.ILNestingDepth) + +// Here is the code query to get the method with higher ILNestingDepth. +// Application.Methods.OrderByDescending(m => m.ILNestingDepth).Take(1).Select(m => new {m, m.ILNestingDepth})]]> + +Application.Methods.Where(m => m.NbILInstructions> 0) + .Average(m => m.ILNestingDepth)]]> + +Application.Types + .Max(t => t.NbMethods) + +// Here is the code query to get the (JustMyCode) type with largest # of Methods +// JustMyCode.Types.OrderByDescending(t => t.NbMethods).Take(1).Select(t => new {t, t.Methods})]]> + +Application.Types.Average(t => t.NbMethods)]]> + +Application.Types.Where(t => t.IsInterface) + .Max(t => t.NbMethods) + +// Here is the code query to get the (JustMyCode) type with largest # of Methods +// JustMyCode.Types.OrderByDescending(t => t.NbMethods).Take(1).Select(t => new {t, t.Methods})]]> + +JustMyCode.Types.Where(t => t.IsInterface) + .Average(t => t.NbMethods)]]> + + + +codeBase.PercentageCoverage]]> + +codeBase.NbLinesOfCodeCovered]]> + +codeBase.NbLinesOfCodeNotCovered]]> + +(codeBase.NbLinesOfCode - codeBase.NbLinesOfCodeCovered - codeBase.NbLinesOfCodeNotCovered).ToNullableDouble() + +// +// **Lines of Code Uncoverable** are lines of code in methods tagged with the *Uncoverable attribute* +// or methods in types or assemblies tagged with the *Uncoverable attribute*. +// +// The *Uncoverable attribute* is defined in the: +// NDepend Project Properties > Analysis > Code Coverage > Un-Coverable attributes. +// +// These methods can be listed with the code query: +// *from m in Methods where !m.CoverageDataAvailable && m.NbLinesOfCode > 0 select new { m, m.NbLinesOfCode }* +// +// If coverage data imported at analysis time is not *in-sync* with the analyzed code base, +// this code query will also list methods not defined in the coverage data imported. +//]]> + +Application.Types.Where(t => t.PercentageCoverage == 100) + .Sum(t => t.NbLinesOfCodeCovered) + +// +// A line of code covered by tests is *even more valuable* if it is in a type 100% covered by test. +// +// Covering 90% of a class is not enough. +// +// • It means that this 10% uncovered code is hard-to-test, +// +// • which means that this code is not well-designed, +// +// • which means that it is error-prone. +// +// Better test error-prone code, isn't it? +//]]> + +Application.Methods.Where(m => m.PercentageCoverage == 100) + .Sum(m => m.NbLinesOfCodeCovered) + +// +// The same remark than in the Trend Metric **# Lines of Code in Types 100% Covered** +// applies for method 100% covered. +// +// A line of code covered by tests is *even more valuable* if it is in a method 100% covered by test. +//]]> + + +(from m in JustMyCode.Methods + +// Don't match too short methods +where m.NbLinesOfCode > 10 + +let CC = m.CyclomaticComplexity +let uncov = (100 - m.PercentageCoverage) / 100f +let CRAP = (CC * CC * uncov * uncov * uncov) + CC +where CRAP != null && CRAP > 30 select CRAP) +.Max(CRAP => CRAP) + +// +// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric +// that helps in pinpointing overly complex and untested code. +// Is has been first defined here: +// http://www.artima.com/weblogs/viewpost.jsp?thread=215899 +// +// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)** +// +// • where *CC(m)* is the *cyclomatic complexity* of the method *m* +// +// • and *cov(m)* is the *percentage coverage* by tests of the method *m* +// +// Matched methods cumulates two highly *error prone* code smells: +// +// • A complex method, difficult to develop and maintain. +// +// • Non 100% covered code, difficult to refactor without any regression bug. +// +// The higher the CRAP score, the more painful to maintain and error prone is the method. +// +// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors. +// +// Notice that no amount of testing will keep methods with a Cyclomatic Complexity +// higher than 30, out of CRAP territory. +// +// Notice that CRAP score is not computed for too short methods +// with less than 10 lines of code. +// +// To list methods with higher C.R.A.P scores, please refer to the default rule: +// *Test and Code Coverage* > *C.R.A.P method code metric* +//]]> + + +(from m in JustMyCode.Methods + +// Don't match too short methods +where m.NbLinesOfCode > 10 + +let CC = m.CyclomaticComplexity +let uncov = (100 - m.PercentageCoverage) / 100f +let CRAP = (CC * CC * uncov * uncov * uncov) + CC +where CRAP != null && CRAP > 30 select CRAP) +.Average(CRAP => CRAP) + +// +// **Change Risk Analyzer and Predictor** (i.e. CRAP) is a code metric +// that helps in pinpointing overly complex and untested code. +// Is has been first defined here: +// http://www.artima.com/weblogs/viewpost.jsp?thread=215899 +// +// The Formula is: **CRAP(m) = CC(m)^2 * (1 – cov(m)/100)^3 + CC(m)** +// +// • where *CC(m)* is the *cyclomatic complexity* of the method *m* +// +// • and *cov(m)* is the *percentage coverage* by tests of the method *m* +// +// Matched methods cumulates two highly *error prone* code smells: +// +// • A complex method, difficult to develop and maintain. +// +// • Non 100% covered code, difficult to refactor without any regression bug. +// +// The higher the CRAP score, the more painful to maintain and error prone is the method. +// +// An arbitrary threshold of 30 is fixed for this code rule as suggested by inventors. +// +// Notice that no amount of testing will keep methods with a Cyclomatic Complexity +// higher than 30, out of CRAP territory. +// +// Notice that CRAP score is not computed for too short methods +// with less than 10 lines of code. +// +// To list methods with higher C.R.A.P scores, please refer to the default rule: +// *Test and Code Coverage* > *C.R.A.P method code metric* +//]]> + + + +from a in ThirdParty.Assemblies +select new { a, a.AssembliesUsingMe }]]> + +from n in ThirdParty.Namespaces +select new { n, n.NamespacesUsingMe }]]> + +from t in ThirdParty.Types +select new { t, t.TypesUsingMe }]]> + +from m in ThirdParty.Methods +select new { m, m.MethodsCallingMe }]]> + +from f in ThirdParty.Fields +where !f.ParentType.IsEnumeration +select new { f, f.MethodsUsingMe }]]> + +from elem in ThirdParty.CodeElements +where !(elem.IsField && elem.AsField.ParentType.IsEnumeration) +let users = elem.IsMethod ? elem.AsMethod.MethodsCallingMe.Cast() : + elem.IsField ? elem.AsField.MethodsUsingMe.Cast() : + elem.IsType ? elem.AsType.TypesUsingMe.Cast() : + elem.IsNamespace ? elem.AsNamespace.NamespacesUsingMe.Cast() : + elem.AsAssembly.AssembliesUsingMe.Cast() +select new { elem, users } +]]> + + + + New assemblies +from a in Application.Assemblies where a.WasAdded() +select new { a, a.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *assemblies* that have been added since the *baseline*. +//]]> + Assemblies removed +from a in codeBase.OlderVersion().Application.Assemblies where a.WasRemoved() +select new { a, a.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *assemblies* that have been removed since the *baseline*. +//]]> + Assemblies where code was changed +from a in Application.Assemblies where a.CodeWasChanged() +select new { + a, + a.NbLinesOfCode, + oldNbLinesOfCode = a.OlderVersion().NbLinesOfCode.GetValueOrDefault() , + delta = (int) a.NbLinesOfCode.GetValueOrDefault() - a.OlderVersion().NbLinesOfCode.GetValueOrDefault() +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *assemblies* in which, code has been changed since the *baseline*. +//]]> + New namespaces +from n in Application.Namespaces where + !n.ParentAssembly.WasAdded() && + n.WasAdded() +select new { n, n.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *namespaces* that have been added since the *baseline*. +//]]> + Namespaces removed +from n in codeBase.OlderVersion().Application.Namespaces where + !n.ParentAssembly.WasRemoved() && + n.WasRemoved() +select new { n, n.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *namespaces* that have been removed since the *baseline*. +//]]> + Namespaces where code was changed +from n in Application.Namespaces where n.CodeWasChanged() +select new { + n, + n.NbLinesOfCode, + oldNbLinesOfCode = n.OlderVersion().NbLinesOfCode.GetValueOrDefault() , + delta = (int) n.NbLinesOfCode.GetValueOrDefault() - n.OlderVersion().NbLinesOfCode.GetValueOrDefault() +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *namespaces* in which, code has been changed since the *baseline*. +//]]> + New types +from t in Application.Types where + !t.ParentNamespace.WasAdded() && + t.WasAdded() && + !t.IsGeneratedByCompiler +select new { t, t.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* that have been added since the *baseline*. +//]]> + Types removed +from t in codeBase.OlderVersion().Application.Types where + !t.ParentNamespace.WasRemoved() && + t.WasRemoved() && + !t.IsGeneratedByCompiler +select new { t, t.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* that have been removed since the *baseline*. +//]]> + Types where code was changed + +from t in Application.Types where t.CodeWasChanged() +//select new { t, t.NbLinesOfCode } +select new { + t, + t.NbLinesOfCode, + oldNbLinesOfCode = t.OlderVersion().NbLinesOfCode , + delta = (int?) t.NbLinesOfCode - t.OlderVersion().NbLinesOfCode +} +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* in which, code has been changed since the *baseline*. +// +// To visualize changes in code, right-click a matched type and select: +// +// • Compare older and newer versions of source file +// +// • Compare older and newer versions disassembled with Reflector +//]]> + Heuristic to find types moved from one namespace or assembly to another +let typesRemoved = codeBase.OlderVersion().Types.Where(t => t.WasRemoved()) +let typesAdded = Types.Where(t => t.WasAdded()) + +from tMoved in typesAdded.Join( + typesRemoved, + t => t.Name, + t => t.Name, + (tNewer, tOlder) => new { tNewer, + OlderParentNamespace = tOlder.ParentNamespace, + OlderParentAssembly = tOlder.ParentAssembly } ) +select tMoved + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* moved from one namespace or assembly to another. +// The heuristic implemented consists in making a **join LINQ query** on +// type name (without namespace prefix), applied to the two sets of types *added* +// and types *removed*. +//]]> + Types directly using one or several types changed +let typesChanged = Application.Types.Where(t => t.CodeWasChanged()).ToHashSet() + +from t in JustMyCode.Types.UsingAny(typesChanged) where + !t.CodeWasChanged() && + !t.WasAdded() +let typesChangedUsed = t.TypesUsed.Intersect(typesChanged) +select new { t, typesChangedUsed } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists types *unchanged* since the *baseline* +// but that use directly some *types* where code has been changed +// since the *baseline*. +// +// For such matched type, the code hasen't been changed, but still the overall +// behavior might have been changed. +// +// The query result includes types changed directly used, +//]]> + Types indirectly using one or several types changed +let typesChanged = Application.Types.Where(t => t.CodeWasChanged()).ToHashSet() + +// 'depth' represents a code metric defined on types using +// directly or indirectly any type where code was changed. +let depth = JustMyCode.Types.DepthOfIsUsingAny(typesChanged) + +from t in depth.DefinitionDomain where + !t.CodeWasChanged() && + !t.WasAdded() + +let typesChangedDirectlyUsed = t.TypesUsed.Intersect(typesChanged) +let depthOfUsingTypesChanged = depth[t] +orderby depthOfUsingTypesChanged + +select new { + t, + depthOfUsingTypesChanged, + typesChangedDirectlyUsed +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists types *unchanged* since the *baseline* +// but that **use directly or indirectly** some *types* where +// code has been changed since the *baseline*. +// +// For such matched type, the code hasen't been changed, but still the overall +// behavior might have been changed. +// +// The query result includes types changed directly used, and the **depth of usage** +// of types indirectly used, *depth of usage* as defined in the documentation of +// *DepthOfIsUsingAny()* NDepend API method: +// https://www.ndepend.com/api/webframe.html?NDepend.API~NDepend.CodeModel.ExtensionMethodsSequenceUsage~DepthOfIsUsingAny.html +//]]> + New methods +from m in Application.Methods where + !m.ParentType.WasAdded() && + m.WasAdded() && + !m.IsGeneratedByCompiler +select new { m, m.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* that have been added since the *baseline*. +//]]> + Methods removed +from m in codeBase.OlderVersion().Application.Methods where + !m.ParentType.WasRemoved() && + m.WasRemoved() && + !m.IsGeneratedByCompiler +select new { m, m.NbLinesOfCode } + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* that have been removed since the *baseline*. +//]]> + Methods where code was changed +from m in Application.Methods where m.CodeWasChanged() +select new { + m, + m.NbLinesOfCode, + oldNbLinesOfCode = m.OlderVersion().NbLinesOfCode , + delta = (int?) m.NbLinesOfCode - m.OlderVersion().NbLinesOfCode +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* in which, code has been changed since the *baseline*. +// +// To visualize changes in code, right-click a matched method and select: +// +// • Compare older and newer versions of source file +// +// • Compare older and newer versions disassembled with Reflector +//]]> + Methods directly calling one or several methods changed +let methodsChanged = Application.Methods.Where(m => m.CodeWasChanged()).ToHashSet() + +from m in JustMyCode.Methods.UsingAny(methodsChanged ) where + !m.CodeWasChanged() && + !m.WasAdded() +let methodsChangedCalled = m.MethodsCalled.Intersect(methodsChanged) +select new { + m, + methodsChangedCalled +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists methods *unchanged* since the *baseline* +// but that call directly some *methods* where code has been changed +// since the *baseline*. +// +// For such matched method, the code hasen't been changed, but still the overall +// behavior might have been changed. +// +// The query result includes methods changed directly used, +//]]> + Methods indirectly calling one or several methods changed +let methodsChanged = Application.Methods.Where(m => m.CodeWasChanged()).ToHashSet() + +// 'depth' represents a code metric defined on methods using +// directly or indirectly any method where code was changed. +let depth = JustMyCode.Methods.DepthOfIsUsingAny(methodsChanged) + +from m in depth.DefinitionDomain where + !m.CodeWasChanged() && + !m.WasAdded() + +let methodsChangedDirectlyUsed = m.MethodsCalled.Intersect(methodsChanged) +let depthOfUsingMethodsChanged = depth[m] +orderby depthOfUsingMethodsChanged + +select new { + m, + depthOfUsingMethodsChanged, + methodsChangedDirectlyUsed +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists methods *unchanged* since the *baseline* +// but that **use directly or indirectly** some *methods* where +// code has been changed since the *baseline*. +// +// For such matched method, the code hasen't been changed, but still the overall +// behavior might have been changed. +// +// The query result includes methods changed directly used, and the **depth of usage** +// of methods indirectly used, *depth of usage* as defined in the documentation of +// *DepthOfIsUsingAny()* NDepend API method: +// https://www.ndepend.com/api/webframe.html?NDepend.API~NDepend.CodeModel.ExtensionMethodsSequenceUsage~DepthOfIsUsingAny.html +//]]> + New fields +from f in Application.Fields where + !f.ParentType.WasAdded() && + f.WasAdded() && + !f.IsGeneratedByCompiler +select f + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *fields* that have been added since the *baseline*. +//]]> + Fields removed +from f in codeBase.OlderVersion().Application.Fields where + !f.ParentType.WasRemoved() && + f.WasRemoved() && + !f.IsGeneratedByCompiler +select f + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *fields* that have been removed since the *baseline*. +//]]> + Third party types that were not used and that are now used +from t in ThirdParty.Types where t.IsUsedRecently() +select new { + t, + t.Methods, + t.Fields, + t.TypesUsingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* defined in **third-party assemblies**, that were not +// used at *baseline* time, and that are now used. +//]]> + Third party types that were used and that are not used anymore +from t in codeBase.OlderVersion().Types where t.IsNotUsedAnymore() +select new { + t, + t.Methods, + t.Fields, + TypesThatUsedMe = t.TypesUsingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *types* defined in **third-party assemblies**, that were +// used at *baseline* time, and that are not used anymore. +//]]> + Third party methods that were not used and that are now used +from m in ThirdParty.Methods where + m.IsUsedRecently() && + !m.ParentType.IsUsedRecently() +select new { + m, + m.MethodsCallingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* defined in **third-party assemblies**, that were not +// used at *baseline* time, and that are now used. +//]]> + Third party methods that were used and that are not used anymore +from m in codeBase.OlderVersion().Methods where + m.IsNotUsedAnymore() && + !m.ParentType.IsNotUsedAnymore() +select new { + m, + MethodsThatCalledMe = m.MethodsCallingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *methods* defined in **third-party assemblies**, that were +// used at *baseline* time, and that are not used anymore. +//]]> + Third party fields that were not used and that are now used +from f in ThirdParty.Fields where + f.IsUsedRecently() && + !f.ParentType.IsUsedRecently() +select new { + f, + f.MethodsUsingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *fields* defined in **third-party assemblies**, that were not +// used at *baseline* time, and that are now used. +//]]> + Third party fields that were used and that are not used anymore +from f in codeBase.OlderVersion().Fields where + f.IsNotUsedAnymore() && + !f.ParentType.IsNotUsedAnymore() +select new { + f, + MethodsThatUsedMe = f.MethodsUsingMe +} + +// +// This query is executed only if a *baseline for comparison* is defined (*diff mode*). +// +// This code query lists *fields* defined in **third-party assemblies**, that were +// used at *baseline* time, and that are not used anymore. +//]]> + + + Most used types (Rank) +(from t in Application.Types + where !t.IsGeneratedByCompiler + orderby t.Rank descending + select new { t, t.Rank, t.TypesUsingMe }).Take(100) + +// +// **TypeRank** values are computed by applying +// the **Google PageRank** algorithm on the +// graph of types' dependencies. Types with +// high *Rank* are the most used ones. Not necessarily +// the ones with the most users types, but the ones +// used by many types, themselves having a lot of +// types users. +// +// See the definition of the TypeRank metric here: +// https://www.ndepend.com/docs/code-metrics#TypeRank +// +// This code query lists the 100 application types +// with the higher rank. +// +// The main consequence of being used a lot for a +// type is that each change (both *syntax change* +// and *behavior change*) will result in potentially +// a lot of **pain** since most types clients will be +// **impacted**. +// +// Hence it is preferable that types with higher +// *TypeRank*, are **interfaces**, that are typically +// less subject changes. +// +// Also interfaces avoid clients relying on +// implementations details. Hence, when the behavior of +// classes implementing an interface changes, this +// shouldn't impact clients of the interface. +// This is *in essence* the +// **Liskov Substitution Principle**. +// http://en.wikipedia.org/wiki/Liskov_substitution_principle +//]]> + Most used methods (Rank) +(from m in Application.Methods + where !m.IsGeneratedByCompiler + orderby m.Rank descending + select new { m, m.Rank, m.MethodsCallingMe }).Take(100) + +// +// **MethodRank** values are computed by applying +// the **Google PageRank** algorithm on the +// graph of methods' dependencies. Methods with +// high *Rank* are the most used ones. Not necessarily +// the ones with the most callers methods, but the ones +// called by many methods, themselves having a lot +// of callers. +// +// See the definition of the MethodRank metric here: +// https://www.ndepend.com/docs/code-metrics#MethodRank +// +// This code query lists the 100 application methods +// with the higher rank. +// +// The main consequence of being used a lot for a +// method is that each change (both *signature change* +// and *behavior change*) will result in potentially +// a lot of **pain** since most methods callers will be +// **impacted**. +// +// Hence it is preferable that methods with highest +// *MethodRank*, are **abstract methods**, that are +// typically less subject to signature changes. +// +// Also abstract methods avoid callers relying on +// implementations details. Hence, when the code +// of a method implementing an abstract method changes, +// this shouldn't impact callers of the abstract method. +// This is *in essence* the +// **Liskov Substitution Principle**. +// http://en.wikipedia.org/wiki/Liskov_substitution_principle +//]]> + Most used assemblies (#AssembliesUsingMe) +(from a in Assemblies orderby a.AssembliesUsingMe.Count() descending + select new { a, a.AssembliesUsingMe }).Take(100) + +// +// This code query lists the 100 *application* and *third-party* +// assemblies, with the higher number of assemblies users. +//]]> + Most used namespaces (#NamespacesUsingMe ) +(from n in Namespaces orderby n.NbNamespacesUsingMe descending + select new { n, n.NamespacesUsingMe }).Take(100) + +// +// This code query lists the 100 *application* and *third-party* +// namespaces, with the higher number of namespaces users. +//]]> + Most used types (#TypesUsingMe ) +(from t in Types orderby t.NbTypesUsingMe descending + where !t.IsGeneratedByCompiler + select new { t, t.TypesUsingMe }).Take(100) + +// +// This code query lists the 100 *application* and *third-party* +// types, with the higher number of types users. +//]]> + Most used methods (#MethodsCallingMe ) +(from m in Methods orderby m.NbMethodsCallingMe + where !m.IsGeneratedByCompiler + select new { m, m.MethodsCallingMe }).Take(100) + +// +// This code query lists the 100 *application* and *third-party* +// methods, with the higher number of methods callers. +//]]> + Namespaces that use many other namespaces (#NamespacesUsed ) +(from n in Application.Namespaces orderby n.NbNamespacesUsed descending + select new { n, n.NamespacesUsed }).Take(100) + +// +// This code query lists the 100 *application* namespaces +// with the higher number of namespaces used. +//]]> + Types that use many other types (#TypesUsed ) +(from t in Application.Types orderby t.NbTypesUsed descending + select new { t, t.TypesUsed, isMyCode = JustMyCode.Contains(t) }).Take(100) + +// +// This code query lists the 100 *application* types +// with the higher number of types used. +//]]> + Methods that use many other methods (#MethodsCalled ) +(from m in Application.Methods orderby m.NbMethodsCalled descending + select new { m, m.MethodsCalled, isMyCode = JustMyCode.Contains(m) }).Take(100) + +// +// This code query lists the 100 *application* methods +// with the higher number of methods called. +//]]> + High-level to low-level assemblies (Level) +from a in Application.Assemblies orderby a.Level descending +select new { a, a.Level } + +// +// This code query lists assemblies ordered by **Level** values. +// See the definition of the *AssemblyLevel* metric here: +// https://www.ndepend.com/docs/code-metrics#Level +//]]> + High-level to low-level namespaces (Level) +from n in Application.Namespaces orderby n.Level descending +select new { n, n.Level } + +// +// This code query lists namespaces ordered by **Level** values. +// See the definition of the *NamespaceLevel* metric here: +// https://www.ndepend.com/docs/code-metrics#Level +//]]> + High-level to low-level types (Level) +from t in Application.Types orderby t.Level descending +select new { t, t.Level } + +// +// This code query lists types ordered by **Level** values. +// See the definition of the *TypeLevel* metric here: +// https://www.ndepend.com/docs/code-metrics#Level +//]]> + High-level to low-level methods (Level) +from m in Application.Methods orderby m.Level descending +select new { m, m.Level } + +// +// This code query lists methods ordered by **Level** values. +// See the definition of the *MethodLevel* metric here: +// https://www.ndepend.com/docs/code-metrics#Level +//]]> + + + + Check that the assembly Asm1 is not using the assembly Asm2 +warnif count > 0 from a in Application.Assemblies where + a.IsUsing ("Asm2".AllowNoMatch().MatchAssembly()) && + (a.Name == @"Asm1") +select new { + a, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to be warned if a particular assembly is using +// another particular assembly. +// +// Such rule can be generated for assemblies **A** and **B**: +// +// • by right clicking the cell in the *Dependency Matrix* +// with **B** in row and **A** in column, +// +// • or by right-clicking the concerned arrow in the *Dependency +// Graph* from **A** to **B**, +// +// and in both cases, click the menu +// **Generate a code rule that warns if this dependency exists** +// +// The generated rule will look like this one. +// It is now up to you to adapt this rule to check exactly +// your needs. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the namespace N1.N2 is not using the namespace N3.N4.N5 +warnif count > 0 from n in Application.Namespaces where + n.IsUsing ("N3.N4.N5".AllowNoMatch().MatchNamespace()) && + (n.Name == @"N1.N2") +select new { + n, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to be warned if a particular namespace is using +// another particular namespace. +// +// Such rule can be generated for namespaces **A** and **B**: +// +// • by right clicking the cell in the *Dependency Matrix* +// with **B** in row and **A** in column, +// +// • or by right-clicking the concerned arrow in the *Dependency +// Graph* from **A** to **B**, +// +// and in both cases, click the menu +// **Generate a code rule that warns if this dependency exists** +// +// The generated rule will look like this one. +// It is now up to you to adapt this rule to check exactly +// your needs. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the assembly Asm1 is only using the assemblies Asm2, Asm3 or mscorlib +warnif count > 0 from a in Application.Assemblies where + ( !a.IsUsing ("Asm2".AllowNoMatch().MatchAssembly()) || + !a.IsUsing ("Asm3".AllowNoMatch().MatchAssembly()) || + !a.IsUsing ("mscorlib".MatchAssembly()) || + a.AssembliesUsed.Count() != 3) // Must not be used more than 3 assemblies +&& + (a.Name == @"Asm1") +select new { + a, + a.AssembliesUsed, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that a particular assembly +// is only using a particular set of assemblies. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the namespace N1.N2 is only using the namespaces N3.N4, N5 or System +warnif count > 0 from n in Application.Namespaces where + ( !n.IsUsing("N3.N4".AllowNoMatch().MatchNamespace()) || + !n.IsUsing("N5".AllowNoMatch().MatchNamespace()) || + !n.IsUsing("System".MatchNamespace()) || + n.NamespacesUsed.Count() != 3) // Must not be used more than 3 assemblies + // AsmCe = Efferent Coupling for assembly +&& + (n.Name == @"N1.N2") +select new { + n, + n.NamespacesUsed, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that a particular namespace +// **is only using** a particular set of namespaces. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that AsmDrawing is the only assembly that is using System.Drawing +warnif count> 0 from a in Application.Assemblies where + a.IsUsing ("System.Drawing".AllowNoMatch().MatchAssembly()) && + !(a.Name == @"AsmDrawing") +select new { + a, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that a particular assembly +// is **only used by** another particular assembly. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that only 3 assemblies are using System.Drawing +warnif count != 3 from a in Application.Assemblies where + a.IsUsing ("System.Drawing".AllowNoMatch().MatchAssembly()) +select new { + a, + Debt = 30.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that a particular assembly +// is **only used by** 3 any others assemblies. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all methods that call Foo.Fct1() also call Foo.Fct2(Int32) +warnif count > 0 from m in Application.Methods where + m.IsUsing ("Foo.Fct1()".AllowNoMatch()) && + !m.IsUsing ("Foo.Fct2(Int32)".AllowNoMatch()) +select new { + m, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that if a method calls a particular method, +// it must call another particular method. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all types that derive from Foo, also implement IFoo +warnif count > 0 from t in Application.Types where + t.DeriveFrom ("Foo".AllowNoMatch().MatchType()) && + !t.Implement ("IFoo".AllowNoMatch().MatchType()) +select new { + t, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that all classes that derive from a particular base class, +// also implement a particular interface. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all types that has the attribute FooAttribute are declared in the namespace N1.N2* +warnif count > 0 from t in + Application.Namespaces.WithNameWildcardMatchNotIn("N1.N2*").ChildTypes() + where + t.HasAttribute ("FooAttribute".AllowNoMatch()) +select new { + t, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that all types that are tagged +// with a particular attribute, are declared in a +// particular namespace. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all synchronization objects are only used from the namespaces under MyNamespace.Sync +warnif count > 0 from n in Application.Namespaces + where + (n.IsUsing ("System.Threading.Monitor".AllowNoMatch()) || + n.IsUsing ("System.Threading.ReaderWriterLock".AllowNoMatch()) || + n.IsUsing ("System.Threading.Mutex".AllowNoMatch()) || + n.IsUsing ("System.Threading.EventWaitHandle".AllowNoMatch()) || + n.IsUsing ("System.Threading.Semaphore".AllowNoMatch()) || + n.IsUsing ("System.Threading.Interlocked".AllowNoMatch())) + && !n.NameLike (@"^MyNamespace.Sync") +select new { + n, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that all synchronization objects +// are used from a particular namespace. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + + + Check that the namespace N1.N2 is 100% covered by tests +warnif count > 0 from n in Application.Namespaces where + (n.Name == @"N1.N2") && + n.PercentageCoverage < 100 +select new { + n, + n.PercentageCoverage, + Debt = n.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This is a sample rule that shows how to check +// if a particular namespace is 100% covered by tests. +// Both the string **@"N1.N2"** and the threshold **100** can be adapted to your own needs. +// +// To execute this sample rule, coverage data must be imported. +// More info here: https://www.ndepend.com/docs/code-coverage +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the assembly Asm is 100% covered by tests +warnif count > 0 from a in Application.Assemblies where + (a.Name == @"Asm") && + a.PercentageCoverage < 100 +select new { + a, + a.PercentageCoverage, + Debt = a.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This is a sample rule that shows how to check +// if a particular assembly is 100% covered by tests. +// Both the string **@"Asm"** and the threshold **100** can be adapted to your own needs. +// +// To execute this sample rule, coverage data must be imported. +// More info here: https://www.ndepend.com/docs/code-coverage +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the class Namespace.Foo is 100% covered by tests +warnif count > 0 from t in Application.Types where + (t.FullName == @"Namespace.Foo") && + t.PercentageCoverage < 100 +select new { + t, + t.PercentageCoverage, + Debt = t.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// This is a sample rule that shows how to check +// if a particular class is 100% covered by tests. +// Both the string **@"Namespace.Foo"** and the threshold **100** +// can be adapted to your own needs. +// +// To execute this sample rule, coverage data must be imported. +// More info here: https://www.ndepend.com/docs/code-coverage +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that the class Namespace.Foo.Method(Int32) is 100% covered by tests +warnif count > 0 from t in Application.Types where + (t.FullName == @"Namespace.Foo.Method(Int32)") && + t.PercentageCoverage < 100 +select new { + t, + t.PercentageCoverage, + Debt = t.NbLinesOfCodeNotCovered.ToMinutes().ToDebt(), + Severity = Severity.High +} + + +// +// This is a sample rule that shows how to check +// if a particular method is 100% covered by tests. +// Both the string **@"Namespace.Foo.Method(Int32)"** and the threshold **100** +// can be adapted to your own needs. +// +// To execute this sample rule, coverage data must be imported. +// More info here: https://www.ndepend.com/docs/code-coverage +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + + + Check that all types that derive from Foo, has a name that ends up with Foo +warnif count > 0 from t in Application.Types where + t.DeriveFrom ("Foo".AllowNoMatch().MatchType()) && + !t.NameLike (@"Foo$") +select new { + t, + t.NbLinesOfCode, + Debt = 5.ToMinutes().ToDebt(), + Severity = Severity.Medium +} + +// +// This rule is a *sample rule that can be adapted to your need*. +// +// It shows how to enforce that all classes that derive from +// a particular class, are named with a particular suffix. +// + +// +// This is a *sample rule* there is nothing to fix *as is*. +//]]> + Check that all namespaces begins with CompanyName.ProductName +warnif count > 0 from n in Application.Namespaces where + !n.NameLike (@"^CompanyName.ProductName") +select new { + n, + n.NbLinesOfCode, + Debt = 10.ToMinutes().ToDebt(), + Severity = Severity.High +} + +// +// A practice widely adopted is that, in a product source code, +// all namespaces start with "CompanyName.ProductName". +// +// This rule must be adapted with your own **"CompanyName.ProductName"**. +// + +// +// Update all namespaces definitions in source code to satisfy this rule. +//]]> + + + + + \ No newline at end of file