/// <summary>
        /// Produces code minifiction of JS content by using
        /// 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;

            var errorReporter = new MsAjaxJsErrorReporter();
            var errors        = new List <MinificationErrorInfo>();
            var warnings      = new List <MinificationErrorInfo>();

            var jsParserConfiguration = isInlineCode ? GetInlineJsParserSettings() : GetEmbeddedJsParserSettings();

            var jsParser = new JSParser
            {
                Settings = jsParserConfiguration
            };

            jsParser.CompilerError += errorReporter.JsMinificationErrorHandler;

            try
            {
                var stringBuilder = new StringBuilder();

                using (var stringWriter = new StringWriter(stringBuilder, CultureInfo.InvariantCulture))
                {
                    Block block = jsParser.Parse(content);
                    if (block != null)
                    {
                        if (jsParserConfiguration.Format == JavaScriptFormat.JSON)
                        {
                            // Use a JSON output visitor
                            if (!JSONOutputVisitor.Apply(stringWriter, block, jsParserConfiguration))
                            {
                                errors.Add(new MinificationErrorInfo(MsAjaxStrings.ErrorMessage_InvalidJsonOutput));
                            }
                        }
                        else
                        {
                            // Use normal output visitor
                            OutputVisitor.Apply(stringWriter, block, jsParserConfiguration);
                        }
                    }
                }

                newContent = stringBuilder.ToString();
            }
            finally
            {
                jsParser.CompilerError -= errorReporter.JsMinificationErrorHandler;
            }

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

            return(new CodeMinificationResult(newContent, errors, warnings));
        }
Example #2
0
        private void InnerMinify(IAsset asset, JSParser jsParser)
        {
            string newContent;
            string assetUrl = asset.Url;

            var documentContext = new DocumentContext(asset.Content)
            {
                FileContext = assetUrl
            };

            jsParser.CompilerError += ParserErrorHandler;

            try
            {
                var stringBuilder = new StringBuilder();

                using (var stringWriter = new StringWriter(stringBuilder, CultureInfo.InvariantCulture))
                {
                    Block block = jsParser.Parse(documentContext);
                    if (block != null)
                    {
                        if (_jsParserConfiguration.Format == JavaScriptFormat.JSON)
                        {
                            // Use a JSON output visitor
                            if (!JSONOutputVisitor.Apply(stringWriter, block, _jsParserConfiguration))
                            {
                                throw new MicrosoftAjaxParsingException(Strings.Minifiers_InvalidJsonOutput);
                            }
                        }
                        else
                        {
                            // Use normal output visitor
                            OutputVisitor.Apply(stringWriter, block, _jsParserConfiguration);
                        }
                    }
                }

                newContent = stringBuilder.ToString();
            }
            catch (MicrosoftAjaxParsingException e)
            {
                throw new AssetMinificationException(
                          string.Format(CoreStrings.Minifiers_MinificationSyntaxError,
                                        CODE_TYPE, assetUrl, MINIFIER_NAME, e.Message), e);
            }
            catch (Exception e)
            {
                throw new AssetMinificationException(
                          string.Format(CoreStrings.Minifiers_MinificationFailed,
                                        CODE_TYPE, assetUrl, MINIFIER_NAME, e.Message), e);
            }
            finally
            {
                jsParser.CompilerError -= ParserErrorHandler;
            }

            asset.Content  = newContent;
            asset.Minified = true;
        }
Example #3
0
        private void ProcessJavaScript(IList <InputGroup> inputGroups, SwitchParser switchParser, string outputPath, SymbolMap symbolMap, Encoding outputEncoding)
        {
            var        settings  = switchParser.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, switchParser.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 <= switchParser.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, switchParser.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();
                }
            }
        }
Example #4
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(
                m_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 SwitchParser();

            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 <ContextError>();
            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);

            Assert.IsTrue(CompareTextFiles(outputPath, expectedPath), "The expected output ({1}) and actual output ({0}) do not match!", outputPath, expectedPath);
            Assert.IsTrue(testPassed, "Test failed");
        }