Feat/js engine conformance sweep - #97
Merged
Merged
Conversation
Ignore IDEOGRAPHIC SPACE
…l-demo-video GPU Surface Overlays + Native Shell MCP support
Optimize JsLexer/JsParser with ref struct and JsTokenKind
Remove the home-rolled IDiagnostics sink. Logging now uses ILogger with source-generated [LoggerMessage] methods. Spans and metrics move to a static StarlingTelemetry facade (ActivitySource + Meter) in Starling.Common. Layering and shape: - Libraries depend only on Microsoft.Extensions.Logging.Abstractions. Concrete providers (console, OpenTelemetry, in-memory DevTools) register at the host. - Leaf classes take ILogger<T> in the constructor with a NullLogger<T> default, so they stay new-able without a container. - Only the four composition points that mint several child-category loggers keep an ILoggerFactory: StarlingEngine, Painter, StarlingHttpClient, WebviewPanel. Engine also needs it for the dynamic "Starling.engine.js" console category. - Static classes use CreateLogger(typeof(X)), since T cannot be a static type. - Classes that only emit spans or metrics carry no logger at all (the HTML parse chain, BlockLayout, InlineLayout, ImageSharpBackend). DI: - AddStarlingEngine(IServiceCollection) registers Painter, StarlingEngine, and BrowserSession. The Avalonia host calls it and resolves BrowserSession from the container. Tests, bench, and the headless CLI still construct directly. Also fix every empty catch in src so caught exceptions are logged (debug or trace for best-effort paths, warning or error otherwise). Solution builds clean with no warnings. Tests pass across Net, Layout, Css, Paint, Html, AngleSharp, Bindings, Js, Common, and the Engine capture-double set.
refactor(diag): replace IDiagnostics with ILogger and StarlingTelemetry
Move regexp back in-tree, drop the submodule
Fix bugs and add missing features in the managed SVG decoder, and add a test suite modeled on the resvg test suite. Bug fixes: - Circle and ellipse rendered at half their radius (EllipsePolygon takes a full width/height, not a radius). - Recursive use/pattern/mask references overflowed the stack or fanned out exponentially. Added a depth limit plus reference-cycle detection. New features: - display:none, visibility, switch, transform-origin, paint-order, mix-blend-mode. - Gradient attribute inheritance through href, and objectBoundingBox patterns. - clip-path and clip-rule, mask (luminance), filters (feGaussianBlur, feFlood, feOffset), text and tspan, and markers. - XML internal-DTD entity expansion (external fetch stays blocked). Tests: - Authored conformance tests grouped by SVG 1.1 chapter (shapes, painting, structure, paint servers, masking, text, filters). - A data-driven corpus test with one case per vendored resvg file. The corpus data under testdata/spec/resvg is not committed here; the test reads it from the test output directory.
The Jint JS backend ships in builds, so its binaries (Jint, BSD-2-Clause) and Jint's only dependency (Acornima, BSD-3-Clause) require their copyright notice, conditions, and disclaimer to travel with binary distributions. - Add verbatim upstream license texts under third-party-licenses/. - List both packages in THIRD_PARTY_NOTICES.md. - Copy LICENSE, NOTICE, THIRD_PARTY_NOTICES.md, and third-party-licenses/ into the output of the distributable apps (Starling.Headless, Starling.Gui) so they ship on build and publish.
Add the resvg test suite's 1,679 SVG files as the conformance corpus that ResvgCorpusTests decodes. The upstream reference PNGs (~10 MB) are not vendored because the decode test never pixel-compares against them; this keeps the corpus around 1 MB of SVG text. Credit resvg (MIT, Copyright (c) 2018 Reizner Evgeniy) in THIRD_PARTY_NOTICES.md with thanks for openly sharing an excellent static-SVG test suite. results.csv, LICENSE, and a README are kept for attribution and reference.
Reflection test asserting the renderer-neutral contract surface in Starling.Paint (the DisplayList scene records, the backend/compositor interfaces, and the GPU hand-off DTOs) exposes no SixLabors type. Seven known GPU leaks (GpuPaintTexture, GpuPaintDeviceContext) are allow-listed with the step that removes each; the list must shrink to empty.
…tInterpolation (Step 1) Move the 13 pure CSS Color 4 conversion methods (sRGB<->Oklab/Lab/HSL/HWB, hue interpolation, sRGB<->linear) out of the ImageSharp adapter into a neutral Starling.Paint.Gradients.GradientInterpolation. Pure double math, no Rgba32 or SixLabors dependency, so a second backend can interpolate stops identically on the CPU. Verbatim move; Rgba32 packing (ToStraightAlpha/SampleStops) stays in the adapter and calls the neutral class. Output is byte-identical.
…ory (Step 2) Introduce IPaintBackendFactory (Kind/CreateBackend/CreateMeasurer) and ImageSharpPaintBackendFactory, and dispatch PaintBackendSelector.Create/ CreateMeasurer through a single FactoryFor(kind) switch. The factory pairs the backend with its measurer so layout and raster share a font stack, and is the registration point a second backend plugs into. Public Create/CreateMeasurer signatures are unchanged, so Painter is untouched; behavior is identical.
… Slice (Step 3) Add ShapedRun.CanReuseAtSize(double) virtual (default false; ImageSharp run returns Font.Size == size) and use it at the three backend text-draw sites instead of reading the concrete Font.Size — the reuse decision lives on the run, ready for a non-ImageSharp run to answer false and take a glyph path. Also clamp ImageSharpShapedRun.Slice's character-range substring to the string bounds so a non-1:1 (glyphs != chars) run can never read out of range — exact glyph slicing, best-effort text, never a crash. Adds a regression test.
…Step 4) Move the box-shadow / conic-gradient LRU layer caches and the deferred-disposal bag out of the 2,637-line ImageSharpBackend.cs into a same-class partial, Backend/ImageSharp/ImageSharpBackend.Caches.cs, so the ImageSharp-only cache vocabulary reads as one clearly-adapter concern. These were already nested private (maximally encapsulated, excluded from the neutral-seam guard); the partial split keeps identical behavior and visibility while shrinking the main adapter file. Output unchanged.
Split GpuPaintTexture into a neutral DTO (native texture/view nint handles + size + a Starling PaintTextureFormat enum + an opaque IDisposable owner) and a new adapter-internal ImageSharpGpuTexture that owns the SixLabors WebGPURenderTarget and reflects out its native handles. ImageSharpBackend constructs the adapter texture and hands the compositor a neutral GpuPaintTexture; GpuBlendEngine already consumed only the nint handles, so it is unchanged. The neutral-seam guard test loses its five GpuPaintTexture exceptions; only the two GpuPaintDeviceContext leaks remain (removed in Step 6).
Replace the SixLabors-typed GpuPaintDeviceContext at the seam with a neutral GpuPaintDevice(nint Device, nint Queue). The compositor's IGpuLayerTextureCache now exposes GpuDevice (the native handle pair) instead of ImageSharpContext, and RenderTexture takes GpuPaintDevice. The reflection bridge to ImageSharp's WebGPU device context moves to an adapter-internal ImageSharpGpuContext that caches one context per device handle and is torn down by the existing device-state cleanup. GpuBlendEngine no longer holds an ImageSharp context. The neutral-seam guard test's allow-list is now empty: every contract type in Starling.Paint.Backend / .Compositor / .DisplayList is free of SixLabors.
… (Step 7) Investigated making ShapedRun.Glyphs the raster input. Two findings make a glyph-by-id draw path the wrong move: (1) ImageSharp.Drawing's public API does not expose OpenType glyph indices (GlyphMetrics.GlyphId is internal), and (2) glyph ids are not portable across shaping engines, so a non-ImageSharp backend (skrifa/HarfBuzz) re-shapes the source text with its own ids regardless. The real renderer-neutral text contract is therefore the source text + FontSpec on the draw item, with ShapedGlyph positions for layout — which already exists. Document this on ShapedGlyph and in the measurer (positions authoritative, ids best-effort/non-portable). Structural readiness for a second backend's own text path is already in place via ShapedRun.CanReuseAtSize (Step 3) and the neutral GlyphShapedRun. No behavioral change.
…p 8) The agents assumed SvgImageDecoder's public surface was already neutral, but its Decode/DecodeText took a SixLabors Color for the SVG currentColor keyword, and Starling.Engine/ImageFetcher constructed a SixLabors.ImageSharp.Color to call it — a real cross-assembly leak. Change the parameter to neutral CssColor and move the sRGB->ImageSharp conversion inside the decoder. ImageFetcher now passes the CssColor it already has and references no SixLabors type (0 refs). The decoder's internals stay ImageSharp (correctly — it is the adapter's SVG rasterizer); only its public contract is neutralized. Full resvg corpus and SVG conformance suites stay green.
Reading a namespace export whose live cell still holds the TDZ sentinel (module not yet evaluated to the declaration) throws ReferenceError instead of leaking the sentinel; the namespace captures its realm at build time. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
With any default present, default/pattern parameter bindings are declared up front in the uninitialized state, so self/forward default references (`(x = x)`, `(a = b, b)`) throw ReferenceError; the actual parameter binding runs in declaration-init mode (the missing piece last attempt — stores were routing through TDZ-checked writes and throwing on their own initialization). language 98.19 -> 98.33, floor 98.2. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
IsLetDeclarationStart only looked for an Identifier lookahead; the lexer always classifies `yield` as its keyword token, so `let yield = 4` misparsed as the identifier `let` plus a stray token. language 98.33 -> 98.35. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
….1.1.2.5) A getter side effect can delete the with-binding between the compound read and write; the strict write must throw ReferenceError without re-creating the property. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
Two rules: (1) at statement-list level the `let` head COMMITS to a
LexicalDeclaration for identifier/yield/[/{ lookaheads — inside a generator
`let\nyield` therefore hits the binding-identifier rejection instead of an
ASI rescue; (2) in single-statement positions a sloppy declaration-looking
`let` is parsed as a FORCED ExpressionStatement (falling into
ParseStatement re-detected the declaration and wrongly accepted
`do let x = 1; while (false)`). language 98.36 -> 98.43.
Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
Update expressions on a resolvable const local emit the read (TDZ check + coercion) then ThrowConstAssignment; for-head let/const bindings are now marked lexical/const in the loop scope (they previously skipped both TDZ and const semantics — `for (const i = 0; …; i++)` never threw). Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
…rototype Rewrite JsArray around an authoritative uint length decoupled from the dense backing: growing length materializes nothing (new Array(1e9) is O(1)), deleted elements become real holes (HasOwn false, skipped by iteration), far-sparse and attribute-deviating elements live in the property bag, and getOwnPropertyNames now reports index keys, length, then the rest in spec order. ArraySetLength runs both observable coercions and clamps shrinks at the highest non-configurable element. Rewrite Array.prototype/statics generically: lengths are LengthOfArrayLike longs (huge array-likes no longer busy-loop 2^31 reads), length reads precede callable checks, mutators use throwing Set/DeletePropertyOrThrow (push on a non-writable length is a TypeError), sort collects only present elements with a stable merge sort (inconsistent comparators can't crash the BCL introsort) and deletes the hole tail, flat/flatMap follow FlattenIntoArray, from/of honor a constructor this, and at / @@unscopables / element-wise toLocaleString are installed. ArrayCreate paths reject lengths above 2^32-1 with RangeError. OrdinarySet's receiver tail now follows §10.1.9.2: the receiver's own descriptor comes from [[GetOwnProperty]] and exotic receivers get a value-only [[DefineOwnProperty]], so writing length through a Proxy reaches ArraySetLength instead of silently failing. built-ins/Array 83.03% -> 98.55% (947+59T -> 86 fails); built-ins/Object 92.20% -> 94.58% as a side effect. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
Object.assign follows CopyDataProperties ([[OwnPropertyKeys]] order with symbols, ToObject targets, getters fire, throwing writes). defineProperties and Object.create accept any coercible props object and honor symbol-keyed, enumerable-checked descriptors. keys/values/entries snapshot the key list (getters that mutate the object mid-walk no longer crash the host) and read through the vm. freeze/seal follow SetIntegrityLevel: preventExtensions runs first and a false answer (Proxy trap) is a TypeError, attribute changes are partial defines. Object.preventExtensions throws on a false trap answer. getOwnPropertySymbols routes through [[OwnPropertyKeys]] so proxy invariants fire. hasOwn coerces the object before the key. fromEntries is strict-iterator-only with IteratorClose on abrupt entries. Object.groupBy lands. Annex B __defineGetter__/__defineSetter__/ __lookupGetter__/__lookupSetter__ land as partial accessor defines and trap-aware chain walks. Primitive wrappers (Boolean/Number/BigInt/Symbol) now hold their value in a host slot instead of a visible '__primitiveValue' own property. Object.prototype.toLocaleString is Invoke(O, 'toString') with the original receiver. Object.prototype.toString classifies proxied arrays via IsArray. BigInt.prototype gets @@toStringTag. Builtins define length before name. built-ins/Object 94.66% -> 98.34%; built-ins/Array holds at 99.54%. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
The §15.2.5 check ran only inside the local-resolution branch; hoist it and thread detection through the parent compiler chain so nested-closure writes resolve the same immutable binding. (An eval-context nested shape still bypasses it — logged for follow-up.) Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
…prototype% Non-configurable redefine validation now matches §10.1.6.3: writable may transition true→false, a non-writable data slot pins its value by SameValue (NaN redefines pass, -0/+0 flips reject), and non-configurable accessor redefines require identical get/set. The partial-define path compares values with SameValue too. %Object.prototype% is an immutable-prototype exotic object (§10.4.7). String wrappers enumerate out-of-range index keys numerically before 'length'. built-ins/Object 98.34% -> 99.66%. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
Script-top capture analysis is real now: block-nested lexicals at global scope get cells when captured, so a hoisted block function no longer snapshots a TDZ sentinel (and closures see later writes). Globally-bound names (vars, top-level functions and lexicals) are stripped so no cell shadows the global binding. Array tail: %Object.prototype.toString% is captured as an intrinsic for the Array toString fallback, ArraySetLength's RangeError comes from the RUNNING realm (new JsVm.ActiveOnThread), the array iterator throws TypeError on an out-of-bounds typed array, and toLocaleString method lookups use GetV receivers (primitives stay primitive). Object tail: generic descriptors keep the current property kind in the array-index partial-define merge, %GeneratorFunction% / %AsyncGeneratorFunction% / %AsyncFunction% constructors exist with dynamic-source compilation and prototype round-trips, generator/async function objects inherit from their kind's prototype (toStringTag), typed arrays on resizable buffers reject [[PreventExtensions]] (freeze throws), and string wrappers order bag indices before 'length'. JsObject.OwnPropertyKeys routes through the virtual Keys so host objects that only override Keys (storage, DOM collections) surface exotic keys. built-ins/Array 99.54% -> 100.00%; built-ins/Object 99.66% -> 100.00%. Gates: Js.Tests 2258/0, Engine 181/0, Bindings 436/0. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
…bles, spec RegExpBuiltinExec, RegExp.escape, lookbehind/quantifier capture semantics
Regex engine (src/Starling.RegExp):
- Generated exact Unicode 17 property tables (UnicodePropertyData.g.cs):
all Scripts/Script_Extensions/General_Category values + binaries with
every tested alias spelling; case-sensitive Name=Value validation
- Reversed-program lookbehind: right-to-left matching, correct capture
positions, no start-point scan; captures propagate out of positive
lookarounds (shared numbering, group 0 preserved)
- RepeatMatcher semantics: per-iteration capture reset (ResetCaptures)
and the empty-iteration progress guard (MarkPos/ProgressJmp) with
nullable-body loops routed to the backtracking matcher
- Simple case folding (scf) for u/v ignore-case literal comparison
- Group names always parse escapes in Unicode mode (\u{...}, pairs)
- In-class \0<digit> rejected under u/v; huge bounded quantifiers
compile as unbounded tails instead of exploding
- Emoji_Keycap_Sequence enumerates as class-set strings so && and --
work; bare skin-tone modifiers no longer start RGI ZWJ units
- Binary-search char-class membership for the big property tables
JS intrinsics (RegExpCtor, StringCtor.fromCodePoint):
- RegExpBuiltinExec: observable lastIndex Get/ToLength/Set-throw order,
spec ToString of the argument
- §22.2.4.1 constructor: IsRegExp protocol (source/flags via Get),
plain-call short-circuit, flags override, coercion order
- RegExp.escape (§22.2.5.1) with ControlEscape/otherPunctuators/
surrogate hex escaping
- @@match/@@matchAll/@@search/@@split/@@replace: flags-string reads,
SpeciesConstructor per §7.3.24, observable ToString/ToLength/ToUint32
coercions, SameValue lastIndex save/restore, throwing lastIndex Sets
- Match results: null-prototype groups object, d-flag indices array
gets its own groups property, unclosed $< stays literal
- String.fromCodePoint encodes lone surrogates (UTF16EncodeCodePoint)
.NET regex backend: rewrite JS-divergent constructs for ECMAScript mode
(\s/\S/\W/\D classes, '.' line terminators, $ end anchor, multiline
anchors via lookarounds) and fall back to the Pike VM for in-class \S/\W/\D,
ignore-case \W, forward/in-class backreferences, and nullable loop bodies.
Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
…onformance-sweep Unicode 17 property tables + RegExp spec conformance (property-escapes, lookbehind, RepeatMatcher captures, RegExpBuiltinExec, RegExp.escape). Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
built-ins/RegExp 61.8%->98.8% and the bucket 89.83%->93.64% after the Unicode 17 property tables + RegExp spec merge. Claude-Session: https://claude.ai/code/session_017bx26FXvX899HzMXo9cWAs
…mance-sweep # Conflicts: # .github/workflows/ci.yml # AGENTS.md # Directory.Build.props # Directory.Packages.props # README.md # THIRD_PARTY_NOTICES.md # bench/README.md # bench/Starling.HtmlParserBench/HtmlParserComparisonBench.cs # bench/Starling.HtmlParserBench/Starling.HtmlParserBench.csproj # bench/Starling.JsEngineBench/Scripts/README.md # bench/Starling.JsEngineBench/Starling.JsEngineBench.csproj # bench/Starling.JsEngineBench/StarlingFeatureBench.cs # bench/Starling.JsEngineBench/StarlingScalingBench.cs # bench/engine-comparison.md # bench/html-parser-comparison.md # browser-plan/09_JS_ENGINE.md # browser-plan/anglesharp-backend-plan.md # src/Starling.AppHost/AppHost.cs # src/Starling.Bindings/PerformanceBinding.cs # src/Starling.Dom/Starling.Dom.csproj # src/Starling.Engine/Engine.cs # src/Starling.Engine/JsEngineSelector.cs # src/Starling.Gui/MainWindow.cs # src/Starling.Gui/Starling.Gui.csproj # src/Starling.Headless/Starling.Headless.csproj # src/Starling.Js.Hosting/Starling.Js.Hosting.csproj # src/Starling.Js/Bytecode/Disassembler.cs # src/Starling.Js/Bytecode/JsCompiler.Classes.cs # src/Starling.Js/Bytecode/JsCompiler.Modules.cs # src/Starling.Js/Bytecode/JsCompiler.cs # src/Starling.Js/Bytecode/Opcode.cs # src/Starling.Js/Intrinsics/ArrayBufferCtor.cs # src/Starling.Js/Intrinsics/ArrayCtor.cs # src/Starling.Js/Intrinsics/BigIntCtor.cs # src/Starling.Js/Intrinsics/BooleanCtor.cs # src/Starling.Js/Intrinsics/DataViewCtor.cs # src/Starling.Js/Intrinsics/DateCtor.cs # src/Starling.Js/Intrinsics/ErrorCtor.cs # src/Starling.Js/Intrinsics/FunctionCtor.cs # src/Starling.Js/Intrinsics/GeneratorIntrinsics.cs # src/Starling.Js/Intrinsics/IntlObj.cs # src/Starling.Js/Intrinsics/IntrinsicHelpers.cs # src/Starling.Js/Intrinsics/IteratorIntrinsics.cs # src/Starling.Js/Intrinsics/NumberCtor.cs # src/Starling.Js/Intrinsics/ObjectCtor.cs # src/Starling.Js/Intrinsics/PromiseCtor.cs # src/Starling.Js/Intrinsics/ReflectObj.cs # src/Starling.Js/Intrinsics/RegExpCtor.cs # src/Starling.Js/Intrinsics/RegExpStringIterator.cs # src/Starling.Js/Intrinsics/StringCtor.cs # src/Starling.Js/Intrinsics/SymbolCtor.cs # src/Starling.Js/Intrinsics/TypedArrayCtors.cs # src/Starling.Js/Lex/JsLexer.cs # src/Starling.Js/Modules/ModuleLoader.cs # src/Starling.Js/Parse/JsParser.Modules.cs # src/Starling.Js/Parse/JsParser.Patterns.cs # src/Starling.Js/Parse/JsParser.Scoping.cs # src/Starling.Js/Parse/JsParser.Strict.cs # src/Starling.Js/Parse/JsParser.cs # src/Starling.Js/Runtime/AbstractOperations.cs # src/Starling.Js/Runtime/JsArray.cs # src/Starling.Js/Runtime/JsArrayBuffer.cs # src/Starling.Js/Runtime/JsFunction.cs # src/Starling.Js/Runtime/JsModuleNamespace.cs # src/Starling.Js/Runtime/JsObject.cs # src/Starling.Js/Runtime/JsPromise.cs # src/Starling.Js/Runtime/JsRealm.cs # src/Starling.Js/Runtime/JsRegExp.cs # src/Starling.Js/Runtime/JsRuntime.cs # src/Starling.Js/Runtime/JsStringObject.cs # src/Starling.Js/Runtime/JsTypedArray.cs # src/Starling.Js/Runtime/JsVm.cs # src/Starling.Js/Runtime/Regex/DotNetRegexMatcher.cs # src/Starling.Js/Runtime/Regex/IRegexMatcher.cs # src/Starling.RegExp/RegexCharClass.cs # src/Starling.RegExp/RegexCompiler.cs # src/Starling.RegExp/RegexInstruction.cs # src/Starling.RegExp/RegexParser.cs # src/Starling.RegExp/RegexPikeVm.cs # src/Starling.RegExp/RegexProgram.cs # tasks/wpt/PLAN-98.md # tests/Starling.BindingSurface.Tests/IdlSurfaceManifestTests.cs # tests/Starling.BindingSurface.Tests/Starling.BindingSurface.Tests.csproj # tests/Starling.Bindings.Tests/README.md # tests/Starling.Engine.Tests/GithubRuntimeTests.cs # tests/Starling.Gui.Headless.Tests/ProgrammaticInputTests.cs # tests/Starling.Gui.Headless.Tests/SidebarBuildInfoTests.cs # tests/Starling.Gui.Headless.Tests/TodoInteractionTests.cs # tests/Starling.Js.Test262.Tests/README.md # tests/Starling.Js.Test262.Tests/Starling.Js.Test262.Tests.csproj # tests/Starling.Js.Test262.Tests/Test262Corpus.cs # tests/Starling.Js.Test262.Tests/Test262Runner.cs # tests/Starling.Js.Test262.Tests/Test262Tests.cs # tests/Starling.Js.Tests/Intrinsics/NumberTests.cs # tests/Starling.Js.Tests/Parse/JsParserModuleTests.cs # tests/Starling.Js.Tests/Runtime/JsErrorPositionTests.cs # tests/Starling.Js.Tests/Runtime/JsOperatorsGapTests.cs # tests/Starling.Js.Tests/Runtime/JsVmTests.cs # tests/Starling.RegExp.Tests/IdentifierPropertyTests.cs # tests/Starling.Wpt.Tests/README.md # tools/Starling.IdlGen/README.md
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
No description provided.