Browse Source

basic spacing rules and such

bmallred 9 years ago
parent
commit
d0e998781f
31 changed files with 1304 additions and 1 deletions
  1. 2 1
      .gitignore
  2. 8 0
      LICENSE
  3. 76 0
      main.go
  4. 33 0
      rules/closingParenthesisMustBeSpacedCorrectly.go
  5. 26 0
      rules/closingParenthesisMustBeSpacedCorrectly_test.go
  6. 18 0
      rules/codeMustNotContainMultipleBlankLinesInARow.go
  7. 25 0
      rules/codeMustNotContainMultipleBlankLinesInARow_test.go
  8. 18 0
      rules/codeMustNotContainMultipleWhitespaceInARow.go
  9. 20 0
      rules/codeMustNotContainMultipleWhitespaceInARow_test.go
  10. 12 0
      rules/colonsMustBeSpacedCorrectly.go
  11. 31 0
      rules/commasMustBeSpacedCorrectly.go
  12. 35 0
      rules/commasMustBeSpacedCorrectly_test.go
  13. 18 0
      rules/documentationLinesMustBeginWithSingleSpace.go
  14. 26 0
      rules/documentationLinesMustBeginWithSingleSpace_test.go
  15. 33 0
      rules/openingParenthesisMustBeSpacedCorrectly.go
  16. 28 0
      rules/openingParenthesisMustBeSpacedCorrectly_test.go
  17. 19 0
      rules/preprocessorKeywordsMustNotBePrecededBySpace.go
  18. 52 0
      rules/preprocessorKeywordsMustNotBePrecededBySpace_test.go
  19. 243 0
      rules/rules.go
  20. 25 0
      rules/semicolonsMustBeSpacedCorrectly.go
  21. 32 0
      rules/semicolonsMustBeSpacedCorrectly_test.go
  22. 26 0
      rules/singleLineCommentsMustBeginWithSingleSpace.go
  23. 26 0
      rules/singleLineCommentsMustBeginWithSingleSpace_test.go
  24. 148 0
      rules/symbolsMustBeSpacedCorrectly.go
  25. 56 0
      rules/symbolsMustBeSpacedCorrectly_test.go
  26. 18 0
      rules/tabsMustNotBeUsed.go
  27. 32 0
      rules/tabsMustNotBeUsed_test.go
  28. 70 0
      rules/usingDirectivesMustBeOrderedAlphabeticallyByNamespace.go
  29. 55 0
      rules/usingDirectivesMustBeOrderedAlphabeticallyByNamespace_test.go
  30. 93 0
      sourceFile.go
  31. 0 0
      testdata/empty.cs

+ 2 - 1
.gitignore

@ -19,4 +19,5 @@ _cgo_export.*
19 19
20 20
_testmain.go
21 21
22
*.exe
22
*.exe
23
csfmt

+ 8 - 0
LICENSE

@ -0,0 +1,8 @@
1
"THE BEER-WARE LICENSE" (Revision 42)
2
=====================================
3
4
[Bryan Allred](bryan@revolvingcow.com) wrote this file. As long as you retain this
5
notice you can do whatever you want with this stuff. If we meet some day, and you
6
think this stuff is worth it, you can buy me a beer in return.
7
8
Bryan Allred

+ 76 - 0
main.go

@ -0,0 +1,76 @@
1
package main
2
3
import (
4
	"bytes"
5
	"log"
6
	"os"
7
8
	"code.revolvingcow.com/revolvingcow/csfmt/rules"
9
)
10
11
func main() {
12
	sourceFiles := []SourceFile{}
13
14
	// Determine what files to format
15
	argc := len(os.Args)
16
	if argc < 2 {
17
		return
18
	} else if argc == 2 && os.Args[1] == "..." {
19
		// Walk the file structure from the current working directory
20
		cwd, err := os.Getwd()
21
		if err != nil {
22
			return
23
		}
24
25
		s := SourceFile{
26
			Path: cwd,
27
		}
28
29
		for sourceFile := range s.Walk() {
30
			sourceFiles = append(sourceFiles, sourceFile)
31
		}
32
	} else {
33
		// Assuming multiple files were given
34
		// for _, a := range os.Args[1:] {
35
		a := os.Args[1]
36
		s := SourceFile{
37
			Path: a,
38
		}
39
40
		if s.Exists() {
41
			if s.IsDir() {
42
				for sourceFile := range s.Walk() {
43
					sourceFiles = append(sourceFiles, sourceFile)
44
				}
45
			} else if s.IsDotNet() {
46
				sourceFiles = append(sourceFiles, s)
47
			}
48
		}
49
		// }
50
	}
51
52
	count := len(sourceFiles)
53
	modified := 0
54
	queuedRules := rules.EnabledRules()
55
56
	for _, s := range sourceFiles {
57
		contents, err := s.Read()
58
		if err != nil {
59
			log.Fatalln(err)
60
		}
61
		original := contents
62
63
		for _, rule := range queuedRules {
64
			contents = rule.Apply(contents)
65
		}
66
67
		if bytes.Compare(original, contents) != 0 {
68
			modified++
69
		}
70
71
		s.Write(contents)
72
		// fmt.Println(string(contents))
73
	}
74
75
	log.Printf("Modified %d of %d files using %d rules\n", modified, count, len(queuedRules))
76
}

+ 33 - 0
rules/closingParenthesisMustBeSpacedCorrectly.go

@ -0,0 +1,33 @@
1
package rules
2
3
import "regexp"
4
5
var closingParenthesisMustBeSpacedCorrectly = &Rule{
6
	Name:        "Closing parenthesis must be spaced correctly",
7
	Enabled:     true,
8
	Apply:       applyClosingParenthesisMustBeSpacedCorrectly,
9
	Description: ``,
10
}
11
12
func applyClosingParenthesisMustBeSpacedCorrectly(source []byte) []byte {
13
	spaceBetween := `([A-z]|=|\+|\-|\*|/|&|\||\^|\{)`
14
15
	// Remove leading spaces
16
	re := regexp.MustCompile(`([\S])(\t| )([\)])`)
17
	for re.Match(source) {
18
		source = re.ReplaceAll(source, []byte("$1$3"))
19
	}
20
21
	// Remove trailing spaces
22
	re = regexp.MustCompile(`([\)])(\t| )([\S])`)
23
	for re.Match(source) {
24
		source = re.ReplaceAll(source, []byte("$1$3"))
25
	}
26
27
	// Add space between operators and keywords
28
	re = regexp.MustCompile(`([\)])` + spaceBetween)
29
	for re.Match(source) {
30
		source = re.ReplaceAll(source, []byte("$1 $2"))
31
	}
32
	return source
33
}

