Ejemplo n.º 1
0
        public void SourceChange()
        {
            const string FileName            = "SourceChange.js";
            const string OriginalFileContext = "NULL";

            // get the source code
            string source;
            var    inputPath = Path.Combine(InputFolder, FileName);

            Trace.Write("Source: ");
            Trace.WriteLine(inputPath);
            using (var reader = new StreamReader(inputPath))
            {
                source = reader.ReadToEnd();
            }
            Trace.WriteLine(source);
            Trace.WriteLine("");
            Trace.WriteLine("-----------------------");
            Trace.WriteLine("");

            // get the expected results
            string expected;
            var    expectedPath = new FileInfo(Path.Combine(ExpectedFolder, FileName));

            Trace.Write("Expected: ");
            Trace.WriteLine(inputPath);
            using (var reader = new StreamReader(expectedPath.FullName))
            {
                expected = reader.ReadToEnd();
            }

            Trace.WriteLine(expected);
            Trace.WriteLine("");
            Trace.WriteLine("-----------------------");
            Trace.WriteLine("");

            // parse the source, keeping track of the errors
            var errors = new List <ContextError>();
            var parser = new JSParser();

            parser.CompilerError += (sender, ea) =>
            {
                errors.Add(ea.Error);
            };

            var settings = new CodeSettings()
            {
                LocalRenaming = LocalRenaming.KeepAll
            };
            var block = parser.Parse(new DocumentContext(source)
            {
                FileContext = OriginalFileContext
            }, settings);
            var minified = OutputVisitor.Apply(block, parser.Settings);

            // write the output so we can diagnose later if we need to
            if (!Directory.Exists(OutputFolder))
            {
                Directory.CreateDirectory(OutputFolder);
            }

            var actualPath = new FileInfo(Path.Combine(OutputFolder, FileName));

            Trace.Write("Actual: ");
            Trace.WriteLine(actualPath);
            using (var writer = new StreamWriter(actualPath.FullName, false, Encoding.UTF8))
            {
                writer.Write(minified);
            }

            Trace.WriteLine(minified);
            Trace.WriteLine("");
            Trace.WriteLine("-----------------------");
            Trace.WriteLine("");

            Trace.WriteLine("Output Comparison:");
            Trace.WriteLine(string.Format("odd.exe \"{0}\" \"{1}\"", expectedPath.FullName, actualPath.FullName));
            Trace.WriteLine("");

            // and compare them -- they should be equal
            Assert.IsTrue(string.CompareOrdinal(minified, expected) == 0, "actual is not the expected");

            var expectedErrors = new[] {
                new { FileContext = "anonfunc.js", StartLine = 2, EndLine = 2, StartColumn = 20, EndColumn = 21, ErrorCode = "JS1010" },
                new { FileContext = "anonfunc.js", StartLine = 5, EndLine = 5, StartColumn = 3, EndColumn = 4, ErrorCode = "JS1195" },
                new { FileContext = "addclass.js", StartLine = 2, EndLine = 2, StartColumn = 8, EndColumn = 14, ErrorCode = "JS1135" },
                new { FileContext = "addclass.js", StartLine = 10, EndLine = 10, StartColumn = 42, EndColumn = 48, ErrorCode = "JS1135" },
            };

            // now, the errors should be the same -- in particular we are looking for the line/column
            // numbers and source path. they should be what got reset by the ///#SOURCE comments, not the
            // real values from the source file.
            Trace.WriteLine("Errors:");
            foreach (var error in errors)
            {
                Trace.WriteLine(error.ToString());

                var foundIt = false;
                foreach (var expectedError in expectedErrors)
                {
                    if (expectedError.StartLine == error.StartLine &&
                        expectedError.EndLine == error.EndLine &&
                        expectedError.StartColumn == error.StartColumn &&
                        expectedError.EndColumn == error.EndColumn &&
                        string.CompareOrdinal(expectedError.FileContext, error.File) == 0 &&
                        string.CompareOrdinal(expectedError.ErrorCode, error.ErrorCode) == 0)
                    {
                        foundIt = true;
                        break;
                    }
                }

                Assert.IsTrue(foundIt, "Unexpected error");
            }

            Trace.WriteLine("");
        }
Ejemplo n.º 2
0
        private 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);
        }
Ejemplo n.º 3
0
        /// <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 UgliflyResult 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);

                            // if we are asking for a symbols map, give it a chance to output a little something
                            // to the minified file.
                            if (codeSettings.SymbolsMap != null)
                            {
                                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 UgliflyResult(crunched, errorList));
        }
