Skip to content

Commit f34e28b

Browse files
authored
Merge pull request #1256 from Workiva/breakupAugment
Breakup parseAndAugment method
2 parents 022eb64 + 17263fa commit f34e28b

File tree

2 files changed

+443
-104
lines changed

2 files changed

+443
-104
lines changed

build/build.go

Lines changed: 153 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
117117
return pkg, nil
118118
}
119119

120+
// overrideInfo is used by parseAndAugment methods to manage
121+
// directives and how the overlay and original are merged.
122+
type overrideInfo struct {
123+
// KeepOriginal indicates that the original code should be kept
124+
// but the identifier will be prefixed by `_gopherjs_original_foo`.
125+
// If false the original code is removed.
126+
keepOriginal bool
127+
128+
// pruneMethodBody indicates that the body of the methods should be
129+
// removed because they contain something that is invalid to GopherJS.
130+
pruneMethodBody bool
131+
}
132+
120133
// parseAndAugment parses and returns all .go files of given pkg.
121134
// Standard Go library packages are augmented with files in compiler/natives folder.
122135
// If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests.
@@ -132,84 +145,86 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
132145
// the original identifier gets replaced by `_`. New identifiers that don't exist in original
133146
// package get added.
134147
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) {
135-
var files []*ast.File
148+
jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet)
149+
150+
originalFiles, err := parserOriginalFiles(pkg, fileSet)
151+
if err != nil {
152+
return nil, nil, err
153+
}
154+
155+
overrides := make(map[string]overrideInfo)
156+
for _, file := range overlayFiles {
157+
augmentOverlayFile(file, overrides)
158+
}
159+
delete(overrides, "init")
136160

