Add alias support

PiperOrigin-RevId: 208220162
diff --git a/ts_auto_deps/analyze/loader.go b/ts_auto_deps/analyze/loader.go
index e3a70cb..bb99ea8 100644
--- a/ts_auto_deps/analyze/loader.go
+++ b/ts_auto_deps/analyze/loader.go
@@ -2,8 +2,10 @@
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"os/exec"
+	"path/filepath"
 	"strings"
 
 	"github.com/bazelbuild/buildtools/edit"
@@ -13,17 +15,6 @@
 	appb "github.com/bazelbuild/buildtools/build_proto"
 )
 
-var (
-	commonModuleLocations = []string{}
-
-	ts_auto_depsManagedRuleClasses = []string{
-		"ts_library",
-		"ts_declaration",
-		"ng_module",
-		"js_library",
-	}
-)
-
 // QueryBasedTargetLoader uses Bazel query to load targets from BUILD files.
 type QueryBasedTargetLoader struct {
 	workdir     string
@@ -61,7 +52,7 @@
 
 // LoadImportPaths uses Bazel Query to load targets associated with import
 // paths from BUILD files.
-func (q *QueryBasedTargetLoader) LoadImportPaths(_ string, paths []string) (map[string]*appb.Rule, error) {
+func (q *QueryBasedTargetLoader) LoadImportPaths(ctx context.Context, workspaceRoot string, paths []string) (map[string]*appb.Rule, error) {
 	var remainingImportPaths []string
 	results := make(map[string]*appb.Rule)
 	for _, path := range paths {
@@ -96,7 +87,7 @@
 			remainingImportPaths = append(remainingImportPaths, path)
 		}
 	}
-	var potentialCommonImports []string
+
 	// Attempt to locate the file rooted in the workspace even though it isn't
 	// prefixed by 'google3/'.
 	for _, imp := range remainingImportPaths {
@@ -122,11 +113,9 @@
 			results[imp] = rule
 			continue
 		}
-		potentialCommonImports = append(potentialCommonImports, imp)
+
 	}
-	if err := q.resolveImportsInCommonLocations(results, potentialCommonImports); err != nil {
-		return nil, err
-	}
+
 	return results, nil
 }
 
@@ -167,49 +156,6 @@
 	return nil, fmt.Errorf("failed to resolved a target for file label %q", fileLabel)
 }
 
-// searchCommonLocations searches through common locations like third_party to
-// find a target providing imports which cannot be resolved using other
-// techniques.
-func (q *QueryBasedTargetLoader) resolveImportsInCommonLocations(results map[string]*appb.Rule, paths []string) error {
-	var queries []string
-	labelToPath := make(map[string]string)
-
-	for _, path := range paths {
-		// third_party uses '_' instead of '-' since the latter is not allowed
-		// in target labels
-		underscored := strings.Replace(path, "-", "_", -1)
-		file := underscored
-		module := underscored
-		if i := strings.Index(underscored, "/"); i >= 0 {
-			// Use the slash in the import path as a separator between the
-			// module name and the path under the module.
-			file = underscored[i+1:]
-			module = commonModuleName(underscored[:i])
-		}
-		for _, l := range commonModuleLocations {
-			// Construct the potential target label in the common location.
-			target := fmt.Sprintf("%s:%s", fmt.Sprintf(l, module), file)
-			queries = append(queries, target)
-			labelToPath[target] = path
-		}
-	}
-	r, err := q.batchQuery(queries)
-	if err != nil {
-		return err
-	}
-	for _, target := range r.GetTarget() {
-		r := target.GetRule()
-		// TODO(jdhamlik): Determine if it's required that the alias resolves to
-		// an allowedRuleClass.
-		// Allow alias rules to provide imports. Alias rules should only appear
-		// in this context if they are special-cased above.
-		if c := r.GetRuleClass(); isTazeManagedRuleClass(c) || c == "alias" {
-			results[labelToPath[r.GetName()]] = r
-		}
-	}
-	return nil
-}
-
 // batchQuery runs a set of queries with a single call to Bazel query and the
 // '--keep_going' flag.
 func (q *QueryBasedTargetLoader) batchQuery(queries []string) (*appb.QueryResult, error) {
@@ -265,18 +211,62 @@
 	return uniqueLabels
 }
 