+ 26 - 0
rules/closingParenthesisMustBeSpacedCorrectly_test.go

@ -0,0 +1,26 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestClosingParenthesisMustBeSpacedCorrectly(t *testing.T) {
10
	input := []byte(`if (true ){}
11
if (true) {}
12
public void something(int i ) {}
13
switch (foo ){}
14
(2)+1`)
15
	expected := []byte(`if (true) {}
16
if (true) {}
17
public void something(int i) {}
18
switch (foo) {}
19
(2) +1`)
20
21
	actual := applyClosingParenthesisMustBeSpacedCorrectly(input)
22
	if !bytes.Equal(expected, actual) {
23
		fmt.Println(string(actual))
24
		t.Fail()
25
	}
26
}

+ 18 - 0
rules/codeMustNotContainMultipleBlankLinesInARow.go

@ -0,0 +1,18 @@
1
package rules
2
3
import "regexp"
4
5
var codeMustNotContainMultipleBlankLinesInARow = &Rule{
6
	Name:        "Code must not contain multiple blank lines in a row",
7
	Enabled:     true,
8
	Apply:       applyCodeMustNotContainMultipleBlankLinesInARow,
9
	Description: ``,
10
}
11
12
func applyCodeMustNotContainMultipleBlankLinesInARow(source []byte) []byte {
13
	re := regexp.MustCompile("(\n\n)+")
14
	for re.Match(source) {
15
		source = re.ReplaceAllLiteral(source, []byte("\n"))
16
	}
17
	return source
18
}

+ 25 - 0
rules/codeMustNotContainMultipleBlankLinesInARow_test.go

@ -0,0 +1,25 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"testing"
6
)
7
8
func TestCodeMustNotContainMultipleBlankLinesInARow(t *testing.T) {
9
	input := []byte(`public void FunctionName(string s, int i)
10
{
11
12
13
14
	return s + i.ToString();
15
}`)
16
	expected := []byte(`public void FunctionName(string s, int i)
17
{
18
	return s + i.ToString();
19
}`)
20
21
	actual := applyCodeMustNotContainMultipleBlankLinesInARow(input)
22
	if !bytes.Equal(expected, actual) {
23
		t.Fail()
24
	}
25
}

+ 18 - 0
rules/codeMustNotContainMultipleWhitespaceInARow.go

@ -0,0 +1,18 @@
1
package rules
2
3
import "regexp"
4
5
var codeMustNotContainMultipleWhitespaceInARow = &Rule{
6
	Name:        "Code must not contain multiple whitespaces in a row",
7
	Enabled:     true,
8
	Apply:       applyCodeMustNotContainMultipleWhitespaceInARow,
9
	Description: `A violation of this rule occurs whenver the code contains a tab character.`,
10
}
11
12
func applyCodeMustNotContainMultipleWhitespaceInARow(source []byte) []byte {
13
	re := regexp.MustCompile(`(\S)[ ]{2,}(\S)`)
14
	for re.Match(source) {
15
		source = re.ReplaceAll(source, []byte("$1 $2"))
16
	}
17
	return source
18
}

+ 20 - 0
rules/codeMustNotContainMultipleWhitespaceInARow_test.go

@ -0,0 +1,20 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestCodeMustNotContainMultipleWhitespaceInARow(t *testing.T) {
10
	input := []byte(`
11
	if  (i  == 0)  {  }`)
12
	expected := []byte(`
13
	if (i == 0) { }`)
14
15
	actual := applyCodeMustNotContainMultipleWhitespaceInARow(input)
16
	if !bytes.Equal(expected, actual) {
17
		fmt.Println(string(actual))
18
		t.Fail()
19
	}
20
}

+ 12 - 0
rules/colonsMustBeSpacedCorrectly.go

@ -0,0 +1,12 @@
1
package rules
2
3
var colonsMustBeSpacedCorrectly = &Rule{
4
	Name:        "Colons must be spaced correctly",
5
	Enabled:     false,
6
	Apply:       applyColonsMustBeSpacedCorrectly,
7
	Description: ``,
8
}
9
10
func applyColonsMustBeSpacedCorrectly(source []byte) []byte {
11
	return source
12
}

+ 31 - 0
rules/commasMustBeSpacedCorrectly.go

@ -0,0 +1,31 @@
1
package rules
2
3
import "regexp"
4
5
var commasMustBeSpacedCorrectly = &Rule{
6
	Name:        "Commas must be spaced correctly",
7
	Enabled:     true,
8
	Apply:       applyCommasMustBeSpacedCorrectly,
9
	Description: ``,
10
}
11
12
func applyCommasMustBeSpacedCorrectly(source []byte) []byte {
13
	// Look for leading spaces
14
	re := regexp.MustCompile(`(\n)*[\s]+,`)
15
	for re.Match(source) {
16
		source = re.ReplaceAllLiteral(source, []byte(","))
17
	}
18
19
	// Add trailing spaces as necessary
20
	re = regexp.MustCompile(`(\S),(\w|\d)`)
21
	for re.Match(source) {
22
		source = re.ReplaceAll(source, []byte("$1, $2"))
23
	}
24
25
	// Look for too many trailing spaces
26
	re = regexp.MustCompile(`\,  `)
27
	for re.Match(source) {
28
		source = re.ReplaceAll(source, []byte(", "))
29
	}
30
	return source
31
}

+ 35 - 0
rules/commasMustBeSpacedCorrectly_test.go

@ -0,0 +1,35 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestCommasMustBeSpacedCorrectly(t *testing.T) {
10
	input := []byte(`public void FunctionName(string s ,int i
11
	, object x)
12
{
13
	int[] b = new [1,   3,4 ,5];
14
	var o = new {
15
		blah = "stomething",
16
		meh = 0,
17
		dude = true,
18
	};
19
}`)
20
	expected := []byte(`public void FunctionName(string s, int i, object x)
21
{
22
	int[] b = new [1, 3, 4, 5];
23
	var o = new {
24
		blah = "stomething",
25
		meh = 0,
26
		dude = true,
27
	};
28
}`)
29
30
	actual := applyCommasMustBeSpacedCorrectly(input)
31
	if !bytes.Equal(expected, actual) {
32
		fmt.Println(string(actual))
33
		t.Fail()
34
	}
35
}

