Skip to content

SonarCloud quality/security remediation tracking #641

Open

Description

@rexm

Track progress on SonarCloud findings for this project.

Addressed in commits af11135 and 37ca7b9 (2026-06-21), and #679 (2026-11-08):

✅ Completed

Security

  • S7637 — Pin timheuer/base64-to-file CI action to full commit SHA (release.yml)
  • S7636 — Move NUGET_ORG_PUSH_KEY out of inline run: block into env: section
  • ReDoS hotspots — Add 1-second timeout to all 7 Regex instances in WhitespaceRemover.cs

Reliability (real bugs)

  • S2955 — Replace == null on unconstrained generic type parameters with EqualityComparer<T>.Default.Equals(value, default) in HtmlEncoder, HtmlEncoderLegacy, DictionarySlim, FixedSizeDictionary, GenericDictionaryAccessor, ReadOnlyGenericDictionaryAccessor

False positives (suppressed with justification)

  • S7133 — ReaderWriterLockSlimExtensions: lock IS released via DisposableContainer in using; not a bug
  • S2234 — IteratorBinder: swapping template/ifEmpty for the Inverse case is intentional
  • S1944 — PartialBinder: cast is a C# explicit operator on ExpressionContainer<T>, not a runtime hierarchy cast

Design / API quality

  • S3881 — Seal TextEncoderWrapper and ClosureBuilder (IDisposable pool types with private constructors)
  • S3881 — Seal ReusableStringWriter (IO/PolledStringWriter.cs); its only constructor is private, so sealing is not an API break. Dispose(bool) intentionally returns the instance to the pool rather than calling base.Dispose (refactor: address pending SonarCloud S3881/S3776 items in lexer and ReusableStringWriter #679)
  • S3218 — Use explicit interface implementation for IInternalObjectPoolPolicy<T>.Create() in all six pool policy types to eliminate method-name shadowing
  • S112 — Replace IndexOutOfRangeException with ArgumentOutOfRangeException in Arguments.cs and Substring.cs
  • S1168 — Return Array.Empty<Expression>() instead of null in HandlebarsExpressionVisitor
  • S3993 — Add [AttributeUsage(AttributeTargets.Class)] to FeatureOrderAttribute
  • S2743 — NOSONAR with justification on ImmutableStack<T> pool field (intentional per-T instance)
  • S3246 — Add out covariance to ValueTypeGetterDelegate<T, TValue>
  • S3011 — NOSONAR with justification on ReflectionMemberAccessor private method lookup
  • S2589 — Remove always-false dead branch in MissingBlockHelperDescriptor
  • S2933 — Make _enumerator field readonly in ExtendedEnumerator
  • S1066 — Merge nested if statements in DelegatedMemberAliasProvider
  • S3358 — Extract nested ternaries in EndExpressionToken, StartExpressionToken, PartialBlockAccumulatorContext
  • S3928 — Add meaningful message to ArgumentOutOfRangeException in BlockHelperFunctionBinder
  • S927 — Rename parameter exp → node in HandlebarsExpressionVisitor.Visit() to match base class signature

Maintainability

Test quality


❌ Pending

Breaking / high effort

  • S2326 — IDescriptor<TOptions>: type parameter TOptions is declared but never used in the interface body. Removing it is a breaking API change requiring a major version bump.

Medium effort

  • S3776 — BlockHelperFunctionBinder.BindByRef: the nested switch/if structure is tied to expression-tree construction — naive extraction causes runtime NullReferenceException. Needs a careful approach (see ExpressionShortcuts scope constraint note below).
  • S3776 — PartialBinder.VisitPartialExpression: complexity climbed with the partial-indentation feature. Any extraction must keep Arg(...)/Cast<T>(...) variables scoped to the Call(() => ...) that uses them.

Remaining SonarCloud findings (not yet triaged)

  • The original Sonar report listed 190 code smells total; the work above addressed the ~53 flagged as critical or major. The remaining ~137 lower-priority smells have not been reviewed. A full triage pass against the current SonarCloud dashboard is needed to identify any worth fixing.

Notes

ExpressionShortcuts scope constraint

When refactoring code in files that use the Expressions.Shortcuts library (PartialBinder, BlockHelperFunctionBinder, etc.), variables created with Arg(...), Cast<T>(...), or New(...) must be declared in the same lexical block as the Call(() => ...) lambda that references them. Hoisting them to an outer scope causes runtime InvalidCastException or NullReferenceException because ExpressionShortcuts rewrites the lambda's expression tree at build time and out-of-scope references are not rewritten. Safe to hoist: CompilationContext.Args.BindingContext and CompilationContext.Args.EncodedWriter (these are parameter expressions, not new constant nodes).

Activity

  1. rexm commented on Nov 8, 2026

    @rexm
    MemberAuthor

    /harness adopt

  2. stencil-harness commented on Nov 8, 2026

    @stencil-harness
    Contributor

    I'm the agent managing issue intake on Rex's behalf. I understand this issue tracks remediation of SonarCloud quality and security findings in Handlebars.Net, and I'm checking whether I can work on it autonomously.

  3. stencil-harness commented on Nov 8, 2026

    @stencil-harness
    Contributor

    I'm unable to proceed further on this and am awaiting follow-up from a human maintainer.

  4. stencil-harness commented on Nov 8, 2026

    @stencil-harness
    Contributor

    Picking this up. Plan for this pass: work through the pending items that can be done without breaking anything (ReusableStringWriter sealing, complexity extractions in LiteralParser and Tokenizer). The IDescriptor<TOptions> item (S2326) is a breaking change, so I'll leave it alone. I'll report back with what changed and how it was verified.

  5. stencil-harness commented on Nov 8, 2026

    @stencil-harness
    Contributor

    Handlebars-Net/Handlebars.Net: changes are in #679, which is set to merge automatically once CI passes. No behavior or public API changes.

    • S3881: ReusableStringWriter is now sealed. Its only constructor is private, so no outside code can subclass it.
    • S3776 Tokenizer: Parse is split into smaller helpers (ParseExpressionToken, ReadExtraBraces, ReadExpressionOpening, ReadStaticCharacter). Characters are still read and peeked in the same order.
    • S3776 LiteralParser: escape handling and end-of-literal detection are now separate helpers.

    Verification: the full test suite passes (2207 tests). New HelperLiteralArgumentsAreParsed tests cover escaped quotes and how number literals end. They pass on both the old and the new code.

    Still pending:

    • S2326 IDescriptor<TOptions>: fixing it breaks the API, so it should wait for a major version.
    • S3776 BlockHelperFunctionBinder.BindByRef / PartialBinder.VisitPartialExpression: not attempted because of the ExpressionShortcuts scoping risk noted above.
    • Triage of the remaining lower-priority smells.

    Note: #679 says it closes this issue, so merging it will close the tracker even though the items above are still open. Reopen it if you want to keep tracking them.

  6. reopened this on Nov 8, 2026
  7. rexm commented on Nov 8, 2026

    @rexm
    MemberAuthor

    Update the original issue to reflect what's been addressed.

  8. stencil-harness commented on Nov 8, 2026

    @stencil-harness
    Contributor

    I'm unable to proceed further on this and am awaiting follow-up from a human maintainer.

  9. stencil-harness commented on Nov 8, 2026

    @stencil-harness
    Contributor

    Picked up: a session is starting on this.

  10. stencil-harness commented on Nov 8, 2026

    @stencil-harness
    Contributor

    I've updated the issue description to show what #679 completed:

    Still pending: S2326 (IDescriptor<TOptions>, which needs a major version), the S3776 cleanups in BlockHelperFunctionBinder.BindByRef and PartialBinder.VisitPartialExpression, and reviewing the remaining lower-priority warnings. No code changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions