diff --git a/.editorconfig b/.editorconfig
index bd0de72..dcd945c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,8 +1,19 @@
root = true
-# -------------------------------
-# General
-# -------------------------------
+# ============================================================
+# Canonical .editorconfig for the NextIteration estate.
+#
+# Copied verbatim into every governed repo (STANDARD.md §5.2).
+# Posture: a DELIBERATE ALLOW-LIST of style gates — there is no
+# blanket `dotnet_analyzer_diagnostic.severity`, so a style rule a
+# future SDK ships never auto-gates the build. Every rule that IS
+# gated below (`:warning`) is a hard build failure under
+# `TreatWarningsAsErrors`; that is intentional — the build is what
+# forces the code into the ordained style. Code-quality (CA) rules
+# keep their `AnalysisLevel=latest` defaults.
+# ============================================================
+
+# ---------- All files ----------
[*]
charset = utf-8
end_of_line = lf
@@ -11,99 +22,136 @@ indent_style = space
indent_size = 4
trim_trailing_whitespace = true
-# -------------------------------
-# C# files
-# -------------------------------
-[*.cs]
-
-indent_size = 4
-
-# New lines & braces
-csharp_new_line_before_open_brace = all
-csharp_prefer_braces = true:warning
-
-# Using directives
-dotnet_sort_system_directives_first = true
-dotnet_separate_import_directive_groups = true
-
-# var usage (Spectre-style: pragmatic)
-csharp_style_var_for_built_in_types = true:suggestion
-csharp_style_var_when_type_is_apparent = true:suggestion
-csharp_style_var_elsewhere = false:suggestion
-
-# Expression-bodied members (used where clean)
-csharp_style_expression_bodied_methods = when_on_single_line:suggestion
-csharp_style_expression_bodied_constructors = false:suggestion
-csharp_style_expression_bodied_operators = when_on_single_line:suggestion
-csharp_style_expression_bodied_properties = when_on_single_line:suggestion
-
-# Pattern matching / modern C#
-csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
-csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
-
-# Nullability helpers
-dotnet_style_null_propagation = true:suggestion
-dotnet_style_coalesce_expression = true:suggestion
-
-# Readonly fields
-dotnet_style_readonly_field = true:suggestion
-
-# -------------------------------
-# Naming
-# -------------------------------
+[*.{csproj,props,targets}]
+indent_size = 2
-# Private fields: _camelCase
-dotnet_naming_rule.private_fields_should_be_camel_case.severity = suggestion
-dotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fields
-dotnet_naming_rule.private_fields_should_be_camel_case.style = camel_case_with_underscore
+[*.{json,yml,yaml}]
+indent_size = 2
-dotnet_naming_symbols.private_fields.applicable_kinds = field
-dotnet_naming_symbols.private_fields.applicable_accessibilities = private
-# A const IS a field, so without this the rule demands `_nonceSize` for
-# `private const int NonceSize` — PascalCase constants are correct .NET style and
-# the codebase uses them throughout. Restricting the rule to instance fields keeps
-# it aimed at what it was written for. Found when EnforceCodeStyleInBuild surfaced
-# 76 IDE1006 violations, every one of them a constant.
-dotnet_naming_symbols.private_fields.required_modifiers =
-
-dotnet_naming_style.camel_case_with_underscore.capitalization = camel_case
-dotnet_naming_style.camel_case_with_underscore.required_prefix = _
-
-# Interfaces: IMyInterface
-dotnet_naming_rule.interfaces_should_start_with_i.severity = suggestion
-dotnet_naming_rule.interfaces_should_start_with_i.symbols = interfaces
-dotnet_naming_rule.interfaces_should_start_with_i.style = interface_prefix
+# Trailing whitespace is a hard line break in Markdown
+[*.md]
+trim_trailing_whitespace = false
-dotnet_naming_symbols.interfaces.applicable_kinds = interface
+# ---------- C# ----------
+[*.cs]
+indent_size = 4
+# Braces & new lines
+csharp_new_line_before_open_brace = all
+csharp_prefer_braces = true:warning # braces always (IDE0011)
+
+# Namespaces — block-scoped estate-wide (IDE0160)
+csharp_style_namespace_declarations = block_scoped:warning
+
+# using directives
+csharp_using_directive_placement = outside_namespace:warning # IDE0065
+dotnet_sort_system_directives_first = true # feeds IDE0055 (gated below)
+dotnet_separate_import_directive_groups = true # feeds IDE0055 — matches estate style
+
+# 'this.' qualification — never used in this estate
+dotnet_style_qualification_for_field = false:warning # IDE0003
+dotnet_style_qualification_for_property = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_event = false:warning
+
+# Accessibility — always explicit
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning # IDE0040
+
+# var — ordained: var everywhere it is legal (IDE0007)
+csharp_style_var_for_built_in_types = true:warning
+csharp_style_var_when_type_is_apparent = true:warning
+csharp_style_var_elsewhere = true:warning
+
+# Expression-bodied members — single-line only; block-bodied constructors (IDE0021-0027)
+csharp_style_expression_bodied_methods = when_on_single_line:warning
+csharp_style_expression_bodied_constructors = false:warning
+csharp_style_expression_bodied_operators = when_on_single_line:warning
+csharp_style_expression_bodied_properties = when_on_single_line:warning
+
+# Pattern matching / null handling (IDE0019/0020/0029/0030/0031)
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+dotnet_style_null_propagation = true:warning
+dotnet_style_coalesce_expression = true:warning
+
+# readonly fields (IDE0044) — only flags never-reassigned fields, so genuinely mutable state is safe
+dotnet_style_readonly_field = true:warning
+
+# Modern syntax — gated toward the modern form
+dotnet_style_prefer_collection_expression = when_types_loosely_match:warning # IDE0300+
+csharp_style_implicit_object_creation_when_type_is_apparent = true:warning # IDE0090
+
+# Primary constructors — NOT forced. IDE0290 is one-directional (it can only push toward primary
+# constructors), and forcing them onto service classes with real initialisation is a downgrade.
+# Advisory only; existing class primary constructors are left as they are.
+csharp_style_prefer_primary_constructors = false:suggestion # IDE0290
+
+# Unused expression/assignment values — never nudge toward `_ =` discards (IDE0058/IDE0059).
+# House rule: no discard solely to swallow a return value (see CLAUDE.md for the carve-outs).
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_style_unused_value_assignment_preference = discard_variable:silent
+
+# ---------- Naming (gated) ----------
+# Matches estate reality: _camelCase private fields (incl. static readonly), PascalCase const
+# fields, PascalCase types/members, I-prefixed interfaces, T-prefixed type parameters, camelCase
+# locals/parameters. `const_fields` is declared before `private_fields`: a const is a field, so
+# both specs match it and precedence decides — const → PascalCase must win.
+
+# Styles
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_naming_style.camel_case_underscore.required_prefix = _
+dotnet_naming_style.camel_case_underscore.capitalization = camel_case
+dotnet_naming_style.camel_case_plain.capitalization = camel_case
dotnet_naming_style.interface_prefix.required_prefix = I
dotnet_naming_style.interface_prefix.capitalization = pascal_case
+dotnet_naming_style.type_param_prefix.required_prefix = T
+dotnet_naming_style.type_param_prefix.capitalization = pascal_case
-# -------------------------------
-# Analyzers
-# -------------------------------
-
-# Keep warnings visible but not painful
-dotnet_analyzer_diagnostic.severity = warning
-
-# Unused usings
-dotnet_diagnostic.IDE0005.severity = warning
-
-# Simplification
-dotnet_diagnostic.IDE0007.severity = suggestion
-dotnet_diagnostic.IDE0008.severity = suggestion
-
-# Documentation (Spectre.Console is pragmatic here)
-dotnet_diagnostic.CS1591.severity = silent
-
-# -------------------------------
-# JSON / YAML
-# -------------------------------
-[*.json]
-indent_size = 2
-
-[*.yml]
-indent_size = 2
-
-[*.yaml]
-indent_size = 2
\ No newline at end of file
+# Symbols
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter
+dotnet_naming_symbols.types.applicable_kinds = class, struct, enum, delegate
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, method, event
+dotnet_naming_symbols.const_fields.applicable_kinds = field
+dotnet_naming_symbols.const_fields.required_modifiers = const
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, private_protected, internal, protected_internal
+dotnet_naming_symbols.locals_and_params.applicable_kinds = parameter, local
+
+# Rules (all warning = gated)
+dotnet_naming_rule.interfaces_i.severity = warning
+dotnet_naming_rule.interfaces_i.symbols = interfaces
+dotnet_naming_rule.interfaces_i.style = interface_prefix
+
+dotnet_naming_rule.type_params_t.severity = warning
+dotnet_naming_rule.type_params_t.symbols = type_parameters
+dotnet_naming_rule.type_params_t.style = type_param_prefix
+
+dotnet_naming_rule.types_pascal.severity = warning
+dotnet_naming_rule.types_pascal.symbols = types
+dotnet_naming_rule.types_pascal.style = pascal_case
+
+dotnet_naming_rule.members_pascal.severity = warning
+dotnet_naming_rule.members_pascal.symbols = non_field_members
+dotnet_naming_rule.members_pascal.style = pascal_case
+
+dotnet_naming_rule.const_pascal.severity = warning
+dotnet_naming_rule.const_pascal.symbols = const_fields
+dotnet_naming_rule.const_pascal.style = pascal_case
+
+dotnet_naming_rule.private_underscore.severity = warning
+dotnet_naming_rule.private_underscore.symbols = private_fields
+dotnet_naming_rule.private_underscore.style = camel_case_underscore
+
+dotnet_naming_rule.locals_camel.severity = warning
+dotnet_naming_rule.locals_camel.symbols = locals_and_params
+dotnet_naming_rule.locals_camel.style = camel_case_plain
+
+# ---------- Option-less / compiler severities ----------
+dotnet_diagnostic.IDE0005.severity = warning # unnecessary usings (needs GenerateDocumentationFile — set by §1.6)
+dotnet_diagnostic.IDE0055.severity = warning # formatting: whitespace, using sort/groups
+dotnet_diagnostic.CS1591.severity = warning # public members MUST carry XML docs (STANDARD §1.6, CLAUDE.md non-negotiable)
+
+# ---------- Deliberate exemptions (advisory, never gate) ----------
+dotnet_diagnostic.IDE0046.severity = suggestion # convert to conditional expression — nested ternaries hurt readability
+dotnet_diagnostic.IDE0058.severity = suggestion # unused expression value — see the discard house rule above
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 7177a68..4e37bc7 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -49,10 +49,24 @@ jobs:
# Analyse source only. obj/ and bin/ hold generated and compiled
# output — e.g. the xUnit auto-generated entry point — so findings
# there are noise against code no human maintains.
+ #
+ # query-filters excludes the two audit queries that fire on every
+ # P/Invoke declaration and call site (cs/unmanaged-code,
+ # cs/call-to-unmanaged-code). Native-backend packages (Keychain,
+ # libsecret, DPAPI) exist to call unmanaged code, so these are pure
+ # noise there and non-native repos have no P/Invoke for them to hit.
+ # This excludes ONLY those two queries — every other
+ # security-and-quality query still runs on the interop files, so no
+ # real finding is lost (STANDARD.md 4.4).
config: |
paths-ignore:
- "**/obj/**"
- "**/bin/**"
+ query-filters:
+ - exclude:
+ id: cs/unmanaged-code
+ - exclude:
+ id: cs/call-to-unmanaged-code
# Explicit build rather than autobuild: these repos multi-target, and
# autobuild has picked a single TFM in the past, silently analysing half
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f701f7..ddca025 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,8 +9,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+---
+
+## [1.0.0] — 2026-08-21
+
+First stable release. The public surface — `SplashScreen`, `SplashOptions`,
+`SplashColors` and `SplashTagline` — is considered stable, and from this release the
+package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) from a 1.0
+baseline: a breaking change to that surface bumps the major version. **No public API
+changed in 1.0.0.** It is a stability commitment over the 0.3.0 surface, released
+together with the accumulated repository-standards conformance work below (the estate
+baseline in
+[NextIteration.Standards](https://github.com/StuartMeeks/NextIteration.Standards)).
+
### Added
+- **The house style is now gated in-build.** `EnforceCodeStyleInBuild` is `true`
+ (§1.2.1) and the canonical `.editorconfig` (§5.2) is a deliberate allow-list of named
+ style gates — braces always, `var` throughout, `System`-first using order, and the
+ full naming ruleset — each a hard build failure under `TreatWarningsAsErrors`. The
+ library, test and demo code was brought to green under the flag: the changes are
+ brace insertion on single-statement `if`s, explicit-type-to-`var`, `System`-first
+ using reordering, single-line expression bodies in the tests, and removal of
+ gratuitous `_ =` discards (the estate house rule — write the call plainly).
+ Behaviour-preserving; no public API or rendered output changed. This is the per-repo
+ rollout the standard tracks, not a code redesign — the single `AnsiConsole.Markup`
+ render path, its markup escaping, and the space fast-path are untouched.
- **`Microsoft.SourceLink.GitHub` package reference restored** (§1.7), at the
estate-wide version 10.0.400 and with `PrivateAssets="All"` so it is never a
consumer dependency. It had been removed as redundant — the .NET 8+ SDK does emit
@@ -36,6 +60,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- **CodeQL config gained a `query-filters` block** (§4.4) excluding exactly the two
+ audit queries `cs/unmanaged-code` and `cs/call-to-unmanaged-code`, which fire on every
+ P/Invoke declaration and call site. This repo has no P/Invoke, so the filter matches
+ nothing here — it is carried harmlessly so the workflow stays byte-identical to the
+ estate template (§3.0.1). Every other `security-and-quality` query still runs.
+- **Test and demo projects suppress `IDE0005`** (§2.7). `IDE0005` (remove unnecessary
+ usings) only runs in-build when `GenerateDocumentationFile` is `true`, which both
+ non-shipping projects set to `false`; once `EnforceCodeStyleInBuild` gates it as a
+ warning, the build would otherwise hard-error demanding the doc file be enabled.
+ Suppressing it there resolves the conflict while `IDE0005` still gates the shipping
+ project, where the doc file is on.
- **Central Package Management completed and shared build properties centralised.**
`Directory.Packages.props` gains `CentralPackageVersionOverrideEnabled=false`, so a
stray inline `Version=` alongside CPM is now a hard build failure instead of being
@@ -159,7 +194,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
interpolation, colour validation, tagline strategies, renderer output,
and the quote pool).
-[Unreleased]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.Splash/compare/v0.3.0...HEAD
+[Unreleased]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.Splash/compare/v1.0.0...HEAD
+[1.0.0]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.Splash/compare/v0.3.0...v1.0.0
[0.3.0]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.Splash/releases/tag/v0.3.0
[0.2.0]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.Splash/releases/tag/v0.2.0
[0.1.2]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.Splash/releases/tag/v0.1.2
diff --git a/Directory.Build.props b/Directory.Build.props
index 8447737..1480ced 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -13,16 +13,18 @@
no XML docs, and TreatWarningsAsErrors would otherwise fail the build over
every missing one.
- EnforceCodeStyleInBuild is deliberately absent. STANDARD.md 1.2.1 is blocked:
- TreatWarningsAsErrors promotes every advisory .editorconfig preference to a
- hard failure, which produced 490 build errors in Auth. It needs a per-rule
- gate-versus-advisory decision before it can be added here.
+ EnforceCodeStyleInBuild is on (STANDARD.md 1.2.1). It runs the IDE analyzers
+ in-build, so the canonical .editorconfig (5.2) actually gates the house style
+ rather than merely documenting it. The blanket severity that once turned every
+ advisory preference into an error (490 in Auth) is gone; .editorconfig is now a
+ deliberate allow-list of named gates. See 1.2.1 for the per-rule decisions.
-->
enable
enable
en
latest
+ true
true
true
true
diff --git a/demo/NextIteration.SpectreConsole.Splash.Demo/NextIteration.SpectreConsole.Splash.Demo.csproj b/demo/NextIteration.SpectreConsole.Splash.Demo/NextIteration.SpectreConsole.Splash.Demo.csproj
index 0bc124c..57d386f 100644
--- a/demo/NextIteration.SpectreConsole.Splash.Demo/NextIteration.SpectreConsole.Splash.Demo.csproj
+++ b/demo/NextIteration.SpectreConsole.Splash.Demo/NextIteration.SpectreConsole.Splash.Demo.csproj
@@ -8,6 +8,11 @@
XML docs, and TreatWarningsAsErrors would fail the build over every
missing one. Same reasoning as the test project (STANDARD.md 2.7). -->
false
+
+ $(NoWarn);IDE0005
diff --git a/demo/NextIteration.SpectreConsole.Splash.Demo/Program.cs b/demo/NextIteration.SpectreConsole.Splash.Demo/Program.cs
index 768e781..7de4215 100644
--- a/demo/NextIteration.SpectreConsole.Splash.Demo/Program.cs
+++ b/demo/NextIteration.SpectreConsole.Splash.Demo/Program.cs
@@ -1,5 +1,7 @@
using Figgle.Fonts;
+
using NextIteration.SpectreConsole.Splash;
+
using Spectre.Console;
// Demo 1 — all defaults (Roman font, neutral blue gradient, random built-in tagline).
diff --git a/src/NextIteration.SpectreConsole.Splash/Internal/Gradient.cs b/src/NextIteration.SpectreConsole.Splash/Internal/Gradient.cs
index c4fee40..10d0eb8 100644
--- a/src/NextIteration.SpectreConsole.Splash/Internal/Gradient.cs
+++ b/src/NextIteration.SpectreConsole.Splash/Internal/Gradient.cs
@@ -17,11 +17,14 @@ internal static class Gradient
///
public static Color[] Generate(IReadOnlyList hexStops, int width)
{
- if (width <= 0) return [];
+ if (width <= 0)
+ {
+ return [];
+ }
// Convert hex → RGB once.
var stops = new (byte R, byte G, byte B)[hexStops.Count];
- for (int i = 0; i < hexStops.Count; i++)
+ for (var i = 0; i < hexStops.Count; i++)
{
stops[i] = HexToRgb(hexStops[i]);
}
@@ -50,11 +53,15 @@ public static Color[] Generate(IReadOnlyList hexStops, int width)
var segments = stops.Length - 1;
var step = (float)segments / (width - 1);
- for (int i = 0; i < width; i++)
+ for (var i = 0; i < width; i++)
{
var t = i * step;
var segment = (int)t;
- if (segment >= segments) segment = segments - 1;
+ if (segment >= segments)
+ {
+ segment = segments - 1;
+ }
+
var fraction = t - segment;
var a = stops[segment];
diff --git a/src/NextIteration.SpectreConsole.Splash/Internal/Renderer.cs b/src/NextIteration.SpectreConsole.Splash/Internal/Renderer.cs
index c97cc5e..b3473f4 100644
--- a/src/NextIteration.SpectreConsole.Splash/Internal/Renderer.cs
+++ b/src/NextIteration.SpectreConsole.Splash/Internal/Renderer.cs
@@ -1,6 +1,7 @@
-using Spectre.Console;
using System.Text;
+using Spectre.Console;
+
namespace NextIteration.SpectreConsole.Splash.Internal
{
///
@@ -37,10 +38,13 @@ public static string Render(string logo, Color[] gradient, string? tagline)
// dependent; splitting on '\n' and trimming '\r' handles both.
var lines = logo.Split('\n');
var maxLineLength = 0;
- for (int i = 0; i < lines.Length; i++)
+ for (var i = 0; i < lines.Length; i++)
{
lines[i] = lines[i].TrimEnd('\r');
- if (lines[i].Length > maxLineLength) maxLineLength = lines[i].Length;
+ if (lines[i].Length > maxLineLength)
+ {
+ maxLineLength = lines[i].Length;
+ }
}
// Rough upper-bound allocation: each char gets ~16 chars of
@@ -48,7 +52,7 @@ public static string Render(string logo, Color[] gradient, string? tagline)
var sb = new StringBuilder(capacity: logo.Length * 16);
// Blank line before the logo so there's breathing room.
- _ = sb.AppendLine();
+ sb.AppendLine();
RenderLogo(sb, lines, gradient);
@@ -64,19 +68,19 @@ private static void RenderLogo(StringBuilder sb, string[] lines, Color[] gradien
{
foreach (var line in lines)
{
- for (int i = 0; i < line.Length; i++)
+ for (var i = 0; i < line.Length; i++)
{
var ch = line[i];
if (ch == ' ')
{
// Spaces don't need a colour escape; saves ~14 chars
// of markup per space character.
- _ = sb.Append(' ');
+ sb.Append(' ');
}
else
{
var colour = gradient[Math.Min(i, gradient.Length - 1)];
- _ = sb.Append('[')
+ sb.Append('[')
.Append('#').Append(colour.R.ToString("X2", System.Globalization.CultureInfo.InvariantCulture))
.Append(colour.G.ToString("X2", System.Globalization.CultureInfo.InvariantCulture))
.Append(colour.B.ToString("X2", System.Globalization.CultureInfo.InvariantCulture))
@@ -84,23 +88,38 @@ private static void RenderLogo(StringBuilder sb, string[] lines, Color[] gradien
// Markup escape: characters `[` and `]` in the
// visible logo would otherwise be parsed as markup.
// Figgle glyphs can contain both, so escape always.
- if (ch == '[') _ = sb.Append("[[");
- else if (ch == ']') _ = sb.Append("]]");
- else _ = sb.Append(ch);
- _ = sb.Append("[/]");
+ if (ch == '[')
+ {
+ sb.Append("[[");
+ }
+ else if (ch == ']')
+ {
+ sb.Append("]]");
+ }
+ else
+ {
+ sb.Append(ch);
+ }
+ sb.Append("[/]");
}
}
- _ = sb.AppendLine();
+ sb.AppendLine();
}
}
private static void RenderTagline(StringBuilder sb, string tagline, int maxLineLength, Color[] gradient)
{
var words = tagline.Split(' ', StringSplitOptions.RemoveEmptyEntries);
- if (words.Length == 0) return;
+ if (words.Length == 0)
+ {
+ return;
+ }
var maxWidth = maxLineLength - MinTaglinePadding * 2;
- if (maxWidth < 1) maxWidth = 1;
+ if (maxWidth < 1)
+ {
+ maxWidth = 1;
+ }
// Word-wrap to maxWidth. Greedy: pack as many words per line
// as fit, break before the word that wouldn't fit.
@@ -112,13 +131,21 @@ private static void RenderTagline(StringBuilder sb, string tagline, int maxLineL
if (current.Length > 0 && current.Length + spaceNeeded + word.Length > maxWidth)
{
taglineLines.Add(current.ToString());
- _ = current.Clear();
+ current.Clear();
spaceNeeded = 0;
}
- if (spaceNeeded > 0) _ = current.Append(' ');
- _ = current.Append(word);
+ if (spaceNeeded > 0)
+ {
+ current.Append(' ');
+ }
+
+ current.Append(word);
+ }
+
+ if (current.Length > 0)
+ {
+ taglineLines.Add(current.ToString());
}
- if (current.Length > 0) taglineLines.Add(current.ToString());
// Render each line centred within the logo width, coloured
// with the gradient's midpoint (visually anchored to the
@@ -133,18 +160,22 @@ private static void RenderTagline(StringBuilder sb, string tagline, int maxLineL
foreach (var line in taglineLines)
{
var padding = Math.Max(MinTaglinePadding, (maxLineLength - line.Length) / 2);
- _ = sb.Append(colourHex)
+ sb.Append(colourHex)
.Append(EscapeMarkup(line.PadLeft(line.Length + padding)))
.AppendLine("[/]");
}
- _ = sb.AppendLine();
+ sb.AppendLine();
}
// Spectre's markup parser treats `[` and `]` as control chars; doubled forms are literals.
private static string EscapeMarkup(string text)
{
- if (text.IndexOfAny(['[', ']']) < 0) return text;
+ if (text.IndexOfAny(['[', ']']) < 0)
+ {
+ return text;
+ }
+
return text.Replace("[", "[[").Replace("]", "]]");
}
}
diff --git a/src/NextIteration.SpectreConsole.Splash/NextIteration.SpectreConsole.Splash.csproj b/src/NextIteration.SpectreConsole.Splash/NextIteration.SpectreConsole.Splash.csproj
index 12625cf..11431eb 100644
--- a/src/NextIteration.SpectreConsole.Splash/NextIteration.SpectreConsole.Splash.csproj
+++ b/src/NextIteration.SpectreConsole.Splash/NextIteration.SpectreConsole.Splash.csproj
@@ -10,7 +10,7 @@
NextIteration.SpectreConsole.Splash
- 0.3.0
+ 1.0.0
Configurable Figgle + Spectre.Console splash screen for .NET CLIs — gradient palette, pluggable tagline strategy, single-markup-call render path.
true
$(MSBuildThisFileDirectory)..\..\artifacts\packages
diff --git a/src/NextIteration.SpectreConsole.Splash/SplashColors.cs b/src/NextIteration.SpectreConsole.Splash/SplashColors.cs
index 3f3cc2d..d6bfd4c 100644
--- a/src/NextIteration.SpectreConsole.Splash/SplashColors.cs
+++ b/src/NextIteration.SpectreConsole.Splash/SplashColors.cs
@@ -54,7 +54,7 @@ private static void ValidateHex(string hex)
throw new ArgumentException(
$"Hex colour must be in '#RRGGBB' form, got '{hex}'.", nameof(hex));
}
- for (int i = 1; i < 7; i++)
+ for (var i = 1; i < 7; i++)
{
var c = hex[i];
var isHex = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
diff --git a/src/NextIteration.SpectreConsole.Splash/SplashScreen.cs b/src/NextIteration.SpectreConsole.Splash/SplashScreen.cs
index bfd9e32..e44c4ff 100644
--- a/src/NextIteration.SpectreConsole.Splash/SplashScreen.cs
+++ b/src/NextIteration.SpectreConsole.Splash/SplashScreen.cs
@@ -1,4 +1,5 @@
using NextIteration.SpectreConsole.Splash.Internal;
+
using Spectre.Console;
namespace NextIteration.SpectreConsole.Splash
@@ -58,19 +59,26 @@ private static int MaxLineLength(string logo)
{
var max = 0;
var start = 0;
- for (int i = 0; i < logo.Length; i++)
+ for (var i = 0; i < logo.Length; i++)
{
if (logo[i] == '\n')
{
var end = i > start && logo[i - 1] == '\r' ? i - 1 : i;
var len = end - start;
- if (len > max) max = len;
+ if (len > max)
+ {
+ max = len;
+ }
+
start = i + 1;
}
}
// Trailing line without newline.
var tailLen = logo.Length - start;
- if (tailLen > max) max = tailLen;
+ if (tailLen > max)
+ {
+ max = tailLen;
+ }
return max;
}
}
diff --git a/tests/NextIteration.SpectreConsole.Splash.Tests/GradientTests.cs b/tests/NextIteration.SpectreConsole.Splash.Tests/GradientTests.cs
index c66ec06..f4f012f 100644
--- a/tests/NextIteration.SpectreConsole.Splash.Tests/GradientTests.cs
+++ b/tests/NextIteration.SpectreConsole.Splash.Tests/GradientTests.cs
@@ -1,4 +1,5 @@
using NextIteration.SpectreConsole.Splash.Internal;
+
using Xunit;
namespace NextIteration.SpectreConsole.Splash.Tests
@@ -90,7 +91,10 @@ public void Large_stop_count_still_linear_time()
// Regression guard: 20 stops, 200 cols = 4000 ops, must still
// produce coherent output (endpoints anchored).
var stops = new string[20];
- for (int i = 0; i < 20; i++) stops[i] = i % 2 == 0 ? "#000000" : "#FFFFFF";
+ for (var i = 0; i < 20; i++)
+ {
+ stops[i] = i % 2 == 0 ? "#000000" : "#FFFFFF";
+ }
var result = Gradient.Generate(stops, 200);
diff --git a/tests/NextIteration.SpectreConsole.Splash.Tests/NextIteration.SpectreConsole.Splash.Tests.csproj b/tests/NextIteration.SpectreConsole.Splash.Tests/NextIteration.SpectreConsole.Splash.Tests.csproj
index 8f03bba..fe08d0c 100644
--- a/tests/NextIteration.SpectreConsole.Splash.Tests/NextIteration.SpectreConsole.Splash.Tests.csproj
+++ b/tests/NextIteration.SpectreConsole.Splash.Tests/NextIteration.SpectreConsole.Splash.Tests.csproj
@@ -22,8 +22,15 @@
CA2007 (ConfigureAwait) doesn't apply in test contexts; there
is no SynchronizationContext to recapture.
+
+ IDE0005 (remove unnecessary usings) only runs in-build when
+ GenerateDocumentationFile is true, which is false here. Once
+ EnforceCodeStyleInBuild is on (§1.2.1) the canonical .editorconfig
+ gates IDE0005 as a warning, so a test project would hard-error
+ demanding the doc file be enabled. Suppressing it resolves the
+ conflict; IDE0005 still gates the shipping project. STANDARD.md 2.7.
-->
- $(NoWarn);CA1707;CA1515;CA2007
+ $(NoWarn);CA1707;CA1515;CA2007;IDE0005
diff --git a/tests/NextIteration.SpectreConsole.Splash.Tests/QuotesTests.cs b/tests/NextIteration.SpectreConsole.Splash.Tests/QuotesTests.cs
index 94c46ef..7bd1a07 100644
--- a/tests/NextIteration.SpectreConsole.Splash.Tests/QuotesTests.cs
+++ b/tests/NextIteration.SpectreConsole.Splash.Tests/QuotesTests.cs
@@ -1,17 +1,15 @@
using NextIteration.SpectreConsole.Splash.Internal;
+
using Xunit;
namespace NextIteration.SpectreConsole.Splash.Tests
{
public class QuotesTests
{
+ // Rough lower bound — the whole point of the built-in pool is
+ // that the same quote shouldn't appear twice in quick succession.
[Fact]
- public void Pool_has_at_least_100_entries()
- {
- // Rough lower bound — the whole point of the built-in pool is
- // that the same quote shouldn't appear twice in quick succession.
- Assert.True(Quotes.Count >= 100, $"Expected >= 100 quotes, got {Quotes.Count}");
- }
+ public void Pool_has_at_least_100_entries() => Assert.True(Quotes.Count >= 100, $"Expected >= 100 quotes, got {Quotes.Count}");
[Fact]
public void No_null_or_whitespace_quotes()
@@ -35,7 +33,10 @@ public void Random_eventually_returns_different_values()
// Not strictly guaranteed, but 500 calls from a ~300-entry pool
// with Random.Shared collisions only would be astronomically rare.
var seen = new HashSet();
- for (int i = 0; i < 500; i++) seen.Add(Quotes.Random());
+ for (var i = 0; i < 500; i++)
+ {
+ seen.Add(Quotes.Random());
+ }
Assert.True(seen.Count > 1, "Random() returned the same value 500 times in a row");
}
diff --git a/tests/NextIteration.SpectreConsole.Splash.Tests/RendererTests.cs b/tests/NextIteration.SpectreConsole.Splash.Tests/RendererTests.cs
index bc3c5d4..9faaacc 100644
--- a/tests/NextIteration.SpectreConsole.Splash.Tests/RendererTests.cs
+++ b/tests/NextIteration.SpectreConsole.Splash.Tests/RendererTests.cs
@@ -1,5 +1,7 @@
using NextIteration.SpectreConsole.Splash.Internal;
+
using Spectre.Console;
+
using Xunit;
namespace NextIteration.SpectreConsole.Splash.Tests
diff --git a/tests/NextIteration.SpectreConsole.Splash.Tests/SplashColorsTests.cs b/tests/NextIteration.SpectreConsole.Splash.Tests/SplashColorsTests.cs
index 51bed95..41399b8 100644
--- a/tests/NextIteration.SpectreConsole.Splash.Tests/SplashColorsTests.cs
+++ b/tests/NextIteration.SpectreConsole.Splash.Tests/SplashColorsTests.cs
@@ -1,4 +1,5 @@
using NextIteration.SpectreConsole.Splash;
+
using Xunit;
namespace NextIteration.SpectreConsole.Splash.Tests
@@ -6,16 +7,10 @@ namespace NextIteration.SpectreConsole.Splash.Tests
public class SplashColorsTests
{
[Fact]
- public void Ctor_rejects_empty_stop_list()
- {
- Assert.Throws(() => new SplashColors());
- }
+ public void Ctor_rejects_empty_stop_list() => Assert.Throws(() => new SplashColors());
[Fact]
- public void Ctor_rejects_null()
- {
- Assert.Throws(() => new SplashColors(null!));
- }
+ public void Ctor_rejects_null() => Assert.Throws(() => new SplashColors(null!));
[Theory]
[InlineData("#GGGGGG")] // non-hex digit
@@ -23,10 +18,7 @@ public void Ctor_rejects_null()
[InlineData("#12345678")] // too long
[InlineData("123456")] // missing '#'
[InlineData("")] // empty
- public void Ctor_rejects_malformed_hex(string bad)
- {
- Assert.Throws(() => new SplashColors(bad));
- }
+ public void Ctor_rejects_malformed_hex(string bad) => Assert.Throws(() => new SplashColors(bad));
[Theory]
[InlineData("#000000")]
diff --git a/tests/NextIteration.SpectreConsole.Splash.Tests/SplashScreenTests.cs b/tests/NextIteration.SpectreConsole.Splash.Tests/SplashScreenTests.cs
index ba8083a..97e1219 100644
--- a/tests/NextIteration.SpectreConsole.Splash.Tests/SplashScreenTests.cs
+++ b/tests/NextIteration.SpectreConsole.Splash.Tests/SplashScreenTests.cs
@@ -1,6 +1,8 @@
using NextIteration.SpectreConsole.Splash;
+
using Spectre.Console;
using Spectre.Console.Testing;
+
using Xunit;
namespace NextIteration.SpectreConsole.Splash.Tests
@@ -8,10 +10,7 @@ namespace NextIteration.SpectreConsole.Splash.Tests
public class SplashScreenTests
{
[Fact]
- public void Show_with_null_options_throws()
- {
- Assert.Throws(() => SplashScreen.Show((SplashOptions)null!));
- }
+ public void Show_with_null_options_throws() => Assert.Throws(() => SplashScreen.Show((SplashOptions)null!));
[Fact]
public void Show_with_empty_appName_throws()
diff --git a/tests/NextIteration.SpectreConsole.Splash.Tests/SplashTaglineTests.cs b/tests/NextIteration.SpectreConsole.Splash.Tests/SplashTaglineTests.cs
index 38dcd58..647f820 100644
--- a/tests/NextIteration.SpectreConsole.Splash.Tests/SplashTaglineTests.cs
+++ b/tests/NextIteration.SpectreConsole.Splash.Tests/SplashTaglineTests.cs
@@ -1,4 +1,5 @@
using NextIteration.SpectreConsole.Splash;
+
using Xunit;
namespace NextIteration.SpectreConsole.Splash.Tests
@@ -6,10 +7,7 @@ namespace NextIteration.SpectreConsole.Splash.Tests
public class SplashTaglineTests
{
[Fact]
- public void None_resolves_to_null()
- {
- Assert.Null(SplashTagline.None.Resolve());
- }
+ public void None_resolves_to_null() => Assert.Null(SplashTagline.None.Resolve());
[Fact]
public void RandomBuiltIn_resolves_to_a_non_empty_string()
@@ -36,9 +34,6 @@ public void FromProvider_allows_null_result()
}
[Fact]
- public void FromProvider_rejects_null_callback()
- {
- Assert.Throws(() => SplashTagline.FromProvider(null!));
- }
+ public void FromProvider_rejects_null_callback() => Assert.Throws(() => SplashTagline.FromProvider(null!));
}
}