+ 18 - 0
rules/documentationLinesMustBeginWithSingleSpace.go

@ -0,0 +1,18 @@
1
package rules
2
3
import "regexp"
4
5
var documentationLinesMustBeginWithSingleSpace = &Rule{
6
	Name:        "Documentation lines must begin with a single space",
7
	Enabled:     true,
8
	Apply:       applyDocumentationLinesMustBeginWithSingleSpace,
9
	Description: ``,
10
}
11
12
func applyDocumentationLinesMustBeginWithSingleSpace(source []byte) []byte {
13
	re := regexp.MustCompile(`([/]{3})(\S)`)
14
	for re.Match(source) {
15
		source = re.ReplaceAll(source, []byte("$1 $2"))
16
	}
17
	return source
18
}

+ 26 - 0
rules/documentationLinesMustBeginWithSingleSpace_test.go

@ -0,0 +1,26 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestDocumentationLineMustBeginWithSingleSpace(t *testing.T) {
10
	input := []byte(`///<summary>
11
///The summary.
12
///</summary>
13
/// <param name="foo">The foo.</param>
14
/// <returns>The bar.</returns>`)
15
	expected := []byte(`/// <summary>
16
/// The summary.
17
/// </summary>
18
/// <param name="foo">The foo.</param>
19
/// <returns>The bar.</returns>`)
20
21
	actual := applyDocumentationLinesMustBeginWithSingleSpace(input)
22
	if !bytes.Equal(expected, actual) {
23
		fmt.Println(string(actual))
24
		t.Fail()
25
	}
26
}

+ 33 - 0
rules/openingParenthesisMustBeSpacedCorrectly.go

@ -0,0 +1,33 @@
1
package rules
2
3
import "regexp"
4
5
var openingParenthesisMustBeSpacedCorrectly = &Rule{
6
	Name:        "Opening parenthesis must be spaced correctly",
7
	Enabled:     true,
8
	Apply:       applyOpeningParenthesisMustBeSpacedCorrectly,
9
	Description: ``,
10
}
11
12
func applyOpeningParenthesisMustBeSpacedCorrectly(source []byte) []byte {
13
	spaceBetween := `(if|while|for|switch|foreach|using|\+|\-|\*|/|&|\||\^|=)`
14
15
	// Remove leading spaces
16
	re := regexp.MustCompile(`([\S])(\t| )([\(])`)
17
	for re.Match(source) {
18
		source = re.ReplaceAll(source, []byte("$1$3"))
19
	}
20
21
	// Remove trailing spaces
22
	re = regexp.MustCompile(`([\(])(\t| )([\S])`)
23
	for re.Match(source) {
24
		source = re.ReplaceAll(source, []byte("$1$3"))
25
	}
26
27
	// Add space between operators and keywords
28
	re = regexp.MustCompile(spaceBetween + `([\(])`)
29
	for re.Match(source) {
30
		source = re.ReplaceAll(source, []byte("$1 $2"))
31
	}
32
	return source
33
}

+ 28 - 0
rules/openingParenthesisMustBeSpacedCorrectly_test.go

@ -0,0 +1,28 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestOpeningParenthesisMustBeSpacedCorrectly(t *testing.T) {
10
	input := []byte(`if(true) {}
11
if (true) {}
12
public void something ( int i) {}
13
switch(foo) {}
14
1+(2)
15
if (`)
16
	expected := []byte(`if (true) {}
17
if (true) {}
18
public void something(int i) {}
19
switch (foo) {}
20
1+ (2)
21
if (`)
22
23
	actual := applyOpeningParenthesisMustBeSpacedCorrectly(input)
24
	if !bytes.Equal(expected, actual) {
25
		fmt.Println(string(actual))
26
		t.Fail()
27
	}
28
}

+ 19 - 0
rules/preprocessorKeywordsMustNotBePrecededBySpace.go

@ -0,0 +1,19 @@
1
package rules
2
3
import "regexp"
4
5
var preprocessorKeywordsMustNotBePrecededBySpace = &Rule{
6
	Name:        "Single line comments must begin with single space",
7
	Enabled:     true,
8
	Apply:       applyPreprocessorKeywordsMustNotBePrecededBySpace,
9
	Description: ``,
10
}
11
12
func applyPreprocessorKeywordsMustNotBePrecededBySpace(source []byte) []byte {
13
	keywords := `(if|else|elif|endif|define|undef|warning|error|line|region|endregion|pragma|pragma warning|pragma checksum)`
14
	re := regexp.MustCompile(`([#])(\t| )+` + keywords)
15
	for re.Match(source) {
16
		source = re.ReplaceAll(source, []byte("$1$3"))
17
	}
18
	return source
19
}

+ 52 - 0
rules/preprocessorKeywordsMustNotBePrecededBySpace_test.go

@ -0,0 +1,52 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestPreprocessorKeywordsMustNotBePrecededBySpace(t *testing.T) {
10
	input := []byte(`
11
# if
12
	// Something
13
# else
14
	// Something
15
# elif
16
	// Something
17
# endif
18
# define
19
# undef
20
# warning
21
# error
22
# line
23
# region
24
# endregion
25
# pragma
26
# pragma warning
27
# pragma checksum`)
28
	expected := []byte(`
29
#if
30
	// Something
31
#else
32
	// Something
33
#elif
34
	// Something
35
#endif
36
#define
37
#undef
38
#warning
39
#error
40
#line
41
#region
42
#endregion
43
#pragma
44
#pragma warning
45
#pragma checksum`)
46
47
	actual := applyPreprocessorKeywordsMustNotBePrecededBySpace(input)
48
	if !bytes.Equal(expected, actual) {
49
		fmt.Println(string(actual))
50
		t.Fail()
51
	}
52
}

+ 243 - 0
rules/rules.go