137-
type overrideInfo struct {
138-
keepOriginal bool
139-
pruneOriginal bool
161+
for _, file := range originalFiles {
162+
augmentOriginalImports(pkg.ImportPath, file)
163+
augmentOriginalFile(file, overrides)
140164
}
141-
replacedDeclNames := make(map[string]overrideInfo)
142165

166+
return append(overlayFiles, originalFiles...), jsFiles, nil
167+
}
168+
169+
// parseOverlayFiles loads and parses overlay files
170+
// to augment the original files with.
171+
func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]JSFile, []*ast.File) {
143172
isXTest := strings.HasSuffix(pkg.ImportPath, "_test")
144173
importPath := pkg.ImportPath
145174
if isXTest {
146175
importPath = importPath[:len(importPath)-5]
147176
}
148177

149-
jsFiles := []JSFile{}
150-
151178
nativesContext := overlayCtx(xctx.Env())
179+
nativesPkg, err := nativesContext.Import(importPath, "", 0)
180+
if err != nil {
181+
return nil, nil
182+
}
152183

153-
if nativesPkg, err := nativesContext.Import(importPath, "", 0); err == nil {
154-
jsFiles = nativesPkg.JSFiles
155-
names := nativesPkg.GoFiles
156-
if isTest {
157-
names = append(names, nativesPkg.TestGoFiles...)
158-
}
159-
if isXTest {
160-
names = nativesPkg.XTestGoFiles
184+
jsFiles := nativesPkg.JSFiles
185+
var files []*ast.File
186+
names := nativesPkg.GoFiles
187+
if isTest {
188+
names = append(names, nativesPkg.TestGoFiles...)
189+
}
190+
if isXTest {
191+
names = nativesPkg.XTestGoFiles
192+
}
193+
194+
for _, name := range names {
195+
fullPath := path.Join(nativesPkg.Dir, name)
196+
r, err := nativesContext.bctx.OpenFile(fullPath)
197+
if err != nil {
198+
panic(err)
161199
}
162-
for _, name := range names {
163-
fullPath := path.Join(nativesPkg.Dir, name)
164-
r, err := nativesContext.bctx.OpenFile(fullPath)
165-
if err != nil {
166-
panic(err)
167-
}
168-
// Files should be uniquely named and in the original package directory in order to be
169-
// ordered correctly
170-
newPath := path.Join(pkg.Dir, "gopherjs__"+name)
171-
file, err := parser.ParseFile(fileSet, newPath, r, parser.ParseComments)
172-
if err != nil {
173-
panic(err)
174-
}
175-
r.Close()
176-
for _, decl := range file.Decls {
177-
switch d := decl.(type) {
178-
case *ast.FuncDecl:
179-
k := astutil.FuncKey(d)
180-
replacedDeclNames[k] = overrideInfo{
181-
keepOriginal: astutil.KeepOriginal(d),
182-
pruneOriginal: astutil.PruneOriginal(d),
183-
}
184-
case *ast.GenDecl:
185-
switch d.Tok {
186-
case token.TYPE:
187-
for _, spec := range d.Specs {
188-
replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = overrideInfo{}
189-
}
190-
case token.VAR, token.CONST:
191-
for _, spec := range d.Specs {
192-
for _, name := range spec.(*ast.ValueSpec).Names {
193-
replacedDeclNames[name.Name] = overrideInfo{}
194-
}
195-
}
196-
}
197-
}
198-
}
199-
files = append(files, file)
200+
// Files should be uniquely named and in the original package directory in order to be
201+
// ordered correctly
202+
newPath := path.Join(pkg.Dir, "gopherjs__"+name)
203+
file, err := parser.ParseFile(fileSet, newPath, r, parser.ParseComments)
204+
if err != nil {
205+
panic(err)
200206
}
207+
r.Close()
208+
209+
files = append(files, file)
201210
}
202-
delete(replacedDeclNames, "init")
211+
return jsFiles, files
212+
}
203213

214+
// parserOriginalFiles loads and parses the original files to augment.
215+
func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, error) {
216+
var files []*ast.File
204217
var errList compiler.ErrorList
205218
for _, name := range pkg.GoFiles {
206219
if !filepath.IsAbs(name) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`.
207220
name = filepath.Join(pkg.Dir, name)
208221
}
222+
209223
r, err := buildutil.OpenFile(pkg.bctx, name)
210224
if err != nil {
211-
return nil, nil, err
225+
return nil, err
212226
}
227+
213228
file, err := parser.ParseFile(fileSet, name, r, parser.ParseComments)
214229
r.Close()
215230
if err != nil {
@@ -226,68 +241,102 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
226241
continue
227242
}
228243

229-
switch pkg.ImportPath {
230-
case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "time":
231-
for _, spec := range file.Imports {
232-
path, _ := strconv.Unquote(spec.Path.Value)
233-
if path == "sync" {
234-
if spec.Name == nil {
235-
spec.Name = ast.NewIdent("sync")
244+
files = append(files, file)
245+
}
246+
247+
if errList != nil {
248+
return nil, errList
249+
}
250+
return files, nil
251+
}
252+
253+
// augmentOverlayFile is the part of parseAndAugment that processes
254+
// an overlay file AST to collect information such as compiler directives
255+
// and perform any initial augmentation needed to the overlay.
256+
func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) {
257+
for _, decl := range file.Decls {
258+
switch d := decl.(type) {
259+
case *ast.FuncDecl:
260+
k := astutil.FuncKey(d)
261+
overrides[k] = overrideInfo{
262+
keepOriginal: astutil.KeepOriginal(d),
263+
pruneMethodBody: astutil.PruneOriginal(d),
264+
}
265+
case *ast.GenDecl:
266+
for _, spec := range d.Specs {
267+
switch s := spec.(type) {
268+
case *ast.TypeSpec:
269+
overrides[s.Name.Name] = overrideInfo{}
270+
case *ast.ValueSpec:
271+
for _, name := range s.Names {
272+
overrides[name.Name] = overrideInfo{}
236273
}
237-
spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"`
238274
}
239275
}
240276
}
277+
}
278+
}
241279

