├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── lex.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.5 2 | *.6 3 | *.8 4 | golint 5 | .*.swp 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Scott Lawrence 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, 2012 The Golint Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived from this 14 | software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | Subject to the terms and conditions of this License, Google hereby grants to 28 | You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable 29 | (except as stated in this section) patent license to make, have made, use, 30 | offer to sell, sell, import, and otherwise transfer this implementation of Go, 31 | where such license applies only to those patent claims licensable by Google 32 | that are necessarily infringed by use of this implementation of Go. If You 33 | institute patent litigation against any entity (including a cross-claim or 34 | counterclaim in a lawsuit) alleging that this implementation of Go or a 35 | Contribution incorporated within this implementation of Go constitutes direct 36 | or contributory patent infringement, then any patent licenses granted to You 37 | under this License for this implementation of Go shall terminate as of the date 38 | such litigation is filed. 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | Golint is a static source checker for the Go programming language. 5 | -------------------------------------------------------------------------------- /lex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "unicode" 10 | ) 11 | 12 | const ( 13 | T_COMMENT = iota 14 | T_SEMI 15 | T_IDENT 16 | T_KEYWORD 17 | T_OPER 18 | T_INT 19 | T_FLOAT 20 | T_IMAG 21 | T_CHAR 22 | T_STRING 23 | ) 24 | 25 | type runeReader interface { 26 | ReadRune() (rune, int, error) 27 | } 28 | 29 | type Position struct { 30 | Line uint32 31 | Char uint32 32 | } 33 | 34 | type Token struct { 35 | Kind int 36 | Data interface{} 37 | Pos Position 38 | } 39 | 40 | func (t Token) String() string { 41 | switch t.Kind { 42 | case T_IDENT: fallthrough 43 | case T_KEYWORD: fallthrough 44 | case T_OPER: 45 | return t.Data.(string) 46 | case T_SEMI: 47 | return "" 48 | case T_CHAR: fallthrough 49 | case T_STRING: 50 | return fmt.Sprintf("'%#v'", t.Data) 51 | } 52 | return "" 53 | } 54 | 55 | var keywords = map[string]bool{ 56 | "break": true, 57 | "default": true, 58 | "func": true, 59 | "interface": true, 60 | "select": true, 61 | "case": true, 62 | "defer": true, 63 | "go": true, 64 | "map": true, 65 | "struct": true, 66 | "chan": true, 67 | "else": true, 68 | "goto": true, 69 | "package": true, 70 | "switch": true, 71 | "const": true, 72 | "fallthrough": true, 73 | "if": true, 74 | "range": true, 75 | "type": true, 76 | "continue": true, 77 | "for": true, 78 | "import": true, 79 | "return": true, 80 | "var": true, 81 | } 82 | 83 | func isKeyword(id string) bool { 84 | _, ok := keywords[id] 85 | return ok 86 | } 87 | 88 | // White space, formed from spaces (U+0020), horizontal tabs (U+0009), carriage 89 | // returns (U+000D), and newlines (U+000A) 90 | func isWS(r rune) bool { 91 | return r == ' ' || r == '\t' || r == '\r' || r == '\n' 92 | } 93 | 94 | func isLetter(r rune) bool { 95 | return unicode.IsLetter(r) || r == '_' 96 | } 97 | 98 | func LexString(src []byte) (ts []Token, err error) { 99 | var ltok *Token 100 | s := bytes.NewReader(src) 101 | r, _, err := s.ReadRune() 102 | 103 | pos := Position{1, 0} 104 | token := func(k int, d interface{}) { 105 | ts = append(ts, Token{k, d, pos}) 106 | ltok = &ts[len(ts)-1] 107 | } 108 | nextR := func() bool { 109 | r, _, err = s.ReadRune() 110 | pos.Char++ 111 | return err == nil 112 | } 113 | mustR := func() bool { 114 | if !nextR() { 115 | fmt.Errorf("unexpected error: %s\n", err.Error()) 116 | return false 117 | } 118 | return true 119 | } 120 | 121 | for err == nil { 122 | if r == '\n' { goto Newline } 123 | if isWS(r) { goto Next } 124 | 125 | // identifiers and keywords 126 | if isLetter(r) { 127 | // identifier = letter { letter | unicode_digit } . 128 | str := string(r) 129 | for nextR() && (isLetter(r) || unicode.IsDigit(r)) { 130 | str += string(r) 131 | } 132 | if isKeyword(str) { 133 | token(T_KEYWORD, str) 134 | goto Next 135 | } else { 136 | token(T_IDENT, str) 137 | goto Next 138 | } 139 | } 140 | 141 | if r == ';' { 142 | token(T_SEMI, ";") 143 | goto Next 144 | } 145 | 146 | // character literal 147 | if r == '\'' { 148 | if !mustR() { break } 149 | if r == '\'' { 150 | err = fmt.Errorf("unexpected single quote\n") 151 | break 152 | } 153 | if r == '\\' { 154 | /* 155 | \a U+0007 alert or bell 156 | \b U+0008 backspace 157 | \f U+000C form feed 158 | \n U+000A line feed or newline 159 | \r U+000D carriage return 160 | \t U+0009 horizontal tab 161 | \v U+000b vertical tab 162 | \\ U+005c backslash 163 | \' U+0027 single quote 164 | \000 165 | \x.. 166 | \u.... 167 | \U........ 168 | */ 169 | if !mustR() { break } 170 | switch r { 171 | case 'a': token(T_CHAR, '\a') 172 | case 'b': token(T_CHAR, '\b') 173 | case 'f': token(T_CHAR, '\f') 174 | case 'n': token(T_CHAR, '\n') 175 | case 'r': token(T_CHAR, '\r') 176 | case 't': token(T_CHAR, '\t') 177 | case 'v': token(T_CHAR, '\v') 178 | case '\\': token(T_CHAR, '\\') 179 | case '\'': token(T_CHAR, '\'') 180 | case 'x': 181 | case 'u': 182 | case 'U': 183 | case '0': fallthrough 184 | case '1': fallthrough 185 | case '2': fallthrough 186 | case '3': fallthrough 187 | case '4': fallthrough 188 | case '5': fallthrough 189 | case '6': fallthrough 190 | case '7': 191 | } 192 | goto Next 193 | } 194 | 195 | c := r 196 | if !mustR() { break } 197 | if r != '\'' { 198 | err = fmt.Errorf("expected single quote\n") 199 | break 200 | } 201 | token(T_CHAR, c) 202 | goto Next 203 | } 204 | 205 | // string literal 206 | 207 | // numerical literals 208 | 209 | // operators, delimiters, etc. 210 | Newline: 211 | // attempt semicolon insertion 212 | if ltok != nil { 213 | if ltok.Kind == T_IDENT || 214 | ltok.Kind == T_INT || 215 | ltok.Kind == T_FLOAT || 216 | ltok.Kind == T_IMAG || 217 | ltok.Kind == T_CHAR || 218 | ltok.Kind == T_STRING || 219 | ltok.Kind == T_KEYWORD && ( 220 | ltok.Data == "break" || 221 | ltok.Data == "continue" || 222 | ltok.Data == "fallthrough" || 223 | ltok.Data == "return" ) || 224 | ltok.Kind == T_OPER && ( 225 | ltok.Data == "++" || 226 | ltok.Data == "--" || 227 | ltok.Data == "}" || 228 | ltok.Data == ")" || 229 | ltok.Data == "]") { 230 | token(T_SEMI, "") 231 | } 232 | ltok = nil 233 | } 234 | pos.Char = 0 235 | pos.Line++ 236 | Next: 237 | r, _, err = s.ReadRune() 238 | pos.Char++ 239 | } 240 | if err == io.EOF { err = nil } 241 | return 242 | } 243 | 244 | func Lex(r io.Reader) (ts []Token, err error) { 245 | src, err := ioutil.ReadAll(r) 246 | if err != nil { return } 247 | return LexString(src) 248 | } 249 | 250 | func PrintLex(r io.Reader) { 251 | ts, err := Lex(r) 252 | if err != nil { 253 | fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 254 | } 255 | for _, t := range ts { 256 | fmt.Println(t) 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | p "path/filepath" 8 | "strings" 9 | ) 10 | 11 | var version = `0.3.0` 12 | 13 | var ( 14 | verbose = flag.Bool("v", false, "verbose output") 15 | showversion = flag.Bool("version", false, "show version information") 16 | ) 17 | 18 | func verb(f string, a ...interface{}) { 19 | if *verbose { 20 | fmt.Fprintf(os.Stderr, f, a...) 21 | } 22 | } 23 | 24 | func main() { 25 | flag.Parse() 26 | files := flag.Args() 27 | 28 | if *showversion { 29 | fmt.Printf("golint %s\n", version) 30 | return 31 | } 32 | 33 | verb("Scanning for source files... ") 34 | if len(files) == 0 { 35 | // just use the current directory if no files were specified 36 | files = make([]string, 1) 37 | files[0] = "." 38 | } 39 | 40 | srcs := make([]string, 0) 41 | for _, fname := range files { 42 | srcs = append(srcs, listFiles(fname, ".go")...) 43 | } 44 | verb("\n") 45 | 46 | for _, sf := range srcs { 47 | f, err := os.Open(sf) 48 | if err != nil { 49 | fmt.Fprintf(os.Stderr, " ! %s\n", sf) 50 | continue 51 | } 52 | PrintLex(f) 53 | f.Close() 54 | } 55 | } 56 | 57 | func listFiles(fname string, suf string) (fs []string) { 58 | info, err := os.Stat(fname) 59 | if err != nil { 60 | return 61 | } 62 | if info.IsDir() { 63 | f, _ := os.Open(fname) 64 | dn, _ := f.Readdirnames(-1) 65 | for _, filename := range dn { 66 | if filename[0] == '.' { 67 | continue 68 | } 69 | filename = p.Join(fname, filename) 70 | info, err := os.Stat(filename) 71 | if err != nil { 72 | continue 73 | } 74 | if info.IsDir() { 75 | fs = append(fs, listFiles(filename, suf)...) 76 | } else if strings.HasSuffix(filename, suf) { 77 | verb("%s ", filename) 78 | fs = append(fs, filename) 79 | } 80 | } 81 | } else if strings.HasSuffix(fname, suf) { 82 | verb("%s", fname) 83 | fs = append(fs, fname) 84 | } 85 | return 86 | } 87 | --------------------------------------------------------------------------------