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