242-
for _, decl := range file.Decls {
243-
switch d := decl.(type) {
244-
case *ast.FuncDecl:
245-
k := astutil.FuncKey(d)
246-
if info, ok := replacedDeclNames[k]; ok {
247-
if info.pruneOriginal {
248-
// Prune function bodies, since it may contain code invalid for
249-
// GopherJS and pin unwanted imports.
250-
d.Body = nil
251-
}
252-
if info.keepOriginal {
253-
// Allow overridden function calls
254-
// The standard library implementation of foo() becomes _gopherjs_original_foo()
255-
d.Name.Name = "_gopherjs_original_" + d.Name.Name
256-
} else {
257-
d.Name = ast.NewIdent("_")
258-
}
280+
// augmentOriginalImports is the part of parseAndAugment that processes
281+
// an original file AST to modify the imports for that file.
282+
func augmentOriginalImports(importPath string, file *ast.File) {
283+
switch importPath {
284+
case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "time":
285+
for _, spec := range file.Imports {
286+
path, _ := strconv.Unquote(spec.Path.Value)
287+
if path == "sync" {
288+
if spec.Name == nil {
289+
spec.Name = ast.NewIdent("sync")
259290
}
260-
case *ast.GenDecl:
261-
switch d.Tok {
262-
case token.TYPE:
263-
for _, spec := range d.Specs {
264-
s := spec.(*ast.TypeSpec)
265-
if _, ok := replacedDeclNames[s.Name.Name]; ok {
266-
s.Name = ast.NewIdent("_")
267-
s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}}
268-
s.TypeParams = nil
269-
}
291+
spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"`
292+
}
293+
}
294+
}
295+
}
296+
297+
// augmentOriginalFile is the part of parseAndAugment that processes an
298+
// original file AST to augment the source code using the overrides from
299+
// the overlay files.
300+
func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
301+
for _, decl := range file.Decls {
302+
switch d := decl.(type) {
303+
case *ast.FuncDecl:
304+
if info, ok := overrides[astutil.FuncKey(d)]; ok {
305+
if info.pruneMethodBody {
306+
// Prune function bodies, since it may contain code invalid for
307+
// GopherJS and pin unwanted imports.
308+
d.Body = nil
309+
}
310+
if info.keepOriginal {
311+
// Allow overridden function calls
312+
// The standard library implementation of foo() becomes _gopherjs_original_foo()
313+
d.Name.Name = "_gopherjs_original_" + d.Name.Name
314+
} else {
315+
// By setting the name to an underscore, the method will
316+
// not be outputted. Doing this will keep the dependencies the same.
317+
d.Name = ast.NewIdent("_")
318+
}
319+
}
320+
case *ast.GenDecl:
321+
for _, spec := range d.Specs {
322+
switch s := spec.(type) {
323+
case *ast.TypeSpec:
324+
if _, ok := overrides[s.Name.Name]; ok {
325+
s.Name = ast.NewIdent("_")
326+
// Change to struct type with no type body and not type parameters.
327+
s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}}
328+
s.TypeParams = nil
270329
}
271-
case token.VAR, token.CONST:
272-
for _, spec := range d.Specs {
273-
s := spec.(*ast.ValueSpec)
274-
for i, name := range s.Names {
275-
if _, ok := replacedDeclNames[name.Name]; ok {
276-
s.Names[i] = ast.NewIdent("_")
277-
}
330+
case *ast.ValueSpec:
331+
for i, name := range s.Names {
332+
if _, ok := overrides[name.Name]; ok {
333+
s.Names[i] = ast.NewIdent("_")
278334
}
279335
}
280336
}
281337
}
282338
}
283-
284-
files = append(files, file)
285-
}
286-
287-
if errList != nil {
288-
return nil, nil, errList
289339
}
290-
return files, jsFiles, nil
291340
}
292341

293342
// Options controls build process behavior.

0 commit comments

Comments
 (0)