@ -0,0 +1,243 @@
1
package rules
2
3
// The basic ruleset comes from [StyleCop][1] with them toggled on or off
4
// based solely on the authors supreme opinion(s).
5
//
6
// [1]: http://www.stylecop.com/docs/StyleCop%20Rules.html
7
8
// Rule is a style rule to look for and apply within the source code.
9
type Rule struct {
10
	Name        string
11
	Description string
12
	Enabled     bool
13
	Apply       func(source []byte) []byte
14
}
15
16
// Documentation rules
17
var Documentation = []*Rule{
18
// elementsMustBeDocument,
19
// partialElementsMustBeDocumented,
20
// enumerationItemsMustBeDocumented,
21
// documentationMustContainValidXml,
22
// elementDocumentationMustHaveSummary,
23
// elementDocumentationMustHaveSummaryText,
24
// elementDocumenationMustNotHaveDefaultSummary,
25
// elementDocumentationMustBeSpelledCorrectly,
26
// partialElementDocumentationMustHaveSummary,
27
// partialElementDocumentationMustHaveSummaryText,
28
// propertyDocumentationMustHaveValue,
29
// propertyDocumentationMustHaveValueText,
30
// elementParametersMustBeDocumented,
31
// elementParameterDocumentationMustMatchElementParameters,
32
// elementParameterDocumentationMustDeclareParameterName,
33
// elementParameterDocumentationMustHaveText,
34
// elementReturnValueMustBeDocumented,
35
// elementReturnValueDocumentationMustHaveValue,
36
// voidReturnValueMustNotBeDocumented,
37
// genericTypeParametersMustBeDocumented,
38
// genericTypeParametersMustBeDocumentedPartialClass,
39
// genericTypeParameterDocumentationMustMatchTypeParameters,
40
// genericTypeParameterDocumentationMustDeclareParameterName,
41
// genericTypeParameterDocumentationMustHaveText,
42
// propertySummaryDocumentationMustMatchAccessors,
43
// propertySummaryDocumentationMustOmitSetAccessorWithRestrictedAccess,
44
// elementDocumentationMustNotBeCopiedAndPasted,
45
// singleLineCommentsMustNotUseDocumentationStyleSlashes,
46
// documentationTextMustNotBeEmpty,
47
// documentationTextMustBeginWithACapitalLetter,
48
// documentationTextMustEndWithAPeriod,
49
// documentationTextMustContainWhitespace,
50
// documentationTextMustMeetCharacterPercentage,
51
// documentationTextMustMeetMinimumCharacterLenght,
52
// fileMustHaveHeader,
53
// fileHeaderMustShowCopyright,
54
// fileHeaderMustHaveCopyrightText,
55
// fileHeaderCopyrightTextMustMatch,
56
// fileHeaderMustContainFileName,
57
// fileHeaderFileNameDocumentationMustMatchFileName,
58
// fileHeaderMustHaveSummary,
59
// fileHeaderMustHaveValidCompanyText,
60
// fileHeaderCompanyNameTextMustMatch,
61
// fileHeaderFileNameDocumentationMustMatchTypeName,
62
// constructorSummaryDocumentationMustBeginWithStandardText,
63
// deconstructorSummaryDocumentationMustBeginWithStandardText,
64
// documentationHeadersMustNotContainBlankLines,
65
// includedDocumentationFileDoesNotExist,
66
// includedDocumentationXPathDoesNotExist,
67
// includedNodeDoesNotContainValidFileAndPath,
68
// inheritDocMustBeUsedWithInheritingClass,
69
}
70
71
// Layout rules
72
var Layout = []*Rule{
73
	// curlyBracketsForMultiLineStatementsMustNotShareLine,
74
	// statementMustNotBeOnSingleLine,
75
	// elementMustNotBeOnSingleLine,
76
	// curlyBracketsMustNotBeOmitted,
77
	// allAccessorMustBeMultiLineOrSingleLine,
78
	// openingCurlyBracketsMustNotBeFollowedByBlankLine,
79
	// elementDocumentationHeadersMustNotBeFollowedByBlankLine,
80
	codeMustNotContainMultipleBlankLinesInARow,
81
	// closingCurlyBracketsMustNotBePrecededByBlankLine,
82
	// openingCurlyBracketsMustNotBePrecededByBlankLine,
83
	// chainedStatementBlocksMustNotBePrecededByBlankLine,
84
	// whileDoFooterMustNotBePrecededByBlankLine,
85
	// singleLineCommentsMustNotBeFollowedByBlankLine,
86
	// closingCurlyBracketMustBeFollowedByBlankLine,
87
	// elementDocumentationHeaderMustBePrecededByBlankLine,
88
	// singleLineCommentMustBePrecededByBlankLine,
89
	// elementsMustBeSeparatedByBlankLine,
90
	// codeMustNotContainBlankLinesAtStartOfFile,
91
	// codeMustNotContainBlankLinesAtEndOfFile,
92
}
93
94
// Maintainability Rules
95
var Maintainability = []*Rule{
96
// statementMustNotUseUnnecessaryParenthesis,
97
// accessModifierMustBeDeclared,
98
// fieldsMustBePrivate,
99
// fileMayOnlyContainASingleClass,
100
// fileMayOnlyContainASingleNamespace,
101
// codeAnalysisSuppressionMustHaveJustification,
102
// debugAssertMustProvideMessageText,
103
// debugFailMustProvideMessageText,
104
// arithmeticExpressionsMustDeclarePrecedence,
105
// conditionalExpressionsMustDeclarePrecendence,
106
// removeUnnecessaryCode,
107
// removeDelegateParenthesisWhenPossible,
108
// attributeConstructorMustNotUseUnnecessaryParenthesis,
109
}
110
111
// Naming Rules
112
var Naming = []*Rule{
113
// elementMustBeginWithUpperCaseLetter,
114
// elementMustBeginWithLowerCaseLetter,
115
// interfaceNamesMustBeginWithI,
116
// constFieldNamesMustBeginWithUpperCaseLetter,
117
// nonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter,
118
// fieldNamesMustNotUseHungarianNotation,
119
// fieldNamesMustBeginWithLowerCaseLetter,
120
// accessibleFieldsMustBeginWithUpperCaseLetter,
121
// variableNamesMustNotBePrefixed,
122
// fieldNamesMustNotBeginWithUnderscore,
123
// fieldNamesMustNotContainUnderscore,
124
// staticReadonlyFieldsMustBeginWithUpperCaseLetter,
125
}
126
127
// Ordering Rules
128
var Ordering = []*Rule{
129
	// usingDirectivesMustBePlacedWithinNamespace,
130
	// elementsMustAppearInTheCorrectOrder,
131
	// elementsMustBeOrderedByAccess,
132
	// constantsMustAppearBeforeFields,
133
	// staticElementsMustAppearBeforeInstanceElements,
134
	// partialElementsMustDeclareAccess,
135
	// declarationKeywordsMustFollowOrder,
136
	// protectedMustComeBeforeInternal,
137
	// systemUsingDirectivesMustBePlacedBeforeOtherUsingDirectives,
138
	// usingAliasDirectivesMustBePlacedAfterOtherUsingDirectives,
139
	usingDirectivesMustBeOrderedAlphabeticallyByNamespace,
140
	// usingAliasDirectivesMustBeOrderedAlphabeticallyByAliasName,
141
	// propertyAccessorsMustFollowOrder,
142
	// eventAccessorsMustFollowOrder,
143
	// staticReadonlyElementsMustAppearBeforeStaticNonReadonlyElements,
144
	// instanceReadonlyElementsMustAppearBeforeInstanceNonReadonlyElements,
145
}
146
147
// Readability Rules
148
var Readability = []*Rule{
149
// doNotPrefixCallsWithBaseUnlessLocalImplementationExists,
150
// prefixLocalCallsWithThis,
151
// queryClauseMustFollowPreviousClause,
152
// queryClausesMustBeOnSeparateLinesOrAllOnOneLine,
153
// queryClauseMustBeginOnNewLineWhenPreviousClauseSpansMultipleLines,
154
// queryClausesSpanningMultipleLinesMustBeginOnOwnLine,
155
// codeMustNotContainEmptyStatements,
156
// codeMustNotContainMultipleStatementsOnOneLine,
157
// blockStatementsMustNotContainEmbeddedComments,
158
// blockStatementsMustNotContainEmbeddedRegions,
159
// openingParenthesisMustBeOnDeclarationLine,
160
// closingParenthesisMustBeOnLineOfOpeningParenthesis,
161
// commaMustBeOnSameLineAsPreviousParameter,
162
// parameterListMustFollowDeclaration,
163
// parameterMustFollowComma,
164
// splitParametersMustStartOnLineAfterDeclaration,
165
// parametersMustBeOnSameLineOrSeparateLines,
166
// parametersMustNotSpanMultipleLines,
167
// commentsMustContainText,
168
// useBuiltInTypeAlias,
169
// useStringEmptyForEmptyStrings,
170
// doNotPlaceRegionsWithinElements,
171
// doNotUseRegions,
172
// useShorthandForNullableTypes,
173
// prefixCallsCorrectly,
174
}
175
176
// Spacing Rules
177
var Spacing = []*Rule{
178
	symbolsMustBeSpacedCorrectly,
179
	// keywordsMustBeSpacedCorrectly,
180
	commasMustBeSpacedCorrectly,
181
	semicolonsMustBeSpacedCorrectly,
182
	singleLineCommentsMustBeginWithSingleSpace,
183
	documentationLinesMustBeginWithSingleSpace,
184
	preprocessorKeywordsMustNotBePrecededBySpace,
185
	// operatorKeywordMustBeFollowedBySpace,
186
	openingParenthesisMustBeSpacedCorrectly,
187
	closingParenthesisMustBeSpacedCorrectly,
188
	// openingSquareBracketsMustBeSpacedCorrectly,
189
	// closingSquareBracketsMustBeSpacedCorrectly,
190
	// openingCurlyBracketsMustBeSpacedCorrectly,
191
	// closingCurlyBracketsMustBeSpacedCorrectly,
192
	// openingGenericBracketsMustBeSpacedCorrectly,
193
	// closingGenericBracketsMustBeSpacedCorrectly,
194
	// openingAttributeBracketsMustBeSpacedCorrectly,
195
	// closingAttributeBracketsMustBeSpacedCorrectly,
196
	// nullableTypeSymbolsMustNotBePrecededBySpace,
197
	// memberAccessSymbolsMustBeSpacedCorrectly,
198
	// incrementDecrementSymbolsMustBeSpacedCorrectly,
199
	// negativeSignsMustBeSpacedCorrectly,
200
	// positiveSignsMustBeSpacedCorrectly,
201
	// dereferenceAndAccessOfSymbolsMustBeSpacedCorrectly,
202
	colonsMustBeSpacedCorrectly,
203
	codeMustNotContainMultipleWhitespaceInARow,
204
	// codeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation,
205
	tabsMustNotBeUsed,
206
}
207
208
func EnabledRules() []*Rule {
209
	enabled := []*Rule{}
210
211
	for _, rule := range Documentation {
212
		if rule.Enabled {
213
			enabled = append(enabled, rule)
214
		}
215
	}
216
	for _, rule := range Layout {
217
		if rule.Enabled {
218
			enabled = append(enabled, rule)
219
		}
220
	}
221
	for _, rule := range Maintainability {
222
		if rule.Enabled {
223
			enabled = append(enabled, rule)
224
		}
225
	}
226
	for _, rule := range Ordering {
227
		if rule.Enabled {
228
			enabled = append(enabled, rule)
229
		}
230
	}
231
	for _, rule := range Readability {
232
		if rule.Enabled {
233
			enabled = append(enabled, rule)
234
		}
235
	}
236
	for _, rule := range Spacing {
237
		if rule.Enabled {
238
			enabled = append(enabled, rule)
239
		}
240
	}
241
242
	return enabled
243
}

