diff --git a/build/build.go b/build/build.go index 0f9666f7c..1118ce233 100644 --- a/build/build.go +++ b/build/build.go @@ -225,7 +225,10 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke if err != nil { panic(err) } - file, err := parser.ParseFile(fileSet, fullPath, r, parser.ParseComments) + // Files should be uniquely named and in the original package directory in order to be + // ordered correctly + newPath := path.Join(pkg.Dir, "gopherjs__"+name) + file, err := parser.ParseFile(fileSet, newPath, r, parser.ParseComments) if err != nil { panic(err) } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go new file mode 100644 index 000000000..377f09d94 --- /dev/null +++ b/compiler/compiler_test.go @@ -0,0 +1,146 @@ +package compiler + +import ( + "bytes" + "go/ast" + "go/build" + "go/parser" + "go/token" + "go/types" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/go/loader" +) + +func TestOrder(t *testing.T) { + fileA := ` +package foo + +var Avar = "a" + +type Atype struct{} + +func Afunc() int { + var varA = 1 + var varB = 2 + return varA+varB +} +` + + fileB := ` +package foo + +var Bvar = "b" + +type Btype struct{} + +func Bfunc() int { + var varA = 1 + var varB = 2 + return varA+varB +} +` + files := []source{{"fileA.go", []byte(fileA)}, {"fileB.go", []byte(fileB)}} + + compare(t, "foo", files, false) + compare(t, "foo", files, true) +} + +func compare(t *testing.T, path string, sourceFiles []source, minify bool) { + outputNormal, err := compile(path, sourceFiles, minify) + if err != nil { + t.Fatal(err) + } + + // reverse the array + for i, j := 0, len(sourceFiles)-1; i < j; i, j = i+1, j-1 { + sourceFiles[i], sourceFiles[j] = sourceFiles[j], sourceFiles[i] + } + + outputReversed, err := compile(path, sourceFiles, minify) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(string(outputNormal), string(outputReversed)); diff != "" { + t.Errorf("files in different order produce different JS:\n%s", diff) + } +} + +type source struct { + name string + contents []byte +} + +func compile(path string, sourceFiles []source, minify bool) ([]byte, error) { + conf := loader.Config{} + conf.Fset = token.NewFileSet() + conf.ParserMode = parser.ParseComments + + context := build.Default // make a copy of build.Default + conf.Build = &context + conf.Build.BuildTags = []string{"js"} + + var astFiles []*ast.File + for _, sourceFile := range sourceFiles { + astFile, err := parser.ParseFile(conf.Fset, sourceFile.name, sourceFile.contents, parser.ParseComments) + if err != nil { + return nil, err + } + astFiles = append(astFiles, astFile) + } + conf.CreateFromFiles(path, astFiles...) + prog, err := conf.Load() + if err != nil { + return nil, err + } + + archiveCache := map[string]*Archive{} + var importContext *ImportContext + importContext = &ImportContext{ + Packages: make(map[string]*types.Package), + Import: func(path string) (*Archive, error) { + // find in local cache + if a, ok := archiveCache[path]; ok { + return a, nil + } + + pi := prog.Package(path) + importContext.Packages[path] = pi.Pkg + + // compile package + a, err := Compile(path, pi.Files, prog.Fset, importContext, minify) + if err != nil { + return nil, err + } + archiveCache[path] = a + return a, nil + }, + } + + a, err := importContext.Import(path) + if err != nil { + return nil, err + } + b, err := renderPackage(a) + if err != nil { + return nil, err + } + return b, nil +} + +func renderPackage(archive *Archive) ([]byte, error) { + selection := make(map[*Decl]struct{}) + for _, d := range archive.Declarations { + selection[d] = struct{}{} + } + + buf := &bytes.Buffer{} + + if err := WritePkgCode(archive, selection, goLinknameSet{}, false, &SourceMapFilter{Writer: buf}); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/compiler/package.go b/compiler/package.go index d1ffd13ea..c68957450 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -135,6 +135,12 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor // Some other unexpected panic, catch the stack trace and return as an error. err = bailout(e) }() + + // Files must be in the same order to get reproducible JS + sort.Slice(files, func(i, j int) bool { + return fileSet.File(files[i].Pos()).Name() > fileSet.File(files[j].Pos()).Name() + }) + typesInfo := &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), diff --git a/go.mod b/go.mod index fe517f1c2..593b5090e 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/go.sum b/go.sum index 301b83448..9bb91da22 100644 --- a/go.sum +++ b/go.sum @@ -209,8 +209,6 @@ github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxr github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=