public string Compile(IEnumerable<string> files) { var blocks = new List<Block>(); // ReSharper disable once LoopCanBeConvertedToQuery foreach (string file in files) { var parser = new JSParser(File.ReadAllText(file)) { FileContext = file }; var block = parser.Parse(new CodeSettings { EvalTreatment = EvalTreatment.MakeImmediateSafe, PreserveImportantComments = false }); if (block != null) { blocks.Add(block); } } Block fst = blocks[0]; for (int i = 1; i < blocks.Count; i++) { fst.Append(blocks[i]); } string sequenceCode = fst.ToCode(); var minifier = new Minifier(); string compiled = minifier.MinifyJavaScript( sequenceCode, new CodeSettings { EvalTreatment = EvalTreatment.MakeImmediateSafe, PreserveImportantComments = false }); return compiled; }
string InsertModulePathIntoDefineCall(string moduleScript, string modulePath) { var parser = new JSParser(moduleScript); var sourceTree = parser.Parse(new CodeSettings()); sourceTree.Accept(new ModulePathInserter(modulePath)); return sourceTree.ToCode(); }
public static bool JavaScriptContainsTopLevelVariable(string javaScriptSource, string variableName) { var parser = new JSParser(javaScriptSource); var tree = parser.Parse(new CodeSettings()); var finder = new TopLevelVariableFinder(variableName); tree.Accept(finder); return finder.found; }
/// <summary> /// In addition to combining, also minifies the given Javascript. /// </summary> /// <returns>The combined and minified Javascript code for this bundle.</returns> public override string Combine() { var source = base.Combine(); var result = string.Empty; var errorLines = string.Empty; var hasError = false; try { var jsParser = new JSParser(source); var settings = new CodeSettings() { CombineDuplicateLiterals = true, OutputMode = OutputMode.SingleLine, RemoveUnneededCode = true, TermSemicolons = false, PreserveImportantComments = false, }; jsParser.CompilerError += delegate(object sender, JScriptExceptionEventArgs args) { // The 0 severity means errors. // We can safely ignore the rest. if (args.Error.Severity == 0) { hasError = true; errorLines += string.Format("\r\n/* Javascript parse error when processing the bundle.\r\nStart: line {0} column {1}, end: line {2} column {3}.\r\nError message: {4} */", args.Error.StartLine, args.Error.StartColumn, args.Error.EndLine, args.Error.EndColumn, args.Error.Message); } }; jsParser.UndefinedReference += delegate(object sender, UndefinedReferenceEventArgs args) { // Let's just ignore undefined references. }; var block = jsParser.Parse(settings); result = block.ToCode(); } catch (Exception exc) { hasError = true; Logger.WriteException(exc); } // If there were errors, use the non-minified version and append the errors to the bottom, // so that the portal builder can debug it. if (hasError) result = source + "\r\n\r\n" + errorLines; return result; }
static void Main(string[] args) { var input = File.ReadAllText("input.js"); Console.WriteLine("--- Raw ---"); Console.WriteLine(input); Console.WriteLine("\r\n"); Console.WriteLine("--- Minified ---"); Console.WriteLine(new Minifier().MinifyJavaScript(input)); Console.WriteLine("\r\n"); Console.WriteLine("--- AST ---"); var parser = new JSParser(); parser.CompilerError += (_, ea) => Console.WriteLine(ea.Error); var functions = parser.Parse(input); var functionContext = parser.Parse("var functionContext = {};"); new ObjectLiteralVisitor(functions).Visit(functionContext); OutputVisitor.Apply(Console.Out, functionContext, new CodeSettings() { MinifyCode = false, OutputMode = OutputMode.MultipleLines }); }
public static string InsertModulePathIntoDefineCall(string moduleScript, string modulePath) { var inserter = new ModulePathInserter(); var parser = new JSParser(moduleScript); var sourceTree = parser.Parse(new CodeSettings { MinifyCode = false }); sourceTree.Accept(inserter); if (inserter.insertionIndex > 0) { return moduleScript.Insert(inserter.insertionIndex, "\"" + modulePath + "\","); } else { return moduleScript; } }
public override CombinedFileResult Parse(params string[] files) { var combined = CombineFiles(files); var settings = new CodeSettings { }; var parser = new JSParser(combined); var block = parser.Parse(settings); var result = block.ToCode(); var key = GenerateKey(result); return new CombinedFileResult { Content = result, Key = key, }; }
Block ParseJavaScript(string source) { this.ErrorList = new List<ContextError>(); JSParser jSParser = new JSParser(); jSParser.CompilerError += new EventHandler<ContextErrorEventArgs>(this.OnJavaScriptError); try { var block = jSParser.Parse(source, CodeSettings); return block; } catch (Exception ex) { this.ErrorList.Add(new ContextError { Severity = 0, //File = this.FileName, Message = ex.Message }); throw; } }
private Parsed ParseFunction() { Parsed parsed = Parsed.False; if (CurrentTokenType == TokenType.Function) { bool crunchedRGB = false; if (CurrentTokenText == "rgb(") { // rgb function parsing bool useRGB = false; // converting to #rrggbb or #rgb IF we don't find any significant comments! // skip any space or comments int[] rgb = new int[3]; // we're going to be building up the rgb function just in case we need it StringBuilder sbRGB = new StringBuilder(); sbRGB.Append("rgb("); string comments = NextSignificantToken(); if (comments.Length > 0) { // add the comments sbRGB.Append(comments); // and signal that we need to use the RGB function because of them useRGB = true; } for (int ndx = 0; ndx < 3; ++ndx) { // if this isn't the first number, we better find a comma separator if (ndx > 0) { if (CurrentTokenType != TokenType.Character || CurrentTokenText != ",") { ReportError(0, StringEnum.ExpectedComma, CurrentTokenText); } // add it to the rgb string builder sbRGB.Append(','); // skip to the next significant comments = NextSignificantToken(); if (comments.Length > 0) { // add the comments sbRGB.Append(comments); // and signal that we need to use the RGB function because of them useRGB = true; } } // although we ALLOW negative numbers here, we'll trim them // later. But in the mean time, save a negation flag. bool negateNumber = false; if (CurrentTokenType == TokenType.Character && CurrentTokenText == "-") { negateNumber = true; comments = NextSignificantToken(); if (comments.Length > 0) { // add the comments sbRGB.Append(comments); // and signal that we need to use the RGB function because of them useRGB = true; } } if (CurrentTokenType != TokenType.Number && CurrentTokenType != TokenType.Percentage) { ReportError(0, StringEnum.ExpectedRgbNumberOrPercentage, CurrentTokenText); } // we might adjust the value, so save the token text string tokenText = CurrentTokenText; if (CurrentTokenType == TokenType.Number) { // get the number value float numberValue = System.Convert.ToSingle(tokenText, CultureInfo.InvariantCulture) * (negateNumber ? -1 : 1); // make sure it's between 0 and 255 if (numberValue < 0) { tokenText = "0"; rgb[ndx] = 0; } else if (numberValue > 255) { tokenText = "255"; rgb[ndx] = 255; } else { rgb[ndx] = System.Convert.ToInt32(numberValue); } } else { // percentage float percentageValue = System.Convert.ToSingle(tokenText.Substring(0, tokenText.Length - 1), CultureInfo.InvariantCulture) * (negateNumber ? -1 : 1); if (percentageValue < 0) { tokenText = "0%"; rgb[ndx] = 0; } else if (percentageValue > 100) { tokenText = "100%"; rgb[ndx] = 255; } else { rgb[ndx] = System.Convert.ToInt32(percentageValue * 255 / 100); } } // add the number to the rgb string builder sbRGB.Append(tokenText); // skip to the next significant comments = NextSignificantToken(); if (comments.Length > 0) { // add the comments sbRGB.Append(comments); // and signal that we need to use the RGB function because of them useRGB = true; } } if (useRGB) { // something prevented us from collapsing the rgb function // just output the rgb function we've been building up Append(sbRGB.ToString()); } else { // we can collapse it to either #rrggbb or #rgb // calculate the full hex string and crunch it string fullCode = string.Format(CultureInfo.InvariantCulture, "#{0:x2}{1:x2}{2:x2}", rgb[0], rgb[1], rgb[2]); string hexString = CrunchHexColor(fullCode, Settings.ColorNames); Append(hexString); // set the flag so we know we don't want to add the closing paren crunchedRGB = true; } } else if (CurrentTokenText == "expression(") { AppendCurrent(); NextToken(); // for now, just echo out everything up to the matching closing paren, // taking into account that there will probably be other nested paren pairs. // The content of the expression is JavaScript, so we'd really // need a full-blown JS-parser to crunch it properly. Kinda scary. // Start the parenLevel at 0 because the "expression(" token contains the first paren. var jsBuilder = new StringBuilder(); int parenLevel = 0; while (!m_scanner.EndOfFile && (CurrentTokenType != TokenType.Character || CurrentTokenText != ")" || parenLevel > 0)) { if (CurrentTokenType == TokenType.Function) { // the function token INCLUDES the opening parenthesis, // so up the paren level whenever we find a function. // AND this includes the actual expression( token -- so we'll // hit this branch at the beginning. Make sure the parenLevel // is initialized to take that into account ++parenLevel; } else if (CurrentTokenType == TokenType.Character) { switch (CurrentTokenText) { case "(": // start a nested paren ++parenLevel; break; case ")": // end a nested paren // (we know it's nested because if it wasn't, we wouldn't // have entered the loop) --parenLevel; break; } } jsBuilder.Append(CurrentTokenText); NextToken(); } // create a JSParser object with the source we found, crunch it, and send // the minified script to the output var expressionCode = jsBuilder.ToString(); if (Settings.MinifyExpressions) { // we want to minify the javascript expressions. // create a JSParser object from the code we parsed. JSParser jsParser = new JSParser(expressionCode); // copy the file context jsParser.FileContext = this.FileContext; // hook the error handler and set the "contains errors" flag to false. // the handler will set the value to true if it encounters any errors jsParser.CompilerError += OnScriptError; m_expressionContainsErrors = false; // parse the source with default settings Block block = jsParser.Parse(null); // if we got back a parsed block and there were no errors, output the minified code. // if we didn't get back the block, or if there were any errors at all, just output // the raw expression source. if (block != null && !m_expressionContainsErrors) { Append(block.ToCode()); } else { Append(expressionCode); } } else { // we don't want to minify expression code for some reason. // just output the code exactly as we parsed it Append(expressionCode); } } else { // generic function parsing AppendCurrent(); SkipSpace(); if (ParseFunctionParameters() == Parsed.False) { ReportError(0, StringEnum.ExpectedExpression, CurrentTokenText); } } if (CurrentTokenType == TokenType.Character && CurrentTokenText == ")") { if (!crunchedRGB) { Append(')'); } SkipSpace(); } else { ReportError(0, StringEnum.UnexpectedToken, CurrentTokenText); } parsed = Parsed.True; } return parsed; }
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; }
static Block ParseJavaScript(IAsset asset) { var source = asset.OpenStream().ReadToEnd(); var parser = new JSParser(source); return parser.Parse(new CodeSettings()); }
/// <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 from the source string. // pass null for the assumed globals array var parser = new JSParser(source); // file context is a property on the parser parser.FileContext = FileName; // hook the engine error event parser.CompilerError += OnJavaScriptError; try { if (codeSettings != null && codeSettings.PreprocessOnly) { // just run through the preprocessor only crunched = parser.PreprocessOnly(codeSettings); } else { // parse the input var scriptBlock = parser.Parse(codeSettings); if (scriptBlock != null) { // 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. var sb = new StringBuilder(); using (var stringWriter = new StringWriter(sb, CultureInfo.InvariantCulture)) { if (!JSONOutputVisitor.Apply(stringWriter, scriptBlock)) { m_errorList.Add(new ContextError( true, 0, null, null, null, this.FileName, 0, 0, 0, 0, JScript.InvalidJSONOutput)); } } crunched = sb.ToString(); } else { // just use the normal output visitor crunched = scriptBlock.ToCode(); } } } } catch (Exception e) { m_errorList.Add(new ContextError( true, 0, null, null, null, this.FileName, 0, 0, 0, 0, e.Message)); throw; } return crunched; }
public string RunTest(string sourceCode) { JSParser jsParser = new JSParser(sourceCode); jsParser.CompilerError += OnCompilerError; // kick off the parsing CodeSettings codeSettings = new CodeSettings(); Block programBlock = jsParser.Parse(codeSettings); // return the crunched code return programBlock.ToCode(); }
/// <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); }
private void RunTest(CodeSettings settings) { var source = GetSource(".js"); var expected = GetExpected(".js"); settings = settings ?? new CodeSettings() { MinifyCode = false }; if (source.Length == expected.Length) { for (var ndx = 0; ndx < source.Length; ++ndx) { Trace.WriteLine(""); Trace.WriteLine("----------------------------------------------------------------------------"); Trace.WriteLine(""); // parse the source into an AST var parser = new JSParser(source[ndx]); var block = parser.Parse(settings); // there should only be one statement in the block if (block.Count == 1) { var expression = block[0]; // create the logical-not visitor on the expression var logicalNot = new Microsoft.Ajax.Utilities.LogicalNot(expression, parser); // get the original code var original = expression.ToCode(); Trace.Write("ORIGINAL EXPRESSION: "); Trace.WriteLine(original); // get the measured delta var measuredDelta = logicalNot.Measure(); // perform the logical-not operation logicalNot.Apply(); // get the resulting code -- should still be only one statement in the block var notted = block[0].ToCode(); Trace.Write("LOGICAL-NOT EXPRESSION: "); Trace.WriteLine(notted); Trace.Write("EXPECTED EXPRESSION: "); Trace.WriteLine(expected[ndx]); Trace.Write("DELTA: "); Trace.WriteLine(measuredDelta); // what's the actual difference var actualDelta = notted.Length - original.Length; Assert.IsTrue(actualDelta == measuredDelta, "Measurement was off; calculated {0} but was actually {1}", measuredDelta, actualDelta); Assert.IsTrue(string.CompareOrdinal(expected[ndx], notted) == 0, "Expected output is not the same!!!!"); } else { Assert.Fail(string.Format("Source line {0} parsed to more than one statement!", ndx + 1)); } } } else { Assert.Fail("Input and Expected files have different number of lines!"); } }