joneshf / purs-tools

A collection of PureScript tools
BSD 3-Clause "New" or "Revised" License
11 stars 1 forks source link

FFI files need their JavaScript dependencies #1

Open joneshf opened 3 years ago

joneshf commented 3 years ago

The gazelle Language doesn't do any parsing of FFI files to determine what JavaScript dependencies they have. Even if it did do that, we wouldn't have any way to attach that information since the purescript_library rule doesn't support runfiles (or any data attribute for that matter).

Seems like this is dependent on getting support for runfiles in the purescript_library rule.

joneshf commented 3 years ago

Looks like esbuild provides a way to get the requires pretty easily: https://github.com/evanw/esbuild/issues/761.

Seems like we might be able to make this change to pkg/rules_purescript/internal/gazelle/purescript/language.go:

diff --git a/pkg/rules_purescript/internal/gazelle/purescript/language.go b/pkg/rules_purescript/internal/gazelle/purescript/language.go
index e9381c4..6e1135a 100644
--- a/pkg/rules_purescript/internal/gazelle/purescript/language.go
+++ b/pkg/rules_purescript/internal/gazelle/purescript/language.go
@@ -17,6 +17,52 @@ import (
    "github.com/bazelbuild/bazel-gazelle/repo"
    "github.com/bazelbuild/bazel-gazelle/resolve"
    "github.com/bazelbuild/bazel-gazelle/rule"
+   "github.com/evanw/esbuild/pkg/api"
+)
+
+var (
+   nodeJSBuiltinModuleMap = map[string]bool{
+       "assert":         true,
+       "async_hooks":    true,
+       "buffer":         true,
+       "child_process":  true,
+       "cluster":        true,
+       "console":        true,
+       "constants":      true,
+       "crypto":         true,
+       "dgram":          true,
+       "dns":            true,
+       "domain":         true,
+       "events":         true,
+       "fs":             true,
+       "http":           true,
+       "http2":          true,
+       "https":          true,
+       "inspector":      true,
+       "module":         true,
+       "net":            true,
+       "os":             true,
+       "path":           true,
+       "perf_hooks":     true,
+       "process":        true,
+       "punycode":       true,
+       "querystring":    true,
+       "readline":       true,
+       "repl":           true,
+       "stream":         true,
+       "string_decoder": true,
+       "timers":         true,
+       "tls":            true,
+       "trace_events":   true,
+       "tty":            true,
+       "url":            true,
+       "util":           true,
+       "v8":             true,
+       "vm":             true,
+       "wasi":           true,
+       "worker_threads": true,
+       "zlib":           true,
+   }
 )

 type pureScript struct {
@@ -41,6 +87,58 @@ func findImports(scanner *bufio.Scanner) []string {
    return imports
 }

+func findJSImportsPlugin(imports *[]string) api.Plugin {
+   return api.Plugin{
+       Name: "joneshf_rules_purescript",
+       Setup: func(pluginBuild api.PluginBuild) {
+           pluginBuild.OnResolve(
+               api.OnResolveOptions{
+                   Filter: `.*`,
+               },
+               func(args api.OnResolveArgs) (api.OnResolveResult, error) {
+                   if args.Importer != "" {
+                       _, nodeJSBuiltin := nodeJSBuiltinModuleMap[args.Path]
+                       if !nodeJSBuiltin {
+                           // TODO(joneshf): Take the repo as an argument instead of assuming it will be `npm`.jj
+                           label := label.New("npm", args.Path, args.Path)
+                           *imports = append(*imports, label.String())
+                       }
+                       return api.OnResolveResult{
+                           External: true,
+                           Path:     args.Path,
+                       }, nil
+                   }
+                   return api.OnResolveResult{}, nil
+               },
+           )
+       },
+   }
+}
+
+func findJSImports(jsFilename string) ([]string, error) {
+   imports := []string{}
+
+   result := api.Build(api.BuildOptions{
+       Bundle: true,
+       EntryPoints: []string{
+           jsFilename,
+       },
+       Plugins: []api.Plugin{
+           findJSImportsPlugin(&imports),
+       },
+       Write: false,
+   })
+
+   if len(result.Errors) > 0 {
+       return nil, fmt.Errorf("Error finding imports from %s: %v", jsFilename, result.Errors)
+   }
+   if len(result.Warnings) > 0 {
+       return nil, fmt.Errorf("Warning finding imports from %s: %v", jsFilename, result.Warnings)
+   }
+
+   return imports, nil
+}
+
 func findModule(scanner *bufio.Scanner) (string, bool) {
    var module string
    var ok bool
@@ -199,8 +297,9 @@ func (p *pureScript) purescriptLibraryKinds() rule.KindInfo {
            "module",
        },
        MergeableAttrs: map[string]bool{
-           "ffi": true,
-           "src": true,
+           "data": true,
+           "ffi":  true,
+           "src":  true,
        },
        NonEmptyAttrs: map[string]bool{
            "ffi": true,
@@ -338,6 +437,14 @@ func (p *pureScript) generatePureScriptRules(args language.GenerateArgs, pureScr
    ffiFilename := fmt.Sprintf("%s.js", basename)
    _, err = os.Stat(filepath.Join(args.Rel, ffiFilename))
    if !os.IsNotExist(err) {
+       ffiDependencies, err := findJSImports(filepath.Join(args.Rel, ffiFilename))
+       if err != nil {
+           log.Print(err)
+           return
+       }
+       if len(ffiDependencies) > 0 {
+           r.SetAttr("data", ffiDependencies)
+       }
        r.SetAttr("ffi", ffiFilename)
    }