Merge pull request #248 from smolt/etags-update

Etags updates
This commit is contained in:
Brian Schott 2015-05-09 18:39:50 -07:00
commit f0f0110307
2 changed files with 140 additions and 40 deletions

View File

@ -16,13 +16,24 @@ import std.path;
import std.array; import std.array;
import std.conv; 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. * Prints ETAGS information to the given file.
* Params: * Params:
* outpt = the file that ETAGS info is written to * 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 * fileNames = tags will be generated from these files
*/ */
void printEtags(File output, string[] fileNames) void printEtags(File output, bool tagAll, string[] fileNames)
{ {
LexerConfig config; LexerConfig config;
StringCache cache = StringCache(StringCache.defaultBucketCount); StringCache cache = StringCache(StringCache.defaultBucketCount);
@ -34,15 +45,17 @@ void printEtags(File output, string[] fileNames)
f.rawRead(bytes); f.rawRead(bytes);
auto tokens = getTokensForParser(bytes, config, &cache); auto tokens = getTokensForParser(bytes, config, &cache);
Module m = parseModule(tokens.array, fileName, null, &doNothing); Module m = parseModule(tokens.array, fileName, null, &doNothing);
auto printer = new EtagsPrinter; auto printer = new EtagsPrinter;
printer.moduleName = m.moduleFullName(fileName);
// I think I like this version(UseModuleContext)
enum useModuleContext = true; printer.context = printer.moduleName ~ ".";
if (useModuleContext) printer.privateVisibility = tagAll?
printer.context = m.moduleFullName(fileName) ~ "."; Visibility.exposed :
Visibility.hidden;
printer.bytes = bytes.sansBOM; printer.bytes = bytes.sansBOM;
printer.visit(m); printer.visit(m);
output.writef("\f\n%s,%u\n", fileName, printer.tags.length); output.writef("\f\n%s,%u\n", fileName, printer.tags.length);
printer.tags.copy(output.lockingTextWriter); printer.tags.copy(output.lockingTextWriter);
} }
@ -50,6 +63,8 @@ void printEtags(File output, string[] fileNames)
private: private:
enum Visibility {exposed, hidden}
void doNothing(string, size_t, size_t, string, bool) {} void doNothing(string, size_t, size_t, string, bool) {}
ubyte[] sansBOM(ubyte[] bytes) ubyte[] sansBOM(ubyte[] bytes)
@ -58,7 +73,6 @@ ubyte[] sansBOM(ubyte[] bytes)
return (bytes.length >= 3 && return (bytes.length >= 3 &&
bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf) bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf)
? bytes[3 .. $] : bytes; ? bytes[3 .. $] : bytes;
} }
string moduleFullName(Module m, string fileName) 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("."); 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) override void visit(const ClassDeclaration dec)
{ {
maketag(dec.name); maketag(dec.name);
// class members default to public
visibility = Visibility.exposed;
acceptInContext(dec, dec.name.text); acceptInContext(dec, dec.name.text);
} }
override void visit(const StructDeclaration dec) override void visit(const StructDeclaration dec)
{ {
if (dec.name == tok!"")
{
dec.accept(this);
return;
}
maketag(dec.name); maketag(dec.name);
// struct members default to public
visibility = Visibility.exposed;
acceptInContext(dec, dec.name.text); acceptInContext(dec, dec.name.text);
} }
override void visit(const InterfaceDeclaration dec) override void visit(const InterfaceDeclaration dec)
{ {
maketag(dec.name); maketag(dec.name);
// interface members default to public
visibility = Visibility.exposed;
acceptInContext(dec, dec.name.text); acceptInContext(dec, dec.name.text);
} }
@ -100,36 +155,32 @@ class EtagsPrinter : ASTVisitor
override void visit(const FunctionDeclaration dec) override void visit(const FunctionDeclaration dec)
{ {
maketag(dec.name); maketag(dec.name);
bool was = inFunc; // don't tag declarations in a function like thing
inFunc = true; visibility = Visibility.hidden;
auto c = context;
acceptInContext(dec, dec.name.text); acceptInContext(dec, dec.name.text);
inFunc = was;
} }
override void visit(const Constructor dec) override void visit(const Constructor dec)
{ {
maketag("this", dec.location, dec.line); maketag("this", dec.location, dec.line);
bool was = inFunc; // don't tag declarations in a function like thing
inFunc = true; visibility = Visibility.hidden;
auto c = context;
acceptInContext(dec, "this"); acceptInContext(dec, "this");
inFunc = was;
} }
override void visit(const Destructor dec) override void visit(const Destructor dec)
{ {
maketag("~this", dec.index, dec.line); maketag("~this", dec.index, dec.line);
bool was = inFunc; // don't tag declarations in a function like thing
inFunc = true; visibility = Visibility.hidden;
auto c = context;
acceptInContext(dec, "~this"); acceptInContext(dec, "~this");
inFunc = was;
} }
override void visit(const EnumDeclaration dec) override void visit(const EnumDeclaration dec)
{ {
maketag(dec.name); maketag(dec.name);
// enum members default to public
visibility = Visibility.exposed;
acceptInContext(dec, dec.name.text); acceptInContext(dec, dec.name.text);
} }
@ -141,6 +192,8 @@ class EtagsPrinter : ASTVisitor
return; return;
} }
maketag(dec.name); maketag(dec.name);
// union members default to public
visibility = Visibility.exposed;
acceptInContext(dec, dec.name.text); acceptInContext(dec, dec.name.text);
} }
@ -180,12 +233,49 @@ class EtagsPrinter : ASTVisitor
dec.accept(this); 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) override void visit(const Invariant dec)
{ {
maketag("invariant", dec.index, dec.line); 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 // nest context before journeying on
auto c = context; auto c = context;
@ -201,13 +291,12 @@ class EtagsPrinter : ASTVisitor
void maketag(string text, size_t index, ulong line) void maketag(string text, size_t index, ulong line)
{ {
// skip declaration in unittests and funcs // skip unittests and hidden declarations
if (inUnittest || inFunc) return; if (inUnittest || visibility == Visibility.hidden) return;
// tag is a searchable string from beginning of line // tag is a searchable string from beginning of line
size_t b = index; size_t b = index;
while (b > 0 && bytes[--b] != '\n') {} while (b > 0 && bytes[b-1] != '\n') --b;
++b;
// tag end is one char beyond tag name // tag end is one char beyond tag name
size_t e = index + text.length; size_t e = index + text.length;
@ -226,10 +315,17 @@ class EtagsPrinter : ASTVisitor
alias visit = ASTVisitor.visit; alias visit = ASTVisitor.visit;
// state
// visibility of declarations (i.e. should we tag)
Visibility visibility = Visibility.exposed;
bool inUnittest; bool inUnittest;
bool inFunc;
ubyte[] bytes;
string tags;
string context;
}
// inputs
ubyte[] bytes;
string moduleName;
string context;
Visibility privateVisibility = Visibility.hidden;
// ouput
string tags;
}

View File

@ -49,6 +49,7 @@ int run(string[] args)
bool highlight; bool highlight;
bool ctags; bool ctags;
bool etags; bool etags;
bool etagsAll;
bool help; bool help;
bool tokenCount; bool tokenCount;
bool syntaxCheck; bool syntaxCheck;
@ -69,7 +70,7 @@ int run(string[] args)
{ {
getopt(args, std.getopt.config.caseSensitive, "sloc|l", &sloc, getopt(args, std.getopt.config.caseSensitive, "sloc|l", &sloc,
"highlight", &highlight, "ctags|c", &ctags, "help|h", &help, "highlight", &highlight, "ctags|c", &ctags, "help|h", &help,
"etags|e", &etags, "etags|e", &etags, "etagsAll", &etagsAll,
"tokenCount|t", &tokenCount, "syntaxCheck|s", &syntaxCheck, "tokenCount|t", &tokenCount, "syntaxCheck|s", &syntaxCheck,
"ast|xml", &ast, "imports|i", &imports, "outline|o", &outline, "ast|xml", &ast, "imports|i", &imports, "outline|o", &outline,
"tokenDump", &tokenDump, "styleCheck|S", &styleCheck, "tokenDump", &tokenDump, "styleCheck|S", &styleCheck,
@ -120,9 +121,9 @@ int run(string[] args)
return 0; 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, syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig,
report, symbolName !is null]); report, symbolName !is null, etags, etagsAll]);
if (optionCount > 1) if (optionCount > 1)
{ {
stderr.writeln("Too many options specified"); stderr.writeln("Too many options specified");
@ -177,9 +178,9 @@ int run(string[] args)
{ {
stdout.printCtags(expandArgs(args)); stdout.printCtags(expandArgs(args));
} }
else if (etags) else if (etags || etagsAll)
{ {
stdout.printEtags(expandArgs(args)); stdout.printEtags(etagsAll, expandArgs(args));
} }
else if (styleCheck) else if (styleCheck)
{ {
@ -369,6 +370,9 @@ options:
etags information requires a filename, so stdin cannot be used in place etags information requires a filename, so stdin cannot be used in place
of a filename. of a filename.
--etagsAll sourceFile
Same as --etags except private and package declarations are tagged too.
--ast | --xml sourceFile --ast | --xml sourceFile
Generates an XML representation of the source files abstract syntax Generates an XML representation of the source files abstract syntax
tree. If no files are specified, input is read from stdin. tree. If no files are specified, input is read from stdin.