diff --git a/src/etags.d b/src/etags.d index 08497e4..4fa8fb7 100644 --- a/src/etags.d +++ b/src/etags.d @@ -16,13 +16,24 @@ import std.path; import std.array; import std.conv; +// Prefix tags with their module name. Seems like correct behavior, but just +// in case, make it an option. +version = UseModuleContext; + +// Could not find "official" definition of protection (public/private/etc). +// Behavior modeled here was reversed engineered based on dmd 2.067. +// class/interface and non-anonymous struct/union/enum members default to +// public, regardless of the enclosing declaration. template and anonymous +// struct/union/enum members default to the enclosing protection. + /** * Prints ETAGS information to the given file. * Params: * outpt = the file that ETAGS info is written to + * tagAll = if set, tag private/package declaration too * fileNames = tags will be generated from these files */ -void printEtags(File output, string[] fileNames) +void printEtags(File output, bool tagAll, string[] fileNames) { LexerConfig config; StringCache cache = StringCache(StringCache.defaultBucketCount); @@ -34,15 +45,17 @@ void printEtags(File output, string[] fileNames) f.rawRead(bytes); auto tokens = getTokensForParser(bytes, config, &cache); Module m = parseModule(tokens.array, fileName, null, &doNothing); - auto printer = new EtagsPrinter; - // I think I like this - enum useModuleContext = true; - if (useModuleContext) - printer.context = m.moduleFullName(fileName) ~ "."; - + auto printer = new EtagsPrinter; + printer.moduleName = m.moduleFullName(fileName); + version(UseModuleContext) + printer.context = printer.moduleName ~ "."; + printer.privateVisibility = tagAll? + Visibility.exposed : + Visibility.hidden; printer.bytes = bytes.sansBOM; printer.visit(m); + output.writef("\f\n%s,%u\n", fileName, printer.tags.length); printer.tags.copy(output.lockingTextWriter); } @@ -50,6 +63,8 @@ void printEtags(File output, string[] fileNames) private: +enum Visibility {exposed, hidden} + void doNothing(string, size_t, size_t, string, bool) {} ubyte[] sansBOM(ubyte[] bytes) @@ -58,7 +73,6 @@ ubyte[] sansBOM(ubyte[] bytes) return (bytes.length >= 3 && bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf) ? bytes[3 .. $] : bytes; - } string moduleFullName(Module m, string fileName) @@ -71,23 +85,64 @@ string moduleFullName(Module m, string fileName) return m.moduleDeclaration.moduleName.identifiers.map!(i=>i.text).join("."); } -class EtagsPrinter : ASTVisitor +final class EtagsPrinter : ASTVisitor { + override void visit(const ModuleDeclaration dec) + { + auto tok0 = dec.moduleName.identifiers[0]; + auto was = context; + context = ""; + maketag(moduleName, tok0.index, tok0.line); + context = was; + dec.accept(this); + } + + override void visit(const Declaration dec) + { + // Update value of visibility based on this 'dec'. + if (dec.attributeDeclaration) + { + auto attr = dec.attributeDeclaration.attribute; + updateVisibility(attr.attribute.type); + } + + // visibility needs to be restored to what it was when changed by + // attribute. + auto was = visibility; + foreach (attr; dec.attributes) { + updateVisibility(attr.attribute.type); + } + + dec.accept(this); + visibility = was; + } + override void visit(const ClassDeclaration dec) { maketag(dec.name); + // class members default to public + visibility = Visibility.exposed; acceptInContext(dec, dec.name.text); } override void visit(const StructDeclaration dec) { + if (dec.name == tok!"") + { + dec.accept(this); + return; + } maketag(dec.name); + // struct members default to public + visibility = Visibility.exposed; acceptInContext(dec, dec.name.text); } override void visit(const InterfaceDeclaration dec) { maketag(dec.name); + // interface members default to public + visibility = Visibility.exposed; acceptInContext(dec, dec.name.text); } @@ -100,36 +155,32 @@ class EtagsPrinter : ASTVisitor override void visit(const FunctionDeclaration dec) { maketag(dec.name); - bool was = inFunc; - inFunc = true; - auto c = context; + // don't tag declarations in a function like thing + visibility = Visibility.hidden; acceptInContext(dec, dec.name.text); - inFunc = was; } override void visit(const Constructor dec) { maketag("this", dec.location, dec.line); - bool was = inFunc; - inFunc = true; - auto c = context; + // don't tag declarations in a function like thing + visibility = Visibility.hidden; acceptInContext(dec, "this"); - inFunc = was; } override void visit(const Destructor dec) { maketag("~this", dec.index, dec.line); - bool was = inFunc; - inFunc = true; - auto c = context; + // don't tag declarations in a function like thing + visibility = Visibility.hidden; acceptInContext(dec, "~this"); - inFunc = was; } override void visit(const EnumDeclaration dec) { maketag(dec.name); + // enum members default to public + visibility = Visibility.exposed; acceptInContext(dec, dec.name.text); } @@ -141,6 +192,8 @@ class EtagsPrinter : ASTVisitor return; } maketag(dec.name); + // union members default to public + visibility = Visibility.exposed; acceptInContext(dec, dec.name.text); } @@ -161,7 +214,7 @@ class EtagsPrinter : ASTVisitor dec.accept(this); inUnittest = was; } - + override void visit(const VariableDeclaration dec) { foreach (d; dec.declarators) @@ -180,20 +233,57 @@ class EtagsPrinter : ASTVisitor dec.accept(this); } + override void visit(const AliasDeclaration dec) + { + // Old style alias + if (dec.identifierList) + { + foreach (i; dec.identifierList.identifiers) + maketag(i); + } + dec.accept(this); + } + + override void visit(const AliasInitializer dec) + { + maketag(dec.name); + dec.accept(this); + } + override void visit(const Invariant dec) { maketag("invariant", dec.index, dec.line); } - void acceptInContext(Dec)(Dec dec, string name) +private: + void updateVisibility(IdType type) { + // maybe change visibility based on attribute 'type' + switch (type) + { + case tok!"export": + case tok!"public": + case tok!"protected": + visibility = Visibility.exposed; + break; + case tok!"package": + case tok!"private": + visibility = privateVisibility; + break; + default: + // no change + break; + } + } + + void acceptInContext(const ASTNode dec, string name) { // nest context before journeying on auto c = context; context ~= name ~ "."; dec.accept(this); - context = c; + context = c; } - + void maketag(Token name) { maketag(name.text, name.index, name.line); @@ -201,13 +291,12 @@ class EtagsPrinter : ASTVisitor void maketag(string text, size_t index, ulong line) { - // skip declaration in unittests and funcs - if (inUnittest || inFunc) return; + // skip unittests and hidden declarations + if (inUnittest || visibility == Visibility.hidden) return; // tag is a searchable string from beginning of line size_t b = index; - while (b > 0 && bytes[--b] != '\n') {} - ++b; + while (b > 0 && bytes[b-1] != '\n') --b; // tag end is one char beyond tag name size_t e = index + text.length; @@ -223,13 +312,20 @@ class EtagsPrinter : ASTVisitor line, b); } - + alias visit = ASTVisitor.visit; + // state + // visibility of declarations (i.e. should we tag) + Visibility visibility = Visibility.exposed; bool inUnittest; - bool inFunc; - ubyte[] bytes; - string tags; - string context; -} + // inputs + ubyte[] bytes; + string moduleName; + string context; + Visibility privateVisibility = Visibility.hidden; + + // ouput + string tags; +} diff --git a/src/main.d b/src/main.d index d603c0a..cea527b 100644 --- a/src/main.d +++ b/src/main.d @@ -49,6 +49,7 @@ int run(string[] args) bool highlight; bool ctags; bool etags; + bool etagsAll; bool help; bool tokenCount; bool syntaxCheck; @@ -69,7 +70,7 @@ int run(string[] args) { getopt(args, std.getopt.config.caseSensitive, "sloc|l", &sloc, "highlight", &highlight, "ctags|c", &ctags, "help|h", &help, - "etags|e", &etags, + "etags|e", &etags, "etagsAll", &etagsAll, "tokenCount|t", &tokenCount, "syntaxCheck|s", &syntaxCheck, "ast|xml", &ast, "imports|i", &imports, "outline|o", &outline, "tokenDump", &tokenDump, "styleCheck|S", &styleCheck, @@ -120,9 +121,9 @@ int run(string[] args) return 0; } - auto optionCount = count!"a"([sloc, highlight, ctags, etags, tokenCount, + auto optionCount = count!"a"([sloc, highlight, ctags, tokenCount, syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig, - report, symbolName !is null]); + report, symbolName !is null, etags, etagsAll]); if (optionCount > 1) { stderr.writeln("Too many options specified"); @@ -177,9 +178,9 @@ int run(string[] args) { stdout.printCtags(expandArgs(args)); } - else if (etags) + else if (etags || etagsAll) { - stdout.printEtags(expandArgs(args)); + stdout.printEtags(etagsAll, expandArgs(args)); } else if (styleCheck) { @@ -369,6 +370,9 @@ options: etags information requires a filename, so stdin cannot be used in place of a filename. + --etagsAll sourceFile + Same as --etags except private and package declarations are tagged too. + --ast | --xml sourceFile Generates an XML representation of the source files abstract syntax tree. If no files are specified, input is read from stdin.