Fix the build blocker and the four correctness bugs (#123, #106, #107, #108, #109, #111) - #124
Merged
Merged
Conversation
Closes #123: raise the global.json SDK floor to 10.0.400 and document it. ktsu.Sdk 2.28.0's analyzers are built against Roslyn 5.9, which ships in the 10.0.4xx feature band; on a 10.0.1xx SDK (Roslyn 5.0) every build failed with CS9057 before compiling a line. The pinned floor turns that into an actionable "SDK not found". Band-to-Roslyn mapping confirmed against dotnet/sdk release branches: 1xx -> 5.0, 2xx -> 5.3, 3xx -> 5.6, 4xx -> 5.9. Closes #106: BaseType.Equals compared ToString() with != where it meant ==, so distinct instances of the same type compared unequal while Object("A") and Object("B") compared equal, breaking the equality/hash contract. Equality is now structural per derived type - Object on ClassName, Enum on EnumName, Array on element type, container and key - via a protected EqualsCore hook, with GetHashCode kept in step and == / != operators added. Closes #107: Vector2/3/4 and ColorRGB/RGBA derived from Object, so they inherited an always-empty ClassName. That made Validate() reject every schema using them, rendered their DisplayName blank in the editor's type picker, and wrote a meaningless "className" into every schema file. SystemObject is now rooted at BaseType and both it and Vector are abstract, since neither is registered as a [JsonDerivedType] and an instance of either could not serialize. IsObject now means "references a user-defined class", which is what the validator and Array.IsKeyed already assumed. The TypeSystemTests assertions that encoded the old classification are updated. Closes #109: multi-target Schema.Test to net10.0;net9.0;net8.0 so every published framework is exercised. ktsu.Sdk pins test projects to a single framework from its targets, so the project takes the last word via explicit Sdk.props/Sdk.targets imports. CI now installs the 8.0 and 9.0 runtimes so each test host can start. The documented framework list dropped .NET 7.0, which no project has targeted. Also fixes a serialization bug the new round-trip assertions exposed: SchemaMember.Type has a private setter and lacked [JsonInclude], so System.Text.Json wrote it on save and ignored it on load - every member of every loaded schema came back as None. The existing round-trip tests only counted members, never checked their types. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WmzGm9XniSoqaiVGDT6qT
Closes #108: "Open Externally" named explorer.exe directly, so on Linux and macOS the menu item threw an unhandled Win32Exception and took the editor down. It now hands the path to the platform shell via UseShellExecute, and a launch failure - no shell association, a file removed since it was opened, or a missing xdg-open - surfaces as a message popup instead of terminating the process. Closes #111: Validate() accepted several constructs that break any downstream consumer. It now reports an empty class, enum, member, or enum value name as an error; a member still typed None as a warning; an array with no container as a warning; an array whose container is outside the vocabulary the library produces as a warning; and a map container with no key to map by as an error. The container vocabulary was previously two string literals inside TryGetCollectionElementType. It is now named on Array as VectorContainer, MapContainer and KnownContainers, and both the reflection importer and the validator use it. Container stays deliberately open-ended, so an unrecognized name is a warning rather than an error. Empty enum values cannot be produced through TryAddValue, which rejects them, so that rule is tested through deserialization - the path a hand-edited schema file would actually take. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WmzGm9XniSoqaiVGDT6qT
SonarCloud's quality gate failed the PR on reliability of new code: S1206
("this class overrides GetHashCode and should therefore also override Equals")
fired on Object, Enum and Array, each of which overrode GetHashCode while
overriding EqualsCore rather than Equals itself.
Rather than add an Equals override to each just to satisfy the pairing, hashing
now has the same shape equality does: BaseType.GetHashCode combines the CLR
type with a protected virtual GetHashCodeCore, and the three stateful types
override that hook instead. Equality and hashing are now overridden through
matching hooks, so they cannot drift apart, and no derived type overrides one
of the pair without the other.
Verified with SonarAnalyzer.CSharp run locally: S1206 goes from 6 occurrences
to 0, and every other rule's count is unchanged, so nothing new was introduced.
173 tests still pass on net8.0, net9.0 and net10.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WmzGm9XniSoqaiVGDT6qT
|
This was referenced Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Works through the build blocker, all four
bug-labelled issues, and the two enhancements that depend on them. The larger editor and code-generation issues (#110, #112–#122) are untouched and want their own PRs.#123 — build fails on any SDK that satisfies
global.jsonReproduced exactly as filed: on SDK 10.0.111 every build failed with
CS9057before compiling a line, becausektsu.Sdk2.28.0's analyzers reference Roslyn 5.9.The band-to-Roslyn mapping, read from
dotnet/sdkrelease branches (eng/Version.Details.xml):So the
global.jsonfloor moves to10.0.400, which is what CI already resolves10.0.xto. Anyone on an older 10.0.x now gets an actionable "SDK not found" instead of a compiler error mid-build. The requirement is documented in the README's Building section.#106 —
BaseType.Equalswas invertedThe final comparison was
!=where it meant==, so distinct instances of the same type compared unequal whileObject("A")andObject("B")compared equal — andGetHashCode()hashedToString(), breaking the equality/hash contract.Equality is now structural per derived type through a
protected virtual bool EqualsCore(BaseType)hook:ObjectcomparesClassName,EnumcomparesEnumName,Arraycompares element type, container and key. Hashing has the matching shape —BaseType.GetHashCodecombines the CLR type with aprotected virtual GetHashCodeCorethat derived types override alongsideEqualsCore— so equality and hashing cannot drift apart.==/!=operators are added.The three existing equality tests only exercised the
ReferenceEqualsshort-circuit or two different CLR types, so they are strengthened rather than merely kept passing: distinct-instance equality, per-type structural comparison,HashSetdedupe, the operators, and theobjectoverload.#107 — vector and color types inherited
ObjectVector2/3/4andColorRGB/RGBAderived fromObject, inheriting an always-emptyClassName. That madeValidate()reject every schema using them, leftDisplayNameblank in the editor's type picker, and wrote a meaningless"className": ""into every schema file.SystemObjectis now rooted atBaseType, and both it andVectorareabstract— neither is registered as a[JsonDerivedType], so an instance of either could never have serialized.IsObjectnow means "references a user-defined class", which is whatSchema.ValidationandArray.IsKeyedalready assumed.The
TypeSystemTestsassertions that encoded the old classification are updated, as the issue called for.#108 — "Open Externally" hardcoded
explorer.exeThe menu item threw an unhandled
Win32Exceptionon Linux and macOS and took the editor down with it. It now hands the path to the platform shell viaUseShellExecute, with launch failures surfaced as a message popup. Verified against the real failure path — the dev container has noxdg-open, and the thrownWin32Exceptionis caught by the filter rather than escaping.#109 — test suite ran against one of three published frameworks
Schema.Testnow multi-targetsnet10.0;net9.0;net8.0.Worth flagging:
ktsu.Sdkpins test projects to a single framework and does it fromSdk.targets, which is imported after the project body — so editingTargetFrameworksin the csproj alone is silently stomped. The project takes the last word via explicitSdk.props/Sdk.targetsimports, which is a little unusual and is commented in place. This arguably belongs upstream inktsu.Sdk; if you'd rather it live there, this part is easy to drop.CI also installs the 8.0 and 9.0 runtimes so each test host can start. The documented framework list drops .NET 7.0, which no project in the solution has ever targeted.
#111 — validation coverage
Validate()now reports: empty class / enum / member / enum-value names (error), a member still typedNone(warning), an array with no container (warning), an array whose container is outside the known vocabulary (warning), and amapcontainer with no key to map by (error).The container vocabulary was two string literals buried in
TryGetCollectionElementType; it is now named onArrayasVectorContainer,MapContainerandKnownContainers, used by both the reflection importer and the validator.Containerstays deliberately open-ended, so an unrecognized name is a warning rather than an error.Empty enum values can't be produced through
TryAddValue(it rejects them), so that rule is tested through deserialization — the path a hand-edited schema file actually takes.Also: member types were silently dropped on load
Not a filed issue — the new round-trip assertions exposed it.
SchemaMember.Typehas a private setter and lacked[JsonInclude], soSystem.Text.Jsonwrote it on save and ignored it on load. Every member of every loaded schema came back asNone, which means saved.schema.jsonfiles were effectively losing all type information.The existing round-trip tests only counted members and never checked their types, which is why this survived.
[JsonInclude]is added (matching the pattern already used for the model's collections), andTestRoundtripWithAllTypesnow asserts that each of the 14 built-in types survives the round trip.Verification
173 tests pass on all three target frameworks (146 before this branch). CI is green, including SonarCloud's quality gate — 92.3% coverage on new code, 0 security hotspots, 0% duplication.
One caveat on how this was checked locally: the dev container has SDK 10.0.111 and its network policy blocks
builds.dotnet.microsoft.com, so neither SDK 10.0.400 nor the .NET 8/9 runtimes could be installed there. Local builds usedMicrosoft.Net.Compilers.Toolset5.9.0 to supply exactly the Roslyn that 10.0.400 ships, and the net8.0/net9.0 suites were run rolled forward onto the .NET 10 runtime. Both shims are local only — nothing in the diff depends on them — and CI has since exercised the realglobal.jsonfloor and the real per-framework runtimes on both ubuntu and windows.Closes #106, closes #107, closes #108, closes #109, closes #111, closes #123