SuperDesign/Source/RySmartEditor/API/JsBeautifier/Beautifier.cs

1644 lines
52 KiB
C#
Raw Normal View History

#region License
// MIT License
//
// Copyright (c) 2018 Denis Ivanov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#endregion
namespace Jsbeautifier
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
public class Beautifier
{
public Beautifier()
: this(new BeautifierOptions())
{
}
public Beautifier(BeautifierOptions opts)
{
Opts = opts;
BlankState();
}
public BeautifierOptions Opts { get; set; }
public BeautifierFlags Flags { get; set; }
private List<BeautifierFlags> FlagStore { get; set; }
private bool WantedNewline { get; set; }
private bool JustAddedNewline { get; set; }
private bool DoBlockJustClosed { get; set; }
private string IndentString { get; set; }
private string PreindentString { get; set; }
private string LastWord { get; set; }
private string LastType { get; set; }
private string LastText { get; set; }
private string LastLastText { get; set; }
private string Input { get; set; }
private List<string> Output { get; set; }
private char[] Whitespace { get; set; }
private string Wordchar { get; set; }
private string Digits { get; set; }
private string[] Punct { get; set; }
private string[] LineStarters { get; set; }
private int ParserPos { get; set; }
private int NNewlines { get; set; }
private void BlankState()
{
// internal flags
Flags = new BeautifierFlags("BLOCK");
FlagStore = new List<BeautifierFlags>();
WantedNewline = false;
JustAddedNewline = false;
DoBlockJustClosed = false;
if (Opts.IndentWithTabs)
{
IndentString = "\t";
}
else
{
IndentString = new string(Opts.IndentChar, (int)Opts.IndentSize);
}
PreindentString = "";
LastWord = ""; // last TK_WORD seen
LastType = "TK_START_EXPR"; // last token type
LastText = ""; // last token text
LastLastText = ""; // pre-last token text
Input = null;
Output = new List<string>(); // formatted javascript gets built here
Whitespace = new[] { '\n', '\r', '\t', ' ' };
Wordchar = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$";
Digits = "0123456789";
Punct = "+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= :: <?= <? ?> <%= <% %>".Split(' ');
// Words which always should start on a new line
LineStarters = "continue,try,throw,return,var,if,switch,case,default,for,while,break,function".Split(',');
SetMode("BLOCK");
ParserPos = 0;
}
private void SetMode(string mode)
{
var prev = new BeautifierFlags("BLOCK");
if (Flags != null)
{
FlagStore.Add(Flags);
prev = Flags;
}
Flags = new BeautifierFlags(mode);
if (FlagStore.Count == 1)
{
Flags.IndentationLevel = 0;
}
else
{
Flags.IndentationLevel = prev.IndentationLevel;
if (prev.VarLine && prev.VarLineReindented)
{
Flags.IndentationLevel = Flags.IndentationLevel + 1;
}
}
Flags.PreviousMode = prev.Mode;
}
public string Beautify(string s, BeautifierOptions opts = null)
{
if (opts != null)
{
Opts = opts;
}
BlankState();
while (s.Length != 0 && (s[0] == ' ' || s[0] == '\t'))
{
PreindentString += s[0];
s = s.Remove(0, 1);
}
Input = s;
ParserPos = 0;
while (true)
{
var token = GetNextToken();
// print (token_text, token_type, self.flags.mode)
var tokenText = token.Item1;
var tokenType = token.Item2;
if (tokenType == "TK_EOF")
{
break;
}
var handlers = new Dictionary<string, Action<string>> {
{ "TK_START_EXPR", HandleStartExpr },
{ "TK_END_EXPR", HandleEndExpr },
{ "TK_START_BLOCK", HandleStartBlock },
{ "TK_END_BLOCK", HandleEndBlock },
{ "TK_WORD", HandleWord },
{ "TK_SEMICOLON", HandleSemicolon },
{ "TK_STRING", HandleString },
{ "TK_EQUALS", HandleEquals },
{ "TK_OPERATOR", HandleOperator },
{ "TK_COMMA", HandleComma },
{ "TK_BLOCK_COMMENT", HandleBlockComment },
{ "TK_INLINE_COMMENT", HandleInlineComment },
{ "TK_COMMENT", HandleComment },
{ "TK_DOT", HandleDot },
{ "TK_UNKNOWN", HandleUnknown }
};
handlers[tokenType](tokenText);
if (tokenType != "TK_INLINE_COMMENT")
{
LastLastText = LastText;
LastType = tokenType;
LastText = tokenText;
}
}
var regex = new Regex(@"[\n ]+$");
var sweetCode = PreindentString + regex.Replace(string.Concat(Output), "", 1);
return sweetCode;
}
private void TrimOutput(bool eatNewlines = false)
{
while (Output.Count != 0 &&
(Output[Output.Count - 1] == " " ||
Output[Output.Count - 1] == IndentString ||
Output[Output.Count - 1] == PreindentString ||
(eatNewlines && (Output[Output.Count - 1] == "\n" || Output[Output.Count - 1] == "\r"))))
{
Output.RemoveAt(Output.Count - 1);
}
}
private bool IsSpecialWord(string s)
{
return s == "case" || s == "return" || s == "do" || s == "if" || s == "throw" || s == "else";
}
private bool IsArray(string mode)
{
return mode == "[EXPRESSION]" || mode == "[INDENTED-EXPRESSION]";
}
private bool IsExpression(string mode)
{
return mode == "[EXPRESSION]" ||
mode == "[INDENTED-EXPRESSION]" ||
mode == "(EXPRESSION)" ||
mode == "(FOR-EXPRESSION)" ||
mode == "(COND-EXPRESSION)";
}
private void AllowWrapOrPreservedNewline(string tokenText, bool forceLinwrap = false)
{
if (Opts.WrapLineLength > 0 && !forceLinwrap)
{
var startLine = Output.Count - 1;
while (startLine >= 0)
{
if (Output[startLine] == "\n")
{
break;
}
startLine--;
}
startLine++;
if (startLine < Output.Count)
{
var slice = new string[Output.Count - startLine];
Output.CopyTo(startLine, slice, 0, slice.Length);
var currentLine = string.Concat(slice);
if (currentLine.Length + tokenText.Length >= Opts.WrapLineLength)
{
forceLinwrap = true;
}
}
}
if (!JustAddedNewline && ((Opts.PreserveNewlines && WantedNewline) || forceLinwrap))
{
AppendNewline(true, false);
AppendIndentString();
WantedNewline = false;
}
}
private void AppendNewline(bool ignoreRepeated = true, bool resetStatementFlags = true)
{
if (Opts.KeepArrayIndentation && IsArray(Flags.Mode))
{
return;
}
if (resetStatementFlags)
{
Flags.IfLine = false;
Flags.ChainExtraIndentation = 0;
}
TrimOutput();
if (Output.Count == 0)
{
return;
}
if (Output[Output.Count - 1] != "\n" || !ignoreRepeated)
{
JustAddedNewline = true;
Output.Add("\n");
}
if (!string.IsNullOrEmpty(PreindentString))
{
Output.Add(PreindentString);
}
foreach (var i in Enumerable.Range(0, Flags.IndentationLevel + Flags.ChainExtraIndentation))
{
AppendIndentString();
}
if (Flags.VarLine && Flags.VarLineReindented)
{
AppendIndentString();
}
}
private void AppendIndentString()
{
if (LastText != "")
{
Output.Add(IndentString);
}
}
private void Append(string s)
{
if (s == " ")
{
// do not add just a single space after the // comment, ever
if (LastType == "TK_COMMENT")
{
AppendNewline();
return;
}
// make sure only single space gets drawn
if (Output.Count != 0 &&
Output[Output.Count - 1] != " " &&
Output[Output.Count - 1] != "\n" &&
Output[Output.Count - 1] != IndentString)
{
Output.Add(" ");
}
}
else
{
JustAddedNewline = false;
Output.Add(s);
}
}
private void Indent()
{
Flags.IndentationLevel = Flags.IndentationLevel + 1;
}
private void RemoveIndent()
{
if (Output.Count != 0 &&
(Output[Output.Count - 1] == IndentString ||
Output[Output.Count - 1] == PreindentString))
{
Output.RemoveAt(Output.Count - 1);
}
}
private void RestoreMode()
{
DoBlockJustClosed = Flags.Mode == "DO_BLOCK";
if (FlagStore.Count > 0)
{
var mode = Flags.Mode;
Flags = FlagStore[FlagStore.Count - 1];
FlagStore.RemoveAt(FlagStore.Count - 1);
Flags.PreviousMode = mode;
}
}
private Tuple<string, string> GetNextToken()
{
NNewlines = 0;
if (ParserPos >= Input.Length)
{
return new Tuple<string, string>("", "TK_EOF");
}
WantedNewline = false;
var c = Input[ParserPos];
ParserPos += 1;
var keepWhitespace = Opts.KeepArrayIndentation && IsArray(Flags.Mode);
if (keepWhitespace)
{
var whitespaceCount = 0;
while (Whitespace.Contains(c))
{
if (c == '\n')
{
TrimOutput();
Output.Add("\n");
JustAddedNewline = true;
whitespaceCount = 0;
}
else if (c == '\t')
{
whitespaceCount += 4;
}
else if (c == '\r')
{
}
else
{
whitespaceCount += 1;
}
if (ParserPos >= Input.Length)
{
return new Tuple<string, string>("", "TK_EOF");
}
c = Input[ParserPos];
ParserPos += 1;
}
if (JustAddedNewline)
{
foreach (var i in Enumerable.Range(0, whitespaceCount))
{
Output.Add(" ");
}
}
}
else // not keep_whitespace
{
while (Whitespace.Contains(c))
{
if (c == '\n')
{
if (Opts.MaxPreserveNewlines == 0 || Opts.MaxPreserveNewlines > NNewlines)
{
NNewlines += 1;
}
}
if (ParserPos >= Input.Length)
{
return new Tuple<string, string>("", "TK_EOF");
}
c = Input[ParserPos];
ParserPos += 1;
}
if (Opts.PreserveNewlines && NNewlines > 1)
{
foreach (var i in Enumerable.Range(0, NNewlines))
{
AppendNewline(i == 0);
JustAddedNewline = true;
}
}
WantedNewline = NNewlines > 0;
}
var cc = c.ToString();
if (Wordchar.Contains(c))
{
if (ParserPos < Input.Length)
{
cc = c.ToString();
while (Wordchar.Contains(Input[ParserPos]))
{
cc += Input[ParserPos];
ParserPos += 1;
if (ParserPos == Input.Length)
break;
}
}
// small and surprisingly unugly hack for 1E-10 representation
if (ParserPos != Input.Length && "+-".Contains(Input[ParserPos]) && Regex.IsMatch(cc, "^[0-9]+[Ee]$"))
{
var sign = Input[ParserPos];
ParserPos++;
var t = GetNextToken();
cc += sign + t.Item1;
return new Tuple<string, string>(cc, "TK_WORD");
}
if (cc == "in") // in is an operator, need to hack
{
return new Tuple<string, string>(cc, "TK_OPERATOR");
}
if (WantedNewline
&& LastType != "TK_OPERATOR"
&& LastType != "TK_EQUALS"
&& !Flags.IfLine
&& (Opts.PreserveNewlines || LastText != "var"))
{
AppendNewline();
}
return new Tuple<string, string>(cc, "TK_WORD");
}
if ("([".Contains(c))
{
return new Tuple<string, string>(c.ToString(), "TK_START_EXPR");
}
if (")]".Contains(c))
{
return new Tuple<string, string>(c.ToString(), "TK_END_EXPR");
}
if (c == '{')
{
return new Tuple<string, string>(c.ToString(), "TK_START_BLOCK");
}
if (c == '}')
{
return new Tuple<string, string>(c.ToString(), "TK_END_BLOCK");
}
if (c == ';')
{
return new Tuple<string, string>(c.ToString(), "TK_SEMICOLON");
}
if (c == '/')
{
var comment = "";
var inlineComment = true;
if (Input[ParserPos] == '*') // peek /* .. */ comment
{
ParserPos += 1;
if (ParserPos < Input.Length)
{
while (!(Input[ParserPos] == '*' && ParserPos + 1 < Input.Length && Input[ParserPos + 1] == '/') &&
ParserPos < Input.Length)
{
c = Input[ParserPos];
comment += c;
if ("\r\n".Contains(c))
{
inlineComment = false;
}
ParserPos += 1;
if (ParserPos >= Input.Length)
{
break;
}
}
}
ParserPos += 2;
if (inlineComment && NNewlines == 0)
{
return new Tuple<string, string>("/*" + comment + "*/", "TK_INLINE_COMMENT");
}
return new Tuple<string, string>("/*" + comment + "*/", "TK_BLOCK_COMMENT");
}
if (Input[ParserPos] == '/') // peek // comment
{
comment = c.ToString();
while (!("\r\n").Contains(Input[ParserPos]))
{
comment += Input[ParserPos];
ParserPos += 1;
if (ParserPos >= Input.Length)
{
break;
}
}
if (WantedNewline)
{
AppendNewline();
}
return new Tuple<string, string>(comment, "TK_COMMENT");
}
}
if (c == '\'' || c == '"' ||
(c == '/' &&
((LastType == "TK_WORD" && IsSpecialWord(LastText)) ||
(LastType == "TK_END_EXPR" && (Flags.PreviousMode == "(FOR-EXPRESSION)" || Flags.PreviousMode == "(COND-EXPRESSION)")) ||
((new[] { "TK_COMMENT", "TK_START_EXPR", "TK_START_BLOCK", "TK_END_BLOCK", "TK_OPERATOR", "TK_EQUALS", "TK_EOF", "TK_SEMICOLON", "TK_COMMA" }).Contains(LastType)))))
{
var sep = c;
var esc = false;
var esc1 = 0;
var esc2 = 0;
var resultingString = c.ToString();
if (ParserPos < Input.Length)
{
if (sep == '/')
{
// handle regexp
var inCharClass = false;
while (esc || inCharClass || Input[ParserPos] != sep)
{
resultingString += Input[ParserPos];
if (!esc)
{
esc = Input[ParserPos] == '\\';
if (Input[ParserPos] == '[')
{
inCharClass = true;
}
else if (Input[ParserPos] == ']')
{
inCharClass = false;
}
}
else
{
esc = false;
}
ParserPos += 1;
if (ParserPos >= Input.Length)
{
// ncomplete regex when end-of-file reached
// bail out with what has received so far
return new Tuple<string, string>(resultingString, "TK_STRING");
}
}
}
else
{
// handle string
while (esc || Input[ParserPos] != sep)
{
resultingString += Input[ParserPos];
if (esc1 != 0 && esc1 >= esc2)
{
if (!int.TryParse(new string(resultingString.Skip(Math.Max(0, resultingString.Count() - esc2)).Take(esc2).ToArray()), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out esc1))
esc1 = 0;
if (esc1 != 0 && esc1 >= 0x20 && esc1 <= 0x7e)
{
// FIXME
resultingString = new string(resultingString.Take(2 + esc2).ToArray());
if ((char) esc1 == sep || (char) esc1 == '\\')
{
resultingString += '\\';
}
resultingString += (char)esc1;
}
esc1 = 0;
}
if (esc1 != 0)
{
++esc1;
}
else if (!esc)
{
esc = Input[ParserPos] == '\\';
}
else
{
esc = false;
// TODO
//if (/*this.Opts.UnescapeStrings*/false)
/*{
if (this.Input[this.ParserPos] == 'x')
{
++esc1;
esc2 = 2;
}
else if (this.Input[this.ParserPos] == 'u')
{
++esc1;
esc2 = 4;
}
}*/
}
ParserPos += 1;
if (ParserPos >= Input.Length)
{
// incomplete string when end-of-file reached
// bail out with what has received so far
return new Tuple<string, string>(resultingString, "TK_STRING");
}
}
}
}
ParserPos += 1;
resultingString += sep;
if (sep == '/')
{
// regexps may have modifiers /regexp/MOD, so fetch those too
while (ParserPos < Input.Length && Wordchar.Contains(Input[ParserPos]))
{
resultingString += Input[ParserPos];
ParserPos += 1;
}
}
return new Tuple<string, string>(resultingString, "TK_STRING");
}
if (c == '#')
{
var resultString = "";
// she-bang
if (Output.Count == 0 && Input.Length > 1 && Input[ParserPos] == '!')
{
resultString = c.ToString();
while (ParserPos < Input.Length && c != '\n')
{
c = Input[ParserPos];
resultString += c;
ParserPos += 1;
}
Output.Add(resultString.Trim() + '\n');
AppendNewline();
return GetNextToken();
}
// Spidermonkey-specific sharp variables for circular references
// https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
// http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
var sharp = "#";
if (ParserPos < Input.Length && Digits.Contains(Input[ParserPos]))
{
while (true)
{
c = Input[ParserPos];
sharp += c;
ParserPos += 1;
if (ParserPos >= Input.Length || c == '#' || c == '=')
{
break;
}
}
}
if (c == '#' || ParserPos >= Input.Length)
{
// pass
}
else if (Input[ParserPos] == '[' && Input[ParserPos + 1] == ']')
{
sharp += "[]";
ParserPos += 2;
}
else if (Input[ParserPos] == '{' && Input[ParserPos + 1] == '}')
{
sharp += "{}";
ParserPos += 2;
}
return new Tuple<string, string>(sharp, "TK_WORD");
}
if (c == '<' && Input.Substring(ParserPos - 1, Math.Min(4, Input.Length - ParserPos + 1)) == "<!--")
{
ParserPos += 3;
var ss = "<!--";
while (ParserPos < Input.Length && Input[ParserPos] != '\n')
{
ss += Input[ParserPos];
ParserPos += 1;
}
Flags.InHtmlComment = true;
return new Tuple<string, string>(ss, "TK_COMMENT");
}
if (c == '-' && Flags.InHtmlComment && Input.Substring(ParserPos - 1, 3) == "-->")
{
Flags.InHtmlComment = false;
ParserPos += 2;
if (WantedNewline)
{
AppendNewline();
}
return new Tuple<string, string>("-->", "TK_COMMENT");
}
if (c == '.')
{
return new Tuple<string, string>(".", "TK_DOT");
}
if (Punct.Contains(c.ToString()))
{
var ss = c.ToString();
while (ParserPos < Input.Length && Punct.Contains(ss + Input[ParserPos]))
{
ss += Input[ParserPos];
ParserPos += 1;
if (ParserPos >= Input.Length)
{
break;
}
}
if (ss == "=")
{
return new Tuple<string, string>("=", "TK_EQUALS");
}
if (ss == ",")
{
return new Tuple<string, string>(",", "TK_COMMA");
}
return new Tuple<string, string>(ss, "TK_OPERATOR");
}
return new Tuple<string, string>(c.ToString(), "TK_UNKNOWN");
}
private void HandleStartExpr(string tokenText)
{
if (tokenText == "[")
{
if (LastType == "TK_WORD" || LastText == ")")
{
if (LineStarters.Contains(LastText))
{
Append(" ");
}
SetMode("(EXPRESSION)");
Append(tokenText);
return;
}
if (Flags.Mode == "[EXPRESSION]" || Flags.Mode == "[INDENTED-EXPRESSION]")
{
if (LastLastText == "]" && LastText == ",")
{
// # ], [ goes to a new line
if (Flags.Mode == "[EXPRESSION]")
{
Flags.Mode = "[INDENTED-EXPRESSION]";
if (!Opts.KeepArrayIndentation)
{
Indent();
}
}
SetMode("[EXPRESSION]");
if (!Opts.KeepArrayIndentation)
{
AppendNewline();
}
}
else if (LastText == "[")
{
if (Flags.Mode == "[EXPRESSION]")
{
Flags.Mode = "[INDENTED-EXPRESSION]";
if (!Opts.KeepArrayIndentation)
{
Indent();
}
}
SetMode("[EXPRESSION]");
if (!Opts.KeepArrayIndentation)
{
AppendNewline();
}
}
else
{
SetMode("[EXPRESSION]");
}
}
else
{
SetMode("[EXPRESSION]");
}
}
else
{
if (LastText == "for")
{
SetMode("(FOR-EXPRESSION)");
}
else if (LastText == "if" || LastText == "while")
{
SetMode("(COND-EXPRESSION)");
}
else
{
SetMode("(EXPRESSION)");
}
}
if (LastText == ";" || LastType == "TK_START_BLOCK")
{
AppendNewline();
}
else if (LastType == "TK_END_EXPR" || LastType == "TK_START_EXPR" || LastType == "TK_END_BLOCK" || LastText == ".")
{
// do nothing on (( and )( and ][ and ]( and .(
if (WantedNewline)
{
AppendNewline();
}
}
else if (LastType != "TK_WORD" && LastType != "TK_OPERATOR")
{
Append(" ");
}
else if (LastWord == "function" || LastWord == "typeof")
{
// function() vs function (), typeof() vs typeof ()
if (Opts.JslintHappy)
{
Append(" ");
}
}
else if (LineStarters.Contains(LastText) || LastText == "catch")
{
Append(" ");
}
if (LastType == "TK_EQUALS" ||
LastType == "TK_OPERATOR")
{
if (Flags.Mode != "OJBECT")
{
AllowWrapOrPreservedNewline(tokenText);
}
}
Append(tokenText);
}
private void HandleEndExpr(string tokenText)
{
if (tokenText == "]")
{
if (Opts.KeepArrayIndentation)
{
if (LastText == "}")
{
RemoveIndent();
Append(tokenText);
RestoreMode();
return;
}
}
else if (Flags.Mode == "[INDENTED-EXPRESSION]")
{
if (LastText == "]")
{
RestoreMode();
AppendNewline();
Append(tokenText);
return;
}
}
}
RestoreMode();
Append(tokenText);
}
private void HandleStartBlock(string tokenText)
{
if (LastWord == "do")
{
SetMode("DO_BLOCK");
}
else
{
SetMode("BLOCK");
}
if (Opts.BraceStyle == BraceStyle.Expand)
{
if (LastType != "TK_OPERATOR")
{
if (LastType == "TK_EQUALS" ||
(IsSpecialWord(LastText) && LastText != "else"))
{
Append(" ");
}
else
{
AppendNewline();
}
}
Append(tokenText);
Indent();
}
else
{
if (LastType != "TK_OPERATOR" && LastType != "TK_START_EXPR")
{
if (LastType == "TK_START_BLOCK")
{
AppendNewline();
}
else
{
Append(" ");
}
}
else
{
// if TK_OPERATOR or TK_START_EXPR
if (IsArray(Flags.PreviousMode) && LastText == ",")
{
if (LastLastText == "}")
{
Append(" ");
}
else
{
AppendNewline();
}
}
}
Indent();
Append(tokenText);
}
}
private void HandleEndBlock(string tokenText)
{
RestoreMode();
if (Opts.BraceStyle == BraceStyle.Expand)
{
if (LastText != "{")
{
AppendNewline();
}
}
else
{
if (LastType == "TK_START_BLOCK")
{
if (JustAddedNewline)
{
RemoveIndent();
}
else
{
TrimOutput();
}
}
else
{
if (IsArray(Flags.Mode) && Opts.KeepArrayIndentation)
{
Opts.KeepArrayIndentation = false;
AppendNewline();
Opts.KeepArrayIndentation = true;
}
else
{
AppendNewline();
}
}
}
Append(tokenText);
}
private void HandleWord(string tokenText)
{
if (DoBlockJustClosed)
{
Append(" ");
Append(tokenText);
Append(" ");
DoBlockJustClosed = false;
return;
}
if (tokenText == "function")
{
if (Flags.VarLine && LastText != "=")
{
Flags.VarLineReindented = !Opts.KeepFunctionIndentation;
}
if ((JustAddedNewline || LastText == ";") && LastText != "{")
{
// make sure there is a nice clean space of at least one blank line
// before a new function definition
var haveNewlines = NNewlines;
if (!JustAddedNewline)
{
haveNewlines = 0;
}
if (!Opts.PreserveNewlines)
{
haveNewlines = 1;
}
for (var i = 0; i < (2 - haveNewlines); ++i)
{
AppendNewline(false);
}
}
if ((LastText == "get" || LastText == "set" || LastText == "new") || LastType == "TK_WORD")
{
Append(" ");
}
if (LastType == "TK_WORD")
{
if (LastText == "get" || LastText == "set" || LastText == "new" || LastText == "return")
{
Append(" ");
}
else
{
AppendNewline();
}
}
else if (LastType == "TK_OPERATOR" || LastText == "=")
{
// foo = function
Append(" ");
}
else if (IsExpression(Flags.Mode))
{
// (function
}
else
{
AppendNewline();
}
Append("function");
LastWord = "function";
return;
}
if (tokenText == "case" || (tokenText == "default" && Flags.InCaseStatement))
{
AppendNewline();
if (Flags.CaseBody)
{
RemoveIndent();
Flags.CaseBody = false;
Flags.IndentationLevel -= 1;
}
Append(tokenText);
Flags.InCase = true;
Flags.InCaseStatement = true;
return;
}
var prefix = "NONE";
if (LastType == "TK_END_BLOCK")
{
if (tokenText != "else" && tokenText != "catch" && tokenText != "finally")
{
prefix = "NEWLINE";
}
else
{
if (Opts.BraceStyle == BraceStyle.Expand || Opts.BraceStyle == BraceStyle.EndExpand)
{
prefix = "NEWLINE";
}
else
{
prefix = "SPACE";
Append(" ");
}
}
}
else if (LastType == "TK_SEMICOLON" && (Flags.Mode == "BLOCK" || Flags.Mode == "DO_BLOCK"))
{
prefix = "NEWLINE";
}
else if (LastType == "TK_SEMICOLON" && IsExpression(Flags.Mode))
{
prefix = "SPACE";
}
else if (LastType == "TK_STRING")
{
prefix = "NEWLINE";
}
else if (LastType == "TK_WORD")
{
if (LastText == "else")
{
// eat newlines between ...else *** some_op...
// won't preserve extra newlines in this place (if any), but don't care that much
TrimOutput(true);
}
prefix = "SPACE";
}
else if (LastType == "TK_START_BLOCK")
{
prefix = "NEWLINE";
}
else if (LastType == "TK_END_EXPR")
{
Append(" ");
prefix = "NEWLINE";
}
if (Flags.IfLine && LastType == "TK_END_EXPR")
{
Flags.IfLine = false;
}
if (LastType == "TK_COMMA" ||
LastType == "TK_START_EXPR" ||
LastType == "TK_EQUALS" ||
LastType == "TK_OPERATOR")
{
if (Flags.Mode != "OBJECT")
{
AllowWrapOrPreservedNewline(tokenText);
}
}
if (LineStarters.Contains(tokenText))
{
if (LastText == "else")
{
prefix = "SPACE";
}
else
{
prefix = "NEWLINE";
}
}
if (tokenText == "else" || tokenText == "catch" || tokenText == "finally")
{
if (LastType != "TK_END_BLOCK" || Opts.BraceStyle == BraceStyle.Expand ||
Opts.BraceStyle == BraceStyle.EndExpand)
{
AppendNewline();
}
else
{
TrimOutput(true);
Append(" ");
}
}
else if (prefix == "NEWLINE")
{
if (IsSpecialWord(LastText))
{
// no newline between return nnn
Append(" ");
}
else if (LastType != "TK_END_EXPR")
{
if ((LastType != "TK_START_EXPR" || tokenText != "var") && LastText != ":")
{
// no need to force newline on VAR -
// for (var x = 0...
if (tokenText == "if" && LastWord == "else" && LastText != "{")
{
Append(" ");
}
else
{
Flags.VarLine = false;
Flags.VarLineReindented = false;
AppendNewline();
}
}
}
else if (LineStarters.Contains(tokenText) && LastText != ")")
{
Flags.VarLine = false;
Flags.VarLineReindented = false;
AppendNewline();
}
}
else if (IsArray(Flags.Mode) && LastText == "," && LastLastText == "}")
{
AppendNewline(); //}, in lists get a newline
}
else if (prefix == "SPACE")
{
Append(" ");
}
Append(tokenText);
LastWord = tokenText;
if (tokenText == "var")
{
Flags.VarLine = true;
Flags.VarLineReindented = false;
Flags.VarLineTainted = false;
}
if (tokenText == "if")
{
Flags.IfLine = true;
}
if (tokenText == "else")
{
Flags.IfLine = false;
}
}
private void HandleSemicolon(string tokenText)
{
Append(tokenText);
Flags.VarLine = false;
Flags.VarLineReindented = false;
if (Flags.Mode == "OBJECT")
{
// OBJECT mode is weird and doesn't get reset too well.
Flags.Mode = "BLOCK";
}
}
private void HandleString(string tokenText)
{
if (LastType == "TK_END_EXPR" &&
(Flags.PreviousMode == "(COND-EXPRESSION)" || Flags.PreviousMode == "(FOR-EXPRESSION)"))
{
Append(" ");
}
else if (LastType == "TK_WORD")
{
Append(" ");
}
else if (LastType == "TK_COMMA" ||
LastType == "TK_START_EXPR" ||
LastType == "TK_EQUALS" ||
LastType == "TK_OPERATOR")
{
if (Flags.Mode != "OBJECT")
{
AllowWrapOrPreservedNewline(tokenText);
}
}
else
{
AppendNewline();
}
Append(tokenText);
}
private void HandleEquals(string tokenText)
{
if (Flags.VarLine)
{
// just got an '=' in a var-line, different line breaking rules will apply
Flags.VarLineTainted = true;
}
Append(" ");
Append(tokenText);
Append(" ");
}
private void HandleComma(string tokenText)
{
if (LastType == "TK_COMMENT")
{
AppendNewline();
}
if (Flags.VarLine)
{
if (IsExpression(Flags.Mode) || LastType == "TK_END_BLOCK")
{
// do not break on comma, for ( var a = 1, b = 2
Flags.VarLineTainted = false;
}
if (Flags.VarLineTainted)
{
Append(tokenText);
Flags.VarLineReindented = true;
Flags.VarLineTainted = false;
AppendNewline();
return;
}
else
Flags.VarLineTainted = false;
Append(tokenText);
Append(" ");
return;
}
if (LastType == "TK_END_BLOCK" && Flags.Mode != "(EXPRESSION)")
{
Append(tokenText);
if (Flags.Mode == "OBJECT" && LastText == "}")
{
AppendNewline();
}
else
{
Append(" ");
}
}
else
{
if (Flags.Mode == "OBJECT")
{
Append(tokenText);
AppendNewline();
}
else
{
// EXPR or DO_BLOCK
Append(tokenText);
Append(" ");
}
}
}
private void HandleOperator(string tokenText)
{
var spaceBefore = true;
var spaceAfter = true;
if (IsSpecialWord(LastText))
{
// return had a special handling in TK_WORD
Append(" ");
Append(tokenText);
return;
}
// hack for actionscript's import .*;
if (tokenText == "*" && LastType == "TK_DOT" && !LastLastText.All(char.IsDigit))
{
Append(tokenText);
return;
}
if (tokenText == ":" && Flags.InCase)
{
Flags.CaseBody = true;
Indent();
Append(tokenText);
AppendNewline();
Flags.InCase = true;
return;
}
if (tokenText == "::")
{
// no spaces around the exotic namespacing syntax operator
Append(tokenText);
return;
}
if ((tokenText == "++" || tokenText == "--" || tokenText == "!") || (tokenText == "+" || tokenText == "-") &&
((LastType == "TK_START_BLOCK" || LastType == "TK_START_EXPR" || LastType == "TK_EQUALS" || LastType == "TK_OPERATOR") ||
(LineStarters.Contains(LastText) || LastText == ",")))
{
spaceBefore = false;
spaceAfter = false;
if (LastText == ";" && IsExpression(Flags.Mode))
{
// for (;; ++i)
// ^^
spaceBefore = true;
}
if (LastText == "TK_WORD" && LineStarters.Contains(LastText))
{
spaceBefore = true;
}
if (Flags.Mode == "BLOCK" && (LastText == ";" || LastText == "{"))
{
// { foo: --i }
// foo(): --bar
AppendNewline();
}
}
else if (tokenText == ":")
{
if (Flags.TernaryDepth == 0)
{
if (Flags.Mode == "BLOCK")
{
Flags.Mode = "OBJECT";
}
spaceBefore = false;
}
else
{
Flags.TernaryDepth -= 1;
}
}
else if (tokenText == "?")
{
Flags.TernaryDepth += 1;
}
if (spaceBefore)
{
Append(" ");
}
Append(tokenText);
if (spaceAfter)
{
Append(" ");
}
}
private void HandleBlockComment(string tokenText)
{
var lines = tokenText.Replace("\r", "").Split('\n');
// all lines start with an asterisk? that's a proper box comment
if (lines.Skip(1).Where(x => x.Trim() == "" || x.TrimStart()[0] != '*').All(string.IsNullOrEmpty))
{
AppendNewline();
Append(lines[0]);
foreach (var line in lines.Skip(1))
{
AppendNewline();
Append(" " + line.Trim());
}
}
else
{
// simple block comment: leave intact
if (lines.Length > 1)
{
// multiline comment starts on a new line
AppendNewline();
}
else
{
// single line /* ... */ comment stays on the same line
Append(" ");
}
foreach (var line in lines)
{
Append(line);
Append("\n");
}
}
AppendNewline();
}
private void HandleInlineComment(string tokenText)
{
Append(" ");
Append(tokenText);
Append(" ");
}
private void HandleComment(string tokenText)
{
if (LastText == "," && !WantedNewline)
{
TrimOutput(true);
}
if (LastType != "TK_COMMENT")
{
if (WantedNewline)
{
AppendNewline();
}
else
{
Append(" ");
}
}
Append(tokenText);
AppendNewline();
}
private void HandleDot(string tokenText)
{
if (IsSpecialWord(LastText))
{
Append(" ");
}
else
{
AllowWrapOrPreservedNewline(tokenText, LastText == ")" && Opts.BreakChainedMethods);
}
Append(tokenText);
}
private void HandleUnknown(string tokenText)
{
Append(tokenText);
}
}
}