@@ -124,13 +124,26 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
124124// overrideInfo is used by parseAndAugment methods to manage
125125// directives and how the overlay and original are merged.
126126type overrideInfo struct {
127+ // replace indicates that the original code is expected to exist.
128+ // If the original code is not present, then error to let us know
129+ // the original code has been removed or renamed.
130+ // This will be set to false once the original code has been found.
131+ replace bool
132+
133+ // new indicates that the override code is expected to be new.
134+ // If the original code is present, then error to let us know
135+ // that we are overriding something that we did not expect to replace.
136+ // This lets us know of new or renamed code in the original that happens
137+ // to collide with our overrides.
138+ new bool
139+
127140 // keepOriginal indicates that the original code should be kept
128141 // but the identifier will be prefixed by `_gopherjs_original_foo`.
129142 // If false the original code is removed.
130143 keepOriginal bool
131144
132- // purgeMethods indicates that this info is for a type and
133- // if a method has this type as a receiver should also be removed.
145+ // purgeMethods indicates that this info is for a type and if a method has
146+ // this type as a receiver then the method should also be removed.
134147 // If the method is defined in the overlays and therefore has its
135148 // own overrides, this will be ignored.
136149 purgeMethods bool
@@ -165,8 +178,11 @@ type overrideInfo struct {
165178// to match the overridden function signature. This allows the receiver,
166179// type parameters, parameter, and return values to be modified as needed.
167180// - Otherwise for identifiers that exist in the original and the overrides,
168- // the original is removed.
181+ // the original is removed. Use `gopherjs:replace` to ensure that the
182+ // original existed so that a replace is made.
169183// - New identifiers that don't exist in original package get added.
184+ // Use `gopherjs:new` to ensure that the identifier is new and there was
185+ // no original code for it.
170186func parseAndAugment (xctx XContext , pkg * PackageData , isTest bool , fileSet * token.FileSet ) ([]* ast.File , []incjs.File , error ) {
171187 jsFiles , overlayFiles := parseOverlayFiles (xctx , pkg , isTest , fileSet )
172188
@@ -186,8 +202,13 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
186202 }
187203
188204 if len (overrides ) > 0 {
205+ found := make (map [string ]struct {}, len (overrides ))
189206 for _ , file := range originalFiles {
190- augmentOriginalFile (file , overrides )
207+ augmentOriginalFile (file , overrides , found )
208+ }
209+ err := checkOverrides (overrides , found , pkg .ImportPath )
210+ if err != nil {
211+ return nil , nil , err
191212 }
192213 }
193214
@@ -284,12 +305,17 @@ func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File,
284305func augmentOverlayFile (file * ast.File , overrides map [string ]overrideInfo ) {
285306 anyChange := false
286307 for i , decl := range file .Decls {
308+ replaceDecl := astutil .DirectiveReplace (decl )
309+ newDecl := astutil .DirectiveNew (decl )
287310 purgeDecl := astutil .Purge (decl )
311+
288312 switch d := decl .(type ) {
289313 case * ast.FuncDecl :
290314 k := astutil .FuncKey (d )
291315 oi := overrideInfo {
292316 keepOriginal : astutil .KeepOriginal (d ),
317+ replace : replaceDecl ,
318+ new : newDecl ,
293319 }
294320 if astutil .OverrideSignature (d ) {
295321 oi .overrideSignature = d
@@ -299,14 +325,21 @@ func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) {
299325 case * ast.GenDecl :
300326 for j , spec := range d .Specs {
301327 purgeSpec := purgeDecl || astutil .Purge (spec )
328+ replaceSpec := replaceDecl || astutil .DirectiveReplace (spec )
329+ newSpec := newDecl || astutil .DirectiveNew (spec )
302330 switch s := spec .(type ) {
303331 case * ast.TypeSpec :
304332 overrides [s .Name .Name ] = overrideInfo {
305333 purgeMethods : purgeSpec ,
334+ replace : replaceSpec ,
335+ new : newSpec ,
306336 }
307337 case * ast.ValueSpec :
308338 for _ , name := range s .Names {
309- overrides [name .Name ] = overrideInfo {}
339+ overrides [name .Name ] = overrideInfo {
340+ replace : replaceSpec ,
341+ new : newSpec ,
342+ }
310343 }
311344 }
312345 if purgeSpec {
@@ -346,12 +379,14 @@ func augmentOriginalImports(importPath string, file *ast.File) {
346379// augmentOriginalFile is the part of parseAndAugment that processes an
347380// original file AST to augment the source code using the overrides from
348381// the overlay files.
349- func augmentOriginalFile (file * ast.File , overrides map [string ]overrideInfo ) {
382+ func augmentOriginalFile (file * ast.File , overrides map [string ]overrideInfo , found map [ string ] struct {} ) {
350383 anyChange := false
351384 for i , decl := range file .Decls {
352385 switch d := decl .(type ) {
353386 case * ast.FuncDecl :
354- if info , ok := overrides [astutil .FuncKey (d )]; ok {
387+ funcKey := astutil .FuncKey (d )
388+ if info , ok := overrides [funcKey ]; ok {
389+ found [funcKey ] = struct {}{}
355390 anyChange = true
356391 removeFunc := true
357392 if info .keepOriginal {
@@ -373,6 +408,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
373408 } else if recvKey := astutil .FuncReceiverKey (d ); len (recvKey ) > 0 {
374409 // check if the receiver has been purged, if so, remove the method too.
375410 if info , ok := overrides [recvKey ]; ok && info .purgeMethods {
411+ found [recvKey ] = struct {}{}
376412 anyChange = true
377413 file .Decls [i ] = nil
378414 }
@@ -382,6 +418,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
382418 switch s := spec .(type ) {
383419 case * ast.TypeSpec :
384420 if _ , ok := overrides [s .Name .Name ]; ok {
421+ found [s .Name .Name ] = struct {}{}
385422 anyChange = true
386423 d .Specs [j ] = nil
387424 }
@@ -395,6 +432,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
395432 // to be run, add the call into the overlay.
396433 for k , name := range s .Names {
397434 if _ , ok := overrides [name .Name ]; ok {
435+ found [name .Name ] = struct {}{}
398436 anyChange = true
399437 s .Names [k ] = nil
400438 s .Values [k ] = nil
@@ -410,6 +448,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
410448 nameRemoved := false
411449 for _ , name := range s .Names {
412450 if _ , ok := overrides [name .Name ]; ok {
451+ found [name .Name ] = struct {}{}
413452 nameRemoved = true
414453 name .Name = `_`
415454 }
@@ -438,6 +477,29 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
438477 }
439478}
440479
480+ // checkOverrides performs a final check of the overrides to ensure that
481+ // all overrides that were expected to be found were found and all overrides
482+ // that were not expected to be found were not found.
483+ func checkOverrides (overrides map [string ]overrideInfo , found map [string ]struct {}, pkgPath string ) error {
484+ el := errlist.ErrorList {}
485+ for name , info := range overrides {
486+ _ , wasFound := found [name ]
487+ switch {
488+ case wasFound && info .new :
489+ el = el .Append (fmt .Errorf ("gopherjs: original code for %s.%s was found, but override had `new` directive" , pkgPath , name ))
490+ case ! wasFound && info .replace :
491+ el = el .Append (fmt .Errorf ("gopherjs: original code for %s.%s was not found, but override had `replace` directive" , pkgPath , name ))
492+ case ! wasFound && info .keepOriginal :
493+ el = el .Append (fmt .Errorf ("gopherjs: original code for %s.%s was not found, but override had `keep-original` directive" , pkgPath , name ))
494+ case ! wasFound && info .purgeMethods :
495+ el = el .Append (fmt .Errorf ("gopherjs: original code for %s.%s was not found, but override had `purge` directive" , pkgPath , name ))
496+ case ! wasFound && info .overrideSignature != nil :
497+ el = el .Append (fmt .Errorf ("gopherjs: original code for %s.%s was not found, but override had `override-signature` directive" , pkgPath , name ))
498+ }
499+ }
500+ return el .ErrOrNil ()
501+ }
502+
441503// isOnlyImports determines if this file is empty except for imports.
442504func isOnlyImports (file * ast.File ) bool {
443505 for _ , decl := range file .Decls {
0 commit comments