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
10 changes: 10 additions & 0 deletions .claude/rules/csharp-style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ This project is single-culture. Its diagnostics are English-only and must read i
- In particular, do not mirror `string.Format`'s shape with a nullable provider parameter: passing `null` there resolves to `CurrentCulture`, so the parameter's only real function is to produce locale-dependent output by accident.
- Do not propose a provider-taking overload "for flexibility". That flexibility will never be used, and the parameter only invites mistakes.

## String literals

Pick the form that shows the content most clearly. There is no universal winner, so ReSharper's `UseRawString`, `UseVerbatimString`, and `RawStringCanBeSimplified` are all suppressed: they disagree with each other by design, and the choice between them is a judgment call rather than a rule an inspection can make.

- **Raw** (`"""..."""`) when the content itself contains quotes. This is the big win, and it is why the regexes in `SelfReferenceUpdater` are readable: `[^""]+` collapses to `[^"]+`, and the pattern gets _shorter_ as well as clearer. Quotes are the discriminator, not backslashes: raw and verbatim both take a `\` literally, so a backslash alone is no reason to prefer one over the other.
- **Raw, multi-line** for content that is naturally several lines: JSON documents, expected output, code templates. The indentation of the closing `"""` sets the margin stripped from every line, so the literal lines up with the code around it and needs no `\n`, no concatenation, and no leading-whitespace gymnastics. Prefer this to a single-line literal stitched together with `\n` whenever the content is genuinely multi-line.
- **Regular, with escapes**, when an escape _is_ the point. `"{\n \"name\": 42\n}"` in a test that asserts line and column keeps its newlines explicit and independent of the file's line endings; a multi-line raw literal would make them a property of the source file instead.
- **Verbatim** (`@"..."`) has a narrow remaining niche now that raw strings exist: content heavy in backslashes but free of quotes, such as Windows paths.
- Prefer **consistency with adjacent literals** over shaving delimiters off one of them. Where several literals form a visual group, matching forms read better than one odd member — even if that member would compile as a plain `"..."`.

## Line length

Code and comment lines are limited to 140 characters, including indentation. The limit covers C# source in full: code, ordinary comments, and XML documentation comments alike. Prose files (Markdown and the like) have no line-length limit.
Expand Down
32 changes: 32 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,9 @@ resharper_member_can_be_private_global_highlighting = none
dotnet_diagnostic.CA1848.severity = none
dotnet_diagnostic.CA1873.severity = none

# ReSharper inspection severities live here and nowhere else. Buildvana.slnx.DotSettings can express them too, but a setting
# in both places is a setting a reader cannot resolve without running the tool, so that file keeps only its UI-filter rows.

# Promote selected ReSharper hints to warnings, so that the inspectcode gate (which runs with --severity=WARNING) enforces them.

# Pattern matching is preferred over equivalent comparison / type-check / cast chains: see .claude/rules/csharp-style-guide.md.
Expand All @@ -563,3 +566,32 @@ resharper_can_simplify_dictionary_try_get_value_with_get_value_or_default_highli
resharper_convert_closure_to_method_group_highlighting = warning
resharper_convert_to_local_function_highlighting = warning
resharper_property_can_be_made_init_only_global_highlighting = warning

# Declarations should state what they mean: a local that never changes is a constant, and a lambda's return type belongs
# in its signature rather than in a cast on the returned expression.
resharper_convert_to_constant_local_highlighting = warning
resharper_can_replace_cast_with_lambda_return_type_highlighting = warning

# Redundant syntax with no legitimate exception: parentheses around a single lambda parameter whose type is inferred.
resharper_redundant_lambda_signature_parentheses_highlighting = warning

# Migrated from Buildvana.slnx.DotSettings, preserving severity: these ask for explicit JetBrains nullability annotations,
# which matter here because the SDK exports external-annotation files for the API surface it builds.
resharper_annotate_can_be_null_parameter_highlighting = warning
resharper_annotate_can_be_null_type_member_highlighting = warning
resharper_annotate_not_null_parameter_highlighting = warning
resharper_annotate_not_null_type_member_highlighting = warning

# Suppress ReSharper hints we do not want at all. Hint is the lowest severity ReSharper offers, so silencing one means "none".

# Target-typed `new()` is used freely here: whatever is being assigned already names the type, so repeating it adds nothing.
resharper_arrange_object_creation_when_type_not_evident_highlighting = none

# Primary constructors: no. Migrated from Buildvana.slnx.DotSettings; IDE0290 above is the Roslyn counterpart.
resharper_convert_to_primary_constructor_highlighting = none

# Which literal form shows a string best is a judgment call, not a rule an inspection can make: these three disagree with
# each other by design. See "String literals" in .claude/rules/csharp-style-guide.md.
resharper_use_raw_string_highlighting = none
resharper_use_verbatim_string_highlighting = none
resharper_raw_string_can_be_simplified_highlighting = none
6 changes: 0 additions & 6 deletions Buildvana.slnx.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -419,12 +419,6 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/CodeIssueFilter/IssueTypesToHide/=_003CConfigurableSeverity_0020Id_003D_0022Xaml_002EIgnoredPathHighlighting_0022_0020_002F_003E/@EntryIndexedValue">DoShow</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/CodeIssueFilter/IssueTypesToHide/=_003CConfigurableSeverity_0020Id_003D_0022Xaml_002EMappedPathHighlighting_0022_0020_002F_003E/@EntryIndexedValue">DoShow</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/CodeIssueFilter/IssueTypesToHide/=_003CStaticSeverity_0020Severity_003D_0022_002D1_0022_0020Title_003D_0022Structural_0020Search_0020Pattern_0022_0020GroupId_003D_0022StructuralSearch_0022_0020_002F_003E/@EntryIndexedValue">DoShow</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AnnotateCanBeNullParameter/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AnnotateCanBeNullTypeMember/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AnnotateNotNullParameter/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AnnotateNotNullTypeMember/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToPrimaryConstructor/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedMember_002EGlobal/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">ShowAndRun</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/VsLightBulbDisplayMode/@EntryValue">MergeVsActionsIntoResharperMenu</s:String>
<s:String x:Key="/Default/CodeInspection/JsInspections/LanguageLevel/@EntryValue">Experimental</s:String>
Expand Down
17 changes: 9 additions & 8 deletions src/Buildvana.Core.JsonSchema/JsonSchemaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,20 @@ public static JsonNode Generate(Type type, JsonSerializerOptions options, string
TransformSchemaNode = (context, schema) => TransformSchemaNode(context, schema, nullabilityContext),
};
var schema = options.GetJsonSchemaAsNode(type, exporterOptions);
if (schema is not JsonObject root)
{
return schema;
}

// Declare the dialect and (optionally) a title so editors recognize and label the document.
if (schema is JsonObject root)
root.Insert(0, "$schema", Dialect);
title ??= type.GetCustomAttribute<JsonSchemaTitleAttribute>()?.Title;
if (title is not null)
{
root.Insert(0, "$schema", Dialect);
title ??= type.GetCustomAttribute<JsonSchemaTitleAttribute>()?.Title;
if (title is not null)
{
root.Insert(1, "title", title);
}
root.Insert(1, "title", title);
}

return schema;
return root;
}

private static JsonNode TransformSchemaNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,29 @@

namespace Buildvana.Sdk.SourceGenerators.Internal;

/// <summary>
/// Provides extension methods for <see cref="StringBuilder"/>.
/// </summary>
internal static class StringBuilderExtensions
{
public static StringBuilder AppendAutoGenerated(this StringBuilder @this, string? language)
=> language switch {
LanguageNames.CSharp => @this.AppendLine("// <auto-generated/>"),
LanguageNames.VisualBasic => @this.AppendLine("' <auto-generated/>"),
LanguageNames.FSharp => @this.AppendLine("// <auto-generated/>"),
_ => @this,
};

public static StringBuilder AppendBooleanAssemblyAttribute(this StringBuilder @this, string? language, string name, bool? value)
=> value.HasValue
? language switch {
LanguageNames.CSharp => @this.AppendLine($"[assembly:{name}({(value.Value ? "true" : "false")})]"),
LanguageNames.VisualBasic => @this.AppendLine($"<Assembly:{name}({(value.Value ? "True" : "False")})>"),
LanguageNames.FSharp => @this.AppendLine($"[<assembly: {name}({(value.Value ? "true" : "false")})>]"),
extension(StringBuilder @this)
{
public StringBuilder AppendAutoGenerated(string? language)
=> language switch {
LanguageNames.CSharp => @this.AppendLine("// <auto-generated/>"),
LanguageNames.VisualBasic => @this.AppendLine("' <auto-generated/>"),
LanguageNames.FSharp => @this.AppendLine("// <auto-generated/>"),
_ => @this,
}
: @this;
};

public StringBuilder AppendBooleanAssemblyAttribute(string? language, string name, bool? value)
=> value.HasValue
? language switch {
LanguageNames.CSharp => @this.AppendLine($"[assembly:{name}({(value.Value ? "true" : "false")})]"),
LanguageNames.VisualBasic => @this.AppendLine($"<Assembly:{name}({(value.Value ? "True" : "False")})>"),
LanguageNames.FSharp => @this.AppendLine($"[<assembly: {name}({(value.Value ? "true" : "false")})>]"),
_ => @this,
}
: @this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private static CSharpCompilation CreateCompilation(
var syntaxTrees = compileFilePaths.Select(path => ParseFile(path, parseOptions)).ToList();
var references = referencePaths
.Where(File.Exists)
.Select(path => (MetadataReference)MetadataReference.CreateFromFile(path))
.Select(MetadataReference (path) => MetadataReference.CreateFromFile(path))
.ToList();
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true);
return CSharpCompilation.Create(assemblyName, syntaxTrees, references, options);
Expand Down
18 changes: 18 additions & 0 deletions src/Buildvana.Tool/CommandLine/CliOptionReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ public bool ReadFlag(string longName, string? shortName = null)
return found;
}

/// <summary>
/// Reads a value option and interprets it as a boolean, removing every occurrence (and the consumed value
/// for the space-separated form) from the working set.
/// </summary>
/// <param name="longName">The long name, including the leading <c>"--"</c>.</param>
/// <param name="shortName">The short name, including the leading <c>'-'</c>, or <see langword="null"/>.</param>
/// <returns>The parsed value of the last occurrence of the option, or <see langword="null"/> if it was
/// absent.</returns>
/// <exception cref="BuildFailedException">The option was given in space-separated form with no following
/// value, or its value is neither <c>true</c> nor <c>false</c>.</exception>
public bool? ReadBoolValue(string longName, string? shortName = null)
{
var raw = ReadValue(longName, shortName);
return raw is null ? null
: bool.TryParse(raw, out var value) ? value
: throw new BuildFailedException($"Invalid value '{raw}' for {longName}. Expected 'true' or 'false'.");
}

/// <summary>
/// Reads a value option, removing every occurrence (and the consumed value for the space-separated form)
/// from the working set.
Expand Down
8 changes: 4 additions & 4 deletions src/Buildvana.Tool/Services/DotNetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ public Task<ProcessResult> RunFileBasedAppAsync(
["run", path],
environment: ChildEnvironment(environment),
workingDirectory: workingDirectory,
onStdout: (x) => _reporter.ChildOutput(x, null),
onStderr: (x) => _reporter.ChildError(x, null),
onStdout: x => _reporter.ChildOutput(x, null),
onStderr: x => _reporter.ChildError(x, null),
cancellationToken: cancellationToken);
}

Expand Down Expand Up @@ -406,8 +406,8 @@ private Task<ProcessResult> RunDotNetAsync(
DotNetMuxer.Path,
appendVerbosity ? finalArgs.Append($"--verbosity={_reporter.Verbosity}") : finalArgs,
environment: ChildEnvironment(environment),
onStdout: outputStreaming.Enabled ? (x) => _reporter.ChildOutput(x, outputStreaming.Verbosity) : null,
onStderr: outputStreaming.Enabled ? (x) => _reporter.ChildError(x, outputStreaming.Verbosity) : null,
onStdout: outputStreaming.Enabled ? x => _reporter.ChildOutput(x, outputStreaming.Verbosity) : null,
onStderr: outputStreaming.Enabled ? x => _reporter.ChildError(x, outputStreaming.Verbosity) : null,
cancellationToken: cancellationToken);
}
}
Loading