diff --git a/.gitignore b/.gitignore index 1cea65d5..89269cc2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ _site /.nuke/temp /build/bin /build/obj +/Build/bin +/Build/obj diff --git a/Build/Build.cs b/Build/Build.cs index 4d63c14d..61a52707 100644 --- a/Build/Build.cs +++ b/Build/Build.cs @@ -142,6 +142,7 @@ string ProcessPage(AbsolutePath pageFile) rawContent = StripFrontmatter(rawContent); var content = string.IsNullOrEmpty(category) ? rawContent : BuildCategorySection(category); content = ApplySiteReplacements(content); + content = TransformAlertsForPandoc(content); return string.IsNullOrEmpty(title) ? content : $"

{title}

\n{content}"; } @@ -180,6 +181,80 @@ static string ApplySiteReplacements(string content) @"\(\/.+?(#\w+)\)", "($1)"); } + static string TransformAlertsForPandoc(string content) + { + var lines = content.Replace("\r\n", "\n").Split('\n'); + var transformed = new List(lines.Length); + + for (var index = 0; index < lines.Length;) + { + if (!TryTransformAlert(lines, ref index, transformed)) + { + transformed.Add(lines[index]); + index++; + } + } + + return string.Join("\n", transformed); + } + + static bool TryTransformAlert(string[] lines, ref int index, List transformed) + { + var match = Regex.Match(lines[index], @"^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$"); + if (!match.Success) + return false; + + var alertType = match.Groups[1].Value; + var alertLines = new List(); + index++; + + while (index < lines.Length && lines[index].StartsWith(">")) + { + alertLines.Add(lines[index].Length == 1 ? "" : lines[index][2..]); + index++; + } + + if (alertLines.Count == 0) + { + transformed.Add(lines[index - 1]); + return true; + } + + var title = alertType switch + { + "NOTE" => "Note", + "TIP" => "Tip", + "IMPORTANT" => "Important", + "WARNING" => "Warning", + "CAUTION" => "Caution", + _ => alertType + }; + + var firstContentLineIndex = alertLines.FindIndex(line => !string.IsNullOrWhiteSpace(line)); + if (firstContentLineIndex >= 0 && alertType == "IMPORTANT") + { + var exceptionMatch = Regex.Match(alertLines[firstContentLineIndex], @"^(Exceptions?):\s*(.*)$"); + if (exceptionMatch.Success) + { + title = exceptionMatch.Groups[1].Value; + alertLines[firstContentLineIndex] = exceptionMatch.Groups[2].Value; + } + } + + if (firstContentLineIndex >= 0) + { + var firstLine = alertLines[firstContentLineIndex]; + alertLines[firstContentLineIndex] = string.IsNullOrWhiteSpace(firstLine) + ? $"**{title}:**" + : $"**{title}:** {firstLine}"; + } + + foreach (var alertLine in alertLines) + transformed.Add(alertLine.Length == 0 ? ">" : $"> {alertLine}"); + + return true; + } + static void AppendRuleIfInCategory(StringBuilder content, AbsolutePath ruleFile, string category) { var rule = ruleFile.ReadAllText(); @@ -372,4 +447,4 @@ static string ExtractFrontmatterField(string content, string fieldName) var match = Regex.Match(content, $@"---(.|\n)*?{Regex.Escape(fieldName)}\:\s*([^\r\n]+)"); return match.Success ? match.Groups[2].Value.Trim() : string.Empty; } -} \ No newline at end of file +} diff --git a/_includes/footer/custom.html b/_includes/footer/custom.html index d512599d..ab4d5b6a 100644 --- a/_includes/footer/custom.html +++ b/_includes/footer/custom.html @@ -1,3 +1,53 @@ - \ No newline at end of file + + + diff --git a/_rules/0100.md b/_rules/0100.md index 58d7c7d2..8972e9ea 100644 --- a/_rules/0100.md +++ b/_rules/0100.md @@ -11,4 +11,5 @@ Understanding these boundaries matters for several reasons: - It guides when to introduce an abstraction (across a boundary) versus when to skip it (within a boundary). - It helps decide when to duplicate small pieces of logic instead of pulling in a shared dependency that would increase coupling. -**Tip:** When in doubt whether something belongs inside or outside a boundary, ask yourself: "If this boundary were a separate deployable unit, would this dependency still make sense?" +> [!TIP] +> When in doubt whether something belongs inside or outside a boundary, ask yourself: "If this boundary were a separate deployable unit, would this dependency still make sense?" diff --git a/_rules/0110.md b/_rules/0110.md index c897423b..0bd98e39 100644 --- a/_rules/0110.md +++ b/_rules/0110.md @@ -10,4 +10,5 @@ Prefer composition when: - You want to reuse behavior without exposing or depending on the internals of another class. - You need to vary behavior at runtime (e.g. through the [Strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern)). -**Exception:** Inheritance is appropriate when a true "is-a" relationship exists and when derived classes genuinely extend (rather than replace) the behavior of their base class. Always follow the [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) when using inheritance. +> [!IMPORTANT] +> Exception: Inheritance is appropriate when a true "is-a" relationship exists and when derived classes genuinely extend (rather than replace) the behavior of their base class. Always follow the [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) when using inheritance. diff --git a/_rules/0125.md b/_rules/0125.md index cd345c55..177a63e9 100644 --- a/_rules/0125.md +++ b/_rules/0125.md @@ -31,5 +31,7 @@ This applies primarily to: For complex or domain-critical logic that must be consistent everywhere, a shared library is still the right choice. But always consider the [Rule of Three](https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)) as a practical guide: refactor duplication after the third occurrence and you've identified a real pattern. Also consider a source-only package as it avoids the binary dependencies normal packages have. Check out [Reflectify](https://github.com/dennisdoomen/reflectify?tab=readme-ov-file#readme) or [Pathy](https://github.com/dennisdoomen/pathy?tab=readme-ov-file#readme) for practical examples of that. -**Exception:** -Duplication in tests is often beneficial as it will make the tests easier to understand without the need to dig into all kinds of shared helper methods. +> [!IMPORTANT] +> Exception: +> +> Duplication in tests is often beneficial as it will make the tests easier to understand without the need to dig into all kinds of shared helper methods. diff --git a/_rules/1000.md b/_rules/1000.md index f3028347..cc39d1c2 100644 --- a/_rules/1000.md +++ b/_rules/1000.md @@ -5,12 +5,17 @@ title: A class or interface should have a single purpose --- A class or interface should have a single purpose within the system it functions in. In general, a class either represents a primitive type like an email address or ISBN number, an abstraction of some business concept, a plain data structure, or is responsible for orchestrating the interaction between other classes. It is never a combination of those. This rule is widely known as the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single-responsibility_principle), one of the S.O.L.I.D. principles. -**Tip:** A class with the word `And` in it is an obvious violation of this rule. +> [!TIP] +> A class with the word `And` in it is an obvious violation of this rule. -**Tip:** Use [Design Patterns](http://en.wikipedia.org/wiki/Design_pattern_(computer_science)) to communicate the intent of a class. If you can't assign a single design pattern to a class, chances are that it is doing more than one thing. +> [!TIP] +> Use [Design Patterns](http://en.wikipedia.org/wiki/Design_pattern_(computer_science)) to communicate the intent of a class. If you can't assign a single design pattern to a class, chances are that it is doing more than one thing. -**Tip:** If you can't find a good name for a class, or struggle to document what it does, it is probably doing too much. +> [!TIP] +> If you can't find a good name for a class, or struggle to document what it does, it is probably doing too much. -**Tip:** If you describe what a class does to an AI assistant and it identifies multiple responsibilities, that's a strong signal to split it up. +> [!TIP] +> If you describe what a class does to an AI assistant and it identifies multiple responsibilities, that's a strong signal to split it up. -**Note** If you create a class representing a primitive type you can greatly simplify its use by making it immutable. Consider using an immutable record type for such cases. +> [!NOTE] +> If you create a class representing a primitive type you can greatly simplify its use by making it immutable. Consider using an immutable record type for such cases. diff --git a/_rules/1002.md b/_rules/1002.md index b9600c9d..73d269ec 100644 --- a/_rules/1002.md +++ b/_rules/1002.md @@ -9,4 +9,5 @@ For `internal` types, this is straightforward to apply: since callers are within For `public` types, be more conservative. Removing a constructor parameter is a breaking API change, so consider whether the dependency is likely to be needed by future members before deciding to keep it out of the constructor. -**Exception:** Cross-cutting concerns such as logging or a clock abstraction (`TimeProvider`) are often needed broadly and may reasonably be injected through the constructor even if not every member uses them directly. +> [!IMPORTANT] +> Exception: Cross-cutting concerns such as logging or a clock abstraction (`TimeProvider`) are often needed broadly and may reasonably be injected through the constructor even if not every member uses them directly. diff --git a/_rules/1003.md b/_rules/1003.md index fae9d282..ad2f13b1 100644 --- a/_rules/1003.md +++ b/_rules/1003.md @@ -5,4 +5,5 @@ title: An interface should be small and focused --- Interfaces should have a name that clearly explains their purpose or role in the system. Do not combine many vaguely related members on the same interface just because they were all on the same class. Separate the members based on the responsibility of those members, so that callers only need to call or implement the interface related to a particular task. This rule is more commonly known as the [Interface Segregation Principle](https://en.wikipedia.org/wiki/Interface_segregation_principle). -**Tip:** Avoid taking the name of the class and slapping an `I` in front of it. Instead, consider using role-based names like `IFetchSomething` or `IProvideClusterwideLock` that describe what the interface does rather than what class implements it. +> [!TIP] +> Avoid taking the name of the class and slapping an `I` in front of it. Instead, consider using role-based names like `IFetchSomething` or `IProvideClusterwideLock` that describe what the interface does rather than what class implements it. diff --git a/_rules/1008.md b/_rules/1008.md index 043fae3e..bc44bccb 100644 --- a/_rules/1008.md +++ b/_rules/1008.md @@ -5,4 +5,5 @@ title: Avoid static classes --- With the exception of extension method containers, static classes very often lead to badly designed code. They are also very difficult, if not impossible, to test in isolation, unless you're willing to use some very hacky tools. -**Note:** If you really need that static class, mark it as static so that the compiler can prevent instance members and instantiating your class. This relieves you of creating an explicit private constructor. +> [!NOTE] +> If you really need that static class, mark it as static so that the compiler can prevent instance members and instantiating your class. This relieves you of creating an explicit private constructor. diff --git a/_rules/1011.md b/_rules/1011.md index 1dfe61b0..fe797c8a 100644 --- a/_rules/1011.md +++ b/_rules/1011.md @@ -57,4 +57,5 @@ public class Square : Shape } ``` -**Note:** This rule is also known as the Liskov Substitution Principle, one of the [S.O.L.I.D.](http://www.lostechies.com/blogs/chad_myers/archive/2008/03/07/pablo-s-topic-of-the-month-march-solid-principles.aspx) principles. +> [!NOTE] +> This rule is also known as the Liskov Substitution Principle, one of the [S.O.L.I.D.](http://www.lostechies.com/blogs/chad_myers/archive/2008/03/07/pablo-s-topic-of-the-month-march-solid-principles.aspx) principles. diff --git a/_rules/1014.md b/_rules/1014.md index 380b19ce..9fe92735 100644 --- a/_rules/1014.md +++ b/_rules/1014.md @@ -9,6 +9,8 @@ If you find yourself writing code like this then you might be violating the [Law An object should not expose any other classes it depends on because callers may misuse that exposed property or method to access the object behind it. By doing so, you allow calling code to become coupled to the class you are using, and thereby limiting the chance that you can easily replace it in a future stage. -**Note:** Using a class that is designed using the [Fluent Interface](http://en.wikipedia.org/wiki/Fluent_interface) pattern seems to violate this rule, but it is simply returning itself so that method chaining is allowed. +> [!NOTE] +> Using a class that is designed using the [Fluent Interface](http://en.wikipedia.org/wiki/Fluent_interface) pattern seems to violate this rule, but it is simply returning itself so that method chaining is allowed. -**Exception:** Inversion of Control or Dependency Injection frameworks often require you to expose a dependency as a public property. As long as this property is not used for anything other than dependency injection I would not consider it a violation. +> [!IMPORTANT] +> Exception: Inversion of Control or Dependency Injection frameworks often require you to expose a dependency as a public property. As long as this property is not used for anything other than dependency injection I would not consider it a violation. diff --git a/_rules/1020.md b/_rules/1020.md index 4a99ec26..156c1d11 100644 --- a/_rules/1020.md +++ b/_rules/1020.md @@ -5,4 +5,5 @@ title: Avoid bidirectional dependencies --- This means that two classes know about each other's public members or rely on each other's internal behavior. Refactoring or replacing one of those classes requires changes on both parties and may involve a lot of unexpected work. The most obvious way of breaking that dependency is to introduce an interface for one of the classes and using Dependency Injection. -**Exception:** Domain models such as defined in [Domain-Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) tend to occasionally involve bidirectional associations that model real-life associations. In those cases, make sure they are really necessary, and if they are, keep them in. +> [!IMPORTANT] +> Exception: Domain models such as defined in [Domain-Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) tend to occasionally involve bidirectional associations that model real-life associations. In those cases, make sure they are really necessary, and if they are, keep them in. diff --git a/_rules/1025.md b/_rules/1025.md index d47f50d7..7733d39a 100644 --- a/_rules/1025.md +++ b/_rules/1025.md @@ -5,4 +5,5 @@ title: Classes should have state and behavior --- In general, if you find a lot of data-only classes in your code base, you probably also have a few (static) classes with a lot of behavior (see [{{ site.default_rule_prefix }}1008](#{{ site.default_rule_prefix }}1008)). Use the principles of object-orientation explained in this section and move the logic close to the data it applies to. -**Exception:** The only exceptions to this rule are classes that are used to transfer data over a communication channel, also called [Data Transfer Objects](http://martinfowler.com/eaaCatalog/dataTransferObject.html), or a class that wraps several parameters of a method. For DTOs, consider using an immutable `record` type instead of a class to make the intent explicit. +> [!IMPORTANT] +> Exception: The only exceptions to this rule are classes that are used to transfer data over a communication channel, also called [Data Transfer Objects](http://martinfowler.com/eaaCatalog/dataTransferObject.html), or a class that wraps several parameters of a method. For DTOs, consider using an immutable `record` type instead of a class to make the intent explicit. diff --git a/_rules/1030.md b/_rules/1030.md index c81d4abf..cb71b285 100644 --- a/_rules/1030.md +++ b/_rules/1030.md @@ -14,7 +14,8 @@ Use a **record** when: public record OrderSummary(Guid OrderId, decimal TotalAmount, DateTimeOffset CreatedAt); ``` -**Note:** Record equality is shallow. If a record property is a collection (e.g. `List` or an array), `Equals` and `GetHashCode` will not compare the contents, which can lead to subtle bugs. +> [!NOTE] +> Record equality is shallow. If a record property is a collection (e.g. `List` or an array), `Equals` and `GetHashCode` will not compare the contents, which can lead to subtle bugs. Use a **record struct** when the same criteria apply, but you also want value-type semantics: stack allocation, no null, and copy-on-assignment. This is appropriate for small, frequently passed immutable data such as coordinates, colors, or date ranges. Avoid mutable `record struct` — mutations on a copy don't affect the original, which leads to confusing bugs. diff --git a/_rules/1032.md b/_rules/1032.md index 7ec9c718..9fe44cee 100644 --- a/_rules/1032.md +++ b/_rules/1032.md @@ -34,5 +34,6 @@ Prefer a single-method interface when: - You want to use Default Interface Methods - The interface is expected to gain additional members in the near future -**Note:** A simple lambda is technically compatible with a named delegate, but a named delegate offers better discoverability: find-usages, go-to-definition, and DI registration all work the same as with an interface. +> [!NOTE] +> A simple lambda is technically compatible with a named delegate, but a named delegate offers better discoverability: find-usages, go-to-definition, and DI registration all work the same as with an interface. diff --git a/_rules/1105.md b/_rules/1105.md index e357ff3a..5e03ad25 100644 --- a/_rules/1105.md +++ b/_rules/1105.md @@ -8,4 +8,5 @@ title: Use a method instead of a property - If it returns a different result each time it is called, even if the arguments didn't change. For example, the `NewGuid` method returns a different value each time it is called. - If the operation causes a side effect such as changing some internal state not directly related to the property (which violates the [Command Query Separation](http://martinfowler.com/bliki/CommandQuerySeparation.html) principle). -**Exception:** Populating an internal cache or implementing [lazy-loading](http://www.martinfowler.com/eaaCatalog/lazyLoad.html) is a good exception. +> [!IMPORTANT] +> Exception: Populating an internal cache or implementing [lazy-loading](http://www.martinfowler.com/eaaCatalog/lazyLoad.html) is a good exception. diff --git a/_rules/1130.md b/_rules/1130.md index d711b78f..80e98e8c 100644 --- a/_rules/1130.md +++ b/_rules/1130.md @@ -7,4 +7,5 @@ You generally don't want callers to be able to change an internal collection, so Be aware that `IEnumerable` is often perceived as lazy-evaluated. If your collection is already materialized, consider returning `IReadOnlyCollection` or `IReadOnlyList` to make the intent clear. -**Exception:** Immutable collections such as `ImmutableArray`, `ImmutableList` and `ImmutableDictionary`, as well as `FrozenSet` and `FrozenDictionary` prevent modifications from the outside and are thus allowed. +> [!IMPORTANT] +> Exception: Immutable collections such as `ImmutableArray`, `ImmutableList` and `ImmutableDictionary`, as well as `FrozenSet` and `FrozenDictionary` prevent modifications from the outside and are thus allowed. diff --git a/_rules/1137.md b/_rules/1137.md index c6459da4..ec06fc45 100644 --- a/_rules/1137.md +++ b/_rules/1137.md @@ -5,4 +5,5 @@ title: Define parameters as specific and narrow as possible --- If your method or local function needs a specific piece of data, define parameters as specific as that and don't take a container object instead. For instance, consider a method that needs a connection string that is exposed through a central `IConfiguration` interface. Rather than taking a dependency on the entire configuration, just define a parameter for the connection string. This not only prevents unnecessary coupling, it also improves maintainability in the long run. -**Note:** An easy trick to remember this guideline is the *Don't ship the truck if you only need a package*. +> [!NOTE] +> An easy trick to remember this guideline is the *Don't ship the truck if you only need a package*. diff --git a/_rules/1225.md b/_rules/1225.md index b69ea2f2..84ae8c73 100644 --- a/_rules/1225.md +++ b/_rules/1225.md @@ -5,4 +5,5 @@ title: Use a protected virtual method to raise each event --- Complying with this guideline allows derived classes to handle a base class event by overriding the protected method. The name of the protected virtual method should be the same as the event name prefixed with `On`. For example, the protected virtual method for an event named `TimeChanged` is named `OnTimeChanged`. -**Note:** Derived classes that override the protected virtual method are not required to call the base class implementation. The base class must continue to work correctly even if its implementation is not called. +> [!NOTE] +> Derived classes that override the protected virtual method are not required to call the base class implementation. The base class must continue to work correctly even if its implementation is not called. diff --git a/_rules/1235.md b/_rules/1235.md index 7cfc8d59..35235e30 100644 --- a/_rules/1235.md +++ b/_rules/1235.md @@ -5,4 +5,5 @@ title: Don't pass `null` as the `sender` argument when raising an event --- Often an event handler is used to handle similar events from multiple senders. The sender argument is then used to get to the source of the event. Always pass a reference to the source (typically `this`) when raising the event. Furthermore don't pass `null` as the event data parameter when raising an event. If there is no event data, pass `EventArgs.Empty` instead of `null`. -**Exception:** On static events, the sender argument should be `null`. +> [!IMPORTANT] +> Exception: On static events, the sender argument should be `null`. diff --git a/_rules/1505.md b/_rules/1505.md index 6933ee0a..bfe273c1 100644 --- a/_rules/1505.md +++ b/_rules/1505.md @@ -7,4 +7,5 @@ All DLLs should be named according to the pattern *Company*.*Component*.dll wher As an example, consider a group of classes organized under the namespace `AvivaSolutions.Web.Binding` exposed by a certain assembly. According to this guideline, that assembly should be called `AvivaSolutions.Web.Binding.dll`. -**Exception:** If you decide to combine classes from multiple unrelated namespaces into one assembly, consider suffixing the assembly name with `Core`, but do not use that suffix in the namespaces. For instance, `AvivaSolutions.Consulting.Core.dll`. +> [!IMPORTANT] +> Exception: If you decide to combine classes from multiple unrelated namespaces into one assembly, consider suffixing the assembly name with `Core`, but do not use that suffix in the namespaces. For instance, `AvivaSolutions.Consulting.Core.dll`. diff --git a/_rules/1507.md b/_rules/1507.md index 611f96e6..047caa07 100644 --- a/_rules/1507.md +++ b/_rules/1507.md @@ -3,6 +3,8 @@ rule_id: 1507 rule_category: maintainability title: Limit the contents of a source code file to one type --- -**Exception:** Nested types should be part of the same file. +> [!IMPORTANT] +> Exception: Nested types should be part of the same file. -**Exception:** Types that only differ by their number of generic type parameters should be part of the same file. +> [!IMPORTANT] +> Exception: Types that only differ by their number of generic type parameters should be part of the same file. diff --git a/_rules/1515.md b/_rules/1515.md index e59f64c6..afcb3866 100644 --- a/_rules/1515.md +++ b/_rules/1515.md @@ -25,4 +25,5 @@ If the value of one constant depends on the value of another, attempt to make th public const int HighWaterMark = 3 * MaxItems / 4; // at 75% } -**Note:** An enumeration can often be used for certain types of symbolic constants. +> [!NOTE] +> An enumeration can often be used for certain types of symbolic constants. diff --git a/_rules/1522.md b/_rules/1522.md index b80b0282..381d5833 100644 --- a/_rules/1522.md +++ b/_rules/1522.md @@ -7,12 +7,15 @@ Don't use confusing constructs like the one below: var result = someField = GetSomeMethod(); -**Exception:** Multiple assignments per statement are allowed by using out variables, is-patterns or deconstruction into tuples. Examples: - - bool success = int.TryParse(text, out int result); - - if ((items[0] is string text) || (items[1] is Action action)) - { - } - - (string name, string value) = SplitNameValuePair(text); +> [!IMPORTANT] +> Exception: Multiple assignments per statement are allowed by using out variables, is-patterns or deconstruction into tuples. Examples: +> +> ```csharp +> bool success = int.TryParse(text, out int result); +> +> if ((items[0] is string text) || (items[1] is Action action)) +> { +> } +> +> (string name, string value) = SplitNameValuePair(text); +> ``` diff --git a/_rules/1551.md b/_rules/1551.md index d247ffca..475afe47 100644 --- a/_rules/1551.md +++ b/_rules/1551.md @@ -27,4 +27,5 @@ This guideline only applies to overloads that are intended to provide optional a The class `MyString` provides three overloads for the `IndexOf` method, but two of them simply call the one with one more parameter. Notice that the same rule applies to class constructors; implement the most complete overload and call that one from the other overloads using the `this()` operator. Also notice that the parameters with the same name should appear in the same position in all overloads. -**Important:** If you also want to allow derived classes to override these methods, define the most complete overload as a non-private `virtual` method that is called by all overloads. +> [!IMPORTANT] +> If you also want to allow derived classes to override these methods, define the most complete overload as a non-private `virtual` method that is called by all overloads. diff --git a/_rules/1555.md b/_rules/1555.md index b1ae2215..f14acdc7 100644 --- a/_rules/1555.md +++ b/_rules/1555.md @@ -5,6 +5,9 @@ title: Avoid using named arguments --- C# 4.0's named arguments have been introduced to make it easier to call COM components that are known for offering many optional parameters. If you need named arguments to improve the readability of the call to a method, that method is probably doing too much and should be refactored. -**Exception:** The only exception where named arguments improve readability is when calling a method of some code base you don't control that has a `bool` parameter, like this: - - object[] myAttributes = type.GetCustomAttributes(typeof(MyAttribute), inherit: false); +> [!IMPORTANT] +> Exception: The only exception where named arguments improve readability is when calling a method of some code base you don't control that has a `bool` parameter, like this: +> +> ```csharp +> object[] myAttributes = type.GetCustomAttributes(typeof(MyAttribute), inherit: false); +> ``` diff --git a/_rules/1561.md b/_rules/1561.md index bb23af2c..5020dc6c 100644 --- a/_rules/1561.md +++ b/_rules/1561.md @@ -8,4 +8,5 @@ To keep constructors, methods, delegates and local functions small and focused, If you want to use more parameters, use a structure or class to pass multiple arguments, as explained in the [Specification design pattern](http://en.wikipedia.org/wiki/Specification_pattern). In general, the fewer the parameters, the easier it is to understand the method. Additionally, unit testing a method with many parameters requires many scenarios to test. -**Exception:** A parameter that is a collection of tuples is allowed. +> [!IMPORTANT] +> Exception: A parameter that is a collection of tuples is allowed. diff --git a/_rules/1562.md b/_rules/1562.md index 387802b6..fe21bd2b 100644 --- a/_rules/1562.md +++ b/_rules/1562.md @@ -5,6 +5,9 @@ title: Don't use `ref` or `out` parameters --- They make code less understandable and might cause people to introduce bugs. Instead, return compound objects or tuples. -**Exception:** Calling and declaring members that implement the [TryParse](https://learn.microsoft.com/en-us/dotnet/api/system.int32.tryparse) pattern is allowed. For example: - - bool success = int.TryParse(text, out int number); +> [!IMPORTANT] +> Exception: Calling and declaring members that implement the [TryParse](https://learn.microsoft.com/en-us/dotnet/api/system.int32.tryparse) pattern is allowed. For example: +> +> ```csharp +> bool success = int.TryParse(text, out int number); +> ``` diff --git a/_rules/1578.md b/_rules/1578.md index e250d583..4f7b3d07 100644 --- a/_rules/1578.md +++ b/_rules/1578.md @@ -30,4 +30,5 @@ This approach: - Allows using projects or folders to represent functional boundaries -**Note:** A thin host project (`MyApp.Web`, `MyApp.Api`) that composes all contexts is still appropriate, and shared infrastructure utilities can live in a `MyApp.Infrastructure.Shared` project when genuinely reused. +> [!NOTE] +> A thin host project (`MyApp.Web`, `MyApp.Api`) that composes all contexts is still appropriate, and shared infrastructure utilities can live in a `MyApp.Infrastructure.Shared` project when genuinely reused. diff --git a/_rules/1585.md b/_rules/1585.md index 6b7c0647..6155e2c3 100644 --- a/_rules/1585.md +++ b/_rules/1585.md @@ -29,4 +29,5 @@ Use `required` when: - A property must always be set to a meaningful value for the object to be valid. - The type is a DTO or a record-like class used with object initializers. -**Note:** `required` and `init`-only properties are complementary. Combine them (`public required string Name { get; init; }`) to enforce both initialization and immutability. +> [!NOTE] +> `required` and `init`-only properties are complementary. Combine them (`public required string Name { get; init; }`) to enforce both initialization and immutability. diff --git a/_rules/1702.md b/_rules/1702.md index db12c1d5..135e17df 100644 --- a/_rules/1702.md +++ b/_rules/1702.md @@ -25,4 +25,5 @@ title: Use proper casing for language elements | Variables declared using tuple syntax | Camel | `(string first, string last) = ("John", "Doe");`
`var (first, last) = ("John", "Doe");`
| | Local variable | Camel | `listOfValues` | -**Note:** in case of ambiguity, the rule higher in the table wins. +> [!NOTE] +> in case of ambiguity, the rule higher in the table wins. diff --git a/_rules/1706.md b/_rules/1706.md index 4e69b89c..3c36ddab 100644 --- a/_rules/1706.md +++ b/_rules/1706.md @@ -5,4 +5,5 @@ title: Don't use abbreviations --- For example, use `ChangePassword` rather than `ChangePwd`. Avoid single-character variable names, such as `i` or `q`. Use `index` or `query` instead. -**Exceptions:** Use well-known acronyms and abbreviations that are widely accepted or well-known in your work domain. For instance, use acronym `UI` instead of `UserInterface` and abbreviation `Id` instead of `Identity`. For abbreviations of two letters, use all capitals (e.g. `IO`). For abbreviations longer than two letters, only capitalize the first letter (e.g. `Http`, `Xml`, `Json`). +> [!IMPORTANT] +> Exceptions: Use well-known acronyms and abbreviations that are widely accepted or well-known in your work domain. For instance, use acronym `UI` instead of `UserInterface` and abbreviation `Id` instead of `Identity`. For abbreviations of two letters, use all capitals (e.g. `IO`). For abbreviations longer than two letters, only capitalize the first letter (e.g. `Http`, `Xml`, `Json`). diff --git a/_rules/1725.md b/_rules/1725.md index b6666ab5..f5da6bed 100644 --- a/_rules/1725.md +++ b/_rules/1725.md @@ -12,4 +12,5 @@ For instance, the following namespaces are good examples of that guideline. FluentAssertion.Primitives CaliburnMicro.Extensions -**Note:** Never allow namespaces to contain the name of a type, but a noun in its plural form (e.g. `Collections`) is usually OK. +> [!NOTE] +> Never allow namespaces to contain the name of a type, but a noun in its plural form (e.g. `Collections`) is usually OK. diff --git a/_rules/1739.md b/_rules/1739.md index f811155d..1ff2fee4 100644 --- a/_rules/1739.md +++ b/_rules/1739.md @@ -7,4 +7,5 @@ If you use a lambda expression (for instance, to subscribe to an event) and the button.Click += (_, __) => HandleClick(); -**Note** If using C# 9 or higher, use a single underscore (discard) for all unused lambda parameters and variables. +> [!NOTE] +> If using C# 9 or higher, use a single underscore (discard) for all unused lambda parameters and variables. diff --git a/_rules/1820.md b/_rules/1820.md index d2af5e8e..9c960d73 100644 --- a/_rules/1820.md +++ b/_rules/1820.md @@ -5,4 +5,5 @@ title: Only use `async`/`await` for I/O-bound or long-running activities --- The use of `async`/`await` won't automagically run something on a worker thread as `Task.Run` does. It just suspends execution at the await point and resumes execution after the task has completed. In other words, use `async`/`await` only for I/O-bound operations. -**Exception:** Tasks returned from `Task.Run` (which starts a background operation in parallel) can eventually be awaited to obtain their results, or passed to a method like `Task.WhenAll` that is awaited. +> [!IMPORTANT] +> Exception: Tasks returned from `Task.Run` (which starts a background operation in parallel) can eventually be awaited to obtain their results, or passed to a method like `Task.WhenAll` that is awaited. diff --git a/_rules/2225.md b/_rules/2225.md index 81d369a1..81c4aa8b 100644 --- a/_rules/2225.md +++ b/_rules/2225.md @@ -30,11 +30,12 @@ if (items is [int first, int second, ..]) } ``` -**Tip:** Deconstruction also works in `foreach` loops: - -```csharp -foreach ((int key, int value) in dictionary) -{ - Console.WriteLine($"{key}: {value}"); -} -``` +> [!TIP] +> Deconstruction also works in `foreach` loops: +> +> ```csharp +> foreach ((int key, int value) in dictionary) +> { +> Console.WriteLine($"{key}: {value}"); +> } +> ``` diff --git a/_rules/2305.md b/_rules/2305.md index be923fe8..3abb149e 100644 --- a/_rules/2305.md +++ b/_rules/2305.md @@ -7,6 +7,8 @@ Good functional documentation allows Visual Studio, [Visual Studio Code](https:/ See also [{{ site.default_rule_prefix }}2306](/documentation-guidelines#{{ site.default_rule_prefix }}2306) -**Exception:** Sometimes the purpose is so obvious that the documentation would become repetitive. Don't do that. +> [!IMPORTANT] +> Exception: Sometimes the purpose is so obvious that the documentation would become repetitive. Don't do that. -**Note:** You don't need to use `/// ` on overriding or implementing members. Visual Studio and Rider will automatically inherit documentation from the base type or interface. +> [!NOTE] +> You don't need to use `/// ` on overriding or implementing members. Visual Studio and Rider will automatically inherit documentation from the base type or interface. diff --git a/_sass/custom/_generic.scss b/_sass/custom/_generic.scss index 86be819b..fe4ea69b 100644 --- a/_sass/custom/_generic.scss +++ b/_sass/custom/_generic.scss @@ -49,4 +49,50 @@ li { color: $blue; } } -} \ No newline at end of file +} + +.page__content blockquote.callout { + margin: 0.5rem 0; + padding: 0.5rem 0.5rem; + border-left-width: 0.35rem; + border-radius: 0.25rem; + color: $black; + background: mix($white, $blue-light, 55%); + + p, + li { + line-height: 28px !important; + } + + > :last-child { + margin-bottom: 0; + } +} + +.callout__title { + margin-bottom: 0.1rem !important; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.callout--note { + border-left-color: $blue; +} + +.callout--tip { + border-left-color: #2f855a; + background: #eefaf3 !important; +} + +.callout--important, +.callout--exception { + border-left-color: #c05621; + background: #fff4eb !important; +} + +.callout--warning, +.callout--caution { + border-left-color: #b7791f; + background: #fff9e6 !important; +}