Ejemplo n.º 4
0
        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();
                }
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Produces a code minifiction of JS content by using the Microsoft Ajax JS Minifier
        /// </summary>
        /// <param name="content">JS content</param>
        /// <param name="isInlineCode">Flag whether the content is inline code</param>
        /// <param name="encoding">Text encoding</param>
        /// <returns>Minification result</returns>
        public CodeMinificationResult Minify(string content, bool isInlineCode, Encoding encoding)
        {
            if (string.IsNullOrWhiteSpace(content))
            {
                return(new CodeMinificationResult(string.Empty));
            }

            string newContent = string.Empty;
            var    errors     = new List <MinificationErrorInfo>();
            var    warnings   = new List <MinificationErrorInfo>();

            lock (_minificationSynchronizer)
            {
                if (_errorReporter == null)
                {
                    _errorReporter = new MsAjaxErrorReporter(_settings.WarningLevel);
                }

                JSParser originalJsParser = isInlineCode ?
                                            _originalInlineJsParser : _originalEmbeddedJsParser;
                if (originalJsParser == null)
                {
                    originalJsParser = CreateOriginalJsParserInstance(_settings, isInlineCode);
                    if (isInlineCode)
                    {
                        _originalInlineJsParser = originalJsParser;
                    }
                    else
                    {
                        _originalEmbeddedJsParser = originalJsParser;
                    }
                }

                originalJsParser.CompilerError += _errorReporter.ParseErrorHandler;

                var           stringBuilderPool = AdvancedStringBuilderPool.Shared;
                StringBuilder contentBuilder    = stringBuilderPool.Rent(content.Length);
                var           documentContext   = new DocumentContext(content)
                {
                    FileContext = string.Empty
                };

                try
                {
                    using (var stringWriter = new StringWriter(contentBuilder, CultureInfo.InvariantCulture))
                    {
                        // Parse the input
                        Block scriptBlock = originalJsParser.Parse(documentContext);
                        if (scriptBlock != null)
                        {
                            // Use normal output visitor
                            OutputVisitor.Apply(stringWriter, scriptBlock, originalJsParser.Settings);
                        }
                    }

                    newContent = contentBuilder.ToString();
                }
                finally
                {
                    originalJsParser.CompilerError -= _errorReporter.ParseErrorHandler;
                    stringBuilderPool.Return(contentBuilder);

                    errors.AddRange(_errorReporter.Errors);
                    warnings.AddRange(_errorReporter.Warnings);

                    _errorReporter.Clear();
                }
            }

            return(new CodeMinificationResult(newContent, errors, warnings));
        }
Ejemplo n.º 6
0
        private string Parse(JSParser parser, CodeSettings settings, string source)
        {
            var block = parser.Parse(source, settings);

            return(OutputVisitor.Apply(block, settings));
        }
Ejemplo n.º 7
0
        public void NoErrors()
        {
            // get the source code in the file specified by the first column
            string sourceCode;
            var    fileName = TestContext.DataRow[0].ToString();
            var    filePath = Path.Combine(TestContext.DeploymentDirectory, @"Dll\Input\Frameworks", fileName);

            Assert.IsTrue(File.Exists(filePath), "Input file must exist");

            Trace.Write("Reading source file: ");
            Trace.WriteLine(filePath);
            using (var reader = new StreamReader(filePath))
            {
                sourceCode = reader.ReadToEnd();
            }

            // run the framework file through a parser with standard settings (except for multi-line mode)
            // there should be only the errors specified in the other columns in the CSV file (if any)
            var errorCount = 0;
            var parser     = new JSParser();

            parser.CompilerError += (sender, ea) =>
            {
                if (ea.Error.IsError)
                {
                    Trace.WriteLine(ea.ToString());
                    ++errorCount;
                }
            };
            var block = parser.Parse(new DocumentContext(sourceCode)
            {
                FileContext = filePath
            });                                                                                  //, new CodeSettings() { OutputMode = OutputMode.MultipleLines });
            var minifiedCode = OutputVisitor.Apply(block, parser.Settings);

            // save the results in Output folder
            var outputPath = Path.Combine(TestContext.DeploymentDirectory, @"Dll\Output\Frameworks", fileName);

            Trace.Write("Output path: ");
            Trace.WriteLine(outputPath);

            Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
            using (var writer = new StreamWriter(outputPath, false, Encoding.UTF8))
            {
                writer.Write(minifiedCode);
            }

            // report if there were any errors, then reset the count.
            if (errorCount > 0)
            {
                Trace.Write("Original minification produced ");
                Trace.Write(errorCount);
                Trace.WriteLine(" errors!");
                errorCount = 0;
            }

            // now run the output through another parser with no minify
            // settings -- there should DEFINITELY be no errors this time.
            parser = new JSParser();
            parser.CompilerError += (sender, ea) =>
            {
                if (ea.Error.IsError)
                {
                    Trace.WriteLine(ea.Error.ToString());
                    ++errorCount;
                }
            };
            block = parser.Parse(minifiedCode, new CodeSettings()
            {
                MinifyCode = false
            });

            Assert.IsTrue(errorCount == 0, "Parsing minified " + fileName + " produces errors!");
        }
Ejemplo n.º 8
0
        /// <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="codeSettings">code minification settings</param>
        /// <returns>minified Javascript</returns>
        public string MinifyJavaScript(string source, CodeSettings codeSettings)
        {
            // default is an empty string
            var crunched = string.Empty;

            // reset the errors builder
            m_errorList = new List <ContextError>();

            // create the parser and hook the engine error event
            var parser = new JSParser();

            parser.CompilerError += OnJavaScriptError;

            var sb = StringBuilderPool.Acquire();

            try
            {
                var preprocessOnly = codeSettings != null && 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 = this.FileName
                    }, codeSettings);
                    if (scriptBlock != null && !preprocessOnly)
                    {
                        // we'll return the crunched code
                        if (codeSettings != null && 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))
                            {
                                m_errorList.Add(new ContextError()
                                {
                                    Severity = 0,
                                    File     = this.FileName,
                                    Message  = CommonStrings.InvalidJSONOutput,
                                });
                            }
                        }
                        else
                        {
                            // just use the normal output visitor
                            OutputVisitor.Apply(stringWriter, scriptBlock, codeSettings);

                            // if we are asking for a symbols map, give it a chance to output a little something
                            // to the minified file.
                            if (codeSettings != null && codeSettings.SymbolsMap != null)
                            {
                                codeSettings.SymbolsMap.EndFile(stringWriter, codeSettings.LineTerminator);
                            }
                        }
                    }
                }

                crunched = sb.ToString();
            }
            catch (Exception e)
            {
                m_errorList.Add(new ContextError()
                {
                    Severity = 0,
                    File     = this.FileName,
                    Message  = e.Message,
                });
                throw;
            }
            finally
            {
                sb.Release();
            }

            return(crunched);
        }
Ejemplo n.º 9
0
        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");
        }