1 // Copyright Brian Schott (Hackerpilot) 2012. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module main; 7 8 import std.algorithm; 9 import std.array; 10 import std.conv; 11 import std.file; 12 import std.getopt; 13 import std.path; 14 import std.stdio; 15 import std.range; 16 import std.experimental.lexer; 17 import std.typecons : scoped; 18 import dparse.lexer; 19 import dparse.parser; 20 import dparse.rollback_allocator; 21 22 import highlighter; 23 import stats; 24 import ctags; 25 import etags; 26 import astprinter; 27 import imports; 28 import outliner; 29 import symbol_finder; 30 import analysis.run; 31 import analysis.config; 32 import dscanner_version; 33 import readers; 34 35 import inifiled; 36 37 import dsymbol.modulecache; 38 39 version (unittest) 40 void main() 41 { 42 } 43 else 44 int main(string[] args) 45 { 46 bool sloc; 47 bool highlight; 48 bool ctags; 49 bool etags; 50 bool etagsAll; 51 bool help; 52 bool tokenCount; 53 bool syntaxCheck; 54 bool ast; 55 bool imports; 56 bool recursiveImports; 57 bool muffin; 58 bool outline; 59 bool tokenDump; 60 bool styleCheck; 61 bool defaultConfig; 62 bool report; 63 bool skipTests; 64 string symbolName; 65 string configLocation; 66 string[] importPaths; 67 bool printVersion; 68 bool explore; 69 70 try 71 { 72 // dfmt off 73 getopt(args, std.getopt.config.caseSensitive, 74 "sloc|l", &sloc, 75 "highlight", &highlight, 76 "ctags|c", &ctags, 77 "help|h", &help, 78 "etags|e", &etags, 79 "etagsAll", &etagsAll, 80 "tokenCount|t", &tokenCount, 81 "syntaxCheck|s", &syntaxCheck, 82 "ast|xml", &ast, 83 "imports|i", &imports, 84 "recursiveImports", &recursiveImports, 85 "outline|o", &outline, 86 "tokenDump", &tokenDump, 87 "styleCheck|S", &styleCheck, 88 "defaultConfig", &defaultConfig, 89 "declaration|d", &symbolName, 90 "config", &configLocation, 91 "report", &report, 92 "I", &importPaths, 93 "version", &printVersion, 94 "muffinButton", &muffin, 95 "explore", &explore, 96 "skipTests", &skipTests); 97 //dfmt on 98 } 99 catch (ConvException e) 100 { 101 stderr.writeln(e.msg); 102 return 1; 103 } 104 105 if (muffin) 106 { 107 stdout.writeln(` ___________ 108 __(#*O 0** @%*)__ 109 _(%*o#*O%*0 #O#%##@)_ 110 (*#@%#o*@ #o%O*%@ #o #) 111 \=====================/ 112 |I|I|I|I|I|I|I|I|I|I| 113 |I|I|I|I|I|I|I|I|I|I| 114 |I|I|I|I|I|I|I|I|I|I| 115 |I|I|I|I|I|I|I|I|I|I|`); 116 return 0; 117 } 118 119 if (explore) 120 { 121 stdout.writeln("D-Scanner: Scanning..."); 122 stderr.writeln("D-Scanner: No new astronomical objects discovered."); 123 return 1; 124 } 125 126 if (help) 127 { 128 printHelp(args[0]); 129 return 0; 130 } 131 132 if (printVersion) 133 { 134 version (Windows) 135 writeln(DSCANNER_VERSION); 136 else version (built_with_dub) 137 writeln(DSCANNER_VERSION); 138 else 139 write(DSCANNER_VERSION, " ", GIT_HASH); 140 return 0; 141 } 142 143 const(string[]) absImportPaths = importPaths.map!(a => a.absolutePath() 144 .buildNormalizedPath()).array(); 145 146 auto alloc = scoped!(dsymbol.modulecache.ASTAllocator)(); 147 auto moduleCache = ModuleCache(alloc); 148 149 if (absImportPaths.length) 150 moduleCache.addImportPaths(absImportPaths); 151 152 immutable optionCount = count!"a"([sloc, highlight, ctags, tokenCount, syntaxCheck, ast, imports, 153 outline, tokenDump, styleCheck, defaultConfig, report, 154 symbolName !is null, etags, etagsAll, recursiveImports]); 155 if (optionCount > 1) 156 { 157 stderr.writeln("Too many options specified"); 158 return 1; 159 } 160 else if (optionCount < 1) 161 { 162 printHelp(args[0]); 163 return 1; 164 } 165 166 // --report implies --styleCheck 167 if (report) 168 styleCheck = true; 169 170 immutable usingStdin = args.length == 1; 171 172 StringCache cache = StringCache(StringCache.defaultBucketCount); 173 if (defaultConfig) 174 { 175 string s = getConfigurationLocation(); 176 mkdirRecurse(findSplitBefore(s, "dscanner.ini")[0]); 177 StaticAnalysisConfig saConfig = defaultStaticAnalysisConfig(); 178 writeln("Writing default config file to ", s); 179 writeINIFile(saConfig, s); 180 } 181 else if (tokenDump || highlight) 182 { 183 ubyte[] bytes = usingStdin ? readStdin() : readFile(args[1]); 184 LexerConfig config; 185 config.stringBehavior = StringBehavior.source; 186 187 if (highlight) 188 { 189 auto tokens = byToken(bytes, config, &cache); 190 highlighter.highlight(tokens, args.length == 1 ? "stdin" : args[1]); 191 return 0; 192 } 193 else if (tokenDump) 194 { 195 auto tokens = getTokensForParser(bytes, config, &cache); 196 writeln( 197 "text \tblank\tindex\tline\tcolumn\ttype\tcomment\ttrailingComment"); 198 foreach (token; tokens) 199 { 200 writefln("<<%20s>>\t%b\t%d\t%d\t%d\t%d\t%s\t%s", 201 token.text is null ? str(token.type) : token.text, 202 token.text is null, 203 token.index, 204 token.line, 205 token.column, 206 token.type, 207 token.comment, 208 token.trailingComment); 209 } 210 return 0; 211 } 212 } 213 else if (symbolName !is null) 214 { 215 stdout.findDeclarationOf(symbolName, expandArgs(args)); 216 } 217 else if (ctags) 218 { 219 stdout.printCtags(expandArgs(args)); 220 } 221 else if (etags || etagsAll) 222 { 223 stdout.printEtags(etagsAll, expandArgs(args)); 224 } 225 else if (styleCheck) 226 { 227 StaticAnalysisConfig config = defaultStaticAnalysisConfig(); 228 string s = configLocation is null ? getConfigurationLocation() : configLocation; 229 if (s.exists()) 230 readINIFile(config, s); 231 if (skipTests) 232 config.fillConfig!(Check.skipTests); 233 if (report) 234 generateReport(expandArgs(args), config, cache, moduleCache); 235 else 236 return analyze(expandArgs(args), config, cache, moduleCache, true) ? 1 : 0; 237 } 238 else if (syntaxCheck) 239 { 240 return .syntaxCheck(usingStdin ? ["stdin"] : expandArgs(args), cache, moduleCache) ? 1 : 0; 241 } 242 else 243 { 244 if (sloc || tokenCount) 245 { 246 if (usingStdin) 247 { 248 LexerConfig config; 249 config.stringBehavior = StringBehavior.source; 250 auto tokens = byToken(readStdin(), config, &cache); 251 if (tokenCount) 252 printTokenCount(stdout, "stdin", tokens); 253 else 254 printLineCount(stdout, "stdin", tokens); 255 } 256 else 257 { 258 ulong count; 259 foreach (f; expandArgs(args)) 260 { 261 262 LexerConfig config; 263 config.stringBehavior = StringBehavior.source; 264 auto tokens = byToken(readFile(f), config, &cache); 265 if (tokenCount) 266 count += printTokenCount(stdout, f, tokens); 267 else 268 count += printLineCount(stdout, f, tokens); 269 } 270 writefln("total:\t%d", count); 271 } 272 } 273 else if (imports || recursiveImports) 274 { 275 printImports(usingStdin, args, importPaths, &cache, recursiveImports); 276 } 277 else if (ast || outline) 278 { 279 string fileName = usingStdin ? "stdin" : args[1]; 280 RollbackAllocator rba; 281 LexerConfig config; 282 config.fileName = fileName; 283 config.stringBehavior = StringBehavior.source; 284 auto tokens = getTokensForParser(usingStdin ? readStdin() 285 : readFile(args[1]), config, &cache); 286 auto mod = parseModule(tokens, fileName, &rba, &doNothing); 287 288 if (ast) 289 { 290 auto printer = new XMLPrinter; 291 printer.output = stdout; 292 printer.visit(mod); 293 } 294 else if (outline) 295 { 296 auto outliner = new Outliner(stdout); 297 outliner.visit(mod); 298 } 299 } 300 } 301 return 0; 302 } 303 304 void printHelp(string programName) 305 { 306 stderr.writefln(` 307 Usage: %s <options> 308 309 Options: 310 --help, -h 311 Prints this help message 312 313 --version 314 Prints the program version 315 316 --sloc <file | directory>..., -l <file | directory>... 317 Prints the number of logical lines of code in the given 318 source files. If no files are specified, input is read from stdin. 319 320 --tokenCount <file | directory>..., -t <file | directory>... 321 Prints the number of tokens in the given source files. If no files are 322 specified, input is read from stdin. 323 324 --highlight <file> 325 Syntax-highlight the given source file. The resulting HTML will be 326 written to standard output. If no file is specified, input is read 327 from stdin. 328 329 --imports <file>, -i <file> 330 Prints modules imported by the given source file. If no files are 331 specified, input is read from stdin. Combine with "-I" arguments to 332 resolve import locations. 333 334 --recursive-imports <file> 335 Similar to "--imports", but lists imports of imports recursively. 336 337 --syntaxCheck <file>, -s <file> 338 Lexes and parses sourceFile, printing the line and column number of any 339 syntax errors to stdout. One error or warning is printed per line. 340 If no files are specified, input is read from stdin. %1$s will exit with 341 a status code of zero if no errors are found, 1 otherwise. 342 343 --styleCheck|S <file | directory>..., <file | directory>... 344 Lexes and parses sourceFiles, printing the line and column number of any 345 static analysis check failures stdout. %1$s will exit with a status code 346 of zero if no warnings or errors are found, 1 otherwise. 347 348 --ctags <file | directory>..., -c <file | directory>... 349 Generates ctags information from the given source code file. Note that 350 ctags information requires a filename, so stdin cannot be used in place 351 of a filename. 352 353 --etags <file | directory>..., -e <file | directory>... 354 Generates etags information from the given source code file. Note that 355 etags information requires a filename, so stdin cannot be used in place 356 of a filename. 357 358 --etagsAll <file | directory>... 359 Same as --etags except private and package declarations are tagged too. 360 361 --ast <file> | --xml <file> 362 Generates an XML representation of the source files abstract syntax 363 tree. If no files are specified, input is read from stdin. 364 365 --declaration <symbolName> <file | directory>..., 366 -d <symbolName> <file | directory>... 367 Find the location where symbolName is declared. This should be more 368 accurate than "grep". Searches the given files and directories, or the 369 current working directory if none are specified. 370 371 --report <file | directory>... 372 Generate a static analysis report in JSON format. Implies --styleCheck, 373 however the exit code will still be zero if errors or warnings are 374 found. 375 376 --config <file> 377 Use the given configuration file instead of the default located in 378 $HOME/.config/dscanner/dscanner.ini 379 380 --defaultConfig 381 Generates a default configuration file for the static analysis checks, 382 383 --skipTests 384 Does not analyze in the unittests. Only works if --styleCheck.`, 385 386 programName); 387 } 388 389 private void doNothing(string, size_t, size_t, string, bool) 390 { 391 } 392 393 private enum CONFIG_FILE_NAME = "dscanner.ini"; 394 version (linux) version = useXDG; 395 version (BSD) version = useXDG; 396 version (FreeBSD) version = useXDG; 397 version (OSX) version = useXDG; 398 399 /** 400 * Locates the default configuration file 401 */ 402 string getDefaultConfigurationLocation() 403 { 404 version (useXDG) 405 { 406 import std.process : environment; 407 408 string configDir = environment.get("XDG_CONFIG_HOME", null); 409 if (configDir is null) 410 { 411 configDir = environment.get("HOME", null); 412 if (configDir is null) 413 throw new Exception("Both $XDG_CONFIG_HOME and $HOME are unset"); 414 configDir = buildPath(configDir, ".config", "dscanner", CONFIG_FILE_NAME); 415 } 416 else 417 configDir = buildPath(configDir, "dscanner", CONFIG_FILE_NAME); 418 return configDir; 419 } 420 else version (Windows) 421 return CONFIG_FILE_NAME; 422 } 423 424 /** 425 * Searches upwards from the CWD through the directory hierarchy 426 */ 427 string tryFindConfigurationLocation() 428 { 429 auto path = pathSplitter(getcwd()); 430 string result; 431 432 while (!path.empty) 433 { 434 result = buildPath(buildPath(path), CONFIG_FILE_NAME); 435 436 if (exists(result)) 437 break; 438 439 path.popBack(); 440 } 441 442 if (path.empty) 443 return null; 444 445 return result; 446 } 447 448 /** 449 * Tries to find a config file and returns the default one on failure 450 */ 451 string getConfigurationLocation() 452 { 453 immutable config = tryFindConfigurationLocation(); 454 455 if (config !is null) 456 return config; 457 458 return getDefaultConfigurationLocation(); 459 }