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(prepared.Program!.Body[0]); var unused = Assert.IsType(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(() => PackagedScriptParser.Parse("function unused(){let a;let a;}", "package:early-error", 1000, out _)); Assert.ThrowsAny(() => 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(prepared.Program!.Body[0]); var literal = Assert.IsType(statement.Expression); Assert.Null(literal.Value); // Pattern is validated now; conversion is first-use work. var regex = Assert.IsType(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); } }