+ 25 - 0
rules/semicolonsMustBeSpacedCorrectly.go

@ -0,0 +1,25 @@
1
package rules
2
3
import "regexp"
4
5
var semicolonsMustBeSpacedCorrectly = &Rule{
6
	Name:        "Semicolons must be spaced correctly",
7
	Enabled:     true,
8
	Apply:       applySemicolonsMustBeSpacedCorrectly,
9
	Description: ``,
10
}
11
12
func applySemicolonsMustBeSpacedCorrectly(source []byte) []byte {
13
	// Look for leading spaces
14
	re := regexp.MustCompile(`\s;`)
15
	for re.Match(source) {
16
		source = re.ReplaceAllLiteral(source, []byte(";"))
17
	}
18
19
	// Add trailing spaces as necessary
20
	re = regexp.MustCompile(`;(\S)`)
21
	for re.Match(source) {
22
		source = re.ReplaceAll(source, []byte("; $1"))
23
	}
24
	return source
25
}

+ 32 - 0
rules/semicolonsMustBeSpacedCorrectly_test.go

@ -0,0 +1,32 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestSemicolonsMustBeSpacedCorrectly(t *testing.T) {
10
	input := []byte(`public void FunctionName(string s, int i)
11
{
12
	var i = 0; // blah
13
	for (i = 0;i < 4;i++) {
14
		// Do something
15
	}
16
	return s + i.ToString() ;
17
}`)
18
	expected := []byte(`public void FunctionName(string s, int i)
19
{
20
	var i = 0; // blah
21
	for (i = 0; i < 4; i++) {
22
		// Do something
23
	}
24
	return s + i.ToString();
25
}`)
26
27
	actual := applySemicolonsMustBeSpacedCorrectly(input)
28
	if !bytes.Equal(expected, actual) {
29
		fmt.Println(string(actual))
30
		t.Fail()
31
	}
32
}