-// commonModuleName maps module names to their common names. If no common name
-// is set for a module, it returns the module's name as is.
-func commonModuleName(path string) string {
-	return path
-}
-
 // isTazeManagedRuleClass checks if a class is a ts_auto_deps-managed rule class.
 func isTazeManagedRuleClass(class string) bool {
-	for _, c := range ts_auto_depsManagedRuleClasses {
+	for _, c := range []string{
+		"ts_library",
+		"ts_declaration",
+		"ng_module",
+		"js_library",
+	} {
 		if c == class {
 			return true
 		}
 	}
 	return false
 }
+
+// typeScriptRules returns all TypeScript rules in rules.
+func typeScriptRules(rules []*appb.Rule) []*appb.Rule {
+	var tsRules []*appb.Rule
+	for _, rule := range rules {
+		for _, supportedRuleClass := range []string{
+			"ts_library",
+			"ts_declaration",
+			"ng_module",
+		} {
+			if rule.GetRuleClass() == supportedRuleClass {
+				tsRules = append(tsRules, rule)
+				break
+			}
+		}
+	}
+	return tsRules
+}
+
+// resolveAgainstModuleRoot resolves imported against moduleRoot and moduleName.
+func resolveAgainstModuleRoot(label, moduleRoot, moduleName, imported string) string {
+	if moduleRoot == "" && moduleName == "" {
+		return imported
+	}
+	trim := strings.TrimPrefix(imported, moduleName)
+	if trim == imported {
+		return imported
+	}
+	_, pkg, _ := edit.ParseLabel(label)
+	return filepath.Join(pkg, moduleRoot, trim)
+}
+
+// parsePackageName parses and returns the scope and package of imported. For
+// example, "@foo/bar" would have a scope of "@foo" and a package of "bar".
+func parsePackageName(imported string) (string, string) {
+	firstSlash := strings.Index(imported, "/")
+	if firstSlash == -1 {
+		return imported, ""
+	}
+	afterSlash := imported[firstSlash+1:]
+	if secondSlash := strings.Index(afterSlash, "/"); secondSlash > -1 {
+		return imported[:firstSlash], afterSlash[:secondSlash]
+	}
+	return imported[:firstSlash], afterSlash
+}
diff --git a/ts_auto_deps/analyze/loader_test.go b/ts_auto_deps/analyze/loader_test.go
new file mode 100644
index 0000000..ddb460c
--- /dev/null
+++ b/ts_auto_deps/analyze/loader_test.go
@@ -0,0 +1,50 @@
+package analyze
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+
+	appb "github.com/bazelbuild/buildtools/build_proto"
+)
+
+func TestResolveAgainstModuleRoot(t *testing.T) {
+	tests := []struct {
+		label, moduleRoot, moduleName, imported string
+		expectedResolution                      string
+	}{
+		{"//a", "", "", "foo", "foo"},
+		{"//b", "", "foo", "bar", "bar"},
+		{"//c", "", "foo", "foo/bar", "c/bar"},
+		{"//actual/loc:target", "mod/root", "foo/bar", "foo/bar/baz/bam", "actual/loc/mod/root/baz/bam"},
+	}
+	for _, test := range tests {
+		if resolution := resolveAgainstModuleRoot(test.label, test.moduleRoot, test.moduleName, test.imported); resolution != test.expectedResolution {
+			t.Errorf("resolveAgainstModuleRoot(%q): got %q, want %q", test.label, resolution, test.expectedResolution)
+		}
+	}
+}
+
+func TestParsePackageName(t *testing.T) {
+	tests := []struct {
+		input, scope, pkg string
+	}{
+		{"foo/bar", "foo", "bar"},
+		{"foo/bar/baz", "foo", "bar"},
+		{"foo", "foo", ""},
+		{"", "", ""},
+	}
+	for _, test := range tests {
+		if scope, pkg := parsePackageName(test.input); scope != test.scope || pkg != test.pkg {
+			t.Errorf("moduleName(%q): got %q, %q, want %q, %q", test.input, scope, pkg, test.scope, test.pkg)
+		}
+	}
+}
+
+func parseRuleLiteral(literal string) (*appb.Rule, error) {
+	var rule appb.Rule
+	if err := proto.UnmarshalText(literal, &rule); err != nil {
+		return nil, err
+	}
+	return &rule, nil
+}
diff --git a/ts_auto_deps/analyze/query.go b/ts_auto_deps/analyze/query.go
index 66336a3..302dac3 100644
--- a/ts_auto_deps/analyze/query.go
+++ b/ts_auto_deps/analyze/query.go
@@ -3,6 +3,7 @@
 package analyze
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -50,7 +51,7 @@
 	// no target is found associated with a provided import path, the import
 	// path should be excluded from the returned mapping but an error should
 	// not be returned.
-	LoadImportPaths(root string, paths []string) (map[string]*appb.Rule, error)
+	LoadImportPaths(ctx context.Context, root string, paths []string) (map[string]*appb.Rule, error)
 }
 
 // Analyzer uses a BuildLoader to generate dependency reports.
@@ -67,7 +68,7 @@
 //
 // dir is the directory that ts_auto_deps should execute in. Must be a sub-directory
 // of google3.
