1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
9 // ----------------------------------------------------------------------------
12 func identListExports(list
[]*Ident
) []*Ident
{
14 for _
, x
:= range list
{
24 // isExportedType assumes that typ is a correct type.
25 func isExportedType(typ Expr
) bool {
26 switch t
:= typ
.(type) {
30 return isExportedType(t
.X
)
32 // assume t.X is a typename
33 return t
.Sel
.IsExported()
35 return isExportedType(t
.X
)
41 func fieldListExports(fields
*FieldList
, incomplete
*bool) {
47 for _
, f
:= range list
{
49 if len(f
.Names
) == 0 {
51 // (Note that a non-exported anonymous field
52 // may still refer to a type with exported
53 // fields, so this is not absolutely correct.
54 // However, this cannot be done w/o complete
56 exported
= isExportedType(f
.Type
)
59 f
.Names
= identListExports(f
.Names
)
63 exported
= len(f
.Names
) > 0
74 fields
.List
= list
[0:j
]
78 func paramListExports(fields
*FieldList
) {
82 for _
, f
:= range fields
.List
{
88 func typeExports(typ Expr
) {
89 switch t
:= typ
.(type) {
93 fieldListExports(t
.Fields
, &t
.Incomplete
)
95 paramListExports(t
.Params
)
96 paramListExports(t
.Results
)
98 fieldListExports(t
.Methods
, &t
.Incomplete
)
108 func specExports(spec Spec
) bool {
109 switch s
:= spec
.(type) {
111 s
.Names
= identListExports(s
.Names
)
112 if len(s
.Names
) > 0 {
117 if s
.Name
.IsExported() {
126 func specListExports(list
[]Spec
) []Spec
{
128 for _
, s
:= range list
{
138 func declExports(decl Decl
) bool {
139 switch d
:= decl
.(type) {
141 d
.Specs
= specListExports(d
.Specs
)
142 return len(d
.Specs
) > 0
144 d
.Body
= nil // strip body
145 return d
.Name
.IsExported()
151 // FileExports trims the AST for a Go source file in place such that only
152 // exported nodes remain: all top-level identifiers which are not exported
153 // and their associated information (such as type, initial value, or function
154 // body) are removed. Non-exported fields and methods of exported types are
155 // stripped, and the function bodies of exported functions are set to nil.
156 // The File.comments list is not changed.
158 // FileExports returns true if there is an exported declaration; it returns
161 func FileExports(src
*File
) bool {
163 for _
, d
:= range src
.Decls
{
169 src
.Decls
= src
.Decls
[0:j
]
174 // PackageExports trims the AST for a Go package in place such that only
175 // exported nodes remain. The pkg.Files list is not changed, so that file
176 // names and top-level package comments don't get lost.
178 // PackageExports returns true if there is an exported declaration; it
179 // returns false otherwise.
181 func PackageExports(pkg
*Package
) bool {
183 for _
, f
:= range pkg
.Files
{
192 // ----------------------------------------------------------------------------
195 type Filter
func(string) bool
197 func filterIdentList(list
[]*Ident
, f Filter
) []*Ident
{
199 for _
, x
:= range list
{
209 func filterSpec(spec Spec
, f Filter
) bool {
210 switch s
:= spec
.(type) {
212 s
.Names
= filterIdentList(s
.Names
, f
)
213 return len(s
.Names
) > 0
215 return f(s
.Name
.Name
)
221 func filterSpecList(list
[]Spec
, f Filter
) []Spec
{
223 for _
, s
:= range list
{
224 if filterSpec(s
, f
) {
233 func filterDecl(decl Decl
, f Filter
) bool {
234 switch d
:= decl
.(type) {
236 d
.Specs
= filterSpecList(d
.Specs
, f
)
237 return len(d
.Specs
) > 0
239 return f(d
.Name
.Name
)
245 // FilterFile trims the AST for a Go file in place by removing all
246 // names from top-level declarations (but not from parameter lists
247 // or inside types) that don't pass through the filter f. If the
248 // declaration is empty afterwards, the declaration is removed from
250 // The File.comments list is not changed.
252 // FilterFile returns true if there are any top-level declarations
253 // left after filtering; it returns false otherwise.
255 func FilterFile(src
*File
, f Filter
) bool {
257 for _
, d
:= range src
.Decls
{
258 if filterDecl(d
, f
) {
263 src
.Decls
= src
.Decls
[0:j
]
268 // FilterPackage trims the AST for a Go package in place by removing all
269 // names from top-level declarations (but not from parameter lists
270 // or inside types) that don't pass through the filter f. If the
271 // declaration is empty afterwards, the declaration is removed from
273 // The pkg.Files list is not changed, so that file names and top-level
274 // package comments don't get lost.
276 // FilterPackage returns true if there are any top-level declarations
277 // left after filtering; it returns false otherwise.
279 func FilterPackage(pkg
*Package
, f Filter
) bool {
281 for _
, src
:= range pkg
.Files
{
282 if FilterFile(src
, f
) {
290 // ----------------------------------------------------------------------------
291 // Merging of package files
293 // The MergeMode flags control the behavior of MergePackageFiles.
297 // If set, duplicate function declarations are excluded.
298 FilterFuncDuplicates MergeMode
= 1 << iota
299 // If set, comments that are not associated with a specific
300 // AST node (as Doc or Comment) are excluded.
301 FilterUnassociatedComments
304 // separator is an empty //-style comment that is interspersed between
305 // different comment groups when they are concatenated into a single group
307 var separator
= &Comment
{noPos
, "//"}
310 // MergePackageFiles creates a file AST by merging the ASTs of the
311 // files belonging to a package. The mode flags control merging behavior.
313 func MergePackageFiles(pkg
*Package
, mode MergeMode
) *File
{
314 // Count the number of package docs, comments and declarations across
315 // all package files.
319 for _
, f
:= range pkg
.Files
{
321 ndocs
+= len(f
.Doc
.List
) + 1 // +1 for separator
323 ncomments
+= len(f
.Comments
)
324 ndecls
+= len(f
.Decls
)
327 // Collect package comments from all package files into a single
328 // CommentGroup - the collected package documentation. The order
329 // is unspecified. In general there should be only one file with
330 // a package comment; but it's better to collect extra comments
331 // than drop them on the floor.
332 var doc
*CommentGroup
335 list
:= make([]*Comment
, ndocs
-1) // -1: no separator before first group
337 for _
, f
:= range pkg
.Files
{
340 // not the first group - add separator
344 for _
, c
:= range f
.Doc
.List
{
349 // Keep the maximum package clause position as
350 // position for the package clause of the merged
356 doc
= &CommentGroup
{list
}
359 // Collect declarations from all package files.
362 decls
= make([]Decl
, ndecls
)
363 funcs
:= make(map[string]int) // map of global function name -> decls index
364 i
:= 0 // current index
365 n
:= 0 // number of filtered entries
366 for _
, f
:= range pkg
.Files
{
367 for _
, d
:= range f
.Decls
{
368 if mode
&FilterFuncDuplicates
!= 0 {
369 // A language entity may be declared multiple
370 // times in different package files; only at
371 // build time declarations must be unique.
372 // For now, exclude multiple declarations of
373 // functions - keep the one with documentation.
375 // TODO(gri): Expand this filtering to other
376 // entities (const, type, vars) if
377 // multiple declarations are common.
378 if f
, isFun
:= d
.(*FuncDecl
); isFun
{
380 if j
, exists
:= funcs
[name
]; exists
{
381 // function declared already
382 if decls
[j
] != nil && decls
[j
].(*FuncDecl
).Doc
== nil {
383 // existing declaration has no documentation;
384 // ignore the existing declaration
387 // ignore the new declaration
390 n
++ // filtered an entry
401 // Eliminate nil entries from the decls list if entries were
402 // filtered. We do this using a 2nd pass in order to not disturb
403 // the original declaration order in the source (otherwise, this
404 // would also invalidate the monotonically increasing position
405 // info within a single file).
408 for _
, d
:= range decls
{
418 // Collect comments from all package files.
419 var comments
[]*CommentGroup
420 if mode
&FilterUnassociatedComments
== 0 {
421 comments
= make([]*CommentGroup
, ncomments
)
423 for _
, f
:= range pkg
.Files
{
424 i
+= copy(comments
[i
:], f
.Comments
)
428 // TODO(gri) need to compute pkgScope and unresolved identifiers!
429 // TODO(gri) need to compute imports!
430 return &File
{doc
, pos
, NewIdent(pkg
.Name
), decls
, nil, nil, nil, comments
}