diff --git a/src/NUglify.Tests/App.config b/src/NUglify.Tests/App.config index 45437954..9fabdef4 100644 --- a/src/NUglify.Tests/App.config +++ b/src/NUglify.Tests/App.config @@ -1,6 +1,14 @@ - + + + + + + + + + diff --git a/src/NUglify.Tests/JavaScript/AllJavascriptSyntaxTest.cs b/src/NUglify.Tests/JavaScript/AllJavascriptSyntaxTest.cs new file mode 100644 index 00000000..a963614d --- /dev/null +++ b/src/NUglify.Tests/JavaScript/AllJavascriptSyntaxTest.cs @@ -0,0 +1,17 @@ +using NUglify.Tests.JavaScript.Common; +using NUnit.Framework; + +namespace NUglify.Tests.JavaScript +{ + [TestFixture] + public class AllJavascriptSyntaxTest + { + /// + /// check all possible files in input-directory for syntax errors after minification + /// + [Test] + public void SyntaxTestForAllFilesLineBreaks() { + TestHelper.Instance.RunSyntaxTestForAllFilesLineBreaks(); + } + } +} \ No newline at end of file diff --git a/src/NUglify.Tests/JavaScript/Common/TestHelper.cs b/src/NUglify.Tests/JavaScript/Common/TestHelper.cs index e85afd6b..d66ae6f5 100644 --- a/src/NUglify.Tests/JavaScript/Common/TestHelper.cs +++ b/src/NUglify.Tests/JavaScript/Common/TestHelper.cs @@ -18,10 +18,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using System.Xml; +using Microsoft.ClearScript; +using Microsoft.ClearScript.V8; using NUglify.JavaScript; using NUglify.JavaScript.Visitors; using NUnit.Framework; @@ -700,6 +704,113 @@ public void RunErrorTest(params JSError[] expectedErrorArray) RunErrorTest(string.Empty, expectedErrorArray); } + /// + /// check all files in input-directory for syntax errors after minification + /// execute javascript in V8 engine + /// each file that starts successfully without minification will be rerun after minification. + /// in some cases minifier generate endless running code. Example: D:\Git2\NUglify\src\NUglify.Tests\bin\Debug\TestData\JS\Input\ES2015\GeneratorFunction.js + /// + /// + public void RunSyntaxTestForAllFilesLineBreaks() { + DirectoryInfo dir = new DirectoryInfo(InputFolder); + List allTestFiles = dir.GetFiles("*.js", SearchOption.AllDirectories).ToList(); + var sw = new Stopwatch(); + sw.Start(); + var timeout = TimeSpan.FromSeconds(2); + var testResults = allTestFiles.Select(fi => { + string jsSource = File.ReadAllText(fi.FullName); + var (debugFinished, debugExecutionStarted, _) = Execute(jsSource, fi.Name, "debug", sw, timeout); + if (!debugExecutionStarted) { + Trace.WriteLine($"{fi.Name}: Syntax error, skipping minification ({fi.FullName})."); + return null; + } else { + var crunchedCode = Minify(jsSource, "-line:1"); + var (minifiedFinished, minifiedExecutionStarted, minifiedException) = Execute(crunchedCode, fi.Name, "minified", sw, timeout); + if (!minifiedExecutionStarted) { + //There + Assert.NotNull(minifiedException, "There should be an exception if the execution did not even start."); + return new { + testPassed = false, + message = minifiedException.ErrorDetails, + filePath = fi.FullName, + minifiedContent = crunchedCode + }; + } else if (!minifiedFinished && debugFinished) { + //Timeout in minified code, but not in original. + return new { + testPassed = false, + message = "Timeout in minified code, but not in original", + filePath = fi.FullName, + minifiedContent = crunchedCode + }; + } else { + return null; + } + } + }).Where(r => r != null).ToList(); + + if (testResults.Any()) { + throw new Exception("Test failed, following files had errors." + Environment.NewLine + String.Join(Environment.NewLine, testResults.Select(f => $"{f.filePath} => {f.message}{Environment.NewLine}{f.minifiedContent}{Environment.NewLine}"))); + } + } + + private static String Minify(string code, string minifyParameters) { + var switchParser = new UglifyCommandParser(); + switchParser.Parse(minifyParameters); + + var sb = new StringBuilder(); + var parser = new JSParser(); + using (var writer = new StringWriter(sb)) { + if (switchParser.JSSettings.PreprocessOnly) { + parser.EchoWriter = writer; + } + // normal -- just run it through the parser + var block = parser.Parse(new DocumentContext(code), switchParser.JSSettings); + if (!switchParser.JSSettings.PreprocessOnly) { + // look at the settings for the proper output visitor + if (switchParser.JSSettings.Format == JavaScriptFormat.JSON) { + { + if (!JsonOutputVisitor.Apply(writer, block, switchParser.JSSettings)) { + Trace.WriteLine("JSON OUTPUT ERRORS!"); + } + } + } else { + OutputVisitor.Apply(writer, block, switchParser.JSSettings); + } + } + } + return sb.ToString(); + } + + private (bool, bool, ScriptEngineException) Execute(string code, string filename, string type, Stopwatch sw, TimeSpan timeout) { + var engine = new V8ScriptEngine(); + bool executionStarted = false; + ScriptEngineException exception = null; + var task = Task.Run(() => { + try { + Trace.WriteLine($"{sw.ElapsedMilliseconds}: Starting {filename}.{type}."); + engine.Execute(code); + Trace.WriteLine($"{sw.ElapsedMilliseconds}: Finished {filename}.{type}."); + executionStarted = true; + } catch (ScriptEngineException e) { + exception = e; + if (e.ExecutionStarted) { + executionStarted = true; + Trace.WriteLine($"{sw.ElapsedMilliseconds}: Exception from {filename}.{type}. Execution did start."); + } else { + Trace.WriteLine($"{sw.ElapsedMilliseconds}: Exception from {filename}.{type}. Execution did not even start."); + } + } + }); + bool finishedInTime = task.Wait(timeout); + if (!finishedInTime) { + //If a timeout occurs, we do just assume that the execution started. + executionStarted = true; + } + Assert.IsTrue(executionStarted || exception != null, "There should be an exception if the execution did not even start."); + return (finishedInTime, executionStarted, exception); + } + public void RunErrorTest(string settingsSwitches, params JSError[] expectedErrorArray) { // open the stack trace for this call diff --git a/src/NUglify.Tests/NUglify.Tests.csproj b/src/NUglify.Tests/NUglify.Tests.csproj index 4b8c3e01..a44978a3 100644 --- a/src/NUglify.Tests/NUglify.Tests.csproj +++ b/src/NUglify.Tests/NUglify.Tests.csproj @@ -1,5 +1,7 @@  + + Debug @@ -43,13 +45,55 @@ + + ..\packages\Microsoft.ClearScript.Core.7.3.1\lib\net45\ClearScript.Core.dll + + + ..\packages\Microsoft.ClearScript.V8.7.3.1\lib\net45\ClearScript.V8.dll + + + ..\packages\Microsoft.ClearScript.V8.ICUData.7.3.1\lib\netstandard1.0\ClearScript.V8.ICUData.dll + + + ..\packages\Microsoft.ClearScript.Windows.7.3.1\lib\net45\ClearScript.Windows.dll + + + ..\packages\Microsoft.ClearScript.Windows.Core.7.3.1\lib\net45\ClearScript.Windows.Core.dll + + + + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + + + ..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll + + + ..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll + + + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + - @@ -98,6 +142,7 @@ + @@ -166,6 +211,7 @@ + PreserveNewest diff --git a/src/NUglify.Tests/NUglify.Tests.nuget.props b/src/NUglify.Tests/NUglify.Tests.nuget.props index 5929be82..9ed58d6e 100644 --- a/src/NUglify.Tests/NUglify.Tests.nuget.props +++ b/src/NUglify.Tests/NUglify.Tests.nuget.props @@ -15,7 +15,13 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + C:\Users\Andrew Bullock\.nuget\packages\nunit3testadapter\3.5.0 + C:\Users\Andrew Bullock\.nuget\packages\microsoft.clearscript.v8.native.win-x86\7.3.1 + C:\Users\Andrew Bullock\.nuget\packages\microsoft.clearscript.v8.native.win-x64\7.3.1 \ No newline at end of file diff --git a/src/NUglify.Tests/packages.config b/src/NUglify.Tests/packages.config new file mode 100644 index 00000000..0e75d423 --- /dev/null +++ b/src/NUglify.Tests/packages.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NUglify.Tests/project.json b/src/NUglify.Tests/project.json index 1159bcaa..fe7a586b 100644 --- a/src/NUglify.Tests/project.json +++ b/src/NUglify.Tests/project.json @@ -19,6 +19,7 @@ }, "dependencies": { "NUnit": "3.5.0", - "NUnit3TestAdapter": "3.5.0" + "NUnit3TestAdapter": "3.5.0", + "Microsoft.ClearScript": "7.3.1" } } \ No newline at end of file diff --git a/src/NUglify/JavaScript/Visitors/OutputVisitor.cs b/src/NUglify/JavaScript/Visitors/OutputVisitor.cs index 4ea85909..8f497816 100644 --- a/src/NUglify/JavaScript/Visitors/OutputVisitor.cs +++ b/src/NUglify/JavaScript/Visitors/OutputVisitor.cs @@ -767,7 +767,7 @@ public void Visit(CallExpression node) { if (node.OptionalChaining) { - OutputPossibleLineBreak('?'); + Output('?'); OutputPossibleLineBreak('.'); } @@ -2549,7 +2549,7 @@ public void Visit(MemberExpression node) } if (node.OptionalChaining) - OutputPossibleLineBreak('?'); + Output('?'); OutputPossibleLineBreak('.'); MarkSegment(node, node.Name, node.NameContext); @@ -3985,7 +3985,14 @@ void OutputFunctionArgsAndBody(FunctionObject node) Unindent(); if (wrapInParens) { - OutputPossibleLineBreak(')'); + if (node.FunctionType == FunctionType.ArrowFunction) + { + Output(')'); + } + else + { + OutputPossibleLineBreak(')'); + } MarkSegment(node, null, node.ParameterDeclarations.Context); } } @@ -3993,7 +4000,7 @@ void OutputFunctionArgsAndBody(FunctionObject node) { // empty arrow function parameters need the empty parentheses OutputPossibleLineBreak('('); - OutputPossibleLineBreak(')'); + Output(')'); m_startOfStatement = false; }