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
36 changes: 36 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"SuiteGitSha": "f85c9511a380b9abb2877c42cc47e8fce06d56c5",
"SuiteGitSha": "f2d59ea6e4b2f6f87de9f0d18a41d73782b6a9bc",
//"SuiteDirectory": "//mnt/c/work/test262",
"TargetPath": "./Generated",
"Namespace": "Jint.Tests.Test262",
Expand All @@ -12,7 +12,8 @@
"regexp-v-flag",
"source-phase-imports",
"tail-call-optimization",
"Temporal"
"Temporal",
"Intl.Era-monthcode"
],
"ExcludedFlags": [
"CanBlockIsFalse"
Expand Down Expand Up @@ -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"
]
}
2 changes: 1 addition & 1 deletion Jint.Tests/Parser/JavascriptParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,6 @@ public void ShouldThrowErrorForInvalidLeftHandOperation()
[InlineData("-.-")]
public void ShouldThrowParseErrorExceptionForInvalidCode(string code)
{
Assert.Throws<SyntaxErrorException>(() => new Parser().ParseScript(code));
Assert.Throws<Acornima.SyntaxErrorException>(() => new Parser().ParseScript(code));
}
}
4 changes: 2 additions & 2 deletions Jint.Tests/Runtime/Debugger/EvaluateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function test(x)
{
var exception = Assert.Throws<DebugEvaluationException>(() =>
engine.Debugger.Evaluate("this is a syntax error"));
Assert.IsType<SyntaxErrorException>(exception.InnerException);
Assert.IsType<Acornima.SyntaxErrorException>(exception.InnerException);
});
}

Expand Down Expand Up @@ -111,4 +111,4 @@ function test(x)
Assert.Equal(frameBefore.Function, frameAfter.Function);
});
}
}
}
76 changes: 76 additions & 0 deletions Jint/Extensions/Polyfills.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Globalization;

namespace Jint;

internal static class Polyfills
Expand All @@ -15,3 +17,77 @@ internal static class Polyfills
internal static bool Contains(this ReadOnlySpan<string> source, string c) => source.IndexOf(c) != -1;
#endif
}

public static class Int32Extensions
{
extension(int)
{
#if NETFRAMEWORK || NETSTANDARD2_0
public static bool TryParse(ReadOnlySpan<char> 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<char> span, IFormatProvider? provider = null)
{
return int.Parse(span.ToString(), NumberStyles.Integer, provider);
}
#endif

#if NETFRAMEWORK || NETSTANDARD2_0
public static int Parse(ReadOnlySpan<char> 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<char> 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<char> span, IFormatProvider? provider = null)
{
return long.Parse(span.ToString(), NumberStyles.Integer, provider);
}
#endif

#if NETFRAMEWORK || NETSTANDARD2_0
public static long Parse(ReadOnlySpan<char> 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<char> span, IFormatProvider? provider = null)
{
return double.Parse(span.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, provider);
}
#endif

#if NETFRAMEWORK || NETSTANDARD2_0
public static double Parse(ReadOnlySpan<char> span, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null)
{
return double.Parse(span.ToString(), style, provider);
}
#endif
}
}
4 changes: 4 additions & 0 deletions Jint/Native/Disposable/DisposeCapability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions Jint/Native/Generator/GeneratorInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
61 changes: 0 additions & 61 deletions Jint/Native/Intl/DateTimeFormatConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,46 +393,6 @@ public override ObjectInstance Construct(JsCallArguments arguments, JsValue newT
return stringValue;
}

private string? GetNumberingSystemOption(ObjectInstance options, List<string> 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);
Expand Down Expand Up @@ -665,27 +625,6 @@ private static string BuildResolvedLocale(string baseLocale, string requestedLoc
};
}

/// <summary>
/// Extracts a unicode extension value from the locale list.
/// For example, extracts "h11" from "de-u-hc-h11" when key is "hc".
/// </summary>
private static string? ExtractUnicodeExtensionValue(List<string> locales, string key, HashSet<string>? 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;
}

/// <summary>
/// Extracts a specific unicode extension value from a single locale string.
/// </summary>
Expand Down
47 changes: 0 additions & 47 deletions Jint/Native/Intl/DateTimeFormatPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/// <summary>
/// Converts a JavaScript value to DateTime, returning the original JavaScript year
/// when the date is outside .NET DateTime range (for proper era formatting).
Expand Down
4 changes: 1 addition & 3 deletions Jint/Native/Intl/DurationFormatConstructor.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -280,7 +278,7 @@ private string GetNumberingSystemOption(ObjectInstance options)
/// <summary>
/// https://tc39.es/proposal-intl-duration-format/#sec-intl.durationformat.supportedlocalesof
/// </summary>
private JsValue SupportedLocalesOf(JsValue thisObject, JsCallArguments arguments)
private JsArray SupportedLocalesOf(JsValue thisObject, JsCallArguments arguments)
{
var locales = arguments.At(0);
var options = arguments.At(1);
Expand Down
6 changes: 2 additions & 4 deletions Jint/Native/Intl/DurationFormatPrototype.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -72,7 +70,7 @@ private JsValue Format(JsValue thisObject, JsCallArguments arguments)
/// <summary>
/// https://tc39.es/proposal-intl-duration-format/#sec-intl.durationformat.prototype.formattoparts
/// </summary>
private JsValue FormatToParts(JsValue thisObject, JsCallArguments arguments)
private JsArray FormatToParts(JsValue thisObject, JsCallArguments arguments)
{
var durationFormat = ValidateDurationFormat(thisObject);
var duration = arguments.At(0);
Expand All @@ -84,7 +82,7 @@ private JsValue FormatToParts(JsValue thisObject, JsCallArguments arguments)
/// <summary>
/// https://tc39.es/proposal-intl-duration-format/#sec-intl.durationformat.prototype.resolvedoptions
/// </summary>
private JsValue ResolvedOptions(JsValue thisObject, JsCallArguments arguments)
private JsObject ResolvedOptions(JsValue thisObject, JsCallArguments arguments)
{
var durationFormat = ValidateDurationFormat(thisObject);

Expand Down
Loading