// BuildSource invokes the compiler starting at the given root source file path. func BuildSource(rootSourceFilePath string, debug bool, vcsDevelopmentDirectories ...string) bool { // Disable logging unless the debug flag is on. if !debug { log.SetOutput(ioutil.Discard) } // Build a scope graph for the project. This will conduct parsing and type graph // construction on our behalf. log.Println("Starting build") scopeResult := scopegraph.ParseAndBuildScopeGraph(rootSourceFilePath, vcsDevelopmentDirectories, CORE_LIBRARY) outputWarnings(scopeResult.Warnings) if !scopeResult.Status { outputErrors(scopeResult.Errors) return false } // Generate the program's source. filename := path.Base(rootSourceFilePath) + ".js" mapname := filename + ".map" log.Println("Generating ES5") generated, sourceMap, err := es5.GenerateES5(scopeResult.Graph, mapname, "") if err != nil { panic(err) } marshalledMap, err := sourceMap.Build().Marshal() if err != nil { panic(err) } generated += "\n//# sourceMappingURL=" + mapname // Write the source and its map. filepath := path.Join(path.Dir(rootSourceFilePath), filename) mappath := path.Join(path.Dir(rootSourceFilePath), mapname) log.Printf("Writing generated source to %s\n", filepath) ioutil.WriteFile(filepath, []byte(generated), 0644) ioutil.WriteFile(mappath, marshalledMap, 0644) log.Println("Work completed") return true }
// Build performs the build of the source, writing the result to the response writer. func (dt *developTransaction) Build(w http.ResponseWriter, r *http.Request) { // Build a scope graph for the project. This will conduct parsing and type graph // construction on our behalf. scopeResult := scopegraph.ParseAndBuildScopeGraph(dt.rootSourceFilePath, dt.vcsDevelopmentDirectories, builder.CORE_LIBRARY) if !scopeResult.Status { dt.sourceMap = sourcemap.NewSourceMap(dt.name+".develop.js", "source/") for _, warning := range scopeResult.Warnings { dt.emitWarning(w, warning) } for _, err := range scopeResult.Errors { dt.emitError(w, err) } dt.emitInfo(w, "Build failed") dt.closeGroup(w) } else { // Generate the program's source. generated, sourceMap, err := es5.GenerateES5(scopeResult.Graph, dt.name+".develop.js", "source/") if err != nil { panic(err) } dt.sourceMap = sourceMap fmt.Fprint(w, generated) dt.emitInfo(w, "Build completed successfully") dt.closeGroup(w) dt.offsetCount = len(strings.Split(string(generated), "\n")) for _, warning := range scopeResult.Warnings { dt.emitWarning(w, warning) } } fmt.Fprintf(w, "//# sourceMappingURL=/%s.develop.js.map\n", dt.name) }
func TestSourceMapping(t *testing.T) { for _, test := range sourceMappingTests { entrypointFile := "tests/sourcemapping/" + test.name + ".seru" if os.Getenv("FILTER") != "" && !strings.Contains(test.name, os.Getenv("FILTER")) { continue } // Parse and scope. fmt.Printf("Running mapping test %v...\n", test.name) scopeResult := scopegraph.ParseAndBuildScopeGraph(entrypointFile, []string{}, packageloader.Library{TESTLIB_PATH, false, ""}) if !assert.True(t, scopeResult.Status, "Got error for ScopeGraph construction %v: %s", test.name, scopeResult.Errors) { continue } filename := path.Base(entrypointFile) + ".js" mapname := filename + ".map" // Generate the formatted ES5 code. generated, sourceMap, err := GenerateES5(scopeResult.Graph, mapname, "") if !assert.Nil(t, err, "Error when generating ES5 for mapping test %s", test.name) { continue } builtMap := sourceMap.Build() // Create a variant of the ES5 code, with inline comments to the original source. var buf bytes.Buffer outer: for lineNumber, line := range strings.Split(generated, "\n") { for colPosition, character := range line { mapping, hasMapping := builtMap.LookupMapping(lineNumber, colPosition) if hasMapping { buf.WriteString("/*#") sourcePath := mapping.SourcePath snippet, err := getSnippet(sourcePath, mapping.LineNumber, mapping.ColumnPosition) if !assert.Nil(t, err, "Error reading snippet from file %s", sourcePath) { break outer } buf.WriteString(snippet) buf.WriteString("#*/") } buf.WriteRune(character) } buf.WriteRune('\n') } source := buf.String() if os.Getenv("REGEN") == "true" { test.writeExpected(source) } else { // Compare the generated source to the expected. expectedSource := test.expected() assert.Equal(t, expectedSource, source, "Mapped mismatch on test %s\nExpected: %v\nActual: %v\n\n", test.name, expectedSource, source) } } }
func TestGenerator(t *testing.T) { for _, test := range generationTests { entrypointFile := "tests/" + test.input + "/" + test.entrypoint + ".seru" if os.Getenv("FILTER") != "" && !strings.Contains(test.name, os.Getenv("FILTER")) { continue } fmt.Printf("Running test %v...\n", test.name) result := scopegraph.ParseAndBuildScopeGraph(entrypointFile, []string{}, packageloader.Library{TESTLIB_PATH, false, ""}) if !assert.True(t, result.Status, "Got error for ScopeGraph construction %v: %s", test.name, result.Errors) { continue } module, found := result.Graph.TypeGraph().LookupModule(compilercommon.InputSource(entrypointFile)) if !assert.True(t, found, "Could not find entrypoint module %s for test: %s", entrypointFile, test.name) { continue } moduleMap := generateModules(result.Graph) builder, hasBuilder := moduleMap[module] if !assert.True(t, hasBuilder, "Could not find builder for module %s for test: %s", entrypointFile, test.name) { continue } buf := esbuilder.BuildSource(builder) source, err := escommon.FormatECMASource(buf.String()) if !assert.Nil(t, err, "Could not format module source under test %v: %v", test.name, err) { continue } if os.Getenv("REGEN") == "true" { test.writeExpected(source) } else { // Compare the generated source to the expected. expectedSource := test.expected() assert.Equal(t, expectedSource, source, "Source mismatch on test %s\nExpected: %v\nActual: %v\n\n", test.name, expectedSource, source) if test.integrationTest != integrationTestNone { fullSource, _, err := GenerateES5(result.Graph, "", "") if !assert.Nil(t, err, "Error generating full source for test %s: %v", test.name, err) { continue } if os.Getenv("DEBUGLINE") != "" { lines := strings.Split(fullSource, "\n") lineNumber, _ := strconv.Atoi(os.Getenv("DEBUGLINE")) t.Errorf("Line %v: %v", lineNumber, lines[lineNumber-1]) continue } vm := otto.New() vm.Set("debugprint", func(call otto.FunctionCall) otto.Value { t.Errorf("DEBUG: %v\n", call.Argument(0).String()) return otto.Value{} }) vm.Set("testprint", func(call otto.FunctionCall) otto.Value { t.Errorf("TEST: %v\n", call.Argument(0).String()) return otto.Value{} }) vm.Run(`this.debugprint = debugprint; this.testprint = testprint; function setTimeout(f, t) { f() } `) promiseFile, _ := os.Open("es6-promise.js") defer promiseFile.Close() promiseSource, _ := ioutil.ReadAll(promiseFile) promiseScript, cerr := vm.Compile("promise", promiseSource) if !assert.Nil(t, cerr, "Error compiling promise: %v", cerr) { continue } _, perr := vm.Run(promiseScript) if !assertNoOttoError(t, test.name, string(promiseSource), perr) { continue } generatedScript, cgerr := vm.Compile("generated", fullSource) if !assert.Nil(t, cgerr, "Error compiling generated code for test %v: %v", test.name, cgerr) { continue } _, verr := vm.Run(generatedScript) if !assertNoOttoError(t, test.name, fullSource, verr) { continue } if !assert.Nil(t, verr, "Error running full source for test %s: %v", test.name, verr) { continue } testCall := ` $resolved = undefined; $rejected = undefined; this.boolValue = true; this.Serulian.then(function(g) { g.` + test.entrypoint + `.TEST().then(function(r) { $resolved = r.$wrapped; }).catch(function(err) { $rejected = err; }); }); if ($rejected) { throw $rejected; } $resolved` testScript, cterr := vm.Compile("test", testCall) if !assert.Nil(t, cterr, "Error compiling test call: %v", cterr) { continue } rresult, rerr := vm.Run(testScript) if test.integrationTest == integrationTestSuccessExpected { if !assertNoOttoError(t, test.name, testCall, rerr) { continue } if !assert.True(t, rresult.IsBoolean(), "Non-boolean result for running test case %s: %v", test.name, rresult) { continue } boolValue, _ := rresult.ToBoolean() if !assert.True(t, boolValue, "Non-true boolean result for running test case %s: %v", test.name, boolValue) { continue } } else { if !assert.Equal(t, test.expectedErrorMessage, rerr.Error(), "Error message mismatch for test case %v: %v", test.name, rerr) { continue } } } } } }
// buildAndRunTests builds the source found at the given path and then runs its tests via the runner. func buildAndRunTests(filePath string, runner TestRunner) (bool, error) { log.Printf("Building %s...", filePath) filename := path.Base(filePath) scopeResult := scopegraph.ParseAndBuildScopeGraph(filePath, []string{}, builder.CORE_LIBRARY) if !scopeResult.Status { // TODO: better output return false, fmt.Errorf("Compilation errors for test %s: %v", filePath, scopeResult.Errors) } // Generate the source. generated, sourceMap, err := es5.GenerateES5(scopeResult.Graph, filename+".js", "") if err != nil { log.Fatal(err) } // Save the source (with an adjusted call), in a temporary directory. moduleName := filename[0 : len(filename)-len(parser.SERULIAN_FILE_EXTENSION)] adjusted := fmt.Sprintf(` %s window.Serulian.then(function(global) { global.%s.TEST().then(function(a) { }).catch(function(err) { throw err; }) }) //# sourceMappingURL=/%s.js.map `, generated, moduleName, filename) dir, err := ioutil.TempDir("", "testing") if err != nil { log.Fatal(err) } // Clean up once complete. defer os.RemoveAll(dir) // Write the source and map into the directory. marshalled, err := sourceMap.Build().Marshal() if err != nil { log.Fatal(err) } err = ioutil.WriteFile(path.Join(dir, filename+".js"), []byte(adjusted), 0777) if err != nil { log.Fatal(err) } err = ioutil.WriteFile(path.Join(dir, filename+".js.map"), marshalled, 0777) if err != nil { log.Fatal(err) } // Call the runner with the test file. return runner.Run(path.Join(dir, filename+".js")) }