Sourcecode - SourcecodeCheck
Download SourcecodeCheck.cs
/*
*
* 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]);
}
}