diff --git a/.gitignore b/.gitignore index f0bdc0559..9cba98a7d 100644 --- a/.gitignore +++ b/.gitignore @@ -334,4 +334,5 @@ appsettings.development.json appsettings.production.json src/FilterLists.Agent/appsettings.json src/FilterLists.Web/wwwroot/dist +FilterLists.ndproj NDependOut*/ \ No newline at end of file diff --git a/FilterLists.ndproj b/FilterLists.ndproj deleted file mode 100644 index b0e0da3ba..000000000 --- a/FilterLists.ndproj +++ /dev/null @@ -1,11518 +0,0 @@ - - - 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