#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 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 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(); 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(); // 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> { { "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 GetNextToken() { NNewlines = 0; if (ParserPos >= Input.Length) { return new Tuple("", "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("", "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("", "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(cc, "TK_WORD"); } if (cc == "in") // in is an operator, need to hack { return new Tuple(cc, "TK_OPERATOR"); } if (WantedNewline && LastType != "TK_OPERATOR" && LastType != "TK_EQUALS" && !Flags.IfLine && (Opts.PreserveNewlines || LastText != "var")) { AppendNewline(); } return new Tuple(cc, "TK_WORD"); } if ("([".Contains(c)) { return new Tuple(c.ToString(), "TK_START_EXPR"); } if (")]".Contains(c)) { return new Tuple(c.ToString(), "TK_END_EXPR"); } if (c == '{') { return new Tuple(c.ToString(), "TK_START_BLOCK"); } if (c == '}') { return new Tuple(c.ToString(), "TK_END_BLOCK"); } if (c == ';') { return new Tuple(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("/*" + comment + "*/", "TK_INLINE_COMMENT"); } return new Tuple("/*" + 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(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(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(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(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(sharp, "TK_WORD"); } if (c == '<' && Input.Substring(ParserPos - 1, Math.Min(4, Input.Length - ParserPos + 1)) == "") { Flags.InHtmlComment = false; ParserPos += 2; if (WantedNewline) { AppendNewline(); } return new Tuple("-->", "TK_COMMENT"); } if (c == '.') { return new Tuple(".", "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("=", "TK_EQUALS"); } if (ss == ",") { return new Tuple(",", "TK_COMMA"); } return new Tuple(ss, "TK_OPERATOR"); } return new Tuple(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); } } }