+ 26 - 0
rules/singleLineCommentsMustBeginWithSingleSpace.go

@ -0,0 +1,26 @@
1
package rules
2
3
import "regexp"
4
5
var singleLineCommentsMustBeginWithSingleSpace = &Rule{
6
	Name:        "Single line comments must begin with single space",
7
	Enabled:     true,
8
	Apply:       applySingleLineCommentsMustBeginWithSingleSpace,
9
	Description: ``,
10
}
11
12
func applySingleLineCommentsMustBeginWithSingleSpace(source []byte) []byte {
13
	re := regexp.MustCompile(`([/]{2})([\S])`)
14
	for re.Match(source) {
15
		source = re.ReplaceAll(source, []byte("$1 $2"))
16
	}
17
	re = regexp.MustCompile(`([/]{2})([\s]{2,})([\S])`)
18
	for re.Match(source) {
19
		source = re.ReplaceAll(source, []byte("$1 $3"))
20
	}
21
	re = regexp.MustCompile(`([/]{2})([\s])([/]{1,})([\s]*)`)
22
	for re.Match(source) {
23
		source = re.ReplaceAll(source, []byte("$1$3"))
24
	}
25
	return source
26
}

+ 26 - 0
rules/singleLineCommentsMustBeginWithSingleSpace_test.go

@ -0,0 +1,26 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestSingleLineCommentsMustBeingWithSingleSpace(t *testing.T) {
10
	input := []byte(`//This is a comment with no space
11
//  This is a comment with too many spaces
12
////int i = 0;
13
//////int i = 0;
14
////return i;`)
15
	expected := []byte(`// This is a comment with no space
16
// This is a comment with too many spaces
17
////int i = 0;
18
//////int i = 0;
19
////return i;`)
20
21
	actual := applySingleLineCommentsMustBeginWithSingleSpace(input)
22
	if !bytes.Equal(expected, actual) {
23
		fmt.Println(string(actual))
24
		t.Fail()
25
	}
26
}

+ 148 - 0
rules/symbolsMustBeSpacedCorrectly.go

@ -0,0 +1,148 @@
1
package rules
2
3
import (
4
	"bufio"
5
	"bytes"
6
	"regexp"
7
)
8
9
var symbolsMustBeSpacedCorrectly = &Rule{
10
	Name:        "Symbols must be spaced correctly",
11
	Enabled:     true,
12
	Apply:       applySymbolsMustBeSpacedCorrectly,
13
	Description: ``,
14
}
15
16
func applySymbolsMustBeSpacedCorrectly(source []byte) []byte {
17
	reCommentShortBegin := regexp.MustCompile(`\A\s*(/{2,})`)
18
	reCommentLongBegin := regexp.MustCompile(`\A\s*(/\*)`)
19
	reCommentLongEnd := regexp.MustCompile(`\*/`)
20
21
	short := false
22
	long := false
23
	lines := []byte{}
24
	buffer := bytes.NewBuffer(source)
25
	scanner := bufio.NewScanner(buffer)
26
	for scanner.Scan() {
27
		// Add a newline character on each line after the first
28
		if len(lines) > 0 {
29
			lines = append(lines, byte('\n'))
30
		}
31
32
		line := scanner.Bytes()
33
		short = reCommentShortBegin.Match(line)
34
		long = long || reCommentLongBegin.Match(line)
35
36
		if !(short || long) {
37
			// Look for pairings
38
			re := regexp.MustCompile(`(\w?)([<>!\+\-\*\^%/\^=&\|]?[=\|&]|[<>])(\w?)`)
39
			line = re.ReplaceAll(line, []byte("$1 $2 $3"))
40
41
			// Incrementors and decrementors
42
			re = regexp.MustCompile(`([^\(])([\W])(\+\+|\-\-)(\w)`)
43
			line = re.ReplaceAll(line, []byte("$1$2 $3$4"))
44
			re = regexp.MustCompile(`(\w)(\+\+|\-\-)([^\)])`)
45
			line = re.ReplaceAll(line, []byte("$1$2 $3$4"))
46
47
			// // Singlets
48
			// re = regexp.MustCompile(`([\+\-\*/][^<>=\-\+\)]?)(\w)`)
49
			// line = re.ReplaceAll(line, []byte("$1 $2"))
50
			// re = regexp.MustCompile(`(\w)([\+\-\*/][^<>=\-\+\)]?)`)
51
			// line = re.ReplaceAll(line, []byte("$1 $2"))
52
53
			// Fix generics
54
			re = regexp.MustCompile(`( < )(.*)( >\s*)`)
55
			line = re.ReplaceAll(line, []byte("<$2>"))
56
		}
57
58
		short = false
59
		long = long && !reCommentLongEnd.Match(line)
60
		lines = append(lines, line...)
61
	}
62
63
	return lines
64
}
65
66
// func applySymbolsMustBeSpacedCorrectly(source []byte) []byte {
67
// 	alphaNumeric := `(\S)`
68
// 	symbols := `([|=|\+|\-|\*|/|\||&|\^|<|>|!|])`
69
// 	unary := `([!])`
70
//
71
// 	reCommentShortBegin := regexp.MustCompile(`\A\s*(/{2,})`)
72
// 	reCommentLongBegin := regexp.MustCompile(`\A\s*(/\*)`)
73
// 	reCommentLongEnd := regexp.MustCompile(`\*/`)
74
//
75
// 	short := false
76
// 	long := false
77
// 	lines := []byte{}
78
// 	buffer := bytes.NewBuffer(source)
79
// 	scanner := bufio.NewScanner(buffer)
80
// 	for scanner.Scan() {
81
// 		// Add a newline character on each line after the first
82
// 		if len(lines) > 0 {
83
// 			lines = append(lines, byte('\n'))
84
// 		}
85
//
86
// 		line := scanner.Bytes()
87
//
88
// 		short = reCommentShortBegin.Match(line)
89
// 		long = long || reCommentLongBegin.Match(line)
90
//
91
// 		if !(short || long) {
92
// 			// Break apart all the symbols
93
// 			re := regexp.MustCompile(alphaNumeric + symbols)
94
// 			for re.Match(line) {
95
// 				line = re.ReplaceAll(line, []byte("$1 $2"))
96
// 			}
97
// 			re = regexp.MustCompile(symbols + alphaNumeric)
98
// 			for re.Match(line) {
99
// 				line = re.ReplaceAll(line, []byte("$1 $2"))
100
// 			}
101
//
102
// 			// Fix basic spacing
103
// 			re = regexp.MustCompile(symbols + `([\s])` + symbols)
104
// 			for re.Match(line) {
105
// 				line = re.ReplaceAll(line, []byte("$1$3"))
106
// 			}
107
//
108
// 			// Fix unary operators
109
// 			re = regexp.MustCompile(`([\S])([\s]*)` + unary + `([\s])([\S])`)
110
// 			for re.Match(line) {
111
// 				line = re.ReplaceAll(line, []byte("$1 $3$5"))
112
// 			}
113
//
114
// 			// Fix generics
115
// 			re = regexp.MustCompile(`( < )(.*)( >\s*)`)
116
// 			for re.Match(line) {
117
// 				line = re.ReplaceAll(line, []byte("<$2>"))
118
// 			}
119
//
120
// 			// Fix incrementer/decrementer
121
// 			re = regexp.MustCompile(`(--|\+\+)` + symbols)
122
// 			for re.Match(line) {
123
// 				line = re.ReplaceAll(line, []byte("$1 $2"))
124
// 			}
125
// 			re = regexp.MustCompile(symbols + `(--|\+\+)`)
126
// 			for re.Match(line) {
127
// 				line = re.ReplaceAll(line, []byte("$1 $2"))
128
// 			}
129
// 			re = regexp.MustCompile(`([A-z]|[\d]|[\(])([\s])(--|\+\+)`)
130
// 			for re.Match(line) {
131
// 				line = re.ReplaceAll(line, []byte("$1$3"))
132
// 			}
133
// 			re = regexp.MustCompile(`(--|\+\+)([\s])([A-z]|[\d]|[\)])`)
134
// 			for re.Match(line) {
135
// 				line = re.ReplaceAll(line, []byte("$1$3"))
136
// 			}
137
//
138
// 			lines = append(lines, line...)
139
// 		} else {
140
// 			lines = append(lines, line...)
141
// 		}
142
//
143
// 		short = false
144
// 		long = long && !reCommentLongEnd.Match(line)
145
// 	}
146
//
147
// 	return lines
148
// }

