Beispiel #1
0
        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);
        }
Beispiel #2
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 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));
        }
Beispiel #3
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");
        }
        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();
                }
            }
        }