/* * * OUT OF DATE: * This code has been completely rewritten, the new version is working much better... * However, it is not available as standalone executable, but is integrated in * the open source SharpDevelop IDE. * http://www.sharpdevelop.com/ * */ //////////////////////////////////////////////// //// Sourcecode Check //// //// Version 1.0.2 01.08.04 //// //// www.danielgrunwald.de //// //////////////////////////////////////////////// // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. using System; using System.IO; using System.Text; using System.Collections; /* * This program parses all *.cs file in its working directory and subdirectories and changes * them to be compliant with Mike's Coding Style Guide * http://www.icsharpcode.net/TechNotes/SharpDevelopCodingStyle03.pdf * * Things done by this script: * - Parsing the file and calculating the correct indentation level * - Completely replacing the indentation by the correct amount of tabs. * - Pulling { on the same line as the construct (exceptions: namespace, class, interface, struct and method declarations) * - Putting else, catch and finally on the same line as } * * Things this script should do, but does not do yet: * - Putting { on the next line in class definitions * - Breaking "if (a) b();" on two lines (and else on the 3rd line...) * - Correctly indenting multiline array value definitions * * If you implement any missing feature or find some bug, please write an e-mail to * daniel@danielgrunwald.de * * Changelog: * 1.0.1: added line continuation, complete replacing of the indentation * added conversion to UTF-8 * 1.0.2: changed encoding detection to allow UTF-8 and UTF-8 Cookie */ public class MainClass { // keywords that let an opening brace have it's own line. static string[] newline_keywords = new string[] { "namespace ", "class ", "interface ", "struct ", "enum ", "(" } ; // ( means every line containing an ( will have it's { on a new line // these are the exceptions to the above rule (so no_newline_keywords don't get a new line even if they have a newline_keyword) static string[] no_newline_keywords = new string[] { "if", "else if", "while", "using", "for", "lock", "foreach", "catch", "switch" } ; // Note: no_newline_keywords are found only with " (", so for "if" the check method searches for "if (" static string[] behind_closing = new string[] { "else", "catch", "finally" } ; // so there are three types: // Lines without keywords (e.g. properties, set/get-Accessors): they don't get a new line // Lines with newline_keywords (class definitions, method definitions) // Lines with no_newline_keywords (if, while, etc.) public static void Main() { Timing.Start(Timer.Total); Work(new DirectoryInfo(".")); Timing.End(Timer.Total); Timing.Display(); System.Threading.Thread.Sleep(2000); } static void Work(DirectoryInfo dir) { foreach (FileInfo file in dir.GetFiles("*.cs")) { Work(file); } foreach (DirectoryInfo subdir in dir.GetDirectories()) { if (subdir.Name.StartsWith(".")) continue; if (subdir.Name == "bin" || subdir.Name == "obj") continue; Work(subdir); } } static bool MakeUTF8(string filename) { // Problem: Most files are saved as ISO-Latin-1 // I want to convert all my sourcecode to UTF-8, so why not put that into this tool? // first attempt to detect the encoding FileStream fs = new FileStream(filename, FileMode.Open); bool changeEncoding = false; if (fs.ReadByte() < 128) { // no UTF-8 BOM int highbytes = 0; for(int i = 1; i < fs.Length; i++) { int d = fs.ReadByte(); if (d < 128) { if (highbytes == 1) { // this cannot be UTF-8 changeEncoding = true; break; } highbytes = 0; } else { highbytes += 1; } } } fs.Close(); if (changeEncoding) { // check for byte ordering mark Timing.Start(Timer.Encoding); StreamReader sr = new StreamReader(filename, Encoding.Default); string data = sr.ReadToEnd(); sr.Close(); fs = new FileStream(filename, FileMode.Create, FileAccess.Write); StreamWriter w = new StreamWriter(fs, Encoding.UTF8); w.Write(data); w.Close(); fs.Close(); Timing.End(Timer.Encoding); return true; } else { return false; } } static void Work(FileInfo file) { bool madeUTF8 = MakeUTF8(file.FullName); Timing.Start(Timer.Changing); StringBuilder b = new StringBuilder(); StreamReader sr = new StreamReader(file.FullName, Encoding.UTF8); string line; State state = State.Code; int indent = 0; bool comment = false; bool lastLineComment; bool inString; bool continuation = false; int lastCommentStart = 0; Stack attributeStack = new Stack(); char stringType = '"'; string lastLine = ""; while ((line = sr.ReadLine()) != null) { line = line.TrimEnd(); string trimmed = line.TrimStart(); if (state == State.Code) { // Skandal! welcher Schrotteditor fügt denn immer Leerzeichen ein? line = trimmed; // weg mit dem Müll // Jetzt kommen echte Tabs: if (line.StartsWith("}") && indent > 0) line = new String('\t', indent - 1) + line; else if (continuation && !line.StartsWith("{")) line = new String('\t', indent + 1) + line; else line = new String('\t', indent) + line; } lastLineComment = comment; comment = false; inString = false; char lastchar = ' '; bool backslash = false; for (int i = 0; i < line.Length; i++) { char c = line[i]; if (state == State.Code && !comment) { if (inString) { if (backslash) { backslash = false; } else { if (c == '\\') backslash = true; if (c == stringType) inString = false; } } else { if (c == '{') { if (!char.IsWhiteSpace(lastchar)) { // always have a space before { line = line.Substring(0, i) + " " + line.Substring(i); i++; } if (i + 1 < line.Length) { if (!char.IsWhiteSpace(line[i + 1])) { line = line.Substring(0, i + 1) + " " + line.Substring(i + 1); } } continuation = false; indent++; } else if (c == '}') { if (!char.IsWhiteSpace(lastchar)) { // always have a space before { line = line.Substring(0, i) + " " + line.Substring(i); i++; } if (i + 1 < line.Length) { if (!char.IsWhiteSpace(line[i + 1])) { line = line.Substring(0, i + 1) + " " + line.Substring(i + 1); } } continuation = false; if (indent == 0) Console.WriteLine("Indentation problem in " + file.Name); else indent--; } else if (c == '#' || (c == '/' && lastchar == '/')) { // count #region etc. as comment to prevent line continuation comment = true; // one line comment lastCommentStart = i; } else if (c == '*' && lastchar == '/') state = State.Comment; else if (c == '"') inString = true; else if (c == '\'') inString = true; else if (c == ';') continuation = false; else if (c == '[') attributeStack.Push(continuation); else if (c == ']') continuation = (bool)attributeStack.Pop(); else { if (c != ',' && c != '/' && !char.IsWhiteSpace(c)) continuation = true; } if (inString) { // string betreten if (c == '"' && lastchar == '@') state = State.String; else stringType = c; } } } else if (state == State.Comment && c == '/' && lastchar == '*') { state = State.Code; } else if (state == State.String && c == '"') { // @"string" if (backslash) { backslash = false; } else if (i + 1 < line.Length) { if (line[i + 1] == '"') backslash = true; else state = State.Code; } else { state = State.Code; } } lastchar = c; } if (state == State.Code) { for (int i = 0; i < no_newline_keywords.Length; i++) { if (trimmed.StartsWith(no_newline_keywords[i] + "(") || trimmed.StartsWith("} " + no_newline_keywords[i] + "(")) { int pos = line.IndexOf(no_newline_keywords[i]); line = line.Substring(0, pos) + no_newline_keywords[i] + " " + line.Substring(pos + no_newline_keywords[i].Length); break; } } if (lastLine.Trim() == "}") { for (int i = 0; i < behind_closing.Length; i++) { if (trimmed.StartsWith(behind_closing[i] + " ")) { b.Remove(b.Length - 2, 2); // remove last newline line = " " + trimmed; } } } } if (state == State.Code && trimmed == "{") { int i; for (i = 0; i < newline_keywords.Length; i++) { if (lastLine.IndexOf(newline_keywords[i]) >= 0) break; } bool joinLines = false; if (i == newline_keywords.Length) { joinLines = true; } else { for (i = 0; i < no_newline_keywords.Length; i++) { if (lastLine.IndexOf(no_newline_keywords[i] + " (") >= 0) { joinLines = true; break; } } } if (joinLines) { // put this bracket on the previous line b.Remove(b.Length - 2, 2); // remove last newline if (lastLineComment) { // if (a) // do something // { // -> put { in front of the comment b.Insert(b.Length - lastLine.Length + lastCommentStart - 1, "{\r\n" + new String('\t', indent)); line = ""; // append nothing } else { line = " {"; } } } else if (state == State.Code && line.EndsWith("{") && !line.EndsWith(" {")) { line = line.Substring(0, line.Length - 1) + " {"; } b.Append(line); b.Append("\r\n"); lastLine = line; } sr.Close(); string data = b.ToString(); Timing.End(Timer.Changing); sr = new StreamReader(file.FullName, Encoding.UTF8); string data2 = sr.ReadToEnd(); if (data2 != data) { Timing.Start(Timer.Writing); sr.Close(); FileStream fs = new FileStream(file.FullName, FileMode.Create, FileAccess.Write); StreamWriter w = new StreamWriter(fs, Encoding.UTF8); w.Write(data); w.Close(); fs.Close(); if (madeUTF8) Console.WriteLine(" ME {0}", file.FullName); else Console.WriteLine(" M {0}", file.FullName); Timing.End(Timer.Writing); } else { sr.Close(); if (madeUTF8) Console.WriteLine(" E {0}", file.FullName); } } } enum State { Code, Comment, String } enum Timer { Total = 0, Encoding = 1, Changing = 2, Writing = 3, EndOfEnum = 4 } class Timing { static int[] start = new int[(int)Timer.EndOfEnum]; static int[] times = new int[(int)Timer.EndOfEnum]; public static void Start(Timer timer) { start[(int)timer] = Environment.TickCount; } public static void End(Timer timer) { int end = Environment.TickCount; times[(int)timer] += (end - start[(int)timer]); } public static void Display() { Console.WriteLine("Total time: {0} ms", times[(int)Timer.Total]); Console.WriteLine("Time encoding: {0} ms", times[(int)Timer.Encoding]); Console.WriteLine("Time working: {0} ms", times[(int)Timer.Changing]); Console.WriteLine("Time writing: {0} ms", times[(int)Timer.Writing]); } }