+ 56 - 0
rules/symbolsMustBeSpacedCorrectly_test.go

@ -0,0 +1,56 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestSymbolsMustBeSpacedCorrectly(t *testing.T) {
10
	input := []byte(`/// <summary>
11
	int i=0;
12
	if (i==0) i+=2;
13
	if (i!=0) i-=2;
14
	if (i>0) i/=1;
15
	if (i<0) i*=1;
16
	// This is a comment
17
	if (i<=0) i++*0;
18
	/* This is another comment */
19
	if (i>=0)++i;
20
	/* <This is a comment>
21
	 *
22
	 */
23
	for (var i=1; i>-1; i--) {}
24
	if (i||i)--i;
25
	if (i&&i) i--*0;
26
	if (1+1==2) i-1+(i*3)/(i/1)
27
	a&&!b
28
	if!(true);
29
	as IEnumerable<Namespace.ClassName>`)
30
	expected := []byte(`/// <summary>
31
	int i = 0;
32
	if (i == 0) i += 2;
33
	if (i != 0) i -= 2;
34
	if (i > 0) i /= 1;
35
	if (i < 0) i *= 1;
36
	// This is a comment
37
	if (i <= 0) i++ * 0;
38
	/* This is another comment */
39
	if (i >= 0) ++i;
40
	/* <This is a comment>
41
	 *
42
	 */
43
	for (var i = 1; i > -1; i-- ) {}
44
	if (i || i) --i;
45
	if (i && i) i-- * 0;
46
	if (1 + 1 == 2) i - 1 + (i * 3) / (i / 1)
47
	a && !b
48
	if !(true);
49
	as IEnumerable<Namespace.ClassName>`)
50
51
	actual := applySymbolsMustBeSpacedCorrectly(input)
52
	if !bytes.Equal(expected, actual) {
53
		fmt.Println(string(actual))
54
		t.Fail()
55
	}
56
}

+ 18 - 0
rules/tabsMustNotBeUsed.go

@ -0,0 +1,18 @@
1
package rules
2
3
import "regexp"
4
5
var tabsMustNotBeUsed = &Rule{
6
	Name:        "Tabs must not be used",
7
	Enabled:     true,
8
	Apply:       applyTabsMustNotBeUsed,
9
	Description: `A violation of this rule occurs whenver the code contains a tab character.`,
10
}
11
12
func applyTabsMustNotBeUsed(source []byte) []byte {
13
	re := regexp.MustCompile(`\t`)
14
	for re.Match(source) {
15
		source = re.ReplaceAllLiteral(source, []byte("    "))
16
	}
17
	return source
18
}

+ 32 - 0
rules/tabsMustNotBeUsed_test.go

@ -0,0 +1,32 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestTabsMustNotBeUsed(t *testing.T) {
10
	input := []byte(`public void FunctionName(string s, int i)
11
{
12
	var i = 0; // blah
13
	for (i = 0; i < 4; i++) {
14
		// Do something
15
	}
16
	return s + i.ToString();
17
}`)
18
	expected := []byte(`public void FunctionName(string s, int i)
19
{
20
    var i = 0; // blah
21
    for (i = 0; i < 4; i++) {
22
        // Do something
23
    }
24
    return s + i.ToString();
25
}`)
26
27
	actual := applyTabsMustNotBeUsed(input)
28
	if !bytes.Equal(expected, actual) {
29
		fmt.Println(string(actual))
30
		t.Fail()
31
	}
32
}

+ 70 - 0
rules/usingDirectivesMustBeOrderedAlphabeticallyByNamespace.go

