Skip to content

Commit de142fb

Browse files
Adding Augmentor
1 parent ec6e3ee commit de142fb

File tree

4 files changed

+270
-197
lines changed

4 files changed

+270
-197
lines changed

build/build.go

Lines changed: 53 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -139,18 +139,16 @@ type overrideInfo struct {
139139
}
140140

141141
// pkgOverrideInfo is the collection of overrides still needed for a package.
142-
//
143-
// Even after the overrides are applied to the files of a package and the
144-
// overrides map is empty, this will be kept to indicate that a package has
145-
// already been augmented so that the additional native information is not
146-
// re-applied when there is a file from this package parsed after all the
147-
// overrides are empty.
148142
type pkgOverrideInfo struct {
149143
// overrides is a map of identifier to overrideInfo to override
150144
// individual named structs, interfaces, functions, and methods.
151-
// As identifiers are used, they will be removed from this map.
152145
overrides map[string]overrideInfo
153146

147+
// overlayFiles are the files from the natives that still haven't been
148+
// appended to a file from the package, typically the first file.
149+
overlayFiles []*ast.File
150+
151+
// jsFiles are the additional JS files that are part of the natives.
154152
jsFiles []JSFile
155153
}
156154

@@ -165,40 +163,61 @@ type pkgOverrideInfo struct {
165163
//
166164
// The first file from a package will have any additional methods and
167165
// information from the natives injected into the AST. All files from a package
168-
// will be augmented by the overrides. After augmentation of a whole package,
169-
// the overrides should be empty, but might not be if the natives contain
170-
// overrides for methods that no longer exist.
166+
// will be augmented by the overrides.
171167
type Augmentor struct {
172-
xctx XContext
173-
174168
// packages is a map of package import path to the package's override.
175169
// This is used to keep track of the overrides for a package and indicate
176170
// that additional files from the natives have already been applied.
177171
packages map[string]*pkgOverrideInfo
178172
}
179173

180-
func (aug *Augmentor) Augment(fileSet *token.FileSet, filename string, src *ast.File) error {
181-
pkgName := src.Name.Name
182-
importPath := pkgName // TODO: Determine unique import path for the package.
174+
func (aug *Augmentor) getPackageOverrides(xctx XContext, pkg *PackageData, fileSet *token.FileSet) *pkgOverrideInfo {
175+
importPath := pkg.ImportPath
176+
if pkgAug, ok := aug.packages[importPath]; ok {
177+
return pkgAug
178+
}
183179

184-
pkgAug, ok := aug.packages[importPath]
185-
if !ok {
186-
if aug.packages == nil {
187-
aug.packages = map[string]*pkgOverrideInfo{}
188-
}
189-
jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, fileSet)
180+
jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, fileSet)
190181

191-
overrides := make(map[string]overrideInfo)
192-
for _, file := range overlayFiles {
193-
augmentOverlayFile(file, overrides)
194-
}
195-
delete(overrides, `init`)
182+
overrides := make(map[string]overrideInfo)
183+
for _, file := range overlayFiles {
184+
augmentOverlayFile(file, overrides)
185+
}
186+
delete(overrides, `init`)
187+
188+
pkgAug := &pkgOverrideInfo{
189+
overrides: overrides,
190+
overlayFiles: overlayFiles,
191+
jsFiles: jsFiles,
192+
}
193+
194+
if aug.packages == nil {
195+
aug.packages = map[string]*pkgOverrideInfo{}
196+
}
197+
aug.packages[importPath] = pkgAug
198+
return pkgAug
199+
}
200+
201+
func (aug *Augmentor) Augment(xctx XContext, pkg *PackageData, fileSet *token.FileSet, file *ast.File) error {
202+
pkgAug := aug.getPackageOverrides(xctx, pkg, fileSet)
203+
204+
augmentOriginalImports(pkg.ImportPath, file)
196205

197-
pkgAug = &pkgOverrideInfo{
198-
overrides: overrides,
199-
jsFiles: jsFiles,
206+
if len(pkgAug.overrides) > 0 {
207+
augmentOriginalFile(file, pkgAug.overrides)
208+
}
209+
210+
if len(pkgAug.overlayFiles) > 0 {
211+
// Append the overlay files to the first file of the package.
212+
// This is to ensure that the package is augmented with all the
213+
// additional methods and information from the natives.
214+
err := astutil.ConcatenateFiles(file, pkgAug.overlayFiles...)
215+
if err != nil {
216+
panic(fmt.Errorf("failed to concatenate overlay files onto %q: %w", fileSet.Position(file.Package).Filename, err))
200217
}
201-
aug.packages[importPath] = pkgAug
218+
pkgAug.overlayFiles = nil
219+
220+
// TODO: Finish
202221
}
203222

204223
return nil
@@ -384,8 +403,8 @@ func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) {
384403
}
385404
}
386405
if anyChange {
387-
finalizeRemovals(file)
388-
pruneImports(file)
406+
astutil.FinalizeRemovals(file)
407+
astutil.PruneImports(file)
389408
}
390409
}
391410

