int ProcessJSFile(IList <InputGroup> inputGroups, UglifyCommandParser uglifyCommandParser, StringBuilder outputBuilder) { var returnCode = 0; var settings = uglifyCommandParser.JSSettings; var currentSourceOrigin = SourceOrigin.Project; // blank line before WriteProgress(); // create our parser object and hook up some events var parser = new JSParser(); parser.UndefinedReference += OnUndefinedReference; parser.CompilerError += (sender, ea) => { var error = ea.Error; if (currentSourceOrigin == SourceOrigin.Project || error.Severity == 0) { // ignore severity values greater than our severity level // also ignore errors that are in our ignore list (if any) if (error.Severity <= uglifyCommandParser.WarningLevel) { // we found an error m_errorsFound = true; // write the error out WriteError(error.ToString()); } } }; // output visitor requires a text writer, so make one from the string builder using (var writer = new StringWriter(outputBuilder, CultureInfo.InvariantCulture)) { var outputIndex = 0; var originalTermSetting = settings.TermSemicolons; for (var inputGroupIndex = 0; inputGroupIndex < inputGroups.Count; ++inputGroupIndex) { var inputGroup = inputGroups[inputGroupIndex]; currentSourceOrigin = inputGroup.Origin; // for all but the last item, we want the term-semicolons setting to be true. // but for the last entry, set it back to its original value settings.TermSemicolons = inputGroupIndex < inputGroups.Count - 1 ? true : originalTermSetting; // if this is preprocess-only or echo-input, then set up the writer as the echo writer for the parser if (settings.PreprocessOnly || m_echoInput) { parser.EchoWriter = writer; if (inputGroupIndex > 0) { // separate subsequent input groups with an appropriate line terminator writer.Write(settings.LineTerminator); writer.Write(';'); writer.Write(settings.LineTerminator); } } else { // not a preprocess-only or echo - make sure the echo writer is null parser.EchoWriter = null; } // parse the input code. Don't use a source context because it should already be // in the source using ///#SOURCE comments as we assembled the input groups. var scriptBlock = parser.Parse(inputGroup.Source, settings); if (m_errorsFound) { WriteProgress(); } if (m_outputTimer) { OutputTimingPoints(parser, inputGroupIndex, inputGroups.Count); } if (!settings.PreprocessOnly && !m_echoInput) { if (scriptBlock != null) { if (outputIndex++ > 0) { // separate subsequent input groups with an appropriate line terminator writer.Write(settings.LineTerminator); } // crunch the output and write it to debug stream, but make sure // the settings we use to output THIS chunk are correct if (settings.Format == JavaScriptFormat.JSON) { if (!JsonOutputVisitor.Apply(writer, scriptBlock, settings)) { returnCode = 1; } } else { OutputVisitor.Apply(writer, scriptBlock, settings); } } else { // no code? WriteProgress(NUglify.NoParsedCode); } } } // give the symbols map a chance to write something at the bottom of the source file // (and if this isn't preprocess-only or echo) if (settings.SymbolsMap != null && !settings.PreprocessOnly && !m_echoInput) { settings.SymbolsMap.EndFile(writer, settings.LineTerminator); } } if (uglifyCommandParser.AnalyzeMode) { // blank line before WriteProgress(); // output our report CreateReport(parser.GlobalScope, uglifyCommandParser); } return(returnCode); }
/// <summary> /// Crunched JS string passed to it, returning crunched string. /// The ErrorList property will be set with any errors found during the minification process. /// </summary> /// <param name="source">source Javascript</param> /// <param name="fileName">File name to use in error reporting. Default is <c>input</c></param> /// <param name="codeSettings">code minification settings</param> /// <returns>minified Javascript</returns> public static UglifyResult Js(string source, string fileName = null, CodeSettings codeSettings = null) { if (source == null) { throw new ArgumentNullException(nameof(source)); } fileName = fileName ?? "input"; codeSettings = codeSettings ?? new CodeSettings(); // default is an empty string string crunched; // reset the errors builder var errorList = new List <UglifyError>(); // create the parser and hook the engine error event var parser = new JSParser(); parser.CompilerError += (sender, e) => { var error = e.Error; if (error.Severity <= codeSettings.WarningLevel) { errorList.Add(error); } }; var sb = StringBuilderPool.Acquire(); try { var preprocessOnly = codeSettings.PreprocessOnly; using (var stringWriter = new StringWriter(sb, CultureInfo.InvariantCulture)) { if (preprocessOnly) { parser.EchoWriter = stringWriter; } // parse the input var scriptBlock = parser.Parse(new DocumentContext(source) { FileContext = fileName }, codeSettings); if (scriptBlock != null && !preprocessOnly) { // we'll return the crunched code if (codeSettings.Format == JavaScriptFormat.JSON) { // we're going to use a different output visitor -- one // that specifically returns valid JSON. if (!JsonOutputVisitor.Apply(stringWriter, scriptBlock, codeSettings)) { errorList.Add(new UglifyError() { Severity = 0, File = fileName, Message = CommonStrings.InvalidJSONOutput, }); } } else { // just use the normal output visitor OutputVisitor.Apply(stringWriter, scriptBlock, codeSettings); codeSettings.SymbolsMap?.EndFile(stringWriter, codeSettings.LineTerminator); } } } crunched = sb.ToString(); } catch (Exception e) { errorList.Add(new UglifyError() { Severity = 0, File = fileName, Message = e.Message, }); throw; } finally { sb.Release(); } return(new UglifyResult(crunched, errorList)); }
public void RunErrorTest(string settingsSwitches, params JSError[] expectedErrorArray) { // open the stack trace for this call StackTrace stackTrace = new StackTrace(); string testClass = null; string testName = null; // save the name of the current method (RunTest) string currentMethodName = MethodInfo.GetCurrentMethod().Name; // loop from the previous frame up until we get a method name that is not the // same as the current method name for (int ndx = 1; ndx < stackTrace.FrameCount; ++ndx) { // get the frame StackFrame stackFrame = stackTrace.GetFrame(ndx); // we have different entry points with the same name -- we're interested // in the first one that ISN'T the same name as our method MethodBase methodBase = stackFrame.GetMethod(); if (methodBase.Name != currentMethodName) { // the calling method's name is the test name - we use this as-is for the output file name // and we use any portion before an underscore as the input file testName = methodBase.Name; // get the method's class - we use this as the subfolder under input/output/expected testClass = methodBase.DeclaringType.Name; break; } } // we definitely should be able to find a function on the stack frame that // has a different name than this function, but just in case... Debug.Assert(testName != null && testClass != null, "Couldn't locate calling stack frame"); // the input file is the portion of the test name before the underscore (if any) string inputFile = testName.Split('_')[0]; // get the input and output paths string inputPath = GetJsPath( InputFolder, testClass, inputFile, false); Assert.IsTrue(File.Exists(inputPath), "Input File does not exist: {0}", inputPath); var outputPath = GetJsPath( m_outputFolder, testClass, testName, false); if (File.Exists(outputPath)) { // if it exists already, delete it File.Delete(outputPath); } else { // otherwise make sure the directory exists Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); } /*int expectedErrorCode = (int)(0x800A0000 + (int)expectedError); * Trace.WriteLine(string.Empty); * Trace.WriteLine(string.Format("Expecting error 0x{0:X}", expectedErrorCode));*/ // if we were passed a string containing command-line settings... var switchParser = new UglifyCommandParser(); if (!string.IsNullOrEmpty(settingsSwitches)) { // parse the string now switchParser.Parse(settingsSwitches); } // read the input JS string jsSource; using (var reader = new StreamReader(inputPath, GetJSEncoding(switchParser.EncodingInputName))) { jsSource = reader.ReadToEnd(); } Trace.Write("INPUT FILE: "); Trace.WriteLine(inputPath); Trace.WriteLine(jsSource); var testPassed = true; var expectedErrorList = new List <JSError>(expectedErrorArray); var errorList = new List <UglifyError>(); var parser = new JSParser(); parser.CompilerError += (source, e) => { errorList.Add(e.Error); }; var sb = new StringBuilder(); 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(jsSource) { FileContext = inputPath }, 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); } } } var crunchedCode = sb.ToString(); // output the crunched code using the proper output encoding using (var outputStream = new StreamWriter(outputPath, false, GetJSEncoding(switchParser.EncodingOutputName))) { outputStream.Write(crunchedCode); } Trace.WriteLine(string.Empty); Trace.WriteLine("---ERRORS---"); foreach (var err in errorList) { Trace.WriteLine(((JSError)err.ErrorNumber).ToString()); } Trace.WriteLine(string.Empty); Trace.Indent(); foreach (var err in errorList) { // log the error Trace.WriteLine(string.Empty); Trace.WriteLine(string.Format("Error {0} at Line {1}, Column {2}: {3}", err.ErrorCode, err.StartLine, err.StartColumn, err.Message)); Trace.Indent(); Trace.WriteLine(err.Message); int index = expectedErrorList.IndexOf((JSError)err.ErrorNumber); if (index >= 0) { // expected error -- remove it from the list so we can tell what we're missing later expectedErrorList.RemoveAt(index); } else { // unexpected error testPassed = false; Trace.WriteLine("UNEXPECTED"); } Trace.Unindent(); } Trace.Unindent(); // the list should be empty now -- if it isn't, then there was an expected error that didn't happen if (expectedErrorList.Count > 0) { testPassed = false; Trace.WriteLine(string.Empty); Trace.WriteLine("---MISSING ERRORS---"); Trace.Indent(); foreach (JSError jsError in expectedErrorList) { Trace.WriteLine(jsError.ToString()); } Trace.Unindent(); } if (!testPassed) { Trace.WriteLine(""); Trace.WriteLine("UNEXPECTED ERROR RESULTS"); } // compute the path to the expected file string expectedPath = GetJsPath( m_expectedFolder, testClass, testName, false); Trace.WriteLine(string.Empty); Trace.WriteLine("odd \"" + expectedPath + "\" \"" + outputPath + "\""); Trace.WriteLine(string.Empty); Trace.WriteLine("---Expected Code---"); TraceFileContents(expectedPath); Trace.WriteLine(string.Empty); Trace.WriteLine("---Resulting Code---"); TraceFileContents(outputPath); AssertCompareTextFiles(outputPath, expectedPath); Assert.IsTrue(testPassed, "Test failed"); }
void ProcessJavaScript(IList <InputGroup> inputGroups, UglifyCommandParser uglifyCommandParser, string outputPath, SymbolMap symbolMap, Encoding outputEncoding) { var settings = uglifyCommandParser.JSSettings; TextWriter mapWriter = null; ISourceMap sourceMap = null; try { // if we want a symbols map, we need to set it up now if (symbolMap != null && !settings.PreprocessOnly) { // if we specified the path, use it. Otherwise just use the output path with // ".map" appended to the end. Eg: output.js => output.js.map var symbolMapPath = symbolMap.Path.IsNullOrWhiteSpace() ? outputPath + ".map" : symbolMap.Path; // create the map writer and the source map implementation. // look at the Name attribute and implement the proper one. // the encoding needs to be UTF-8 WITHOUT a BOM or it won't work. if (!FileWriteOperation(symbolMapPath, uglifyCommandParser.Clobber, () => { mapWriter = new StreamWriter(symbolMapPath, false, new UTF8Encoding(false)); sourceMap = SourceMapFactory.Create(mapWriter, symbolMap.Name); if (sourceMap != null) { // if we get here, the symbol map now owns the stream and we can null it out so // we don't double-close it mapWriter = null; settings.SymbolsMap = sourceMap; // copy some property values sourceMap.SourceRoot = symbolMap.SourceRoot.IfNullOrWhiteSpace(null); sourceMap.SafeHeader = symbolMap.SafeHeader.GetValueOrDefault(false); // start the package sourceMap.StartPackage(outputPath, symbolMapPath); } return(true); })) { // could not write file Log.LogError(Strings.CouldNotWriteOutputFile, symbolMapPath); } } // save the original term settings. We'll make sure to set this back again // for the last item in the group, but we'll make sure it's TRUE for all the others. var originalTermSetting = settings.TermSemicolons; var currentSourceOrigin = SourceOrigin.Project; var parser = new JSParser(); parser.CompilerError += (sender, ea) => { // if the input group isn't project, then we only want to report sev-0 errors. // regardless, don't show any errors that have a severity lower (greater numeric value) // than the warning level specified. if ((currentSourceOrigin == SourceOrigin.Project || ea.Error.Severity == 0) && ea.Error.Severity <= uglifyCommandParser.WarningLevel) { LogContextError(ea.Error); } }; var outputBuilder = new StringBuilder(8192); using (var writer = new StringWriter(outputBuilder, CultureInfo.InvariantCulture)) { for (var inputGroupIndex = 0; inputGroupIndex < inputGroups.Count; ++inputGroupIndex) { var inputGroup = inputGroups[inputGroupIndex]; currentSourceOrigin = inputGroup.Origin; // for all but the last item, we want the term-semicolons setting to be true. // but for the last entry, set it back to its original value settings.TermSemicolons = inputGroupIndex < inputGroups.Count - 1 ? true : originalTermSetting; if (settings.PreprocessOnly) { parser.EchoWriter = writer; if (inputGroupIndex > 0) { // not the first group, so output the appropriate newline // sequence before we output the group. writer.Write(settings.LineTerminator); } } else { // not preprocess-only, so make sure the echo writer is null parser.EchoWriter = null; } // parse the input var block = parser.Parse(inputGroup.Source, settings); if (block != null && !settings.PreprocessOnly) { if (inputGroupIndex > 0) { // not the first group, so output the appropriate newline // sequence before we output the group. writer.Write(settings.LineTerminator); } // minify the AST to the output if (settings.Format == JavaScriptFormat.JSON) { if (!JsonOutputVisitor.Apply(writer, block, settings)) { Log.LogError(Strings.InvalidJSONOutput); } } else { OutputVisitor.Apply(writer, block, settings); } } } } // write output if (!Log.HasLoggedErrors) { if (!FileWriteOperation(outputPath, uglifyCommandParser.Clobber, () => { using (var writer = new StreamWriter(outputPath, false, outputEncoding)) { // write the combined minified code writer.Write(outputBuilder.ToString()); if (!settings.PreprocessOnly) { // give the map (if any) a chance to add something settings.SymbolsMap.IfNotNull(m => m.EndFile( writer, settings.LineTerminator)); } } return(true); })) { // could not write file Log.LogError(Strings.CouldNotWriteOutputFile, outputPath); } } else { Log.LogWarning(Strings.DidNotMinify, outputPath, Strings.ThereWereErrors); if (File.Exists(outputPath)) { File.Delete(outputPath); } } } finally { if (sourceMap != null) { mapWriter = null; settings.SymbolsMap = null; sourceMap.EndPackage(); sourceMap.Dispose(); } if (mapWriter != null) { mapWriter.Close(); } } }