@@ -117,6 +117,22 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
117
117
return pkg , nil
118
118
}
119
119
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
+ keepOriginal bool
126
+
127
+ // pruneFuncBody indicates that the body of the function should
128
+ // be removed to prevent statements that are invalid in GopherJS.
129
+ pruneFuncBody bool
130
+
131
+ // purgeMethods indicates that this info is for a struct and
132
+ // if a method has this struct as a receiver should also be removed.
133
+ purgeMethods bool
134
+ }
135
+
120
136
// parseAndAugment parses and returns all .go files of given pkg.
121
137
// Standard Go library packages are augmented with files in compiler/natives folder.
122
138
// If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests.
@@ -125,31 +141,55 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
125
141
// The native packages are augmented by the contents of natives.FS in the following way.
126
142
// The file names do not matter except the usual `_test` suffix. The files for
127
143
// native overrides get added to the package (even if they have the same name
128
- // as an existing file from the standard library). For function identifiers that exist
129
- // in the original AND the overrides AND that include the following directive in their comment:
130
- // //gopherjs:keep-original, the original identifier in the AST gets prefixed by
131
- // `_gopherjs_original_`. For other identifiers that exist in the original AND the overrides,
132
- // the original identifier gets replaced by `_`. New identifiers that don't exist in original
133
- // package get added.
144
+ // as an existing file from the standard library).
145
+ //
146
+ // - For function identifiers that exist in the original and the overrides and have the
147
+ // directive `gopherjs:keep-original`, the original identifier in the AST gets
148
+ // prefixed by `_gopherjs_original_`.
149
+ // - For functions that exist in the original and the overrides and have the
150
+ // directive `gopherjs:prune-original`, the original function body in the AST
151
+ // is removed. This removes code that is invalid in GopherJS.
152
+ // - For identifiers that exist in the original and the overrides
153
+ // and have the directive `gopherjs:purge`, both the original and override are removed.
154
+ // - Otherwise for identifiers that exist in the original and the overrides,
155
+ // the original identifier is removed.
156
+ // - New identifiers that don't exist in original package get added.
134
157
func parseAndAugment (xctx XContext , pkg * PackageData , isTest bool , fileSet * token.FileSet ) ([]* ast.File , []JSFile , error ) {
135
- var files [] * ast. File
158
+ jsFiles , overlayFiles := parseOverlayFiles ( xctx , pkg , isTest , fileSet )
136
159
137
- type overrideInfo struct {
138
- keepOriginal bool
139
- pruneOriginal bool
160
+ originalFiles , err := parserOriginalFiles ( pkg , fileSet )
161
+ if err != nil {
162
+ return nil , nil , err
140
163
}
164
+
141
165
replacedDeclNames := make (map [string ]overrideInfo )
166
+ for _ , file := range overlayFiles {
167
+ augmentOverlayFile (file , replacedDeclNames )
168
+ pruneImports (file )
169
+ }
170
+ delete (replacedDeclNames , "init" )
142
171
172
+ for _ , file := range originalFiles {
173
+ augmentOriginalImports (pkg .ImportPath , file )
174
+ augmentOriginalFile (file , replacedDeclNames )
175
+ pruneImports (file )
176
+ }
177
+
178
+ return append (overlayFiles , originalFiles ... ), jsFiles , nil
179
+ }
180
+
181
+ // parseOverlayFiles loads and parses overlay files
182
+ // to augment the original files with.
183
+ func parseOverlayFiles (xctx XContext , pkg * PackageData , isTest bool , fileSet * token.FileSet ) ([]JSFile , []* ast.File ) {
143
184
isXTest := strings .HasSuffix (pkg .ImportPath , "_test" )
144
185
importPath := pkg .ImportPath
145
186
if isXTest {
146
187
importPath = importPath [:len (importPath )- 5 ]
147
188
}
148
189
149
- jsFiles := []JSFile {}
150
-
190
+ var jsFiles []JSFile
191
+ var files [] * ast. File
151
192
nativesContext := overlayCtx (xctx .Env ())
152
-
153
193
if nativesPkg , err := nativesContext .Import (importPath , "" , 0 ); err == nil {
154
194
jsFiles = nativesPkg .JSFiles
155
195
names := nativesPkg .GoFiles
@@ -159,6 +199,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
159
199
if isXTest {
160
200
names = nativesPkg .XTestGoFiles
161
201
}
202
+
162
203
for _ , name := range names {
163
204
fullPath := path .Join (nativesPkg .Dir , name )
164
205
r , err := nativesContext .bctx .OpenFile (fullPath )
@@ -173,42 +214,25 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
173
214
panic (err )
174
215
}
175
216
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
- }
217
+
199
218
files = append (files , file )
200
219
}
201
220
}
202
- delete (replacedDeclNames , "init" )
203
221
222
+ return jsFiles , files
223
+ }
224
+
225
+ // parserOriginalFiles loads and parses the original files to augment.
226
+ func parserOriginalFiles (pkg * PackageData , fileSet * token.FileSet ) ([]* ast.File , error ) {
227
+ var files []* ast.File
204
228
var errList compiler.ErrorList
205
229
for _ , name := range pkg .GoFiles {
206
230
if ! filepath .IsAbs (name ) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`.
207
231
name = filepath .Join (pkg .Dir , name )
208
232
}
209
233
r , err := buildutil .OpenFile (pkg .bctx , name )
210
234
if err != nil {
211
- return nil , nil , err
235
+ return nil , err
212
236
}
213
237
file , err := parser .ParseFile (fileSet , name , r , parser .ParseComments )
214
238
r .Close ()
@@ -226,68 +250,155 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
226
250
continue
227
251
}
228
252
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" )
253
+ files = append (files , file )
254
+ }
255
+
256
+ if errList != nil {
257
+ return nil , errList
258
+ }
259
+ return files , nil
260
+ }
261
+
262
+ // augmentOverlayFile is the part of parseAndAugment that processes
263
+ // an overlay file AST to collect information such as compiler directives and
264
+ // perform any initial augmentation needed to the overlay.
265
+ func augmentOverlayFile (file * ast.File , replacedDeclNames map [string ]overrideInfo ) {
266
+ for i , decl := range file .Decls {
267
+ purgeDecl := astutil .Purge (decl )
268
+ switch d := decl .(type ) {
269
+ case * ast.FuncDecl :
270
+ k := astutil .FuncKey (d )
271
+ replacedDeclNames [k ] = overrideInfo {
272
+ keepOriginal : astutil .KeepOriginal (d ),
273
+ pruneFuncBody : astutil .PruneOriginal (d ),
274
+ }
275
+ case * ast.GenDecl :
276
+ for j , spec := range d .Specs {
277
+ purgeSpec := purgeDecl || astutil .Purge (spec )
278
+ switch d .Tok {
279
+ case token .TYPE :
280
+ replacedDeclNames [spec .(* ast.TypeSpec ).Name .Name ] = overrideInfo {
281
+ purgeMethods : purgeSpec ,
236
282
}
237
- spec .Path .Value = `"github.com/gopherjs/gopherjs/nosync"`
283
+ case token .VAR , token .CONST :
284
+ for _ , name := range spec .(* ast.ValueSpec ).Names {
285
+ replacedDeclNames [name .Name ] = overrideInfo {}
286
+ }
287
+ }
288
+ if purgeSpec {
289
+ d .Specs [j ] = nil
238
290
}
239
291
}
292
+ d .Specs = astutil .Squeeze (d .Specs )
293
+ if len (d .Specs ) == 0 {
294
+ file .Decls [i ] = nil
295
+ }
296
+ }
297
+ if purgeDecl {
298
+ file .Decls [i ] = nil
240
299
}
300
+ }
301
+ file .Decls = astutil .Squeeze (file .Decls )
302
+ }
241
303
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
- }
304
+ // augmentOriginalImports is the part of parseAndAugment that processes
305
+ // an original file AST to modify the imports for that file.
306
+ func augmentOriginalImports (importPath string , file * ast.File ) {
307
+ switch importPath {
308
+ case "crypto/rand" , "encoding/gob" , "encoding/json" , "expvar" , "go/token" , "log" , "math/big" , "math/rand" , "regexp" , "time" :
309
+ for _ , spec := range file .Imports {
310
+ path , _ := strconv .Unquote (spec .Path .Value )
311
+ if path == "sync" {
312
+ if spec .Name == nil {
313
+ spec .Name = ast .NewIdent ("sync" )
259
314
}
260
- case * ast.GenDecl :
315
+ spec .Path .Value = `"github.com/gopherjs/gopherjs/nosync"`
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ // augmentOriginalFile is the part of parseAndAugment that processes
322
+ // an original file AST to augment the source code using the directives
323
+ // from the overlay.
324
+ func augmentOriginalFile (file * ast.File , replacedDeclNames map [string ]overrideInfo ) {
325
+ for i , decl := range file .Decls {
326
+ switch d := decl .(type ) {
327
+ case * ast.FuncDecl :
328
+ k := astutil .FuncKey (d )
329
+ if info , ok := replacedDeclNames [k ]; ok {
330
+ if info .pruneFuncBody {
331
+ // Prune function bodies, since it may contain code invalid for
332
+ // GopherJS and pin unwanted imports.
333
+ d .Body = nil
334
+ }
335
+ if info .keepOriginal {
336
+ // Allow overridden function calls
337
+ // The standard library implementation of foo() becomes _gopherjs_original_foo()
338
+ d .Name .Name = "_gopherjs_original_" + d .Name .Name
339
+ } else {
340
+ file .Decls [i ] = nil
341
+ }
342
+ } else if recvKey := astutil .FuncReceiverKey (d ); len (recvKey ) > 0 {
343
+ // check if the receiver has been purged, if so, remove the method too.
344
+ if info , ok := replacedDeclNames [recvKey ]; ok && info .purgeMethods {
345
+ file .Decls [i ] = nil
346
+ }
347
+ }
348
+ case * ast.GenDecl :
349
+ for j , spec := range d .Specs {
261
350
switch d .Tok {
262
351
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
- }
352
+ if _ , ok := replacedDeclNames [spec .(* ast.TypeSpec ).Name .Name ]; ok {
353
+ d .Specs [j ] = nil
270
354
}
271
355
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
- }
356
+ s := spec .(* ast.ValueSpec )
357
+ for k , name := range s .Names {
358
+ if _ , ok := replacedDeclNames [name .Name ]; ok {
359
+ s .Names [k ] = nil
278
360
}
279
361
}
362
+ s .Names = astutil .Squeeze (s .Names )
363
+ if len (s .Names ) == 0 {
364
+ d .Specs [j ] = nil
365
+ }
280
366
}
281
367
}
368
+ d .Specs = astutil .Squeeze (d .Specs )
369
+ if len (d .Specs ) == 0 {
370
+ file .Decls [i ] = nil
371
+ }
282
372
}
373
+ }
374
+ file .Decls = astutil .Squeeze (file .Decls )
375
+ }
283
376
284
- files = append (files , file )
377
+ // pruneImports will remove any unused imports from the file.
378
+ //
379
+ // This will not remove any unnamed (`.`) or unused (`_`) imports.
380
+ func pruneImports (file * ast.File ) {
381
+ unused := make (map [string ]int , len (file .Imports ))
382
+ for i , in := range file .Imports {
383
+ if name := astutil .ImportName (in ); len (name ) > 0 {
384
+ unused [name ] = i
385
+ }
285
386
}
286
387
287
- if errList != nil {
288
- return nil , nil , errList
388
+ ast .Walk (astutil .NewCallbackVisitor (func (n ast.Node ) bool {
389
+ if sel , ok := n .(* ast.SelectorExpr ); ok {
390
+ if id , ok := sel .X .(* ast.Ident ); ok && id .Obj == nil {
391
+ delete (unused , id .Name )
392
+ }
393
+ }
394
+ return len (unused ) > 0
395
+ }), file )
396
+
397
+ for _ , i := range unused {
398
+ file .Imports [i ] = nil
289
399
}
290
- return files , jsFiles , nil
400
+
401
+ file .Imports = astutil .Squeeze (file .Imports )
291
402
}
292
403
293
404
// Options controls build process behavior.
@@ -678,7 +789,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
678
789
archive := s .buildCache .LoadArchive (pkg .ImportPath )
679
790
if archive != nil && ! pkg .SrcModTime .After (archive .BuildTime ) {
680
791
if err := archive .RegisterTypes (s .Types ); err != nil {
681
- panic (fmt .Errorf ("Failed to load type information from %v: %w" , archive , err ))
792
+ panic (fmt .Errorf ("failed to load type information from %v: %w" , archive , err ))
682
793
}
683
794
s .UpToDateArchives [pkg .ImportPath ] = archive
684
795
// Existing archive is up to date, no need to build it from scratch.
0 commit comments