From 335eecb4e4931fbd98a1efad3447cd9be928d806 Mon Sep 17 00:00:00 2001 From: Andrew Carter Date: Fri, 24 Jul 2015 16:37:37 -0700 Subject: [PATCH] Add exclude-private option Added flag `-exclude-private` to chose to filter any symbols that are not public when building the tag file. This is very useful for building a tag file for the Go standard library or a third party API. --- README.md | 1 + main.go | 22 ++++++++-------- parser.go | 50 +++++++++++++++++++++++-------------- parser_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 9b92d0c..9a998f4 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Or using package manager `brew` on OS X -L="": source file names are read from the specified file. If file is "-", input is read from standard in. -R=false: recurse into directories in the file list. + -exclude-private=false: exclude private symbols. -f="": write output to specified file. If file is "-", output is written to standard out. -silent=false: do not produce any output on error. -sort=true: sort tags. diff --git a/main.go b/main.go index c954a01..aa41769 100644 --- a/main.go +++ b/main.go @@ -22,15 +22,16 @@ const ( ) var ( - printVersion bool - inputFile string - outputFile string - recurse bool - sortOutput bool - silent bool - relative bool - listLangs bool - fields string + printVersion bool + inputFile string + outputFile string + recurse bool + sortOutput bool + silent bool + relative bool + listLangs bool + fields string + excludePrivate bool ) // ignore unknown flags @@ -47,6 +48,7 @@ func init() { flags.BoolVar(&relative, "tag-relative", false, "file paths should be relative to the directory containing the tag file.") flags.BoolVar(&listLangs, "list-languages", false, "list supported languages.") flags.StringVar(&fields, "fields", "", "include selected extension fields (only +l).") + flags.BoolVar(&excludePrivate, "exclude-private", false, "exclude private symbols.") flags.Usage = func() { fmt.Fprintf(os.Stderr, "gotags version %s\n\n", Version) @@ -180,7 +182,7 @@ func main() { tags := []Tag{} for _, file := range files { - ts, err := Parse(file, relative, basedir) + ts, err := Parse(file, relative, basedir, excludePrivate) if err != nil { if !silent { fmt.Fprintf(os.Stderr, "parse error: %s\n\n", err) diff --git a/parser.go b/parser.go index e4c74af..d943edd 100644 --- a/parser.go +++ b/parser.go @@ -12,22 +12,25 @@ import ( // tagParser contains the data needed while parsing. type tagParser struct { - fset *token.FileSet - tags []Tag // list of created tags - types []string // all types we encounter, used to determine the constructors - relative bool // should filenames be relative to basepath - basepath string // output file directory + fset *token.FileSet + tags []Tag // list of created tags + types []string // all types we encounter, used to determine the constructors + relative bool // should filenames be relative to basepath + basepath string // output file directory + excludePrivate bool // exclude private symbols } -// Parse parses the source in filename and returns a list of tags. If relative -// is true, the filenames in the list of tags are relative to basepath. -func Parse(filename string, relative bool, basepath string) ([]Tag, error) { +// Parse parses the source in filename and returns a list of tags. +// If relative is true, the filenames in the list of tags are relative to basepath. +// If excludePrivate is true, any symbols that are not exported will be excluded. +func Parse(filename string, relative bool, basepath string, excludePrivate bool) ([]Tag, error) { p := &tagParser{ - fset: token.NewFileSet(), - tags: []Tag{}, - types: make([]string, 0), - relative: relative, - basepath: basepath, + fset: token.NewFileSet(), + tags: []Tag{}, + types: make([]string, 0), + relative: relative, + basepath: basepath, + excludePrivate: excludePrivate, } f, err := parser.ParseFile(p.fset, filename, nil, 0) @@ -104,7 +107,7 @@ func (p *tagParser) parseFunction(f *ast.FuncDecl) { tag.Type = Function } - p.tags = append(p.tags, tag) + p.addTag(tag) } // parseTypeDeclaration creates a tag for type declaration ts and for each @@ -127,7 +130,7 @@ func (p *tagParser) parseTypeDeclaration(ts *ast.TypeSpec) { tag.Fields[TypeField] = getType(ts.Type, true) } - p.tags = append(p.tags, tag) + p.addTag(tag) } // parseValueDeclaration creates a tag for each variable or constant declaration, @@ -151,7 +154,8 @@ func (p *tagParser) parseValueDeclaration(v *ast.ValueSpec) { case ast.Con: tag.Type = Constant } - p.tags = append(p.tags, tag) + + p.addTag(tag) } } @@ -166,7 +170,7 @@ func (p *tagParser) parseStructFields(name string, s *ast.StructType) { tag.Fields[Access] = getAccess(tag.Name) tag.Fields[ReceiverType] = name tag.Fields[TypeField] = getType(f.Type, true) - p.tags = append(p.tags, tag) + p.addTag(tag) } } else { // embedded field @@ -174,7 +178,7 @@ func (p *tagParser) parseStructFields(name string, s *ast.StructType) { tag.Fields[Access] = getAccess(tag.Name) tag.Fields[ReceiverType] = name tag.Fields[TypeField] = getType(f.Type, true) - p.tags = append(p.tags, tag) + p.addTag(tag) } } } @@ -200,7 +204,7 @@ func (p *tagParser) parseInterfaceMethods(name string, s *ast.InterfaceType) { tag.Fields[InterfaceType] = name - p.tags = append(p.tags, tag) + p.addTag(tag) } } @@ -249,6 +253,14 @@ func (p *tagParser) belongsToReceiver(types *ast.FieldList) (name string, ok boo return "", false } +// addTag adds tag to the list or is skipped if exclude private and tag is private +func (p *tagParser) addTag(tag Tag) { + if p.excludePrivate && tag.Fields[Access] != "public" { + return + } + p.tags = append(p.tags, tag) +} + // getTypes returns a comma separated list of types in fields. If includeNames is // true each type is preceded by a comma separated list of parameter names. func getTypes(fields *ast.FieldList, includeNames bool) string { diff --git a/parser_test.go b/parser_test.go index 6a38b01..0435993 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "path/filepath" "runtime" "strconv" @@ -10,11 +11,12 @@ import ( type F map[TagField]string var testCases = []struct { - filename string - relative bool - basepath string - minversion string - tags []Tag + filename string + relative bool + basepath string + minversion string + tags []Tag + excludePrivate bool }{ {filename: "tests/const.go-src", tags: []Tag{ tag("Test", 1, "p", F{}), @@ -95,6 +97,51 @@ var testCases = []struct { tag("fmt", 3, "i", F{}), tag("main", 5, "f", F{"access": "private", "signature": "()"}), }}, + // excludePrivate tests + {filename: "tests/func.go-src", excludePrivate: true, tags: []Tag{ + tag("Test", 1, "p", F{}), + tag("Function1", 3, "f", F{"access": "public", "signature": "()", "type": "string"}), + }}, + {filename: "tests/interface.go-src", excludePrivate: true, tags: []Tag{ + tag("Test", 1, "p", F{}), + tag("InterfaceMethod", 4, "m", F{"access": "public", "signature": "(int)", "ntype": "Interface", "type": "string"}), + tag("OtherMethod", 5, "m", F{"access": "public", "signature": "()", "ntype": "Interface"}), + tag("io.Reader", 6, "e", F{"access": "public", "ntype": "Interface"}), + tag("Interface", 3, "n", F{"access": "public", "type": "interface"}), + }}, + {filename: "tests/struct.go-src", excludePrivate: true, tags: []Tag{ + tag("Test", 1, "p", F{}), + tag("Field1", 4, "w", F{"access": "public", "ctype": "Struct", "type": "int"}), + tag("Field2", 4, "w", F{"access": "public", "ctype": "Struct", "type": "int"}), + tag("Struct", 3, "t", F{"access": "public", "type": "struct"}), + tag("Struct", 20, "e", F{"access": "public", "ctype": "TestEmbed", "type": "Struct"}), + tag("*io.Writer", 21, "e", F{"access": "public", "ctype": "TestEmbed", "type": "*io.Writer"}), + tag("TestEmbed", 19, "t", F{"access": "public", "type": "struct"}), + tag("Struct2", 27, "t", F{"access": "public", "type": "struct"}), + tag("Connection", 36, "t", F{"access": "public", "type": "struct"}), + tag("NewStruct", 9, "f", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "*Struct"}), + tag("F1", 13, "m", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "[]bool, [2]*string"}), + tag("F2", 16, "m", F{"access": "public", "ctype": "Struct", "signature": "()", "type": "bool"}), + tag("NewTestEmbed", 24, "f", F{"access": "public", "ctype": "TestEmbed", "signature": "()", "type": "TestEmbed"}), + tag("NewStruct2", 30, "f", F{"access": "public", "ctype": "Struct2", "signature": "()", "type": "*Struct2, error"}), + tag("Dial", 33, "f", F{"access": "public", "ctype": "Connection", "signature": "()", "type": "*Connection, error"}), + tag("Dial2", 39, "f", F{"access": "public", "ctype": "Connection", "signature": "()", "type": "*Connection, *Struct2"}), + tag("Dial3", 42, "f", F{"access": "public", "signature": "()", "type": "*Connection, *Connection"}), + }}, + {filename: "tests/type.go-src", excludePrivate: true, tags: []Tag{ + tag("Test", 1, "p", F{}), + }}, + {filename: "tests/var.go-src", excludePrivate: true, tags: []Tag{ + tag("Test", 1, "p", F{}), + tag("A", 7, "v", F{"access": "public"}), + tag("B", 8, "v", F{"access": "public"}), + tag("C", 8, "v", F{"access": "public"}), + tag("D", 9, "v", F{"access": "public"}), + }}, + {filename: "tests/range.go-src", excludePrivate: true, minversion: "go1.4", tags: []Tag{ + tag("main", 1, "p", F{}), + tag("fmt", 3, "i", F{}), + }}, } func TestParse(t *testing.T) { @@ -110,14 +157,21 @@ func TestParse(t *testing.T) { continue } - tags, err := Parse(testCase.filename, testCase.relative, basepath) + tags, err := Parse(testCase.filename, testCase.relative, basepath, testCase.excludePrivate) if err != nil { t.Errorf("[%s] Parse error: %s", testCase.filename, err) continue } if len(tags) != len(testCase.tags) { - t.Errorf("[%s] len(tags) == %d, want %d", testCase.filename, len(tags), len(testCase.tags)) + msg := fmt.Sprintf("[%s] len(tags) == %d, want %d", + testCase.filename, + len(tags), + len(testCase.tags)) + if testCase.excludePrivate { + msg += " (exclude private)" + } + t.Error(msg) continue }