using Acornima;
using Acornima.Ast;
using Jint;
namespace XFESystem.Browser.Tests;
public sealed class PackagedScriptParserTests
{
[Theory]
[InlineData("function f(a=2,...rest){let x=a;return ()=>++x+rest.length}var g=f(40,1);g()+g()")]
[InlineData("class A{#x=40;static count=0;constructor(){A.count++}get value(){return this.#x+A.count}}new A().value+new A().value")]
[InlineData("function f(a){arguments[0]=42;return a}f(1)")]
[InlineData("'use strict';function f(a){arguments[0]=42;return a}f(1)")]
[InlineData("function f(){if(true){function g(){return 42}}return g()}f()")]
[InlineData("let x=1;function f({a=x}={}){let x=2;return a}f()")]
[InlineData("function* gen(){try{yield 40}finally{yield 2}}Array.from(gen()).join(',')")]
[InlineData("var r=/x/g;var a=[r.test('xx'),r.lastIndex];a.join(',')")]
[InlineData("var a=[];for(let i=0;i<3;i++)a.push(()=>i);a.map(f=>f()).join(',')")]
[InlineData("var value=40;({value}).value+2")]
[InlineData("function f(a){try{throw a}finally{a++}}try{f(42)}catch(e){e}")]
[InlineData("const f=()=>({get value(){return 42}});f().value")]
[InlineData("var a={value:40};a.value++;a['value']++;a?.value")]
[InlineData("class A{get value(){return 40}}class B extends A{get value(){return super.value+2}}new B().value")]
public void LazyAnalysisMatchesPinnedEagerEngineAcrossFreshEngines(string source)
{
string expected = new Engine().Evaluate(Engine.PrepareScript(source)).ToString();
var prepared = PackagedScriptParser.Parse(source, "package:semantics", 10_000, out int nodes);
Assert.True(prepared.IsValid); Assert.InRange(nodes, 1, 10_000);
for (int instance = 0; instance < 3; instance++)
Assert.Equal(expected, new Engine().Evaluate(prepared).ToString());
}
[Fact]
public void UnusedFunctionsAreValidatedButTheirRuntimeAnalysisWaitsForUse()
{
var prepared = PackagedScriptParser.Parse("function used(a){return a+2}function unused(a){return a*2}used(40)", "package:lazy", 1000, out _);
var used = Assert.IsType<FunctionDeclaration>(prepared.Program!.Body[0]);
var unused = Assert.IsType<FunctionDeclaration>(prepared.Program.Body[1]);
Assert.Null(used.UserData); Assert.Null(unused.UserData);
var engine = new Engine(); Assert.Equal("42", engine.Evaluate(prepared).ToString());
Assert.NotNull(used.UserData); Assert.Null(unused.UserData);
Assert.Equal("42", engine.Evaluate("unused(21)").ToString());
Assert.NotNull(unused.UserData);
Assert.ThrowsAny<ParseErrorException>(() => PackagedScriptParser.Parse("function unused(){let a;let a;}", "package:early-error", 1000, out _));
Assert.ThrowsAny<ParseErrorException>(() => PackagedScriptParser.Parse("function unused(){return /[/;}", "package:bad-regexp", 1000, out _));
}
[Fact]
public void ParserRetainsSourceLocationsAndExplicitRegexpTimeoutWithoutCompiledRegex()
{
var prepared = PackagedScriptParser.Parse("\n /x+/g;", "package:locations", 1000, out _);
var statement = Assert.IsAssignableFrom<ExpressionStatement>(prepared.Program!.Body[0]);
var literal = Assert.IsType<RegExpLiteral>(statement.Expression);
Assert.Null(literal.Value); // Pattern is validated now; conversion is first-use work.
var regex = Assert.IsType<Jint.Native.JsRegExp>(new Engine().Evaluate(prepared)).Value;
Assert.Equal(TimeSpan.FromMilliseconds(100), regex.MatchTimeout);
Assert.False(regex.Options.HasFlag(System.Text.RegularExpressions.RegexOptions.Compiled));
Assert.Equal("package:locations", literal.Location.SourceFile);
Assert.Equal(2, literal.Location.Start.Line);
Assert.Equal(new Version(4, 11, 0, 0), typeof(Engine).Assembly.GetName().Version);
}
}
using Acornima;
using Acornima.Ast;
using Jint;
namespace XFESystem.Browser.Tests;
public sealed class PackagedScriptParserTests
{
[Theory]
[InlineData("function f(a=2,...rest){let x=a;return ()=>++x+rest.length}var g=f(40,1);g()+g()")]
[InlineData("class A{#x=40;static count=0;constructor(){A.count++}get value(){return this.#x+A.count}}new A().value+new A().value")]
[InlineData("function f(a){arguments[0]=42;return a}f(1)")]
[InlineData("'use strict';function f(a){arguments[0]=42;return a}f(1)")]
[InlineData("function f(){if(true){function g(){return 42}}return g()}f()")]
[InlineData("let x=1;function f({a=x}={}){let x=2;return a}f()")]
[InlineData("function* gen(){try{yield 40}finally{yield 2}}Array.from(gen()).join(',')")]
[InlineData("var r=/x/g;var a=[r.test('xx'),r.lastIndex];a.join(',')")]
[InlineData("var a=[];for(let i=0;i<3;i++)a.push(()=>i);a.map(f=>f()).join(',')")]
[InlineData("var value=40;({value}).value+2")]
[InlineData("function f(a){try{throw a}finally{a++}}try{f(42)}catch(e){e}")]
[InlineData("const f=()=>({get value(){return 42}});f().value")]
[InlineData("var a={value:40};a.value++;a['value']++;a?.value")]
[InlineData("class A{get value(){return 40}}class B extends A{get value(){return super.value+2}}new B().value")]
public void LazyAnalysisMatchesPinnedEagerEngineAcrossFreshEngines(string source)
{
string expected = new Engine().Evaluate(Engine.PrepareScript(source)).ToString();
var prepared = PackagedScriptParser.Parse(source, "package:semantics", 10_000, out int nodes);
Assert.True(prepared.IsValid); Assert.InRange(nodes, 1, 10_000);
for (int instance = 0; instance < 3; instance++)
Assert.Equal(expected, new Engine().Evaluate(prepared).ToString());
}
[Fact]
public void UnusedFunctionsAreValidatedButTheirRuntimeAnalysisWaitsForUse()
{
var prepared = PackagedScriptParser.Parse("function used(a){return a+2}function unused(a){return a*2}used(40)", "package:lazy", 1000, out _);
var used = Assert.IsType<FunctionDeclaration>(prepared.Program!.Body[0]);
var unused = Assert.IsType<FunctionDeclaration>(prepared.Program.Body[1]);
Assert.Null(used.UserData); Assert.Null(unused.UserData);
var engine = new Engine(); Assert.Equal("42", engine.Evaluate(prepared).ToString());
Assert.NotNull(used.UserData); Assert.Null(unused.UserData);
Assert.Equal("42", engine.Evaluate("unused(21)").ToString());
Assert.NotNull(unused.UserData);
Assert.ThrowsAny<ParseErrorException>(() => PackagedScriptParser.Parse("function unused(){let a;let a;}", "package:early-error", 1000, out _));
Assert.ThrowsAny<ParseErrorException>(() => PackagedScriptParser.Parse("function unused(){return /[/;}", "package:bad-regexp", 1000, out _));
}
[Fact]
public void ParserRetainsSourceLocationsAndExplicitRegexpTimeoutWithoutCompiledRegex()
{
var prepared = PackagedScriptParser.Parse("\n /x+/g;", "package:locations", 1000, out _);
var statement = Assert.IsAssignableFrom<ExpressionStatement>(prepared.Program!.Body[0]);
var literal = Assert.IsType<RegExpLiteral>(statement.Expression);
Assert.Null(literal.Value); // Pattern is validated now; conversion is first-use work.
var regex = Assert.IsType<Jint.Native.JsRegExp>(new Engine().Evaluate(prepared)).Value;
Assert.Equal(TimeSpan.FromMilliseconds(100), regex.MatchTimeout);
Assert.False(regex.Options.HasFlag(System.Text.RegularExpressions.RegexOptions.Compiled));
Assert.Equal("package:locations", literal.Location.SourceFile);
Assert.Equal(2, literal.Location.Start.Line);
Assert.Equal(new Version(4, 11, 0, 0), typeof(Engine).Assembly.GetName().Version);
}
}