@@ -496,167 +515,9 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) {
496515
}
497516
}
498517
if anyChange {
499-
finalizeRemovals(file)
500-
pruneImports(file)
501-
}
502-
}
503-
504-
// isOnlyImports determines if this file is empty except for imports.
505-
func isOnlyImports(file *ast.File) bool {
506-
for _, decl := range file.Decls {
507-
if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.IMPORT {
508-
continue
509-
}
510-
511-
// The decl was either a FuncDecl or a non-import GenDecl.
512-
return false
518+
astutil.FinalizeRemovals(file)
519+
astutil.PruneImports(file)
513520
}
514-
return true
515-
}
516-
517-
// pruneImports will remove any unused imports from the file.
518-
//
519-
// This will not remove any dot (`.`) or blank (`_`) imports, unless
520-
// there are no declarations or directives meaning that all the imports
521-
// should be cleared.
522-
// If the removal of code causes an import to be removed, the init's from that
523-
// import may not be run anymore. If we still need to run an init for an import
524-
// which is no longer used, add it to the overlay as a blank (`_`) import.
525-
//
526-
// This uses the given name or guesses at the name using the import path,
527-
// meaning this doesn't work for packages which have a different package name
528-
// from the path, including those paths which are versioned
529-
// (e.g. `github.com/foo/bar/v2` where the package name is `bar`)
530-
// or if the import is defined using a relative path (e.g. `./..`).
531-
// Those cases don't exist in the native for Go, so we should only run
532-
// this pruning when we have native overlays, but not for unknown packages.
533-
func pruneImports(file *ast.File) {
534-
if isOnlyImports(file) && !astutil.HasDirectivePrefix(file, `//go:linkname `) {
535-
// The file is empty, remove all imports including any `.` or `_` imports.
536-
file.Imports = nil
537-
file.Decls = nil
538-
return
539-
}
540-
541-
unused := make(map[string]int, len(file.Imports))
542-
for i, in := range file.Imports {
543-
if name := astutil.ImportName(in); len(name) > 0 {
544-
unused[name] = i
545-
}
546-
}
547-
548-
// Remove "unused imports" for any import which is used.
549-
ast.Inspect(file, func(n ast.Node) bool {
550-
if sel, ok := n.(*ast.SelectorExpr); ok {
551-
if id, ok := sel.X.(*ast.Ident); ok && id.Obj == nil {
552-
delete(unused, id.Name)
553-
}
554-
}
555-
return len(unused) > 0
556-
})
557-
if len(unused) == 0 {
558-
return
559-
}
560-
561-
// Remove "unused imports" for any import used for a directive.
562-
directiveImports := map[string]string{
563-
`unsafe`: `//go:linkname `,
564-
`embed`: `//go:embed `,
565-
}
566-
for name, index := range unused {
567-
in := file.Imports[index]
568-
path, _ := strconv.Unquote(in.Path.Value)
569-
directivePrefix, hasPath := directiveImports[path]
570-
if hasPath && astutil.HasDirectivePrefix(file, directivePrefix) {
571-
// since the import is otherwise unused set the name to blank.
572-
in.Name = ast.NewIdent(`_`)
573-
delete(unused, name)
574-
}
575-
}
576-
if len(unused) == 0 {
577-
return
578-
}
579-
580-
// Remove all unused import specifications
581-
isUnusedSpec := map[*ast.ImportSpec]bool{}
582-
for _, index := range unused {
583-
isUnusedSpec[file.Imports[index]] = true
584-
}
585-
for _, decl := range file.Decls {
586-
if d, ok := decl.(*ast.GenDecl); ok {
587-
for i, spec := range d.Specs {
588-
if other, ok := spec.(*ast.ImportSpec); ok && isUnusedSpec[other] {
589-
d.Specs[i] = nil
590-
}
591-
}
592-
}
593-
}
594-
595-
// Remove the unused import copies in the file
596-
for _, index := range unused {
597-
file.Imports[index] = nil
598-
}
599-
600-
finalizeRemovals(file)
601-
}
602-
603-
// finalizeRemovals fully removes any declaration, specification, imports
604-
// that have been set to nil. This will also remove any unassociated comment
605-
// groups, including the comments from removed code.
606-
func finalizeRemovals(file *ast.File) {
607-
fileChanged := false
608-
for i, decl := range file.Decls {
609-
switch d := decl.(type) {
610-
case nil:
611-
fileChanged = true
612-
case *ast.GenDecl:
613-
declChanged := false
614-
for j, spec := range d.Specs {
615-
switch s := spec.(type) {
616-
case nil:
617-
declChanged = true
618-
case *ast.ValueSpec:
619-
specChanged := false
620-
for _, name := range s.Names {
621-
if name == nil {
622-
specChanged = true
623-
break
624-
}
625-
}
626-
if specChanged {
627-
s.Names = astutil.Squeeze(s.Names)
628-
s.Values = astutil.Squeeze(s.Values)
629-
if len(s.Names) == 0 {
630-
declChanged = true
631-
d.Specs[j] = nil
632-
}
633-
}
634-
}
635-
}
636-
if declChanged {
637-
d.Specs = astutil.Squeeze(d.Specs)
638-
if len(d.Specs) == 0 {
639-
fileChanged = true
640-
file.Decls[i] = nil
641-
}
642-
}
643-
}
644-
}
645-
if fileChanged {
646-
file.Decls = astutil.Squeeze(file.Decls)
647-
}
648-
649-
file.Imports = astutil.Squeeze(file.Imports)
650-
651-
file.Comments = nil // clear this first so ast.Inspect doesn't walk it.
652-
remComments := []*ast.CommentGroup{}
653-
ast.Inspect(file, func(n ast.Node) bool {
654-
if cg, ok := n.(*ast.CommentGroup); ok {
655-
remComments = append(remComments, cg)
656-
}
657-
return true
658-
})
659-
file.Comments = remComments
660521
}
661522

662523
// Options controls build process behavior.

build/build_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strconv"
88
"testing"
99

10+
"github.com/gopherjs/gopherjs/compiler/astutil"
1011
"github.com/gopherjs/gopherjs/internal/srctesting"
1112
"github.com/shurcooL/go/importgraphutil"
1213
)
@@ -423,7 +424,7 @@ func TestOverlayAugmentation(t *testing.T) {
423424

424425
overrides := map[string]overrideInfo{}
425426
augmentOverlayFile(fileSrc, overrides)
426-
pruneImports(fileSrc)
427+
astutil.PruneImports(fileSrc)
427428

428429
got := srctesting.Format(t, f.FileSet, fileSrc)
429430

@@ -724,7 +725,7 @@ func TestOriginalAugmentation(t *testing.T) {
724725

725726
augmentOriginalImports(importPath, fileSrc)
726727
augmentOriginalFile(fileSrc, test.info)
727-
pruneImports(fileSrc)
728+
astutil.PruneImports(fileSrc)
728729

729730
got := srctesting.Format(t, f.FileSet, fileSrc)
730731

0 commit comments

Comments
 (0)