/// <summary> /// /// </summary> /// <param name="flags"></param> public void Indent(IndentFlags flags) { IndentStack.Push(flags); }
/// <summary> /// Formats the given JavaScript string. /// </summary> /// <param name="javascript">JavaScript script to format.</param> /// <returns>The formatted string.</returns> public string Format(string javascript) { Tokenizer tokenizer = new Tokenizer(javascript); bool endLine = false; // Cause new line bool isLineStart = true; // Current token is first on line Token peek; Builder.Capacity = javascript.Length; Indents.Reset(); ParenCount = 0; BracketCount = 0; LineFlags = LineFlags.None; NextLineFlags = LineFlags.None; // Process each token in input string while (tokenizer.GetToken()) { // Get current token Token token = tokenizer.CurrentToken; // Test for new line if (Builder.Length > 0) { isLineStart = endLine; if (endLine) { NewLine(); endLine = false; } } // Process this token switch (token.Type) { case TokenType.OpenBrace: if (!isLineStart) { if (FormatOptions.OpenBraceOnNewLine && Builder.Length > 0) { // Put open brace on new line NewLine(); } else { // Put open brace on same line if (token.PreviousType != TokenType.OpenParen && token.PreviousType != TokenType.OpenBracket) { Builder.Append(' '); } } } // Write token Builder.Append(token.Value); // Start new indent block peek = tokenizer.PeekToken(); if (peek.Type == TokenType.CloseBrace) { // Special handling for "{}" tokenizer.GetToken(); Builder.Append(tokenizer.CurrentToken.Value); peek = tokenizer.PeekToken(); if (peek.Type != TokenType.SemiColon && peek.Type != TokenType.Comma) { // Unindent if in conditional block without braces while (Indents.Current.HasFlag(IndentFlags.NoBraces)) { Indents.Unindent(); } endLine = true; } else if (peek.Type == TokenType.Comma) { // Normally, we insert a new line after // a closing brace and comma but not here tokenizer.GetToken(); Builder.Append(tokenizer.CurrentToken.Value); } } else { // Increase indentation IndentFlags flags = IndentFlags.None; if (LineFlags.HasFlag(LineFlags.DoKeyword)) { flags |= IndentFlags.DoBlock; } else if (LineFlags.HasFlag(LineFlags.CaseKeyword)) { flags |= IndentFlags.CaseBlock; } Indents.Indent(flags); endLine = true; } break; case TokenType.CloseBrace: // End indent block if (Indents.Current.HasFlag(IndentFlags.CaseBlock)) { // Extra unindent if in case/default block Indents.Unindent(); if (isLineStart) { Indents.StripTrailingIndent(Builder); } } // Unindent if in conditional block without braces while (Indents.Current.HasFlag(IndentFlags.NoBraces)) { Indents.Unindent(); } // Regular unindent Indents.Unindent(); if (isLineStart) { Indents.StripTrailingIndent(Builder); } else { NewLine(); } Builder.Append(token.Value); // Don't unindent without braces for catch/finally peek = tokenizer.PeekToken(); if (peek.Value != "catch" && peek.Value != "finally" && peek.Value != ":") { // Unindent if in conditional block without braces while (Indents.Current.HasFlag(IndentFlags.NoBraces)) { Indents.Unindent(); } } if (Indents.LastIndent.HasFlag(IndentFlags.DoBlock)) { LineFlags |= LineFlags.EndDoBlock; } // Insert new line after code block if (peek.Type != TokenType.SemiColon && peek.Type != TokenType.CloseParen && peek.Type != TokenType.CloseBracket && peek.Type != TokenType.Comma && peek.Type != TokenType.OpenParen && peek.Type != TokenType.Colon && !LineFlags.HasFlag(LineFlags.EndDoBlock)) { endLine = true; } break; case TokenType.OpenParen: if (!isLineStart && token.PreviousType != TokenType.OpenParen && token.PreviousType != TokenType.UnaryPrefix && token.PreviousType != TokenType.CloseBracket && token.PreviousType != TokenType.CloseParen && token.PreviousType != TokenType.CloseBrace && (token.PreviousType != TokenType.Symbol || (LineFlags.HasFlag(LineFlags.BlockKeyword) && ParenCount == 0))) { Builder.Append(' '); } Builder.Append(token.Value); ParenCount++; break; case TokenType.CloseParen: // Append closing parenthesis Builder.Append(token.Value); ParenCount = Math.Max(ParenCount - 1, 0); // Test for indent block start without braces if (ParenCount == 0 && LineFlags.HasFlag(LineFlags.BlockKeyword)) { // Examine next token peek = tokenizer.PeekToken(); if (peek.Type != TokenType.OpenBrace) { // Single line indent with no conditions or braces Indents.Indent(IndentFlags.NoBraces); endLine = true; } } break; case TokenType.OpenBracket: if (!isLineStart && token.PreviousType != TokenType.Symbol && token.PreviousType != TokenType.OpenParen && token.PreviousType != TokenType.CloseParen && token.PreviousType != TokenType.CloseBracket) { Builder.Append(' '); } // Special handling for JSON syntax? peek = tokenizer.PeekToken(); if (LineFlags.HasFlag(LineFlags.JsonColon) && peek.Type != TokenType.CloseBracket && peek.Type == TokenType.OpenBrace && ParenCount == 0) { if (FormatOptions.OpenBraceOnNewLine) { NewLine(); } Indents.Indent(IndentFlags.BracketBlock); endLine = true; } Builder.Append(token.Value); BracketCount++; break; case TokenType.CloseBracket: BracketCount = Math.Max(BracketCount - 1, 0); if (Indents.Current.HasFlag(IndentFlags.BracketBlock)) { Indents.Unindent(); if (isLineStart) { Indents.StripTrailingIndent(Builder); Builder.Append(token.Value); } else { NewLine(); Builder.Append(token.Value); } } else { Builder.Append(token.Value); } break; case TokenType.Symbol: bool isBlockKeyword = BlockKeywords.Contains(token.Value); // Special handling for function if (token.Value == "function" && FormatOptions.NewLineBetweenFunctions && token.PreviousType == TokenType.CloseBrace) { NewLine(); } // Special handling for else without if if (token.Value == "else" && tokenizer.PeekToken().Value != "if") { isBlockKeyword = true; } // Special handling for switch..case..default if (Indents.Current.HasFlag(IndentFlags.CaseBlock) && (token.Value == "case" || token.Value == "default")) { Indents.StripTrailingIndent(Builder); Indents.Unindent(); } if (ParenCount == 0 && isBlockKeyword) { // Keyword that starts an indented block if (!isLineStart) { Builder.Append(' '); } // Append this symbol Builder.Append(token.Value); if (!LineFlags.HasFlag(LineFlags.EndDoBlock) || token.Value != "while") { // Test for special-case blocks if (token.Value == "do") { LineFlags |= LineFlags.DoKeyword; } // Examine next token peek = tokenizer.PeekToken(); if (peek.Type == TokenType.OpenBrace || peek.Type == TokenType.OpenParen) { // Handle indentation at ')' or '{' LineFlags |= LineFlags.BlockKeyword; } else { // Single line indent with no conditions or braces IndentFlags flags = IndentFlags.NoBraces; if (LineFlags.HasFlag(LineFlags.DoKeyword)) { flags |= IndentFlags.DoBlock; } Indents.Indent(flags); endLine = true; } } } else { // All other symbols if (!isLineStart && token.PreviousType != TokenType.OpenParen && token.PreviousType != TokenType.OpenBracket && token.PreviousType != TokenType.UnaryPrefix && token.PreviousType != TokenType.Dot) { Builder.Append(' '); } // Flag line for case block if (token.Value == "case" || token.Value == "default") { LineFlags |= LineFlags.CaseKeyword; } Builder.Append(token.Value); } break; case TokenType.String: case TokenType.Number: case TokenType.RegEx: // Emit constant if (!isLineStart && token.PreviousType != TokenType.OpenParen && token.PreviousType != TokenType.OpenBracket && token.PreviousType != TokenType.UnaryPrefix) { Builder.Append(' '); } Builder.Append(token.Value); break; case TokenType.SemiColon: Builder.Append(token.Value); if (ParenCount == 0) { // Unindent if in conditional block without braces while (Indents.Current.HasFlag(IndentFlags.NoBraces)) { Indents.Unindent(); } if (Indents.LastIndent.HasFlag(IndentFlags.DoBlock)) { NextLineFlags |= LineFlags.EndDoBlock; } // Determine if end of single-line indent block peek = tokenizer.PeekToken(); if (peek.Type == TokenType.LineComment || peek.Type == TokenType.InlineComment) { bool newLine; if (peek.Type == TokenType.LineComment) { newLine = FormatOptions.NewLineBeforeLineComment; } else { newLine = FormatOptions.NewLineBeforeInlineComment; } tokenizer.GetToken(); if (newLine) { NewLine(); } else { Builder.Append(' '); } Builder.Append(tokenizer.CurrentToken.Value); } endLine = true; } break; case TokenType.Comma: Builder.Append(token.Value); // Append newline if it looks like JSON syntax if (token.PreviousType == TokenType.CloseBrace || (LineFlags.HasFlag(LineFlags.JsonColon) && ParenCount == 0 && BracketCount == 0 && Indents.Count > 0)) { endLine = true; } break; case TokenType.Colon: if (!LineFlags.HasFlag(LineFlags.CaseKeyword)) { // Standard colon handling if (!isLineStart && (LineFlags.HasFlag(LineFlags.QuestionMark) || token.PreviousType == TokenType.CloseBrace)) { Builder.Append(' '); } Builder.Append(token.Value); // May be JSON syntax if (!LineFlags.HasFlag(LineFlags.QuestionMark)) { LineFlags |= LineFlags.JsonColon; } } else { // Special handling for case and default Builder.Append(token.Value); Indents.Indent(IndentFlags.CaseBlock); endLine = true; } break; case TokenType.QuestionMark: LineFlags |= LineFlags.QuestionMark; if (!isLineStart) { Builder.Append(' '); } Builder.Append(token.Value); break; case TokenType.BinaryOperator: case TokenType.UnaryPrefix: if (!isLineStart && token.PreviousType != TokenType.OpenParen && token.PreviousType != TokenType.OpenBracket && token.PreviousType != TokenType.UnaryPrefix) { Builder.Append(' '); } Builder.Append(token.Value); break; case TokenType.LineComment: // Separate line comment from previous token if (!isLineStart) { if (FormatOptions.NewLineBeforeLineComment) { NewLine(); // Separate with new line } else { Builder.Append(' '); // Separate with space } } // Append comment Builder.Append(token.Value); // Line comment always followed by new line endLine = true; break; case TokenType.InlineComment: // Separate line comment from previous token if (!isLineStart) { if (FormatOptions.NewLineBeforeInlineComment) { NewLine(); // Separate with new line } else { Builder.Append(' '); // Separate with space } } // Append comment Builder.Append(token.Value); // New line after comment if (FormatOptions.NewLineAfterInlineComment) { endLine = true; } break; default: Builder.Append(token.Value); break; } } Builder.AppendLine(); return(Builder.ToString()); }