diff --git a/CLAUDE.md b/CLAUDE.md index 95baacd075..1ef93d582f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -128,6 +128,42 @@ Acornima Parser (external) → AST → Interpreter → Runtime → Interop - **Analysis**: Latest analyzers enabled with EnforceCodeStyleInBuild - **Performance**: Try to make code as perfomant as possible. +### Data Structures + +**Prefer readonly record structs over tuples** for returning multiple values. Record structs provide better readability, named properties, and IDE support. Pass them into methods with 'in' modifier. + +```csharp +// ❌ Avoid: Tuples with unnamed or poorly named fields +public (JsPlainDate?, JsZonedDateTime?) GetRelativeTo(ObjectInstance options) +{ + // Item1 and Item2 are unclear at call site + return (plainDate, zonedDateTime); +} + +// ✅ Prefer: readonly record struct with descriptive names +[System.Runtime.InteropServices.StructLayout(LayoutKind.Auto)] +public readonly record struct RelativeToResult( + JsPlainDate? PlainRelativeTo, + JsZonedDateTime? ZonedRelativeTo); + +public RelativeToResult GetRelativeTo(ObjectInstance options) +{ + // Clear, self-documenting at call site + return new RelativeToResult(plainDate, zonedDateTime); +} + +// Usage is clear and type-safe +var result = GetRelativeTo(options); +if (result.PlainRelativeTo != null) +{ + // Use result.PlainRelativeTo +} +``` + +**When to use each:** +- **readonly record struct**: Multiple related return values (2+), especially when used across multiple methods +- **Class/struct**: Complex data with behavior, validation, or many fields (5+) + ## Testing - **Jint.Tests/**: Main test suite using xUnit v3 diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index a2a9f090cc..14cc18d9f2 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -1,5 +1,5 @@ { - "SuiteGitSha": "f85c9511a380b9abb2877c42cc47e8fce06d56c5", + "SuiteGitSha": "f2d59ea6e4b2f6f87de9f0d18a41d73782b6a9bc", //"SuiteDirectory": "//mnt/c/work/test262", "TargetPath": "./Generated", "Namespace": "Jint.Tests.Test262", @@ -12,7 +12,8 @@ "regexp-v-flag", "source-phase-imports", "tail-call-optimization", - "Temporal" + "Temporal", + "Intl.Era-monthcode" ], "ExcludedFlags": [ "CanBlockIsFalse" @@ -180,7 +181,10 @@ // Misc intl402 tests requiring full locale support "intl402/fallback-locales-are-supported.js", - "intl402/supportedLocalesOf-consistent-with-resolvedOptions.js" + "intl402/supportedLocalesOf-consistent-with-resolvedOptions.js", + // misc features that require investigation + "intl402/NumberFormat/prototype/format/numbering-systems.js", + "intl402/Intl/supportedValuesOf/numberingSystems-with-simple-digit-mappings.js" ] } \ No newline at end of file diff --git a/Jint.Tests/Parser/JavascriptParserTests.cs b/Jint.Tests/Parser/JavascriptParserTests.cs index e224b61316..c256e45738 100644 --- a/Jint.Tests/Parser/JavascriptParserTests.cs +++ b/Jint.Tests/Parser/JavascriptParserTests.cs @@ -177,6 +177,6 @@ public void ShouldThrowErrorForInvalidLeftHandOperation() [InlineData("-.-")] public void ShouldThrowParseErrorExceptionForInvalidCode(string code) { - Assert.Throws(() => new Parser().ParseScript(code)); + Assert.Throws(() => new Parser().ParseScript(code)); } } diff --git a/Jint.Tests/Runtime/Debugger/EvaluateTests.cs b/Jint.Tests/Runtime/Debugger/EvaluateTests.cs index 56a589fc90..b5ba778503 100644 --- a/Jint.Tests/Runtime/Debugger/EvaluateTests.cs +++ b/Jint.Tests/Runtime/Debugger/EvaluateTests.cs @@ -72,7 +72,7 @@ function test(x) { var exception = Assert.Throws(() => engine.Debugger.Evaluate("this is a syntax error")); - Assert.IsType(exception.InnerException); + Assert.IsType(exception.InnerException); }); } @@ -111,4 +111,4 @@ function test(x) Assert.Equal(frameBefore.Function, frameAfter.Function); }); } -} \ No newline at end of file +} diff --git a/Jint/Extensions/Polyfills.cs b/Jint/Extensions/Polyfills.cs index 0fdb9dc89a..2334d1059c 100644 --- a/Jint/Extensions/Polyfills.cs +++ b/Jint/Extensions/Polyfills.cs @@ -1,3 +1,5 @@ +using System.Globalization; + namespace Jint; internal static class Polyfills @@ -15,3 +17,77 @@ internal static class Polyfills internal static bool Contains(this ReadOnlySpan source, string c) => source.IndexOf(c) != -1; #endif } + +public static class Int32Extensions +{ + extension(int) + { +#if NETFRAMEWORK || NETSTANDARD2_0 + public static bool TryParse(ReadOnlySpan span, NumberStyles style, IFormatProvider provider, out int value) + { + return int.TryParse(span.ToString(), style, provider, out value); + } +#endif + +#if NETFRAMEWORK || NETSTANDARD + public static int Parse(ReadOnlySpan span, IFormatProvider? provider = null) + { + return int.Parse(span.ToString(), NumberStyles.Integer, provider); + } +#endif + +#if NETFRAMEWORK || NETSTANDARD2_0 + public static int Parse(ReadOnlySpan span, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + return int.Parse(span.ToString(), style, provider); + } +#endif + } +} + +public static class Int64Extensions +{ + extension(long) + { +#if NETFRAMEWORK || NETSTANDARD2_0 + public static bool TryParse(ReadOnlySpan span, NumberStyles style, IFormatProvider formatProvider, out long value) + { + return long.TryParse(span.ToString(), style, formatProvider, out value); + } +#endif + +#if NETFRAMEWORK || NETSTANDARD + public static long Parse(ReadOnlySpan span, IFormatProvider? provider = null) + { + return long.Parse(span.ToString(), NumberStyles.Integer, provider); + } +#endif + +#if NETFRAMEWORK || NETSTANDARD2_0 + public static long Parse(ReadOnlySpan span, NumberStyles style = NumberStyles.Integer, IFormatProvider? provider = null) + { + return long.Parse(span.ToString(), style, provider); + } +#endif + } +} + +public static class DoubleExtensions +{ + extension(double) + { +#if NETFRAMEWORK || NETSTANDARD + public static double Parse(ReadOnlySpan span, IFormatProvider? provider = null) + { + return double.Parse(span.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, provider); + } +#endif + +#if NETFRAMEWORK || NETSTANDARD2_0 + public static double Parse(ReadOnlySpan span, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null) + { + return double.Parse(span.ToString(), style, provider); + } +#endif + } +} diff --git a/Jint/Native/Disposable/DisposeCapability.cs b/Jint/Native/Disposable/DisposeCapability.cs index 66984b1c0f..506be88cb2 100644 --- a/Jint/Native/Disposable/DisposeCapability.cs +++ b/Jint/Native/Disposable/DisposeCapability.cs @@ -89,6 +89,10 @@ public Completion DisposeResources(Completion c) { result = result.UnwrapIfPromise(_engine.Options.Constraints.PromiseTimeout); } + catch (PromiseRejectedException e) + { + exception = new JavaScriptException(e.RejectedValue); + } catch (JavaScriptException e) { exception = e; diff --git a/Jint/Native/Generator/GeneratorInstance.cs b/Jint/Native/Generator/GeneratorInstance.cs index 33e0872dd6..cfccbb2b92 100644 --- a/Jint/Native/Generator/GeneratorInstance.cs +++ b/Jint/Native/Generator/GeneratorInstance.cs @@ -237,6 +237,13 @@ private ObjectInstance ResumeExecution(in ExecutionContext genContext, Evaluatio var result = _generatorBody.Execute(context); _engine.LeaveExecutionContext(); + // https://tc39.es/ecma262/#sec-generatorstart step 4.i-j + // Dispose resources when generator body completes (not when yielding) + if (_generatorState != GeneratorState.SuspendedYield) + { + result = genContext.LexicalEnvironment.DisposeResources(result); + } + ObjectInstance? resultValue = null; if (result.Type == CompletionType.Normal) { diff --git a/Jint/Native/Intl/DateTimeFormatConstructor.cs b/Jint/Native/Intl/DateTimeFormatConstructor.cs index a09c1647a0..87baa5a63c 100644 --- a/Jint/Native/Intl/DateTimeFormatConstructor.cs +++ b/Jint/Native/Intl/DateTimeFormatConstructor.cs @@ -393,46 +393,6 @@ public override ObjectInstance Construct(JsCallArguments arguments, JsValue newT return stringValue; } - private string? GetNumberingSystemOption(ObjectInstance options, List requestedLocales) - { - var value = options.Get("numberingSystem"); - string? requestedNS = null; - - if (!value.IsUndefined()) - { - requestedNS = TypeConverter.ToString(value); - - // Validate against pattern - if (!IntlUtilities.IsValidUnicodeExtensionValue(requestedNS)) - { - Throw.RangeError(_realm, $"Invalid value '{requestedNS}' for option 'numberingSystem'"); - } - } - else if (requestedLocales.Count > 0) - { - // Check for unicode extension -u-nu- - requestedNS = ExtractUnicodeExtensionFromLocale(requestedLocales[0], "nu"); - } - - // Validate against supported numbering systems - // Only return numbering systems we actually support (have digit mappings for) - if (requestedNS != null) - { - var supported = _engine.Options.Intl.CldrProvider.GetSupportedNumberingSystems(); - foreach (var ns in supported) - { - if (string.Equals(ns, requestedNS, StringComparison.OrdinalIgnoreCase)) - { - return ns; // Return canonical form - } - } - // Unsupported numbering system - fall back to null (will use locale default) - return null; - } - - return null; - } - private int? GetNumberOption(ObjectInstance options, string property, int minimum, int maximum, int? fallback) { var value = options.Get(property); @@ -665,27 +625,6 @@ private static string BuildResolvedLocale(string baseLocale, string requestedLoc }; } - /// - /// Extracts a unicode extension value from the locale list. - /// For example, extracts "h11" from "de-u-hc-h11" when key is "hc". - /// - private static string? ExtractUnicodeExtensionValue(List locales, string key, HashSet? validValues) - { - foreach (var locale in locales) - { - var value = ExtractUnicodeExtensionFromLocale(locale, key); - if (value != null) - { - // Validate against allowed values if provided - if (validValues == null || validValues.Contains(value)) - { - return value; - } - } - } - return null; - } - /// /// Extracts a specific unicode extension value from a single locale string. /// diff --git a/Jint/Native/Intl/DateTimeFormatPrototype.cs b/Jint/Native/Intl/DateTimeFormatPrototype.cs index 2a6a3c62d4..e96283921f 100644 --- a/Jint/Native/Intl/DateTimeFormatPrototype.cs +++ b/Jint/Native/Intl/DateTimeFormatPrototype.cs @@ -105,53 +105,6 @@ private JsArray FormatToParts(JsValue thisObject, JsCallArguments arguments) return result; } - private DateTime ToDateTime(JsValue value) - { - if (value.IsUndefined()) - { - return DateTime.Now; - } - - if (value is JsDate jsDate) - { - // Check if date is within .NET DateTime range - if (!jsDate.DateTimeRangeValid) - { - // Date is outside .NET range - return min/max based on sign - return jsDate.DateValue < 0 ? DateTime.MinValue : DateTime.MaxValue; - } - - // ECMA-402 requires formatting in local time unless a specific timezone is provided - var dt = jsDate.ToDateTime(); - if (dt.Kind == DateTimeKind.Utc || dt.Kind == DateTimeKind.Unspecified) - { - dt = dt.ToLocalTime(); - } - return dt; - } - - var timeValue = TypeConverter.ToNumber(value); - DatePresentation presentation = timeValue; - presentation = presentation.TimeClip(); - - if (presentation.IsNaN) - { - Throw.RangeError(_realm, "Invalid time value"); - } - - // Clamp to .NET DateTime range if necessary - if (presentation.Value < JsDate.Min) - { - return DateTime.MinValue; - } - if (presentation.Value > JsDate.Max) - { - return DateTime.MaxValue; - } - - return presentation.ToDateTime().ToLocalTime(); - } - /// /// Converts a JavaScript value to DateTime, returning the original JavaScript year /// when the date is outside .NET DateTime range (for proper era formatting). diff --git a/Jint/Native/Intl/DurationFormatConstructor.cs b/Jint/Native/Intl/DurationFormatConstructor.cs index 8926e89844..5b7112fe86 100644 --- a/Jint/Native/Intl/DurationFormatConstructor.cs +++ b/Jint/Native/Intl/DurationFormatConstructor.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- ClrFunction requires JsValue - using System.Globalization; using Jint.Native.Function; using Jint.Native.Object; @@ -280,7 +278,7 @@ private string GetNumberingSystemOption(ObjectInstance options) /// /// https://tc39.es/proposal-intl-duration-format/#sec-intl.durationformat.supportedlocalesof /// - private JsValue SupportedLocalesOf(JsValue thisObject, JsCallArguments arguments) + private JsArray SupportedLocalesOf(JsValue thisObject, JsCallArguments arguments) { var locales = arguments.At(0); var options = arguments.At(1); diff --git a/Jint/Native/Intl/DurationFormatPrototype.cs b/Jint/Native/Intl/DurationFormatPrototype.cs index b1caa926d1..96d346c3fa 100644 --- a/Jint/Native/Intl/DurationFormatPrototype.cs +++ b/Jint/Native/Intl/DurationFormatPrototype.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- prototype methods return JsValue - using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; @@ -72,7 +70,7 @@ private JsValue Format(JsValue thisObject, JsCallArguments arguments) /// /// https://tc39.es/proposal-intl-duration-format/#sec-intl.durationformat.prototype.formattoparts /// - private JsValue FormatToParts(JsValue thisObject, JsCallArguments arguments) + private JsArray FormatToParts(JsValue thisObject, JsCallArguments arguments) { var durationFormat = ValidateDurationFormat(thisObject); var duration = arguments.At(0); @@ -84,7 +82,7 @@ private JsValue FormatToParts(JsValue thisObject, JsCallArguments arguments) /// /// https://tc39.es/proposal-intl-duration-format/#sec-intl.durationformat.prototype.resolvedoptions /// - private JsValue ResolvedOptions(JsValue thisObject, JsCallArguments arguments) + private JsObject ResolvedOptions(JsValue thisObject, JsCallArguments arguments) { var durationFormat = ValidateDurationFormat(thisObject); diff --git a/Jint/Native/Intl/IntlUtilities.cs b/Jint/Native/Intl/IntlUtilities.cs index 05f3472ced..adfbfa647f 100644 --- a/Jint/Native/Intl/IntlUtilities.cs +++ b/Jint/Native/Intl/IntlUtilities.cs @@ -1280,51 +1280,6 @@ private sealed class KeyValueParts public List Values { get; set; } = new List(); } - /// - /// Manually canonicalize a language tag according to BCP 47 rules. - /// - private static string CanonicalizeLanguageTag(string tag) - { - var parts = tag.Split('-'); - if (parts.Length == 0) - { - return tag; - } - - var result = new List(); - - // Language subtag (first part) - lowercase - result.Add(parts[0].ToLowerInvariant()); - - for (var i = 1; i < parts.Length; i++) - { - var part = parts[i]; - - if (part.Length == 4 && char.IsLetter(part[0])) - { - // Script subtag - title case - result.Add(char.ToUpperInvariant(part[0]) + part.Substring(1).ToLowerInvariant()); - } - else if (part.Length == 2 && char.IsLetter(part[0])) - { - // Region subtag (2 letters) - uppercase - result.Add(part.ToUpperInvariant()); - } - else if (part.Length == 3 && char.IsDigit(part[0])) - { - // Region subtag (3 digits) - as is - result.Add(part); - } - else - { - // Singleton, variant, or extension subtag - lowercase - result.Add(part.ToLowerInvariant()); - } - } - - return string.Join("-", result); - } - /// /// https://tc39.es/ecma402/#sec-resolvelocale /// diff --git a/Jint/Native/Intl/JsDurationFormat.cs b/Jint/Native/Intl/JsDurationFormat.cs index f2d8769c68..63923d05d2 100644 --- a/Jint/Native/Intl/JsDurationFormat.cs +++ b/Jint/Native/Intl/JsDurationFormat.cs @@ -788,114 +788,6 @@ private bool HasAnyAlwaysDisplay() string.Equals(NanosecondsDisplay, "always", StringComparison.Ordinal); } - private static string FormatUnit(double value, string singularLong, string pluralLong, string shortForm, string narrowForm, string unitStyle) - { - var isPlural = System.Math.Abs(value) != 1; - - // Handle numeric and 2-digit styles - if (string.Equals(unitStyle, "numeric", StringComparison.Ordinal)) - { - return ((long) value).ToString(CultureInfo.InvariantCulture); - } - - if (string.Equals(unitStyle, "2-digit", StringComparison.Ordinal)) - { - return ((long) value).ToString("D2", CultureInfo.InvariantCulture); - } - - return unitStyle switch - { - "long" => $"{(long) value} {(isPlural ? pluralLong : singularLong)}", - "short" => $"{(long) value} {shortForm}", - "narrow" => $"{(long) value}{narrowForm}", - _ => $"{(long) value} {shortForm}" // Default to short - }; - } - - private static string FormatUnitWithSign(double absValue, string singularLong, string pluralLong, string shortForm, string narrowForm, string unitStyle, bool isNegative = false, bool isNegativeZero = false) - { - var isPlural = absValue != 1; - var prefix = (isNegative || isNegativeZero) ? "-" : ""; - - // Handle numeric and 2-digit styles - if (string.Equals(unitStyle, "numeric", StringComparison.Ordinal)) - { - return $"{prefix}{(long) absValue}"; - } - - if (string.Equals(unitStyle, "2-digit", StringComparison.Ordinal)) - { - return $"{prefix}{(long) absValue:D2}"; - } - - return unitStyle switch - { - "long" => $"{prefix}{(long) absValue} {(isPlural ? pluralLong : singularLong)}", - "short" => $"{prefix}{(long) absValue} {shortForm}", - "narrow" => $"{prefix}{(long) absValue}{narrowForm}", - _ => $"{prefix}{(long) absValue} {shortForm}" // Default to short - }; - } - - /// - /// Formats sub-second units (milliseconds, microseconds, nanoseconds). - /// CLDR uses singular form for short/narrow and proper plural for long. - /// - private static string FormatSubSecondUnit(double value, string singular, string plural, string unitStyle) - { - var isPlural = System.Math.Abs(value) != 1; - - // Handle numeric and 2-digit styles - if (string.Equals(unitStyle, "numeric", StringComparison.Ordinal)) - { - return ((long) value).ToString(CultureInfo.InvariantCulture); - } - - if (string.Equals(unitStyle, "2-digit", StringComparison.Ordinal)) - { - return ((long) value).ToString("D2", CultureInfo.InvariantCulture); - } - - // For sub-second units: - // - long uses proper plural - // - short/narrow use singular form always - return unitStyle switch - { - "long" => $"{(long) value} {(isPlural ? plural : singular)}", - "short" => $"{(long) value} {singular}", - "narrow" => $"{(long) value}{singular}", // narrow has no space - _ => $"{(long) value} {singular}" // Default to short (singular) - }; - } - - private static string FormatSubSecondUnitWithSign(double absValue, string singular, string plural, string unitStyle, bool isNegative = false, bool isNegativeZero = false) - { - var isPlural = absValue != 1; - var prefix = (isNegative || isNegativeZero) ? "-" : ""; - - // Handle numeric and 2-digit styles - if (string.Equals(unitStyle, "numeric", StringComparison.Ordinal)) - { - return $"{prefix}{(long) absValue}"; - } - - if (string.Equals(unitStyle, "2-digit", StringComparison.Ordinal)) - { - return $"{prefix}{(long) absValue:D2}"; - } - - // For sub-second units: - // - long uses proper plural - // - short/narrow use singular form always - return unitStyle switch - { - "long" => $"{prefix}{(long) absValue} {(isPlural ? plural : singular)}", - "short" => $"{prefix}{(long) absValue} {singular}", - "narrow" => $"{prefix}{(long) absValue}{singular}", // narrow has no space - _ => $"{prefix}{(long) absValue} {singular}" // Default to short (singular) - }; - } - /// /// Formats a duration object and returns parts. /// diff --git a/Jint/Native/Intl/JsRelativeTimeFormat.cs b/Jint/Native/Intl/JsRelativeTimeFormat.cs index 73c99b2a08..98774c2f47 100644 --- a/Jint/Native/Intl/JsRelativeTimeFormat.cs +++ b/Jint/Native/Intl/JsRelativeTimeFormat.cs @@ -312,42 +312,6 @@ private void AddNumberParts(Engine engine, JsArray result, ref uint index, doubl } } - private static void AddIntegerPart(Engine engine, JsArray result, ref uint index, string value, string unit) - { - var part = OrdinaryObjectCreate(engine, engine.Realm.Intrinsics.Object.PrototypeObject); - part.Set("type", "integer"); - part.Set("value", value); - part.Set("unit", unit); - result.SetIndexValue(index++, part, updateLength: true); - } - - private static void AddGroupPart(Engine engine, JsArray result, ref uint index, string value, string unit) - { - var part = OrdinaryObjectCreate(engine, engine.Realm.Intrinsics.Object.PrototypeObject); - part.Set("type", "group"); - part.Set("value", value); - part.Set("unit", unit); - result.SetIndexValue(index++, part, updateLength: true); - } - - private static void AddDecimalPart(Engine engine, JsArray result, ref uint index, string value, string unit) - { - var part = OrdinaryObjectCreate(engine, engine.Realm.Intrinsics.Object.PrototypeObject); - part.Set("type", "decimal"); - part.Set("value", value); - part.Set("unit", unit); - result.SetIndexValue(index++, part, updateLength: true); - } - - private static void AddFractionPart(Engine engine, JsArray result, ref uint index, string value, string unit) - { - var part = OrdinaryObjectCreate(engine, engine.Realm.Intrinsics.Object.PrototypeObject); - part.Set("type", "fraction"); - part.Set("value", value); - part.Set("unit", unit); - result.SetIndexValue(index++, part, updateLength: true); - } - private string? GetSpecialPhrase(long value, string unit, bool isPast) { // Try to get special phrase from CLDR provider diff --git a/Jint/Native/Intl/JsSegments.cs b/Jint/Native/Intl/JsSegments.cs index b2c2a2c8b5..1747ce0a19 100644 --- a/Jint/Native/Intl/JsSegments.cs +++ b/Jint/Native/Intl/JsSegments.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- iterator protocol requires JsValue - using System.Globalization; using Jint.Native.Iterator; using Jint.Native.Object; @@ -79,7 +77,7 @@ private JsValue Containing(JsValue thisObject, JsCallArguments arguments) /// /// https://tc39.es/ecma402/#sec-%segmentsprototype%-@@iterator /// - private JsValue GetIterator(JsValue thisObject, JsCallArguments arguments) + private SegmentIterator GetIterator(JsValue thisObject, JsCallArguments arguments) { return new SegmentIterator(_engine, this); } diff --git a/Jint/Native/Intl/NumberFormatConstructor.cs b/Jint/Native/Intl/NumberFormatConstructor.cs index 9e8d8ec1d4..362c585605 100644 --- a/Jint/Native/Intl/NumberFormatConstructor.cs +++ b/Jint/Native/Intl/NumberFormatConstructor.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.Globalization; +using System.Text; using Jint.Native.Function; using Jint.Native.Object; using Jint.Runtime; @@ -22,7 +23,6 @@ internal sealed class NumberFormatConstructor : Constructor private static readonly string[] NotationValues = ["standard", "scientific", "engineering", "compact"]; private static readonly string[] CompactDisplayValues = ["short", "long"]; private static readonly string[] SignDisplayValues = ["auto", "never", "always", "exceptZero", "negative"]; - private static readonly string[] UseGroupingValues = ["auto", "always", "min2", "true", "false"]; private static readonly string[] RoundingModeValues = ["ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand", "halfTrunc", "halfEven"]; private static readonly string[] RoundingPriorityValues = ["auto", "morePrecision", "lessPrecision"]; private static readonly string[] TrailingZeroDisplayValues = ["auto", "stripIfInteger"]; @@ -466,7 +466,8 @@ private static string RemoveNumberingSystemFromLocale(string locale) // Find and remove the nu key-value pair var extensionStart = uIndex + 3; - var result = new System.Text.StringBuilder(locale.Substring(0, uIndex)); + var result = new ValueStringBuilder(); + result.Append(locale.AsSpan(0, uIndex)); var i = extensionStart; var hasOtherExtensions = false; @@ -482,9 +483,7 @@ private static string RemoveNumberingSystemFromLocale(string locale) // Single char means we've hit another singleton extension if (key.Length == 1) { -#pragma warning disable CA1846 - result.Append(locale.Substring(keyStart - 1)); -#pragma warning restore CA1846 + result.Append(locale.AsSpan(keyStart - 1)); break; } @@ -530,9 +529,7 @@ private static string RemoveNumberingSystemFromLocale(string locale) result.Append('-'); } var len = i - keyStart - (i < locale.Length && locale[i - 1] == '-' ? 1 : 0); -#pragma warning disable CA1846 - result.Append(locale.Substring(keyStart, len)); -#pragma warning restore CA1846 + result.Append(locale.AsSpan(keyStart, len)); } } } @@ -608,17 +605,6 @@ private string GetStringOption(ObjectInstance options, string property, string[] return stringValue; } - private static bool GetBooleanOption(ObjectInstance options, string property, bool fallback) - { - var value = options.Get(property); - if (value.IsUndefined()) - { - return fallback; - } - - return TypeConverter.ToBoolean(value); - } - private string GetUseGroupingOption(ObjectInstance options, string notation) { var value = options.Get("useGrouping"); diff --git a/Jint/Native/Intl/SegmenterConstructor.cs b/Jint/Native/Intl/SegmenterConstructor.cs index ed02b9616e..94b53d57f5 100644 --- a/Jint/Native/Intl/SegmenterConstructor.cs +++ b/Jint/Native/Intl/SegmenterConstructor.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- ClrFunction requires JsValue - using System.Globalization; using Jint.Native.Function; using Jint.Native.Object; @@ -122,7 +120,7 @@ private string GetStringOption(ObjectInstance options, string property, string[] /// /// https://tc39.es/ecma402/#sec-intl.segmenter.supportedlocalesof /// - private JsValue SupportedLocalesOf(JsValue thisObject, JsCallArguments arguments) + private JsArray SupportedLocalesOf(JsValue thisObject, JsCallArguments arguments) { var locales = arguments.At(0); var options = arguments.At(1); diff --git a/Jint/Native/Intl/SegmenterPrototype.cs b/Jint/Native/Intl/SegmenterPrototype.cs index e8582e67c6..f2caa3dc0a 100644 --- a/Jint/Native/Intl/SegmenterPrototype.cs +++ b/Jint/Native/Intl/SegmenterPrototype.cs @@ -1,5 +1,3 @@ -#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- prototype methods return JsValue - using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime; @@ -59,7 +57,7 @@ private JsSegmenter ValidateSegmenter(JsValue thisObject) /// /// https://tc39.es/ecma402/#sec-intl.segmenter.prototype.segment /// - private JsValue Segment(JsValue thisObject, JsCallArguments arguments) + private JsSegments Segment(JsValue thisObject, JsCallArguments arguments) { var segmenter = ValidateSegmenter(thisObject); var input = arguments.At(0); @@ -71,7 +69,7 @@ private JsValue Segment(JsValue thisObject, JsCallArguments arguments) /// /// https://tc39.es/ecma402/#sec-intl.segmenter.prototype.resolvedoptions /// - private JsValue ResolvedOptions(JsValue thisObject, JsCallArguments arguments) + private JsObject ResolvedOptions(JsValue thisObject, JsCallArguments arguments) { var segmenter = ValidateSegmenter(thisObject); diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index bd2a17d115..04cf99705f 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -1411,9 +1411,9 @@ internal static JsObject OrdinaryObjectCreate(Engine engine, ObjectInstance? pro method.Call(this); promiseCapability.Resolve.Call(Undefined, Undefined); } - catch + catch (JavaScriptException e) { - promiseCapability.Reject.Call(Undefined, Undefined); + promiseCapability.Reject.Call(Undefined, e.Error); } return promiseCapability.PromiseInstance; }; diff --git a/Jint/Options.cs b/Jint/Options.cs index 5d3667ac04..963d377ee7 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -4,15 +4,15 @@ using System.Reflection; using Jint.Native; using Jint.Native.Function; +using Jint.Native.Intl; using Jint.Native.Object; +using Jint.Native.Temporal; using Jint.Runtime; -using Jint.Runtime.Interop; +using Jint.Runtime.CallStack; using Jint.Runtime.Debugger; using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; using Jint.Runtime.Modules; -using Jint.Runtime.CallStack; -using Jint.Native.Intl; -using Jint.Native.Temporal; namespace Jint; diff --git a/Jint/Runtime/Interpreter/JintStatementList.cs b/Jint/Runtime/Interpreter/JintStatementList.cs index b1e964b796..b8f8764152 100644 --- a/Jint/Runtime/Interpreter/JintStatementList.cs +++ b/Jint/Runtime/Interpreter/JintStatementList.cs @@ -172,6 +172,7 @@ internal static Completion HandleException(EvaluationContext context, Exception JavaScriptException javaScriptException => CreateThrowCompletion(s, javaScriptException), TypeErrorException typeErrorException => CreateThrowCompletion(context.Engine.Realm.Intrinsics.TypeError, typeErrorException, typeErrorException.Node ?? s!._statement), RangeErrorException rangeErrorException => CreateThrowCompletion(context.Engine.Realm.Intrinsics.RangeError, rangeErrorException, s!._statement), + SyntaxErrorException syntaxErrorException => CreateThrowCompletion(context.Engine.Realm.Intrinsics.SyntaxError, syntaxErrorException, s!._statement), _ => throw exception }; } diff --git a/Jint/Runtime/SyntaxErrorException.cs b/Jint/Runtime/SyntaxErrorException.cs new file mode 100644 index 0000000000..eced9c2334 --- /dev/null +++ b/Jint/Runtime/SyntaxErrorException.cs @@ -0,0 +1,11 @@ +namespace Jint.Runtime; + +/// +/// Workaround for situation where engine is not easily accessible. +/// +internal sealed class SyntaxErrorException : JintException +{ + public SyntaxErrorException(string? message) : base(message) + { + } +} diff --git a/Jint/Runtime/Throw.cs b/Jint/Runtime/Throw.cs index 44879ce791..55ab309b99 100644 --- a/Jint/Runtime/Throw.cs +++ b/Jint/Runtime/Throw.cs @@ -64,6 +64,12 @@ public static void ReferenceError(Realm realm, string? message) throw new JavaScriptException(realm.Intrinsics.ReferenceError, message).SetJavaScriptLocation(location); } + [DoesNotReturn] + public static void SyntaxErrorNoEngine(string? message = null) + { + throw new SyntaxErrorException(message); + } + [DoesNotReturn] public static void TypeErrorNoEngine(string? message = null, Node? source = null) { diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index 9dd1daa464..d2da862abd 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -613,8 +613,7 @@ internal static BigInteger StringToBigInt(string str) { if (!TryStringToBigInt(str, out var result)) { - // TODO: this doesn't seem a JS syntax error, use a dedicated exception type? - throw new SyntaxError("CannotConvertToBigInt", " Cannot convert " + str + " to a BigInt").ToException(); + Throw.SyntaxErrorNoEngine("Cannot convert " + str + " to a BigInt"); } return result;