Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 140 additions & 92 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
# 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
14 changes: 14 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### 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
Expand All @@ -36,6 +47,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
Expand Down
10 changes: 6 additions & 4 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -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.
-->
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<AnalysisLevel>latest</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnablePackageValidation>true</EnablePackageValidation>
<IncludeSymbols>true</IncludeSymbols>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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). -->
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<!-- IDE0005 only runs in-build when GenerateDocumentationFile is true (off
here), but EnforceCodeStyleInBuild + the canonical .editorconfig gate it
as a warning, which hard-errors demanding the doc file be enabled. Suppress
it, exactly as the test project does (STANDARD.md 2.7). -->
<NoWarn>$(NoWarn);IDE0005</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions demo/NextIteration.SpectreConsole.Splash.Demo/Program.cs
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
15 changes: 11 additions & 4 deletions src/NextIteration.SpectreConsole.Splash/Internal/Gradient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ internal static class Gradient
/// </summary>
public static Color[] Generate(IReadOnlyList<string> 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]);
}
Expand Down Expand Up @@ -50,11 +53,15 @@ public static Color[] Generate(IReadOnlyList<string> 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];
Expand Down
Loading