@ -0,0 +1,70 @@
1
package rules
2
3
import (
4
	"bufio"
5
	"bytes"
6
	"fmt"
7
	"regexp"
8
	"sort"
9
	"strings"
10
)
11
12
var usingDirectivesMustBeOrderedAlphabeticallyByNamespace = &Rule{
13
	Name:        "Using directives must be ordered alphabetically by namespace",
14
	Enabled:     true,
15
	Apply:       applyUsingDirectivesMustBeOrderedAlphabeticallyByNamespace,
16
	Description: ``,
17
}
18
19
func applyUsingDirectivesMustBeOrderedAlphabeticallyByNamespace(source []byte) []byte {
20
	reCommentShortBegin := regexp.MustCompile(`\A\s*(/{2,})`)
21
	reCommentLongBegin := regexp.MustCompile(`\A\s*(/\*)`)
22
	reCommentLongEnd := regexp.MustCompile(`\*/`)
23
24
	short := false
25
	long := false
26
	skipNewline := false
27
	lines := []byte{}
28
	usings := []string{}
29
30
	buffer := bytes.NewBuffer(source)
31
	scanner := bufio.NewScanner(buffer)
32
	for scanner.Scan() {
33
		// Add a newline character on each line after the first
34
		if len(lines) > 0 && !skipNewline {
35
			lines = append(lines, byte('\n'))
36
		}
37
38
		skipNewline = false
39
		line := scanner.Bytes()
40
		short = reCommentShortBegin.Match(line)
41
		long = long || reCommentLongBegin.Match(line)
42
43
		if !(short || long) {
44
			// Find usings
45
			re := regexp.MustCompile(`(.*)(using)([\t| ])([^\(])(.*)([;])`)
46
			if re.Match(line) {
47
				using := re.ReplaceAll(line, []byte("$2 $4$5"))
48
				usings = append(usings, string(using))
49
				skipNewline = true
50
				line = []byte{}
51
			}
52
		}
53
54
		short = false
55
		long = long && !reCommentLongEnd.Match(line)
56
		lines = append(lines, line...)
57
	}
58
59
	if len(usings) > 0 {
60
		// Sort the usings and add them to the top of the file
61
		s := sort.StringSlice(usings)
62
		if !sort.IsSorted(s) {
63
			sort.Sort(s)
64
		}
65
66
		lines = append([]byte(fmt.Sprintf("%s;\n\n", strings.Join(s, ";\n"))), lines...)
67
	}
68
69
	return lines
70
}

+ 55 - 0
rules/usingDirectivesMustBeOrderedAlphabeticallyByNamespace_test.go

@ -0,0 +1,55 @@
1
package rules
2
3
import (
4
	"bytes"
5
	"fmt"
6
	"testing"
7
)
8
9
func TestUsingDirectivesMustBeOrderedAlphabeticallyByNamespace(t *testing.T) {
10
	input := []byte(`using Company;
11
using CompanyB.System;
12
using Company.Collections.Generic;
13
using Company.Linq;
14
15
namespace Company.Blah {}
16
using (var something = new Something()) {}`)
17
	expected := []byte(`using Company;
18
using Company.Collections.Generic;
19
using Company.Linq;
20
using CompanyB.System;
21
22
namespace Company.Blah {}
23
using (var something = new Something()) {}`)
24
25
	actual := applyUsingDirectivesMustBeOrderedAlphabeticallyByNamespace(input)
26
	if !bytes.Equal(expected, actual) {
27
		fmt.Println(string(actual))
28
		t.Fail()
29
	}
30
31
	input = []byte(`using System;
32
using System.Collections.Generic;
33
using System.Linq;
34
using System.Web.Services;
35
using System.Web.UI;
36
using Usar.Eks.ProjDoc.Concrete;
37
using Usar.Eks.ProjDoc.Extensions;
38
39
namespace Company.Blah {}`)
40
	expected = []byte(`using System;
41
using System.Collections.Generic;
42
using System.Linq;
43
using System.Web.Services;
44
using System.Web.UI;
45
using Usar.Eks.ProjDoc.Concrete;
46
using Usar.Eks.ProjDoc.Extensions;
47
48
namespace Company.Blah {}`)
49
50
	actual = applyUsingDirectivesMustBeOrderedAlphabeticallyByNamespace(input)
51
	if !bytes.Equal(expected, actual) {
52
		fmt.Println(string(actual))
53
		t.Fail()
54
	}
55
}

+ 93 - 0
sourceFile.go

@ -0,0 +1,93 @@
1
package main
2
3
import (
4
	"io/ioutil"
5
	"os"
6
	"path/filepath"
7
	"strings"
8
)
9
10
var (
11
	extensions = [...]string{".cs"}
12
)
13
14
// SourceFile represents a file declared as source code.
15
type SourceFile struct {
16
	Path string
17
}
18
19
func (f *SourceFile) Exists() bool {
20
	if _, err := os.Stat(f.Path); os.IsNotExist(err) {
21
		return false
22
	}
23
	return true
24
}
25
26
func (f *SourceFile) IsDir() bool {
27
	fi, err := os.Stat(f.Path)
28
	if err != nil {
29
		return false
30
	}
31
	return fi.IsDir()
32
}
33
34
func (f *SourceFile) IsDotNet() bool {
35
	name := strings.ToLower(f.Path)
36
	for _, extension := range extensions {
37
		if strings.HasSuffix(name, extension) {
38
			return true
39
		}
40
	}
41
	return false
42
}
43
44
// Read the file contents.
45
func (f *SourceFile) Read() ([]byte, error) {
46
	contents, err := ioutil.ReadFile(f.Path)
47
	if err != nil {
48
		return nil, err
49
	}
50
	return contents, nil
51
}
52
53
// Write the contents out to a stream.
54
func (f *SourceFile) Write(contents []byte) error {
55
	fi, err := os.OpenFile(f.Path, os.O_TRUNC|os.O_WRONLY, 0666)
56
	if err != nil {
57
		return err
58
	}
59
	defer fi.Close()
60
61
	_, err = fi.Write(contents)
62
	if err != nil {
63
		return err
64
	}
65
	return nil
66
}
67
68
// Walk a directory's file structure looking for source files.
69
func (f *SourceFile) Walk() chan SourceFile {
70
	c := make(chan SourceFile)
71
72
	go func() {
73
		filepath.Walk(f.Path, func(p string, fi os.FileInfo, e error) error {
74
			if e != nil {
75
				return e
76
			}
77
78
			s := SourceFile{
79
				Path: p,
80
			}
81
82
			if s.IsDotNet() {
83
				c <- s
84
			}
85
86
			return nil
87
		})
88
89
		defer close(c)
90
	}()
91
92
	return c
93
}

+ 0 - 0
testdata/empty.cs