-func (a *Analyzer) Analyze(dir string, labels []string) ([]*arpb.DependencyReport, error) {
+func (a *Analyzer) Analyze(ctx context.Context, dir string, labels []string) ([]*arpb.DependencyReport, error) {
 	root, err := workspace.Root(dir)
 	if err != nil {
 		return nil, err
@@ -76,7 +77,7 @@
 	if err != nil {
 		return nil, err
 	}
-	resolved, err := a.resolveImportsForTargets(root, targets)
+	resolved, err := a.resolveImportsForTargets(ctx, root, targets)
 	if err != nil {
 		return nil, err
 	}
@@ -137,7 +138,7 @@
 
 // resolveImportsForTargets attempts to resolve the imports in the sources of
 // each target in targets.
-func (a *Analyzer) resolveImportsForTargets(root string, allTargets map[string]*appb.Rule) (map[string]*resolvedTarget, error) {
+func (a *Analyzer) resolveImportsForTargets(ctx context.Context, root string, allTargets map[string]*appb.Rule) (map[string]*resolvedTarget, error) {
 	targets := make(map[string]*resolvedTarget)
 	var allDeps, allSrcs []string
 	for _, t := range allTargets {
@@ -185,7 +186,7 @@
 			}
 		}
 	}
-	if err := a.resolveImports(root, targets); err != nil {
+	if err := a.resolveImports(ctx, root, targets); err != nil {
 		return nil, err
 	}
 	return targets, nil
@@ -193,7 +194,7 @@
 
 // resolveImports finds targets which provide the imported file or library
 // for imports without known targets.
-func (a *Analyzer) resolveImports(root string, targets map[string]*resolvedTarget) error {
+func (a *Analyzer) resolveImports(ctx context.Context, root string, targets map[string]*resolvedTarget) error {
 	var paths []string
 	needingResolution := make(map[string][]*ts_auto_depsImport)
 	for _, target := range targets {
@@ -226,7 +227,7 @@
 	if len(needingResolution) == 0 {
 		return nil
 	}
-	res, err := a.loader.LoadImportPaths(root, paths)
+	res, err := a.loader.LoadImportPaths(ctx, root, paths)
 	if err != nil {
 		return err
 	}
@@ -287,7 +288,7 @@
 
 // redirectedLabel looks in the target's tags for a tag starting with
 // 'alt_dep=' followed by a label. If such a tag is found, the label is
-// returned. Otherwise, the target's own label is shortened and returned.
+// returned. Otherwise, the target's own label is returned.
 func redirectedLabel(target *appb.Rule) string {
 	for _, tag := range listAttribute(target, "tags") {
 		if trimmedTag := strings.TrimPrefix(tag, "alt_dep="); trimmedTag != tag {
@@ -295,7 +296,7 @@
 		}
 	}
 	// No 'alt_dep=' tag was present on the target so no redirects need to occur.
-	return edit.ShortenLabel(target.GetName(), "")
+	return target.GetName()
 }
 
 // sources creates an array of all sources listed in the 'srcs' attribute
diff --git a/ts_auto_deps/analyze/query_test.go b/ts_auto_deps/analyze/query_test.go
index ae82d56..4b39267 100644
--- a/ts_auto_deps/analyze/query_test.go
+++ b/ts_auto_deps/analyze/query_test.go
@@ -1,6 +1,7 @@
 package analyze
 
 import (
+	"context"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -51,7 +52,7 @@
 	bl.targetsByLabels[label] = value
 }
 
-func (bl *fakeTargetLoader) LoadImportPaths(_ string, paths []string) (map[string]*appb.Rule, error) {
+func (bl *fakeTargetLoader) LoadImportPaths(_ context.Context, _ string, paths []string) (map[string]*appb.Rule, error) {
 	return bl.loadTargets(bl.targetsByImportPaths, paths)
 }
 
@@ -112,7 +113,7 @@
 			loader.byImportPath(i, r)
 		}
 	}
-	r, err := New(loader).Analyze(testTmpDir, labels)
+	r, err := New(loader).Analyze(context.Background(), testTmpDir, labels)
 	if err != nil {
 		t.Errorf("Analyze(%q): failed to generate reports: %q", labels, err)
 		return nil
diff --git a/ts_auto_deps/updater/updater.go b/ts_auto_deps/updater/updater.go
index 39beef7..a34e3fe 100644
--- a/ts_auto_deps/updater/updater.go
+++ b/ts_auto_deps/updater/updater.go
@@ -525,7 +525,7 @@
 	if err != nil {
 		return nil, nil, err
 	}
-	reports, err := analyze.New(analyze.NewQueryBasedTargetLoader(root, bazelBinary())).Analyze(buildFilePath, targets)
+	reports, err := analyze.New(analyze.NewQueryBasedTargetLoader(root, bazelBinary())).Analyze(context.Background(), buildFilePath, targets)
 	if err != nil {
 		return nil, nil, err
 	}