diff --git a/README.md b/README.md index 060be12..2091967 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,15 @@ the given source files. * Public declarations not documented * Assignment in conditionals +### Find Declaration +Ack, grep, and The Silver Searcher are useful for finding usages of symbols, but +their signal to noise ratio is not very good when searching for a symbol's +declaration. The "--declaration" or "-d" options allow you to search for a +symbols declaration. For example: + + $ dscanner -d TokenStructure + ./libdparse/src/std/lexer.d(248:8) + ### Line of Code Count The "--sloc" or "-l" option prints the number of lines of code in the file. Instead of simply printing the number of line breaks, this counts the number of diff --git a/main.d b/main.d index 7531d38..c4cb5a2 100644 --- a/main.d +++ b/main.d @@ -23,6 +23,7 @@ import ctags; import astprinter; import imports; import outliner; +import symbol_finder; import analysis.run; import analysis.config; @@ -45,7 +46,6 @@ int run(string[] args) bool sloc; bool highlight; bool ctags; - bool recursive; bool help; bool tokenCount; bool syntaxCheck; @@ -56,15 +56,17 @@ int run(string[] args) bool tokenDump; bool styleCheck; bool defaultConfig; + string symbolName; try { getopt(args, std.getopt.config.caseSensitive, "sloc|l", &sloc, - "highlight", &highlight, "ctags|c", &ctags, "recursive|r|R", &recursive, - "help|h", &help, "tokenCount|t", &tokenCount, "syntaxCheck|s", &syntaxCheck, + "highlight", &highlight, "ctags|c", &ctags, "help|h", &help, + "tokenCount|t", &tokenCount, "syntaxCheck|s", &syntaxCheck, "ast|xml", &ast, "imports|i", &imports, "outline|o", &outline, "tokenDump", &tokenDump, "styleCheck|S", &styleCheck, - "defaultConfig", &defaultConfig, "muffinButton", &muffin); + "defaultConfig", &defaultConfig, "declaration|d", &symbolName, + "muffinButton", &muffin); } catch (ConvException e) { @@ -94,7 +96,8 @@ int run(string[] args) } auto optionCount = count!"a"([sloc, highlight, ctags, tokenCount, - syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig]); + syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig, + symbolName !is null]); if (optionCount > 1) { stderr.writeln("Too many options specified"); @@ -138,9 +141,13 @@ int run(string[] args) return 0; } } + else if (symbolName !is null) + { + stdout.findDeclarationOf(symbolName, expandArgs(args)); + } else if (ctags) { - stdout.printCtags(expandArgs(args, recursive)); + stdout.printCtags(expandArgs(args)); } else if (styleCheck) { @@ -148,11 +155,11 @@ int run(string[] args) string s = getConfigurationLocation(); if (s.exists()) readINIFile(config, s); - stdout.analyze(expandArgs(args, recursive), config); + stdout.analyze(expandArgs(args), config); } else if (syntaxCheck) { - stdout.syntaxCheck(expandArgs(args, recursive)); + stdout.syntaxCheck(expandArgs(args)); } else { @@ -172,7 +179,7 @@ int run(string[] args) else { ulong count; - foreach (f; expandArgs(args, recursive)) + foreach (f; expandArgs(args)) { LexerConfig config; @@ -237,27 +244,24 @@ int run(string[] args) return 0; } -string[] expandArgs(string[] args, bool recursive) +string[] expandArgs(string[] args) { - if (recursive) + string[] rVal; + if (args.length == 1) + args ~= "."; + foreach (arg; args[1 ..$]) { - string[] rVal; - foreach (arg; args[1 ..$]) + if (isFile(arg) && (arg.endsWith(`.d`) || arg.endsWith(`.di`))) + rVal ~= arg; + else foreach (item; dirEntries(arg, SpanMode.breadth).map!(a => a.name)) { - if (isFile(arg) && arg.endsWith(`.d`) || arg.endsWith(`.di`)) - rVal ~= arg; - else foreach (item; dirEntries(arg, SpanMode.breadth).map!(a => a.name)) - { - if (isFile(item) && (item.endsWith(`.d`) || item.endsWith(`.di`))) - rVal ~= item; - else - continue; - } + if (isFile(item) && (item.endsWith(`.d`) || item.endsWith(`.di`))) + rVal ~= item; + else + continue; } - return rVal; } - else - return args[1 .. $]; + return rVal; } ubyte[] readStdin() @@ -332,10 +336,10 @@ options: Generates an XML representation of the source files abstract syntax tree. If no files are specified, input is read from stdin. - --recursive | -R | -r - When used with --ctags, --tokenCount, or --sloc, dscanner will produce - ctags output for all .d and .di files contained within the given - directories and its sub-directories. + --declaration | -d symbolName [sourceFiles sourceDirectories] + Find the location where symbolName is declared. This should be more + accurate than "grep". Searches the given files and directories, or the + current working directory if none are specified. --defaultConfig Generates a default configuration file for the static analysis checks`, diff --git a/makefile b/makefile index 685df1f..b65d322 100644 --- a/makefile +++ b/makefile @@ -10,6 +10,7 @@ SRC = main.d\ ctags.d\ astprinter.d\ outliner.d\ + symbol_finder.d\ libdparse/src/std/*.d\ libdparse/src/std/d/*.d\ analysis/*.d\ diff --git a/symbol_finder.d b/symbol_finder.d new file mode 100644 index 0000000..07d4247 --- /dev/null +++ b/symbol_finder.d @@ -0,0 +1,87 @@ +// Copyright Brian Schott (Hackerpilot) 2014. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +module symbol_finder; + +import std.stdio : File; +import std.d.lexer; +import std.d.parser; +import std.d.ast; +import std.stdio; +import std.file:isFile; + +void findDeclarationOf(File output, string symbolName, string[] fileNames) +{ + import std.array : uninitializedArray, array; + import std.conv : to; + LexerConfig config; + StringCache cache = StringCache(StringCache.defaultBucketCount); + auto visitor = new FinderVisitor(output, symbolName); + foreach (fileName; fileNames) + { + File f = File(fileName); + assert (isFile(fileName)); + if (f.size == 0) continue; + auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size)); + f.rawRead(bytes); + auto tokens = getTokensForParser(bytes, config, &cache); + Module m = parseModule(tokens.array, fileName, null, &doNothing); + visitor.fileName = fileName; + visitor.visit(m); + } +} + +private: + +void doNothing(string, size_t, size_t, string, bool) {} + +class FinderVisitor : ASTVisitor +{ + this(File output, string symbolName) + { + this.output = output; + this.symbolName = symbolName; + } + + mixin generateVisit!FunctionDeclaration; + mixin generateVisit!ClassDeclaration; + mixin generateVisit!InterfaceDeclaration; + mixin generateVisit!StructDeclaration; + mixin generateVisit!UnionDeclaration; + mixin generateVisit!TemplateDeclaration; + + override void visit(const Declarator dec) + { + if (dec.name.text == symbolName) + output.writefln("%s(%d:%d)", fileName, dec.name.line, dec.name.column); + } + + override void visit (const AutoDeclaration ad) + { + foreach (id; ad.identifiers) + { + if (id.text == symbolName) + output.writefln("%s(%d:%d)", fileName, id.line, id.column); + } + } + + override void visit(const FunctionBody) {} + + mixin template generateVisit(T) + { + override void visit(const T t) + { + if (t.name.text == symbolName) + output.writefln("%s(%d:%d)", fileName, t.name.line, t.name.column); + t.accept(this); + } + } + + alias visit = ASTVisitor.visit; + + File output; + string symbolName; + string fileName; +}