Unpublish ts_auto_deps.
PiperOrigin-RevId: 282807385
diff --git a/BUILD.bazel b/BUILD.bazel
index 7f69e55..b0a0051 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -45,8 +45,5 @@
"//devserver:devserver-windows",
"//internal:generated_BUILD",
"//internal:tsc_wrapped",
- "//ts_auto_deps:ts_auto_deps-darwin",
- "//ts_auto_deps:ts_auto_deps-linux",
- "//ts_auto_deps:ts_auto_deps-windows",
],
)
diff --git a/README.md b/README.md
index 41028ad..8b1aceb 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,6 @@
It contains these utilities:
- `ts_devserver`: a Go library and binary that runs a fast local web server which concatenates JavaScript on-the-fly. It requires inputs in a named module format (module ids must be contained in the file, not inferred from the file's path).
-- `ts_auto_deps`: a Go library and binary which generates `BUILD.bazel` files from TypeScript sources.
- `tsc_wrapped`: a TypeScript program which wraps the TypeScript compiler, hosting it under a Bazel worker.
- `tsetse`: a collection of third-party "strictness" checks which we add to the TypeScript compiler.
- `internal/common/*.bzl`: some Starlark utility code for running the `ts_library` rule.
diff --git a/package.bzl b/package.bzl
index 2e4473d..e1c4bb3 100644
--- a/package.bzl
+++ b/package.bzl
@@ -42,7 +42,7 @@
sha256 = "f624fe9ca8d51de192655369ac538c420afb7cde16e1ad052554b582fff09287",
)
- # For building ts_devserver and ts_auto_deps binaries
+ # For building ts_devserver binary
# See https://github.com/bazelbuild/rules_go#setup for the latest version.
_maybe(
http_archive,
@@ -77,15 +77,6 @@
sha256 = "3c681998538231a2d24d0c07ed5a7658cb72bfb5fd4bf9911157c0e9ac6a2687",
)
- # ts_auto_deps depends on com_github_bazelbuild_buildtools
- _maybe(
- http_archive,
- name = "com_github_bazelbuild_buildtools",
- url = "https://github.com/bazelbuild/buildtools/archive/0.19.2.1.zip",
- strip_prefix = "buildtools-0.19.2.1",
- sha256 = "9176a7df34dbed2cf5171eb56271868824560364e60644348219f852f593ae79",
- )
-
def _maybe(repo_rule, name, **kwargs):
if not native.existing_rule(name):
repo_rule(name = name, **kwargs)
diff --git a/ts_auto_deps/BUILD.bazel b/ts_auto_deps/BUILD.bazel
deleted file mode 100644
index b71e8a1..0000000
--- a/ts_auto_deps/BUILD.bazel
+++ /dev/null
@@ -1,48 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
- name = "go_default_library",
- srcs = ["main.go"],
- importpath = "github.com/bazelbuild/rules_typescript/ts_auto_deps",
- visibility = ["//visibility:private"],
- deps = [
- "//ts_auto_deps/platform:go_default_library",
- "//ts_auto_deps/updater:go_default_library",
- ],
-)
-
-go_binary(
- name = "ts_auto_deps_bin",
- embed = [":go_default_library"],
- visibility = ["//visibility:public"],
-)
-
-go_binary(
- name = "ts_auto_deps-darwin",
- out = "ts_auto_deps-darwin_x64",
- embed = [":go_default_library"],
- goarch = "amd64",
- goos = "darwin",
- pure = "on",
- visibility = ["//visibility:public"],
-)
-
-go_binary(
- name = "ts_auto_deps-linux",
- out = "ts_auto_deps-linux_x64",
- embed = [":go_default_library"],
- goarch = "amd64",
- goos = "linux",
- pure = "on",
- visibility = ["//visibility:public"],
-)
-
-go_binary(
- name = "ts_auto_deps-windows",
- out = "ts_auto_deps-win32_x64.exe",
- embed = [":go_default_library"],
- goarch = "amd64",
- goos = "windows",
- pure = "on",
- visibility = ["//visibility:public"],
-)
diff --git a/ts_auto_deps/analyze/BUILD.bazel b/ts_auto_deps/analyze/BUILD.bazel
deleted file mode 100644
index 29213ce..0000000
--- a/ts_auto_deps/analyze/BUILD.bazel
+++ /dev/null
@@ -1,37 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-go_library(
- name = "go_default_library",
- srcs = [
- "analyze.go",
- "imports.go",
- "loader.go",
- ],
- importpath = "github.com/bazelbuild/rules_typescript/ts_auto_deps/analyze",
- visibility = ["//visibility:public"],
- deps = [
- "//ts_auto_deps/platform:go_default_library",
- "//ts_auto_deps/proto:go_default_library",
- "//ts_auto_deps/workspace:go_default_library",
- "@com_github_bazelbuild_buildtools//build_proto:go_default_library",
- "@com_github_bazelbuild_buildtools//edit:go_default_library",
- "@com_github_golang_protobuf//proto:go_default_library",
- ],
-)
-
-go_test(
- name = "go_default_test",
- srcs = [
- "analyze_test.go",
- "imports_test.go",
- "loader_test.go",
- ],
- embed = [":go_default_library"],
- deps = [
- "//ts_auto_deps/platform:go_default_library",
- "//ts_auto_deps/proto:go_default_library",
- "@com_github_bazelbuild_buildtools//build_proto:go_default_library",
- "@com_github_golang_protobuf//proto:go_default_library",
- "@com_github_kylelemons_godebug//pretty:go_default_library",
- ],
-)
diff --git a/ts_auto_deps/analyze/analyze.go b/ts_auto_deps/analyze/analyze.go
deleted file mode 100644
index 9e005cc..0000000
--- a/ts_auto_deps/analyze/analyze.go
+++ /dev/null
@@ -1,603 +0,0 @@
-// Package analyze uses bazel query to determine and locate missing imports
-// in TypeScript source files.
-package analyze
-
-import (
- "context"
- "fmt"
- "os"
- "path/filepath"
- "regexp"
- "strings"
-
- "github.com/bazelbuild/buildtools/edit"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/platform"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/workspace"
- "github.com/golang/protobuf/proto"
-
- appb "github.com/bazelbuild/buildtools/build_proto"
- arpb "github.com/bazelbuild/rules_typescript/ts_auto_deps/proto"
-)
-
-var (
- extensions = []string{
- // '.d.ts' must come before '.ts' to completely remove the '.d.ts'
- // extension.
- ".d.ts",
- ".ts",
- ".tsx",
- }
-)
-
-const (
- // debug enables/disables debug logging. Set to true to have debug statements
- // print to stdout, set to false to disable debug statements.
- debug = false
-)
-
-// debugf prints a formatted message prefixed with "DEBUG:" if the debug
-// flag is enabled.
-func debugf(format string, v ...interface{}) {
- if debug {
- fmt.Printf(fmt.Sprintf("DEBUG: %s\n", format), v...)
- }
-}
-
-// TargetLoader provides methods for loading targets from BUILD files.
-type TargetLoader interface {
- // LoadTargets loads targets from BUILD files associated with labels. A target
- // is a rule, source file, generated file, package group or environment group.
- // It returns a mapping from labels to targets or an error, if any occurred.
- //
- // A label must be the absolute label associated with a target. For example,
- // '//foo/bar:baz' is acceptable whereas 'bar:baz' or '//foo/bar' will result
- // in undefined behavior. TODO(lucassloan): make this an error
- //
- // Only returns targets visible to currentPkg. If currentPkg is an empty
- // string returns all targets regardless of visibility.
- LoadTargets(currentPkg string, labels []string) (map[string]*appb.Target, error)
- // LoadRules loads rules from BUILD files associated with labels.
- // It returns a mapping from labels to rules or an error, if any
- // occurred.
- //
- // A label must be the absolute label associated with a rule. For
- // example, '//foo/bar:baz' is acceptable whereas 'bar:baz' or '//foo/bar'
- // will result in undefined behavior.
- // TODO(lucassloan): make this an error.
- //
- // Only returns rules visible to currentPkg. If currentPkg is an empty string
- // returns all rules regardless of visibility.
- LoadRules(currentPkg string, labels []string) (map[string]*appb.Rule, error)
- // LoadImportPaths loads targets from BUILD files associated with import
- // paths relative to a root directory. It returns a mapping from import
- // paths to targets or an error, if any occurred.
- //
- // An import path is the path present in a TypeScript import statement
- // resolved relative to the workspace root. For example, an import
- // statement 'import baz from "../baz.ts"' declared in the TypeScript
- // source file '//foo/bar.ts' would have the import path of 'baz.ts'. If
- // 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.
- //
- // Only returns rules visible to currentPkg. If currentPkg is an empty string
- // returns all targets regardless of visibility.
- LoadImportPaths(ctx context.Context, targetToAnalyze *appb.Rule, currentPkg, root string, paths []string) (map[string]*appb.Rule, error)
-}
-
-// Analyzer uses a BuildLoader to generate dependency reports.
-type Analyzer struct {
- loader TargetLoader
-}
-
-// New returns a new Analyzer which can be used to generate dependency reports.
-func New(loader TargetLoader) *Analyzer {
- return &Analyzer{loader: loader}
-}
-
-// Analyze generates a dependency report for each target label in labels.
-//
-// dir is the directory that ts_auto_deps should execute in. Must be a sub-directory
-// of the workspace root.
-func (a *Analyzer) Analyze(ctx context.Context, dir string, labels []string) ([]*arpb.DependencyReport, error) {
- if len(labels) == 0 {
- return nil, nil
- }
- _, currentPkg, _ := edit.ParseLabel(labels[0])
- for _, label := range labels {
- if _, pkg, _ := edit.ParseLabel(label); pkg != currentPkg {
- return nil, fmt.Errorf("can't analyze targets in different packages")
- }
- }
- root, err := workspace.Root(dir)
- if err != nil {
- return nil, err
- }
- rules, err := a.loader.LoadRules(currentPkg, labels)
- if err != nil {
- return nil, err
- }
- resolved, err := a.resolveImportsForTargets(ctx, currentPkg, root, rules)
- if err != nil {
- return nil, err
- }
- return a.generateReports(labels, resolved)
-}
-
-// resolvedTarget represents a Bazel target and all resolved information.
-type resolvedTarget struct {
- label string
- // A map of all existing dependencies on a target at the time of analysis.
- // The keys are labels and the values are thes loaded target.
- dependencies map[string]*appb.Rule
- // A map of source file paths to their imports.
- imports map[string][]*ts_auto_depsImport
- // rule is the original rule the target was constructed from.
- rule *appb.Rule
- // missingSources are source files which could not be opened on disk.
- // These are added to the dependency reports and MissingSources.
- missingSources []string
- // A map from the labels in the target's srcs to the Targets those
- // labels refer.
- sources map[string]*appb.Target
- literalSourcePaths []string
- generatedSourcePaths []string
-}
-
-// setSources sets the sources on t. It returns an error if one of the srcs of
-// t's rule isn't in loadedSrcs. It also sorts the sources into literal and
-// generated sources, setting literalSourcePaths and generatedSourcePaths.
-// Returns an error if all the sources are generated - ts_auto_deps can't read the
-// import statements to determine deps.
-func (t *resolvedTarget) setSources(loadedSrcs map[string]*appb.Target) error {
- for _, label := range listAttribute(t.rule, "srcs") {
- src := loadedSrcs[label]
- if src == nil {
- return fmt.Errorf("no source found for label %s", label)
- }
- t.sources[label] = src
- if src.GetType() == appb.Target_SOURCE_FILE {
- t.literalSourcePaths = append(t.literalSourcePaths, labelToPath(label))
- } else {
- t.generatedSourcePaths = append(t.generatedSourcePaths, labelToPath(label))
- }
- }
- if len(t.literalSourcePaths) == 0 && len(t.generatedSourcePaths) > 0 {
- return fmt.Errorf("rule has generated sources - cannot determine dependencies")
- }
- return nil
-}
-
-// srcs returns the labels of the sources of t.
-func (t *resolvedTarget) srcs() ([]string, error) {
- srcs := listAttribute(t.rule, "srcs")
- if srcs == nil {
- return nil, fmt.Errorf("target %q missing \"srcs\" attribute", t.label)
- }
-
- return srcs, nil
-}
-
-// getAllLiteralSrcPaths returns the file paths of all the non-generated sources
-// of the targets.
-func getAllLiteralSrcPaths(targets map[string]*resolvedTarget) ([]string, error) {
- var allLiteralSrcPaths []string
- for _, t := range targets {
- allLiteralSrcPaths = append(allLiteralSrcPaths, t.literalSourcePaths...)
- }
-
- return allLiteralSrcPaths, nil
-}
-
-func (t *resolvedTarget) deps() []string {
- return listAttribute(t.rule, "deps")
-}
-
-// provides returns whether the resolved target can provide the path provided.
-func (t *resolvedTarget) provides(path string) bool {
- for _, label := range listAttribute(t.rule, "srcs") {
- src := t.sources[label]
- if src.GetType() == appb.Target_SOURCE_FILE {
- // For literal sources, check the path of the source
- if labelToPath(label) == path {
- return true
- }
- } else if src.GetType() == appb.Target_RULE {
- // For generated souces, check against the paths of rule's
- // outputs
- for _, genSrc := range src.GetRule().GetRuleOutput() {
- if labelToPath(genSrc) == path {
- return true
- }
- }
- }
- }
- return false
-}
-
-// newTarget constructs a new target instance from a loaded rule.
-func newResolvedTarget(r *appb.Rule) *resolvedTarget {
- return &resolvedTarget{
- label: r.GetName(),
- dependencies: make(map[string]*appb.Rule),
- imports: make(map[string][]*ts_auto_depsImport),
- rule: r,
- sources: make(map[string]*appb.Target),
- }
-}
-
-// resolveImportsForTargets attempts to resolve the imports in the sources of
-// each target in targets.
-func (a *Analyzer) resolveImportsForTargets(ctx context.Context, currentPkg, root string, allTargets map[string]*appb.Rule) (map[string]*resolvedTarget, error) {
- targets := make(map[string]*resolvedTarget)
- var allDeps, allSrcs []string
- for _, t := range allTargets {
- target := newResolvedTarget(t)
- targets[target.label] = target
- srcs, err := target.srcs()
- if err != nil {
- return nil, err
- }
- allDeps = append(allDeps, target.deps()...)
- allSrcs = append(allSrcs, srcs...)
- }
- deps, err := a.loader.LoadRules(currentPkg, allDeps)
- if err != nil {
- return nil, err
- }
- // Associate the loaded existing deps with the target or targets which
- // contained them.
- for _, t := range targets {
- for _, dep := range t.deps() {
- t.dependencies[dep] = deps[dep]
- }
- }
- // load all the sources in the targets, so that literal and generated
- // targets can be distinguished
- srcs, err := a.loader.LoadTargets(currentPkg, allSrcs)
- if err != nil {
- return nil, err
- }
- for _, t := range targets {
- err := t.setSources(srcs)
- if err != nil {
- return nil, err
- }
- }
- // only extract the imports out of the literal sources, since ts_auto_deps can't
- // see the contents of generated files
- allLiteralSrcPaths, err := getAllLiteralSrcPaths(targets)
- if err != nil {
- return nil, err
- }
- imports, errs := extractAllImports(root, allLiteralSrcPaths)
- for _, err := range errs {
- // NotExist errors are caught and added to the generated dependency
- // reports as missing source files. Only errors which are not NotExist
- // errors should be reported.
- if !os.IsNotExist(err) {
- return nil, err
- }
- }
- for _, t := range targets {
- srcs := t.literalSourcePaths
- for _, src := range srcs {
- v, ok := imports[src]
- if ok {
- t.imports[src] = v
- } else {
- // The source was not found on disk during import extraction.
- t.missingSources = append(t.missingSources, relativePathLabel(t.label, src))
- }
- }
- }
- if err := a.resolveImports(ctx, currentPkg, root, targets); err != nil {
- return nil, err
- }
- return targets, nil
-}
-
-// resolveImports finds targets which provide the imported file or library
-// for imports without known targets.
-func (a *Analyzer) resolveImports(ctx context.Context, currentPkg, root string, targets map[string]*resolvedTarget) error {
- for _, target := range targets {
- var paths []string
- needingResolution := make(map[string][]*ts_auto_depsImport)
- for _, imports := range target.imports {
- handlingImports:
- for _, imp := range imports {
- resolvedPath := imp.resolvedPath()
- for _, path := range pathWithExtensions(resolvedPath) {
- if target.provides(path) {
- imp.knownTarget = target.label
- continue handlingImports
- }
- }
- d, err := a.findExistingDepProvidingImport(ctx, root, target, imp)
- if err != nil {
- return err
- }
- if d == "" {
- // A target providing the import was not found on the
- // existing dependencies or in a comment. Use other
- // heuristics.
- paths = append(paths, resolvedPath)
- needingResolution[resolvedPath] = append(needingResolution[resolvedPath], imp)
- continue
- }
- imp.knownTarget = d
- }
- }
- if len(needingResolution) == 0 {
- continue
- }
- res, err := a.loader.LoadImportPaths(ctx, target.rule, currentPkg, root, paths)
- if err != nil {
- return err
- }
- for path, imports := range needingResolution {
- if target, ok := res[path]; ok {
- for _, imp := range imports {
- imp.knownTarget = redirectedLabel(target)
- }
- }
- }
- }
- return nil
-}
-
-func pathWithExtensions(basename string) []string {
- var paths []string
- for _, ext := range extensions {
- paths = append(paths, basename+ext)
- }
- return paths
-}
-
-var ambientModuleDeclRE = regexp.MustCompile("(?m)^\\s*declare\\s+module\\s+['\"]([^'\"]+)['\"]\\s+\\{")
-
-// findExistingDepProvidingImport looks through a map of the existing deps to
-// see if any of them provide the import in a way that can't be queried
-// for. E.g. if the build rule has a "module_name" attribute or if one
-// of the .d.ts sources has an ambient module declaration.
-//
-// If the import already has a knownTarget, findRuleProvidingImport will
-// return the knownTarget.
-func (a *Analyzer) findExistingDepProvidingImport(ctx context.Context, root string, rt *resolvedTarget, i *ts_auto_depsImport) (string, error) {
- if i.knownTarget != "" {
- return i.knownTarget, nil
- }
-
- // check if any of the existing deps declare a module_name that matches the import
- for _, r := range rt.dependencies {
- resolvedImportPath := resolveAgainstModuleRoot(r, i.importPath)
- if resolvedImportPath == i.importPath {
- continue
- }
-
- // enumerate all the possible filepaths for the resolved import path, and
- // compare against all the srcs
- possibleImportPaths := possibleFilepaths(resolvedImportPath)
- for _, src := range listAttribute(r, "srcs") {
- for _, mi := range possibleImportPaths {
- if mi == labelToPath(src) {
- return r.GetName(), nil
- }
- }
- }
- }
-
- // check if any of the other sources or the souces of any of the deps are .d.ts
- // files which have ambient module declarations
- var allRules []*appb.Rule
- for _, r := range rt.dependencies {
- allRules = append(allRules, r)
- }
- allRules = append(allRules, rt.rule)
- for _, r := range allRules {
- for _, src := range listAttribute(r, "srcs") {
- fp := filepath.Join(root, labelToPath(src))
- if !strings.HasSuffix(fp, ".d.ts") {
- continue
- }
-
- contents, err := platform.ReadFile(ctx, fp)
- if err != nil {
- return "", fmt.Errorf("error reading file looking for ambient module decls: %s", err)
- }
-
- matches := ambientModuleDeclRE.FindAllStringSubmatch(string(contents), -1)
-
- // put all the ambient modules into a set
- declaredModules := make(map[string]bool)
- for _, match := range matches {
- declaredModules[match[1]] = true
- }
-
- // remove all the modules that were imported (ie all the modules that
- // were being augmented/re-opened)
- for _, mi := range parseImports(fp, contents) {
- delete(declaredModules, mi.importPath)
- }
-
- if declaredModules[i.importPath] {
- debugf("found import %s in ambient module declaration in %s", i.importPath, r.GetName())
- return r.GetName(), nil
- }
- }
- }
- return "", nil
-}
-
-// stripTSExtension removes TypeScript extensions from a file path. If no
-// TypeScript extensions are present, the filepath is returned unaltered.
-func stripTSExtension(path string) string {
- for _, ext := range extensions {
- if strings.HasSuffix(path, ext) {
- return strings.TrimSuffix(path, ext)
- }
- }
- return path
-}
-
-// 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 returned.
-func redirectedLabel(target *appb.Rule) string {
- for _, tag := range listAttribute(target, "tags") {
- if trimmedTag := strings.TrimPrefix(tag, "alt_dep="); trimmedTag != tag {
- return trimmedTag
- }
- }
- // No 'alt_dep=' tag was present on the target so no redirects need to occur.
- return target.GetName()
-}
-
-func labelToPath(label string) string {
- _, pkg, file := edit.ParseLabel(label)
- return platform.Normalize(filepath.Clean(filepath.Join(pkg, file)))
-}
-
-// generateReports generates reports for each label in labels.
-func (a *Analyzer) generateReports(labels []string, labelToTarget map[string]*resolvedTarget) ([]*arpb.DependencyReport, error) {
- reports := make([]*arpb.DependencyReport, 0, len(labels))
- for _, label := range labels {
- target, ok := labelToTarget[label]
- if !ok {
- // This case should never happen.
- platform.Fatalf("target %s no longer loaded", label)
- }
- report, err := a.generateReport(target)
- if err != nil {
- return nil, err
- }
- reports = append(reports, report)
- }
- return reports, nil
-}
-
-// generateReport generates a dependency report for a target.
-//
-// It adds imports for which no target could be found to unresolved imports.
-// Imports which had locatable targets are added to the necessary dependency
-// or missing dependency properties if the import was already present on target
-// or the import was not already present respectively.
-//
-// Missing source files detected during import resolution are added to the
-// reports. Dependencies which were present on the initial target but are not
-// required are added to the unnecessary dependency array.
-func (a *Analyzer) generateReport(target *resolvedTarget) (*arpb.DependencyReport, error) {
- usedDeps := make(map[string]bool)
- report := &arpb.DependencyReport{
- Rule: proto.String(target.label),
- MissingSourceFile: target.missingSources,
- }
- for _, imports := range target.imports {
- handlingImports:
- for _, imp := range imports {
- if imp.knownTarget == target.label {
- continue
- }
- if imp.knownTarget == "" {
- if strings.HasPrefix(imp.importPath, "goog:") {
- // This feedback needs to be phrased this way since the
- // updater.go relies on parsing the feedback strings to
- // determine which 'goog:' imports to add.
- report.Feedback = append(report.Feedback,
- fmt.Sprintf(
- "ERROR: %s:%d:%d: missing comment for 'goog:' import, "+
- "please add a trailing comment to the import. E.g.\n "+
- "import Bar from '%s'; // from //foo:bar",
- imp.location.sourcePath, imp.location.line, imp.location.offset, imp.importPath))
- }
- report.UnresolvedImport = append(report.UnresolvedImport, imp.resolvedPath())
- continue
- }
-
- for _, dep := range target.deps() {
- if edit.LabelsEqual(dep, imp.knownTarget, "") {
- // fmt.Printf("%s provides %s\n", dep, imp.importPath)
- usedDeps[dep] = true
- report.NecessaryDependency = append(report.NecessaryDependency, imp.knownTarget)
- continue handlingImports
- }
- }
- report.MissingDependencyGroup = append(report.MissingDependencyGroup, &arpb.DependencyGroup{
- Dependency: []string{edit.ShortenLabel(imp.knownTarget, "")},
- ImportPath: []string{imp.importPath},
- })
- }
- }
-
- var unusedDeps []string
- for _, dep := range target.deps() {
- if _, ok := usedDeps[dep]; !ok {
- unusedDeps = append(unusedDeps, dep)
- }
- }
- labelToRule, err := a.loader.LoadRules("", unusedDeps)
- if err != nil {
- return nil, err
- }
- for label, rule := range labelToRule {
- switch class := rule.GetRuleClass(); class {
- case "ts_declaration":
- // TypeScript declarations might declare arbitrary global symbols, so it
- // is impossible to detect reliably if the import is being used (without
- // compiling, at least). Report that the rule has no explicit import as a
- // warning, so that ts_auto_deps can decide to import remove or not based on a
- // flag.
- warning := fmt.Sprintf("WARNING: %s: keeping possibly used %s '%s'", rule.GetLocation(), class, label)
- report.Feedback = append(report.Feedback, warning)
- case "css_library":
- // Similar to ts_declaration, ts_auto_deps can't reliably detect if css_library
- // imports are being used, since ts_auto_deps can't currently parse @requirecss
- // annotations. Unlike ts_declaration, there's no flag to remove them, so
- // there's no need to report a warning.
- default:
- // The contents of generated files aren't visible, so ts_auto_deps can't discover
- // the import statements/deps that they contain. To be safe, don't remove
- // any unused deps, since they might be used by the generated file(s).
- if len(target.generatedSourcePaths) == 0 {
- report.UnnecessaryDependency = append(report.UnnecessaryDependency, label)
- }
- }
- }
- return report, nil
-}
-
-// relativePathLabel converts src to a label for a path relative to the
-// provided target. For example, a target '//foo/bar' and a src 'foo/bar/baz.ts'
-// would result in a relative path label of '//foo/bar:baz.ts'.
-func relativePathLabel(label, src string) string {
- _, pkg, _ := edit.ParseLabel(label)
- return fmt.Sprintf("//%s:%s", pkg, strings.TrimPrefix(src, pkg+"/"))
-}
-
-// listAttribute retrieves the attribute from target with name.
-func listAttribute(target *appb.Rule, name string) []string {
- if a := attribute(target, name); a != nil {
- return a.GetStringListValue()
- }
- return nil
-}
-
-func stringAttribute(target *appb.Rule, name string) string {
- if a := attribute(target, name); a != nil {
- return a.GetStringValue()
- }
- return ""
-}
-
-func attribute(target *appb.Rule, name string) *appb.Attribute {
- for _, a := range target.GetAttribute() {
- if a.GetName() == name {
- return a
- }
- }
- return nil
-}
-
-func isGenerated(rule *appb.Rule) bool {
- return stringAttribute(rule, "generator_name") != ""
-}
diff --git a/ts_auto_deps/analyze/analyze_test.go b/ts_auto_deps/analyze/analyze_test.go
deleted file mode 100644
index 7a7d90a..0000000
--- a/ts_auto_deps/analyze/analyze_test.go
+++ /dev/null
@@ -1,657 +0,0 @@
-package analyze
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "reflect"
- "strings"
- "testing"
-
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/platform"
- "github.com/golang/protobuf/proto"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/kylelemons/godebug/pretty"
-
- appb "github.com/bazelbuild/buildtools/build_proto"
- arpb "github.com/bazelbuild/rules_typescript/ts_auto_deps/proto"
-)
-
-var (
- testTmpDir = os.Getenv("TEST_TMPDIR")
-)
-
-func TestMain(m *testing.M) {
- if err := createWorkspaceFile(); err != nil {
- platform.Fatalf("failed to create WORKSPACE file: %q", err)
- }
- os.Exit(m.Run())
-}
-
-const (
- testDirectory = "a"
-)
-
-type fakeTargetLoader struct {
- targetsByLabels map[string]string
- targetsByImportPaths map[string]string
-}
-
-func newFakeTargetLoader() *fakeTargetLoader {
- return &fakeTargetLoader{
- targetsByLabels: make(map[string]string),
- targetsByImportPaths: make(map[string]string),
- }
-}
-
-func (bl *fakeTargetLoader) LoadRules(_ string, labels []string) (map[string]*appb.Rule, error) {
- return bl.loadRules(bl.targetsByLabels, labels)
-}
-
-func (bl *fakeTargetLoader) LoadTargets(_ string, labels []string) (map[string]*appb.Target, error) {
- targets := make(map[string]*appb.Target)
- for _, l := range labels {
- if strings.Contains(l, ".") {
- targets[l] = &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()}
- } else {
- targets[l] = &appb.Target{Type: appb.Target_RULE.Enum()}
- }
- }
- return targets, nil
-}
-
-func (bl *fakeTargetLoader) byLabel(label, value string) {
- bl.targetsByLabels[label] = value
-}
-
-func (bl *fakeTargetLoader) LoadImportPaths(_ context.Context, _ *appb.Rule, _, _ string, paths []string) (map[string]*appb.Rule, error) {
- return bl.loadRules(bl.targetsByImportPaths, paths)
-}
-
-func (bl *fakeTargetLoader) byImportPath(importPath, value string) {
- bl.targetsByImportPaths[importPath] = value
-}
-
-func (bl *fakeTargetLoader) loadRules(source map[string]string, keys []string) (map[string]*appb.Rule, error) {
- targets := make(map[string]*appb.Rule)
- for _, key := range keys {
- value, ok := source[key]
- if !ok {
- return nil, nil
- }
- var target appb.Rule
- if err := proto.UnmarshalText(strings.Trim(value, " \n\r\t"), &target); err != nil {
- return nil, err
- }
- targets[key] = &target
- }
- return targets, nil
-}
-
-type file struct {
- path string
- contents []string
-}
-
-// testTarget represents a target with a label, a proto literal, and any import
-// paths that should resolve to the target.
-type testTarget struct {
- label, protoLiteral string
- importPaths []string
-}
-
-func analyzeTargets(t *testing.T, labels []string, targets []*testTarget, files []*file) []*arpb.DependencyReport {
- t.Helper()
- for _, file := range files {
- path := filepath.Join(testDirectory, file.path)
- if err := createFile(path, file.contents...); err != nil {
- t.Errorf("failed to create file %q: %q", file.path, err)
- return nil
- }
- defer os.Remove(path)
- }
- for i, label := range labels {
- labels[i] = fmt.Sprintf("//%s:%s", testDirectory, label)
- }
- loader := newFakeTargetLoader()
- for _, t := range targets {
- label := t.label
- if !strings.HasPrefix(label, "//") {
- label = fmt.Sprintf("//%s:%s", testDirectory, label)
- }
- r := fmt.Sprintf("name: %q\n%s", label, t.protoLiteral)
- loader.byLabel(label, r)
- for _, i := range t.importPaths {
- loader.byImportPath(i, r)
- }
- }
- 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
- }
- if len(r) != len(labels) {
- t.Errorf("Analyze(%q): got %d reports, wanted %d", labels, len(r), len(labels))
- return nil
- }
- return r
-}
-
-func TestUnresolvedImports(t *testing.T) {
- tests := []struct {
- filepath, fileContents string
- expectedImports []string
- }{
- {"b/importer.ts", "import X from './path';", []string{"a/b/path"}},
- {"b/importer.ts", "import X from 'absolute/path';", []string{"absolute/path"}},
- {"b/importer.ts", "import X from '../../root';", []string{"root"}},
- {"b/importer.ts", "import X from './multi/subpath';", []string{"a/b/multi/subpath"}},
- {"b/importer.ts", "import X from '/rooted';", []string{"/rooted"}},
- {"b/importer.ts", "import X from 'absolute/path';\nimport Y from './path';", []string{"absolute/path", "a/b/path"}},
- {"b/importer.ts", "import X from 'some/path'; // from //target:location", nil},
- {"importer.d.ts", "import y from 'some/thing/missing';", []string{"some/thing/missing"}},
- }
- for _, test := range tests {
- r := analyzeTargets(t, []string{"a_lib"}, []*testTarget{
- {"a_lib", fmt.Sprintf(`
- rule_class: "ts_library"
- attribute: <
- name: "srcs"
- string_list_value: "//a:%s"
- type: 5
- >`, test.filepath), nil},
- }, []*file{{test.filepath, []string{test.fileContents}}})
- if r == nil {
- continue
- }
- if diff := pretty.Compare(r[0].GetUnresolvedImport(), test.expectedImports); diff != "" {
- t.Errorf("Analyze(%q): failed to detect unresolved imports: (-got, +want)\n%s", test.fileContents, diff)
- }
- }
-}
-
-func TestUnnecessaryDependencies(t *testing.T) {
- tests := [][]string{
- []string{"/* nothing */"},
- }
- for _, test := range tests {
- r := analyzeTargets(t, []string{"a_lib"}, []*testTarget{
- {"a_lib", `
- rule_class: "ts_library"
- attribute: <
- name: "srcs"
- string_list_value: "//a:importer.ts"
- type: 5
- >
- attribute: <
- name: "deps"
- string_list_value: "//a:b_lib"
- type: 5
- >`, nil},
- {"b_lib", `
- rule_class: "ts_library"`, nil},
- }, []*file{{"t/importer.ts", test}})
- if r == nil {
- continue
- }
- if diff := pretty.Compare(r[0].GetUnnecessaryDependency(), []string{"//a:b_lib"}); diff != "" {
- t.Errorf("Analyze(%q): failed to detect unnecessary dependencies: (-got, +want)\n%s", test, diff)
- }
- }
-}
-
-func TestNecessaryDependencies(t *testing.T) {
- tests := [][]string{
- []string{"import x from 'b/target';"},
- []string{"// ts_auto_deps: x from //b:b_lib"},
- []string{"export x from 'b/target';"},
- }
- for _, test := range tests {
- r := analyzeTargets(t, []string{"a_lib"}, []*testTarget{
- {"a_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//a:importer.ts"
- >
- attribute: <
- type: 5
- name: "deps"
- string_list_value: "//b:b_lib"
- >`, nil},
- {"//b:b_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//b:target.ts"
- >`, []string{"b/target"}},
- }, []*file{{"importer.ts", test}})
- if r == nil {
- continue
- }
- if diff := pretty.Compare(r[0].GetNecessaryDependency(), []string{"//b:b_lib"}); diff != "" {
- t.Errorf("Analyze(%q): failed to detect necessary deps: (-got, +want)\n%s", test, diff)
- }
- }
-}
-
-func TestMissingDependencies(t *testing.T) {
- tests := []struct {
- fileContents string
- missingDeps []string
- }{
- {"import x from 'b/c';\nimport y from 'angular';", []string{"//b/c:b_lib", "//third_party/javascript/typings/angular"}},
- {"import * as angular from 'angular';\ndeclare module 'angular' { /* reopen */ }", []string{"//third_party/javascript/typings/angular"}},
- }
- for _, test := range tests {
- r := analyzeTargets(t, []string{"a_lib"}, []*testTarget{
- {"a_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//a:x.ts"
- >`, nil},
- {"//b/c:b_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//b/c:index.ts"
- >`, []string{"b/c"}},
- {"//third_party/javascript/typings/angular:angular", `
- rule_class: "ts_declaration"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//third_party/javascript/typings/angular:index.d.ts"
- >`, []string{"angular"}},
- }, []*file{{"x.ts", []string{test.fileContents}}})
- if r == nil {
- continue
- }
- if diff := pretty.Compare(missingDeps(r[0]), test.missingDeps); diff != "" {
- t.Errorf("Analyze(%q): failed to detect missing dependencies: (-got, +want)\n%s", test.fileContents, diff)
- }
- }
-}
-
-func TestMissingSourceFile(t *testing.T) {
- r := analyzeTargets(t, []string{"a_lib"}, []*testTarget{
- {"a_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//a:f1.ts"
- string_list_value: "//a:f2.ts"
- string_list_value: "//a:p/f3.ts"
- >`, nil},
- }, []*file{{"f1.ts", []string{"/* nothing */"}}})
- if r == nil {
- t.FailNow()
- }
- if diff := pretty.Compare(r[0].GetMissingSourceFile(), []string{"//a:f2.ts", "//a:p/f3.ts"}); diff != "" {
- t.Fatalf("Analyze: failed to detect missing source files: (-got, +want)\n%s", diff)
- }
-}
-
-func TestMultipleLabels(t *testing.T) {
- r := analyzeTargets(t, []string{"a_lib", "b_lib"}, []*testTarget{
- {"a_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//a:a/importer.ts"
- >`, nil},
- {"b_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//a:b/importer.ts"
- >`, nil},
- }, []*file{
- {"a/importer.ts", []string{"import X from './path';"}},
- {"b/importer.ts", []string{"import X from './path';"}},
- })
- if r == nil {
- t.FailNow()
- }
- tests := []struct {
- label string
- unresolvedImports []string
- }{
- {"a_lib", []string{"a/a/path"}},
- {"b_lib", []string{"a/b/path"}},
- }
- for i, test := range tests {
- report := r[i]
- if diff := pretty.Compare(report.GetUnresolvedImport(), test.unresolvedImports); diff != "" {
- t.Errorf("Analyze(%q): failed to detect unresolved imports: (-got, +want)\n%s", test.label, diff)
- }
- }
-}
-
-func TestMultipleSourceFiles(t *testing.T) {
- r := analyzeTargets(t, []string{"a_lib"}, []*testTarget{
- {"a_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//a:importer.ts"
- string_list_value: "//a:exporter.ts"
- >`, nil},
- }, []*file{
- {"importer.ts", []string{"import {x} from 'a/exporter';"}},
- {"exporter.ts", []string{"export let x = 12;"}},
- })
- if r == nil {
- t.FailNow()
- }
- if diff := pretty.Compare(missingDeps(r[0]), []string{}); diff != "" {
- t.Fatalf("Analyze: failed to detect missing dependencies: (-got, +want)\n%s", diff)
- }
-}
-
-func TestRedirectTag(t *testing.T) {
- r := analyzeTargets(t, []string{"a_lib"}, []*testTarget{
- {"a_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//a:x.ts"
- >`, nil},
- {"dlib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "deps"
- string_list_value: "//b:clib"
- >`, nil},
- {"clib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "//clib:c.ts"
- >
- attribute: <
- type: 5
- name: "tags"
- string_list_value: "alt_dep=//d:dlib"
- >`, []string{"b/c"}},
- }, []*file{{"x.ts", []string{"import x from 'b/c';"}}})
- if r == nil {
- t.FailNow()
- }
- if diff := pretty.Compare(missingDeps(r[0]), []string{"//d:dlib"}); diff != "" {
- t.Fatalf("Analyze: failed to detect missing dependencies: (-got, +want)\n%s", diff)
- }
-}
-
-func TestCircularImport(t *testing.T) {
- r := analyzeTargets(t, []string{"a_lib"}, []*testTarget{
- {"a_lib", `
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "f1.ts"
- string_list_value: "f2.ts"
- >`, []string{"a/f1", "a/f2"}},
- }, []*file{
- {"f1.ts", []string{"import {x} from 'a/f1';", "export let y = x + 1;"}},
- {"f2.ts", []string{"import {y} from 'a/f2';", "export let x = y + 0;"}},
- })
- if r == nil {
- t.FailNow()
- }
- if diff := pretty.Compare(missingDeps(r[0]), []string{}); diff != "" {
- t.Fatalf("Analyze: failed to detect missing dependencies: (-got, +want)\n%s", diff)
- }
-}
-
-func TestListAttribute(t *testing.T) {
- tests := []struct {
- name string
- value []string
- }{
- {"srcs", []string{"a.ts", "b.ts"}},
- {"deps", []string{":core"}},
- }
- result, err := createResult(`
- target: <
- type: 1
- rule: <
- name: "//tmp:tmp"
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "srcs"
- string_list_value: "a.ts"
- string_list_value: "b.ts"
- >
- attribute: <
- type: 5
- name: "deps"
- string_list_value: ":core"
- >
- >
- >`)
- if err != nil {
- t.Fatalf("failed to create result: %q", err)
- }
- for _, test := range tests {
- attrValue := listAttribute(result.GetTarget()[0].GetRule(), test.name)
- if attrValue == nil {
- t.Errorf("listAttribute(%q): failed to find attribute", test.name)
- continue
- }
- if diff := pretty.Compare(attrValue, test.value); diff != "" {
- t.Errorf("listAttribute(%q): failed to get correct attribute values: (-got, +want)\n%s", test.name, diff)
- }
- }
-}
-
-func TestStringAttribute(t *testing.T) {
- tests := []struct {
- name, value string
- }{
- {"module_name", "@angular/core"},
- {"module_root", ""},
- }
- result, err := createResult(`
- target: <
- type: 1
- rule: <
- name: "//tmp:tmp"
- rule_class: "ts_library"
- attribute: <
- type: 5
- name: "module_name"
- string_value: "@angular/core"
- >
- >
- >`)
- if err != nil {
- t.Fatalf("failed to create result: %q", err)
- }
- for _, test := range tests {
- attrValue := stringAttribute(result.GetTarget()[0].GetRule(), test.name)
- if diff := pretty.Compare(attrValue, test.value); diff != "" {
- t.Errorf("stringAttribute(%q): failed to get correct attribute values: (-got, +want)\n%s", test.name, diff)
- }
- }
-}
-
-func TestSetSources(t *testing.T) {
- tests := []struct {
- name string
- srcs []string
- loadedSrcs map[string]*appb.Target
- err error
- expected map[string]*appb.Target
- expectedLiteralPaths []string
- expectedGeneratedPaths []string
- }{
- {
- "NoSources",
- nil,
- nil,
- nil,
- nil,
- nil,
- nil,
- },
- {
- "OneSource",
- []string{"//a:file.ts"},
- map[string]*appb.Target{
- "//a:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- },
- nil,
- map[string]*appb.Target{
- "//a:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- },
- []string{"a/file.ts"},
- nil,
- },
- {
- "ExtraSources",
- []string{"//a:file.ts"},
- map[string]*appb.Target{
- "//a:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- "//b:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- "//b:generator": &appb.Target{Type: appb.Target_RULE.Enum()},
- "//b:wiz": &appb.Target{Type: appb.Target_RULE.Enum()},
- },
- nil,
- map[string]*appb.Target{
- "//a:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- },
- []string{"a/file.ts"},
- nil,
- },
- {
- "MultipleLiteralSources",
- []string{"//a:file.ts", "//b:file.ts"},
- map[string]*appb.Target{
- "//a:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- "//b:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- },
- nil,
- map[string]*appb.Target{
- "//a:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- "//b:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- },
- []string{"a/file.ts", "b/file.ts"},
- nil,
- },
- {
- "MultipleGeneratedSources",
- []string{"//b:generator", "//b:wiz"},
- map[string]*appb.Target{
- "//b:generator": &appb.Target{Type: appb.Target_RULE.Enum()},
- "//b:wiz": &appb.Target{Type: appb.Target_RULE.Enum()},
- },
- fmt.Errorf("rule has generated sources - cannot determine dependencies"),
- nil,
- nil,
- nil,
- },
- {
- "MixedSources",
- []string{"//a:file.ts", "//b:file.ts", "//b:generator", "//b:wiz"},
- map[string]*appb.Target{
- "//a:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- "//b:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- "//b:generator": &appb.Target{Type: appb.Target_RULE.Enum()},
- "//b:wiz": &appb.Target{Type: appb.Target_RULE.Enum()},
- },
- nil,
- map[string]*appb.Target{
- "//a:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- "//b:file.ts": &appb.Target{Type: appb.Target_SOURCE_FILE.Enum()},
- "//b:generator": &appb.Target{Type: appb.Target_RULE.Enum()},
- "//b:wiz": &appb.Target{Type: appb.Target_RULE.Enum()},
- },
- []string{"a/file.ts", "b/file.ts"},
- []string{"//b:generator", "//b:wiz"},
- },
- {
- "MissingSources",
- []string{"//a:file.ts"},
- nil,
- fmt.Errorf("no source found for label %s", "//a:file.ts"),
- nil,
- nil,
- nil,
- },
- }
-
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- rt := &resolvedTarget{
- rule: &appb.Rule{
- Attribute: []*appb.Attribute{
- &appb.Attribute{
- Name: proto.String("srcs"),
- Type: appb.Attribute_STRING_LIST.Enum(),
- StringListValue: test.srcs,
- },
- },
- },
- sources: make(map[string]*appb.Target),
- }
-
- err := rt.setSources(test.loadedSrcs)
- if !reflect.DeepEqual(err, test.err) {
- t.Errorf("got err %q, expected %q", err, test.err)
- }
-
- if diff := cmp.Diff(rt.sources, test.expected, cmpopts.EquateEmpty(), cmp.Comparer(proto.Equal)); err == nil && diff != "" {
- t.Errorf("failed to set correct sources: (-got, +want)\n%s", diff)
- }
- })
- }
-}
-
-func missingDeps(report *arpb.DependencyReport) []string {
- var deps []string
- for _, group := range report.GetMissingDependencyGroup() {
- deps = append(deps, group.GetDependency()...)
- }
- return deps
-}
-
-func createResult(str string) (*appb.QueryResult, error) {
- var result appb.QueryResult
- return &result, proto.UnmarshalText(strings.Trim(str, " \n\r\t"), &result)
-}
-
-func createFile(path string, content ...string) error {
- if !filepath.IsAbs(path) {
- path = filepath.Join(filepath.Dir(testTmpDir), path)
- }
- if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
- return err
- }
- return ioutil.WriteFile(path, []byte(strings.Join(content, "\n")), 0666)
-}
-
-// This method creates a WORKSPACE file in the root of the Bazel test
-// directory. This allows the tests to resolve the root path of the
-// workspace by looking for the WORKSPACE file on disk.
-func createWorkspaceFile() error {
- path := filepath.Join(filepath.Dir(testTmpDir), "WORKSPACE")
- return ioutil.WriteFile(path, []byte("workspace(name = 'foo')"), 0666)
-}
diff --git a/ts_auto_deps/analyze/imports.go b/ts_auto_deps/analyze/imports.go
deleted file mode 100644
index 28f6501..0000000
--- a/ts_auto_deps/analyze/imports.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package analyze
-
-import (
- "io/ioutil"
- "path/filepath"
- "regexp"
- "strings"
- "sync"
-
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/platform"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/workspace"
-)
-
-// ts_auto_depsImport represents a single import in a TypeScript source.
-type ts_auto_depsImport struct {
- // importPath can be an ES6 path ('./foo/bar'), but also a namespace ('goog:...').
- // This is the import path as it appears in the TypeScript source.
- importPath string
- // knownTarget is the (fully qualified) bazel target providing importPath.
- // It's either found by locateMissingTargets or taken from a ts_auto_deps comment.
- knownTarget string
- location sourceLocation
-}
-
-// resolvedPath is the path to the import relative to the root of the
-// workspace. For example, an import of './foo' in the 'bar/baz' directory
-// would have a path from root of 'bar/baz/foo'.
-//
-// Absolute imports have no resolvedPath since the physical location of
-// these imports depends on the dependencies of the target the source
-// location is a member of. For example, an import of 'foo/bar' would have
-// no resolvedPath.
-func (i *ts_auto_depsImport) resolvedPath() string {
- if strings.HasPrefix(i.importPath, "./") || strings.HasPrefix(i.importPath, "../") {
- // If the import is relative to the source location, use the source
- // location to form a "canonical" path from the root.
- return platform.Normalize(filepath.Clean(filepath.Join(filepath.Dir(i.location.sourcePath), i.importPath)))
- } else if trim := strings.TrimPrefix(i.importPath, workspace.Name()+"/"); trim != i.importPath {
- return trim
- }
- // The import is an absolute import and therefore does not have a definite
- // resolved path.
- return i.importPath
-}
-
-// sourceLocation points to a position in a source file.
-type sourceLocation struct {
- // Workspace root relative source path.
- sourcePath string
- // offset and length are byte offsets, line is the 1-indexed source line (considering only \n as breaks).
- offset, length, line int
-}
-
-// extractAllImports extracts the TypeScript imports from paths.
-//
-// paths should be relative to root. The root will be joined to each path
-// to construct a physical path to each file.
-func extractAllImports(root string, paths []string) (map[string][]*ts_auto_depsImport, []error) {
- debugf("extracting imports from TypeScript files relative to %q: %q", root, paths)
- allImports := make(map[string][]*ts_auto_depsImport)
- var (
- errors []error
- mutex sync.Mutex
- group sync.WaitGroup
- )
- for _, path := range paths {
- group.Add(1)
- go func(path string) {
- defer group.Done()
- imports, err := extractImports(root, path)
- // Lock the mutex to prevent concurrent writes.
- mutex.Lock()
- defer mutex.Unlock()
- if err != nil {
- errors = append(errors, err)
- return
- }
- allImports[path] = imports
- }(path)
- }
- group.Wait()
- return allImports, errors
-}
-
-// extractImports extracts the TypeScript imports from a single file. path
-// should be a path from the root to the file.
-func extractImports(root, path string) ([]*ts_auto_depsImport, error) {
- d, err := ioutil.ReadFile(filepath.Join(root, path))
- if err != nil {
- return nil, err
- }
- return parseImports(path, d), nil
-}
-
-const (
- ts_auto_depsFrom = `^[ \t]*//[ \t]+ts_auto_deps:[^\n]*?from[ \t]+(?P<Target>//\S+)$`
- importPreface = `^[ \t]*(?:import|export)\b\s*`
- wildcardTerm = `\*(?:\s*as\s+\S+)?` // "as..." is optional to match exports.
- identifiersClause = `(?:\{[^}]*\}|\S+|` + wildcardTerm + `)`
- symbolsTerm = `(?:` + identifiersClause + `(?:,\s*` + identifiersClause + `)?\s*\bfrom\b\s*)?`
- url = `['"](?P<URL>[^'";]+)['"]\s*;?`
- namespaceComment = `(?:\s*//[ \t]*from[ \t]+(?P<Target>//\S+)$)?`
-)
-
-var importRE = regexp.MustCompile("(?ms)" +
- "(?:" + ts_auto_depsFrom + ")|" +
- "(?:" + importPreface + symbolsTerm + url + namespaceComment + ")")
-
-// parseImports scans contents for imports (ES6 modules, ts_auto_deps comments), and
-// returns a list of ts_auto_depsImports. knownTarget is already filled in for imports
-// that have ts_auto_deps comments.
-func parseImports(sourcePath string, contents []byte) []*ts_auto_depsImport {
- var imports []*ts_auto_depsImport
- lastOffset := 0
- line := 1
- column := 1
- for _, matchIndices := range importRE.FindAllSubmatchIndex(contents, -1) {
- imp := &ts_auto_depsImport{}
- imports = append(imports, imp)
- // matchIndices[0, 1]: full RE match
- imp.location.sourcePath = sourcePath
- for lastOffset < matchIndices[1] {
- // Iterate to the *end* of the import statement.
- // The ts_auto_deps comment must be placed at the end of the "import" statement.
- // This offset has to be exactly the end of the import for ts_auto_deps later on
- // to insert the '// from' comment in the correct line.
- column++
- if contents[lastOffset] == '\n' {
- line++
- column = 1
- }
- lastOffset++
- }
- imp.location.offset = matchIndices[0]
- imp.location.length = matchIndices[1] - matchIndices[0]
- imp.location.line = line
- if matchIndices[2] >= 0 {
- // matchIndices[2, 3]: Target for a // ts_auto_deps: ... from ... comment.
- imp.knownTarget = string(contents[matchIndices[2]:matchIndices[3]])
- } else {
- // matchIndices[4, 5]: URL in import x from 'url';
- imp.importPath = string(contents[matchIndices[4]:matchIndices[5]])
- }
- if matchIndices[6] >= 0 {
- // matchIndices[6, 7]: Target for a // from comment
- imp.knownTarget = string(contents[matchIndices[6]:matchIndices[7]])
- }
- }
- return imports
-}
diff --git a/ts_auto_deps/analyze/imports_test.go b/ts_auto_deps/analyze/imports_test.go
deleted file mode 100644
index 029df0e..0000000
--- a/ts_auto_deps/analyze/imports_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package analyze
-
-import (
- "fmt"
- "testing"
-
- "github.com/kylelemons/godebug/pretty"
-)
-
-func TestParseImports(t *testing.T) {
- tests := []struct{ text, importPath, knownTarget string }{
- // Imports
- {"import {a} from 'named';", "named", ""},
- {"code before;\n import {a} from 'before after'; code after",
- "before after", ""},
- {"import A from 'default';", "default", ""},
- {"import A$X from 'default';", "default", ""},
- {"import {x as $} from 'default';", "default", ""},
- {"import ü from 'default';", "default", ""},
- {"import * as prefix from 'wildcard prefixed';", "wildcard prefixed", ""},
- {" \t import {A, B as C} from 'renamed';", "renamed", ""},
- {"import 'sideeffect import';", "sideeffect import", ""},
- {"import\n {A\n, B} from 'newlines';", "newlines", ""},
- {"import*as prefix from'no whitespace';", "no whitespace", ""},
- {"import Symbol from 'goog:some.Symbol'; // from //target:location",
- "goog:some.Symbol", "//target:location"},
- {"import Symbol from 'goog:some.Symbol';//from //target:location",
- "goog:some.Symbol", "//target:location"},
- {"import {a} from 'missing semi'", "missing semi", ""},
- {"import {a} from 'missing semi' // from //target:location",
- "missing semi", "//target:location"},
- {"import A, {B, C} from 'mixed';", "mixed", ""},
- {"import A, * as B from 'mixed';", "mixed", ""},
- {"import * as B, A from 'inverted mixed';", "inverted mixed", ""},
- // Exports
- {"export * from 'wildcard';", "wildcard", ""},
- {"export {a, b} from 'named';", "named", ""},
- {"export {} from 'empty import';", "empty import", ""},
- {"export {a as b} from 'renamed';", "renamed", ""},
- {"export\n {A\n, B} from 'newlines';", "newlines", ""},
- {"export*from'no whitespace';", "no whitespace", ""},
- {"export{}from'no whitespace';", "no whitespace", ""},
- // Comments
- {"x;\n// ts_auto_deps: ng from //some/global:rule\ny;", "", "//some/global:rule"},
- {"// ts_auto_deps: ng from //foo/bar from //some/global:rule", "", "//some/global:rule"},
- }
-
- for i, tst := range tests {
- imports := parseImports(fmt.Sprintf("test%d.ts", i), []byte(tst.text))
- if len(imports) != 1 {
- t.Errorf("parseImports(%q): got %d import(s), want 1", tst.text, len(imports))
- continue
- }
- imp := imports[0]
- if imp.importPath != tst.importPath {
- t.Errorf("parseImports(%q): got %q, want %q", tst.text, imp.importPath, tst.importPath)
- }
- if imp.knownTarget != tst.knownTarget {
- t.Errorf("parseImports(%q): got %q, want %q", tst.text, imp.knownTarget, tst.knownTarget)
- }
- }
-}
-
-func TestParseImportsSourceLocation(t *testing.T) {
- tests := []struct {
- text string
- expectedSourceLocation sourceLocation
- }{
- {"import {a} from 'named';", sourceLocation{line: 1, offset: 0, length: 24}},
- {"\n\timport {a} from 'named';", sourceLocation{line: 2, offset: 1, length: 25}},
- }
- for i, tst := range tests {
- sourcePath := fmt.Sprintf("test%d.ts", i)
- imports := parseImports(sourcePath, []byte(tst.text))
- if len(imports) != 1 {
- t.Errorf("parseImports(%q): got %d import(s), want 1", tst.text, len(imports))
- continue
- }
- imp := imports[0]
- tst.expectedSourceLocation.sourcePath = sourcePath
- if diff := pretty.Compare(imp.location, tst.expectedSourceLocation); diff != "" {
- t.Errorf("parseImports(%q): expected different source location: (-got, +want)\n%s", tst.text, diff)
- }
- }
-}
diff --git a/ts_auto_deps/analyze/loader.go b/ts_auto_deps/analyze/loader.go
deleted file mode 100644
index 4a98600..0000000
--- a/ts_auto_deps/analyze/loader.go
+++ /dev/null
@@ -1,737 +0,0 @@
-package analyze
-
-import (
- "bytes"
- "context"
- "fmt"
- "os/exec"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/bazelbuild/buildtools/edit"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/platform"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/workspace"
- "github.com/golang/protobuf/proto"
-
- appb "github.com/bazelbuild/buildtools/build_proto"
-)
-
-// pkgCacheEntry represents a set of loaded rules and a mapping from alias
-// to rules from a package.
-type pkgCacheEntry struct {
- // rules is all rules in a package.
- rules []*appb.Rule
- // aliases is a map from an alias label to the actual rule of the alias.
- aliases map[string]*appb.Rule
-}
-
-// QueryBasedTargetLoader uses Bazel query to load targets from BUILD files.
-type QueryBasedTargetLoader struct {
- workdir string
- bazelBinary string
-
- // pkgCache is a mapping from a package to all of the rules in said
- // package along with a map from aliases to actual rules.
- //
- // Keys are of the form of "<visibility>|<package>" where visibility
- // is the package that rules in package must be visible to and package
- // is the actual package that has been loaded and cached.
- //
- // Since a new target loader is constructed for each directory being
- // analyzed in the "-recursive" case, these caches will be garbage
- // collected between directories.
- pkgCache map[string]*pkgCacheEntry
- // labelCache is a mapping from a label to its loaded target.
- labelCache map[string]*appb.Target
-
- // queryCount is the total number of queries executed by the target loader.
- queryCount int
-}
-
-// NewQueryBasedTargetLoader constructs a new QueryBasedTargetLoader rooted
-// in workdir.
-func NewQueryBasedTargetLoader(workdir, bazelBinary string) *QueryBasedTargetLoader {
- return &QueryBasedTargetLoader{
- workdir: workdir,
- bazelBinary: bazelBinary,
-
- pkgCache: make(map[string]*pkgCacheEntry),
- labelCache: make(map[string]*appb.Target),
- }
-}
-
-// LoadRules uses Bazel query to load rules associated with labels from BUILD
-// files.
-func (q *QueryBasedTargetLoader) LoadRules(pkg string, labels []string) (map[string]*appb.Rule, error) {
- labelToTarget, err := q.LoadTargets(pkg, labels)
- if err != nil {
- return nil, err
- }
-
- labelToRule := make(map[string]*appb.Rule)
- for _, label := range labels {
- target := labelToTarget[label]
- if target.GetType() == appb.Target_RULE {
- labelToRule[label] = target.GetRule()
- } else {
- return nil, fmt.Errorf("target %s contains object of type %q instead of type %q", label, target.GetType(), appb.Target_RULE)
- }
- }
- return labelToRule, nil
-}
-
-// LoadTargets uses Bazel query to load targets associated with labels from BUILD
-// files.
-func (q *QueryBasedTargetLoader) LoadTargets(pkg string, labels []string) (map[string]*appb.Target, error) {
- var labelCacheMisses []string
- for _, label := range labels {
- if _, ok := q.labelCache[labelCacheKey(pkg, label)]; !ok {
- labelCacheMisses = append(labelCacheMisses, label)
- }
- }
- if len(labelCacheMisses) > 0 {
- var queries []string
- if pkg == "" {
- queries = labelCacheMisses
- } else {
- for _, label := range labelCacheMisses {
- queries = append(queries, fmt.Sprintf("visible(%s:*, %s)", pkg, label))
- }
- }
- r, err := q.batchQuery(queries)
- if err != nil {
- return nil, err
- }
- for _, target := range r.GetTarget() {
- label, err := q.targetLabel(target)
- if err != nil {
- return nil, err
- }
- q.labelCache[labelCacheKey(pkg, label)] = target
- }
- for _, label := range labelCacheMisses {
- key := labelCacheKey(pkg, label)
- if _, ok := q.labelCache[key]; !ok {
- // Set to nil so the result exists in the cache and is not
- // loaded again. If the nil is not added at the appropriate
- // cache key, LoadLabels will attempt to load it again when
- // next requested instead of getting a cache hit.
- q.labelCache[key] = nil
- }
- }
- }
- labelToTarget := make(map[string]*appb.Target)
- for _, label := range labels {
- labelToTarget[label] = q.labelCache[labelCacheKey(pkg, label)]
- }
- return labelToTarget, nil
-}
-
-func labelCacheKey(currentPkg, label string) string {
- return currentPkg + "^" + label
-}
-
-// possibleFilepaths generates the possible filepaths for the ts import path.
-// e.g. google3/foo/bar could be foo/bar.ts or foo/bar.d.ts or foo/bar/index.ts, etc.
-// Also handles special angular import paths (.ngfactory and .ngsummary).
-func possibleFilepaths(importPath string) []string {
- // If the path has a suffix of ".ngfactory" or ".ngsummary", it might
- // be an Angular AOT generated file. We can infer the target as we
- // infer its corresponding ngmodule target by simply stripping the
- // ".ngfactory" / ".ngsummary" suffix
- importPath = strings.TrimSuffix(strings.TrimSuffix(importPath, ".ngsummary"), ".ngfactory")
- importPath = strings.TrimPrefix(importPath, workspace.Name()+"/")
-
- var possiblePaths []string
-
- possiblePaths = append(possiblePaths, pathWithExtensions(importPath)...)
- possiblePaths = append(possiblePaths, pathWithExtensions(filepath.Join(importPath, "index"))...)
-
- return possiblePaths
-}
-
-// LoadImportPaths uses Bazel Query to load targets associated with import
-// paths from BUILD files.
-func (q *QueryBasedTargetLoader) LoadImportPaths(ctx context.Context, targetToAnalyze *appb.Rule, currentPkg, workspaceRoot string, paths []string) (map[string]*appb.Rule, error) {
- debugf("loading imports visible to %q relative to %q: %q", currentPkg, workspaceRoot, paths)
- results := make(map[string]*appb.Rule)
-
- addedPaths := make(map[string]bool)
- var possibleFilePaths []string
- possiblePathToPaths := make(map[string][]string)
- // for all the normalized typescript import paths, generate all the possible
- // corresponding file paths
- for _, path := range paths {
- if strings.HasPrefix(path, "goog:") {
- // 'goog:' imports are resolved using an sstable.
- results[path] = nil
- continue
- }
- if strings.HasPrefix(path, "@") {
- continue
- }
-
- if _, ok := addedPaths[path]; !ok {
- addedPaths[path] = true
-
- // there isn't a one to one mapping from ts import paths to file
- // paths, so look for all the possible file paths
- pfs := possibleFilepaths(path)
- possibleFilePaths = append(possibleFilePaths, pfs...)
- // map the file paths back to the import paths so we can map the file
- // labels back to the import paths
- for _, pf := range pfs {
- possiblePathToPaths[pf] = append(possiblePathToPaths[pf], path)
- }
- }
- }
-
- // query for all the possible filepaths, to determine which ones are real
- r, err := q.batchQuery(possibleFilePaths)
- if err != nil {
- return nil, err
- }
- var fileLabels, packages []string
- fileToGeneratorLabel := make(map[string]string)
- pathToLabels := make(map[string][]string)
- // get the labels for all the files which exist
- for _, target := range r.GetTarget() {
- label, err := q.fileLabel(target)
- if err != nil {
- return nil, err
- }
- switch target.GetType() {
- case appb.Target_GENERATED_FILE:
- file := target.GetGeneratedFile()
- generator := file.GetGeneratingRule()
- label = file.GetName()
-
- fileLabels = append(fileLabels, label)
- _, pkg, _ := edit.ParseLabel(label)
- packages = append(packages, pkg)
- // a generated file can be included as a source by referencing the label
- // of the generated file, or the label of the generating rule, so check
- // for both
- fileToGeneratorLabel[labelToPath(label)] = labelToPath(generator)
- // map file label back to the import paths so that they can be looked for
- // in the srcs of the rules
- for _, path := range possiblePathToPaths[labelToPath(label)] {
- pathToLabels[path] = append(pathToLabels[path], label)
- }
- case appb.Target_SOURCE_FILE:
- fileLabels = append(fileLabels, label)
- _, pkg, _ := edit.ParseLabel(label)
- packages = append(packages, pkg)
- // map file label back to the import paths so that they can be looked for
- // in the srcs of the rules
- for _, path := range possiblePathToPaths[labelToPath(label)] {
- pathToLabels[path] = append(pathToLabels[path], label)
- }
- }
- }
-
- // load all the rules in all the packages files were found in, so we can look
- // for aliases and reexporting libraries in the same package
- pkgToAllRules, pkgToActualToAlias, err := q.loadAllRulesInPackages("", packages)
- if err != nil {
- return nil, err
- }
-
- for _, path := range paths {
- // look up the corresponding file label(s) for the normalized typescript
- // import path
- for _, label := range pathToLabels[path] {
- _, pkg, _ := edit.ParseLabel(label)
- // get the file path that corresponds to the normalized typescript import
- // path
- filePath := labelToPath(label)
- allRules := pkgToAllRules[pkg]
- actualToAlias := pkgToActualToAlias[pkg]
- var matchingDeps []*appb.Rule
- for _, candidate := range typeScriptRules(allRules) {
- // check if the rule has the file or the generator of the file in its
- // srcs
- possibleSources := []string{filePath}
- if gl, ok := fileToGeneratorLabel[filePath]; ok {
- possibleSources = append(possibleSources, gl)
- }
- provides, err := q.ruleProvidesImports(candidate, srcsContainsAnyFilePath(possibleSources))
- if err != nil {
- return nil, err
- }
- if !provides {
- continue
- }
-
- if alias, ok := actualToAlias[candidate.GetName()]; ok {
- candidate = alias
- }
- matchingDeps = append(matchingDeps, candidate)
- }
- if len(matchingDeps) > 0 {
- canonicalRule, err := q.chooseCanonicalDep(currentPkg, targetToAnalyze, matchingDeps)
- if err != nil {
- return nil, err
- }
- results[path] = canonicalRule
- }
- }
- }
-
- return results, nil
-}
-
-// chooseCanonicalDep chooses between rules which include the imported file as
-// a source (ie the rule that includes the file as a src, and any reexporting
-// libraries).
-//
-// It filters the rules in a 3 stage process:
-//
-// 1. If only one of the rules is visible, choose that one, since the rule
-// creator intended it to be imported.
-//
-// 2. If all or none of the rules are visible, choose the rule that directly
-// includes the file as a src, since that reduces the chance of introducing
-// circular dependencies.
-//
-// 3. Choose the rule that is already included as a dep.
-func (q *QueryBasedTargetLoader) chooseCanonicalDep(currentPkg string, targetToAnalyze *appb.Rule, deps []*appb.Rule) (*appb.Rule, error) {
- // check for visibility
- filterForVisibility := func(deps []*appb.Rule) ([]*appb.Rule, error) {
- var labels []string
- for _, d := range deps {
- labels = append(labels, d.GetName())
- }
- visibleDepsMap, err := q.LoadRules(currentPkg, labels)
- if err != nil {
- return nil, err
- }
-
- var visibleDeps []*appb.Rule
- for _, d := range visibleDepsMap {
- if d != nil {
- visibleDeps = append(visibleDeps, d)
- }
- }
-
- return visibleDeps, nil
- }
-
- // if there's a visible reexporting lib and a visible lib with the src, favor
- // the lib with the src, to reduce the chance of introducing a circular
- // dependency
- filterForBaseLibs := func(deps []*appb.Rule) ([]*appb.Rule, error) {
- var baseDeps []*appb.Rule
- for _, d := range deps {
- if !isReexportingLib(d) {
- baseDeps = append(baseDeps, d)
- }
- }
-
- return baseDeps, nil
- }
-
- // favor the dep that's already on the rule
- filterForExistingDeps := func(deps []*appb.Rule) ([]*appb.Rule, error) {
- var existingDeps []*appb.Rule
- for _, d := range deps {
- for _, existing := range listAttribute(targetToAnalyze, "deps") {
- if d.GetName() == existing {
- existingDeps = append(existingDeps, d)
- }
- }
- }
-
- return existingDeps, nil
- }
-
- filters := []func(deps []*appb.Rule) ([]*appb.Rule, error){
- filterForVisibility,
- filterForBaseLibs,
- filterForExistingDeps,
- }
-
- // for each filter, return if it returned a single rule, narrow the set of deps if
- // it discarded some, but not all, and try the full set with the next filter if it
- // discarded them all
- for _, filter := range filters {
- filteredDeps, err := filter(deps)
- if err != nil {
- return nil, err
- }
- if len(filteredDeps) == 1 {
- return filteredDeps[0], nil
- } else if len(filteredDeps) > 0 {
- deps = filteredDeps
- }
- }
-
- // no filter got down to a single rule, just return the first
- return deps[0], nil
-}
-
-// ruleLabel returns the label for a target which is a rule. Returns an error if
-// target is not a rule.
-func (q *QueryBasedTargetLoader) ruleLabel(target *appb.Target) (string, error) {
- if t := target.GetType(); t != appb.Target_RULE {
- return "", fmt.Errorf("target contains object of type %q instead of type %q", t, appb.Target_RULE)
- }
- return target.GetRule().GetName(), nil
-}
-
-// fileLabel returns the label for a target which is a file. Returns an error if
-// target is not a source file or a generated file.
-func (q *QueryBasedTargetLoader) fileLabel(target *appb.Target) (string, error) {
- switch t := target.GetType(); t {
- case appb.Target_GENERATED_FILE:
- return target.GetGeneratedFile().GetName(), nil
- case appb.Target_SOURCE_FILE:
- return target.GetSourceFile().GetName(), nil
- default:
- return "", fmt.Errorf("target contains object of type %q instead of type %q or %q", t, appb.Target_SOURCE_FILE, appb.Target_GENERATED_FILE)
- }
-}
-
-// targetLabel returns the label for a target. Returns an error if target is an
-// unknown type.
-func (q *QueryBasedTargetLoader) targetLabel(target *appb.Target) (string, error) {
- switch t := target.GetType(); t {
- case appb.Target_GENERATED_FILE:
- return target.GetGeneratedFile().GetName(), nil
- case appb.Target_SOURCE_FILE:
- return target.GetSourceFile().GetName(), nil
- case appb.Target_RULE:
- return target.GetRule().GetName(), nil
- case appb.Target_PACKAGE_GROUP:
- return target.GetPackageGroup().GetName(), nil
- case appb.Target_ENVIRONMENT_GROUP:
- return target.GetEnvironmentGroup().GetName(), nil
- default:
- return "", fmt.Errorf("target contains object of unknown type %q", t)
- }
-}
-
-// 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) {
- // Join all of the queries with a '+' character according to Bazel's
- // syntax for running multiple queries.
- return q.query("--keep_going", strings.Join(queries, "+"))
-}
-
-func (q *QueryBasedTargetLoader) query(args ...string) (*appb.QueryResult, error) {
- n := len(args)
- if n < 1 {
- return nil, fmt.Errorf("expected at least one argument")
- }
- query := args[n-1]
- if query == "" {
- // An empty query was provided so return an empty result without
- // making a call to Bazel.
- return &appb.QueryResult{}, nil
- }
- var stdout, stderr bytes.Buffer
- args = append([]string{"query", "--output=proto"}, args...)
- q.queryCount++
- debugf("executing query #%d in %q: %s %s %q", q.queryCount, q.workdir, q.bazelBinary, strings.Join(args[:len(args)-1], " "), query)
- cmd := exec.Command(q.bazelBinary, args...)
- cmd.Dir = q.workdir
- cmd.Stdout = &stdout
- cmd.Stderr = &stderr
- startTime := time.Now()
- if err := cmd.Run(); err != nil {
- // Exit status 3 is a direct result of one or more queries in a set of
- // queries not returning a result while running with the '--keep_going'
- // flag. Since one query failing to return a result does not hinder the
- // other queries from returning a result, ignore these errors.
- //
- // Herb prints "printing partial results" to indicate the same as bazel's
- // exit status 3
- if err.Error() != "exit status 3" && !strings.Contains(stderr.String(), "printing partial results") {
- // The error provided as a result is less useful than the contents of
- // stderr for debugging.
- return nil, fmt.Errorf(stderr.String())
- }
- }
- debugf("query #%d took %v", q.queryCount, time.Since(startTime))
- var result appb.QueryResult
- if err := proto.Unmarshal(stdout.Bytes(), &result); err != nil {
- return nil, err
- }
- return &result, nil
-}
-
-// ruleProvidesImports checks if the rule directly provides the import, or if
-// it's a reexporting lib, if one of its deps does.
-func (q *QueryBasedTargetLoader) ruleProvidesImports(rule *appb.Rule, srcMatcher func(rule *appb.Rule) bool) (bool, error) {
- if srcMatcher(rule) {
- return true, nil
- }
-
- if !isReexportingLib(rule) {
- return false, nil
- }
-
- // if the rule is a reexporting library, load all the rules that the rule
- // reexports, and check if they provide the imported paths. This only handles
- // one level of reexport.
- _, pkg, _ := edit.ParseLabel(rule.GetName())
- // TODO(alexeagle): Batch calls to LoadLabels. Batching calls to ruleProvidesImport
- // would also be required.
- exportedRules, err := q.LoadRules(pkg, exportedLabels(rule))
- if err != nil {
- return false, err
- }
- for _, exportedRule := range exportedRules {
- if srcMatcher(exportedRule) {
- return true, nil
- }
- }
-
- return false, nil
-}
-
-// exportedLabels returns the labels exported by rule. Exported labels are the
-// deps of a rule if the rule is an alias.
-func exportedLabels(rule *appb.Rule) []string {
- var exported []string
- if isReexportingLib(rule) {
- exported = append(exported, listAttribute(rule, "deps")...)
- }
- return exported
-}
-
-// isReexportingLib checks if a library has no sources, which the TS rules use a
-// way to mark a library as an alias.
-func isReexportingLib(rule *appb.Rule) bool {
- return len(listAttribute(rule, "srcs")) == 0
-}
-
-// srcsContainsPath returns a function, which takes a rule, which returns true
-// if the rule has a src which matches one of the possible filepaths for the
-// provided typescript import path.
-func srcsContainsPath(path string) func(rule *appb.Rule) bool {
- return func(rule *appb.Rule) bool {
- resolvedImportPath := resolveAgainstModuleRoot(rule, path)
-
- // enumerate all the possible filepaths for the resolved import path, and
- // compare against all the srcs
- possibleImportPaths := possibleFilepaths(resolvedImportPath)
- for _, src := range listAttribute(rule, "srcs") {
- for _, mi := range possibleImportPaths {
- if mi == labelToPath(src) {
- return true
- }
- }
- }
-
- return false
- }
-}
-
-// srcsContainsFilePath returns a function which takes a rule, which returns
-// true if the rule has a src which, if pathified, equals one of the filePaths.
-func srcsContainsAnyFilePath(filePaths []string) func(rule *appb.Rule) bool {
- return func(rule *appb.Rule) bool {
- for _, filePath := range filePaths {
- for _, src := range listAttribute(rule, "srcs") {
- if filePath == labelToPath(src) {
- return true
- }
- }
- }
-
- return false
- }
-}
-
-// loadAllRulesInPackages loads all rules in all packages.
-//
-// If an alias or aliases are present in the package, the rules for each alias'
-// 'actual' attribute are loaded and a map from each 'actual' rule to its alias
-// rule is constructed.
-//
-// loadAllRulesInPackages returns two maps. The first map is a map from a package
-// label to all of the rules in the package. The second map is a map from a
-// package to the map of 'actual' rules to alias rules for that package.
-func (q *QueryBasedTargetLoader) loadAllRulesInPackages(currentPkg string, packages []string) (map[string][]*appb.Rule, map[string]map[string]*appb.Rule, error) {
- var missingPackages []string
- for _, pkg := range packages {
- if _, ok := q.pkgCache[pkgCacheKey(currentPkg, pkg)]; !ok {
- missingPackages = append(missingPackages, pkg)
- }
- }
- if len(missingPackages) > 0 {
- // Load any packages not already available in the cache.
- var queries []string
- pkgToRules := make(map[string][]*appb.Rule)
- pkgToAliasToRule := make(map[string]map[string]*appb.Rule)
- for _, pkg := range missingPackages {
- if currentPkg != "" {
- queries = append(queries, fmt.Sprintf("visible(%s:*, %s:*)", currentPkg, pkg))
- } else {
- queries = append(queries, fmt.Sprintf("%s:*", pkg))
- }
- pkgToAliasToRule[pkg] = make(map[string]*appb.Rule)
- }
- r, err := q.batchQuery(queries)
- if err != nil {
- return nil, nil, err
- }
- actualToAlias := make(map[string]*appb.Rule)
- pkgToActuals := make(map[string][]string)
- for _, target := range r.GetTarget() {
- if target.GetType() == appb.Target_RULE {
- rule := target.GetRule()
- _, pkg, _ := edit.ParseLabel(rule.GetName())
- if rule.GetRuleClass() == "alias" {
- // if the package contains an alias, derefence it (but only one layer
- // of aliases)
- actual := stringAttribute(rule, "actual")
- if actual == "" {
- // probably an alias with a select statement as the value for
- // 'actual' - just ignore
- platform.Infof(`alias %q has non-string "actual" attribute`, rule.GetName())
- continue
- }
- actualToAlias[actual] = rule
- pkgToActuals[pkg] = append(pkgToActuals[pkg], actual)
- } else {
- pkgToRules[pkg] = append(pkgToRules[pkg], rule)
- }
- }
- }
- for pkg, actuals := range pkgToActuals {
- // Load all the aliased targets, checking if they're visible from the
- // package where they're aliased from
- resolvedActuals, err := q.LoadTargets(pkg, actuals)
- if err != nil {
- return nil, nil, err
- }
- for actual, target := range resolvedActuals {
- // aliases can be for anything, but deps can only be rules, so ignore
- // other aliased targets
- if target.GetType() != appb.Target_RULE {
- continue
- }
-
- rule := target.GetRule()
- alias := actualToAlias[actual]
- _, pkg, _ := edit.ParseLabel(alias.GetName())
- pkgToAliasToRule[pkg][rule.GetName()] = alias
- pkgToRules[pkg] = append(pkgToRules[pkg], rule)
- }
- }
- for _, pkg := range missingPackages {
- q.pkgCache[pkgCacheKey(currentPkg, pkg)] = &pkgCacheEntry{
- rules: pkgToRules[pkg],
- aliases: pkgToAliasToRule[pkg],
- }
- }
- }
-
- pkgToRules := make(map[string][]*appb.Rule)
- pkgToRuleToAlias := make(map[string]map[string]*appb.Rule)
- for _, pkg := range packages {
- cacheEntry := q.pkgCache[pkgCacheKey(currentPkg, pkg)]
- pkgToRules[pkg] = cacheEntry.rules
- pkgToRuleToAlias[pkg] = cacheEntry.aliases
- }
-
- return pkgToRules, pkgToRuleToAlias, nil
-}
-
-func pkgCacheKey(currentPkg, pkg string) string {
- return currentPkg + "|" + pkg
-}
-
-// dedupeLabels returns a new set of labels with no duplicates.
-func dedupeLabels(labels []string) []string {
- addedLabels := make(map[string]bool)
- var uniqueLabels []string
- for _, label := range labels {
- if _, added := addedLabels[label]; !added {
- addedLabels[label] = true
- uniqueLabels = append(uniqueLabels, label)
- }
- }
- return uniqueLabels
-}
-
-// 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(rule *appb.Rule, imported string) string {
- moduleName := stringAttribute(rule, "module_name")
- if moduleName == "" {
- return imported
- }
- if !pathStartsWith(imported, moduleName) {
- return imported
- }
- // if module root is a file, remove the file extension, since it'll be added
- // by possibleFilepaths below
- moduleRoot := stripTSExtension(stringAttribute(rule, "module_root"))
- _, pkg, _ := edit.ParseLabel(rule.GetName())
-
- // resolve the import path against the module name and module root, ie if
- // the import path is @foo/bar and there's a moduleName of @foo the resolved
- // import path is location/of/foo/bar, or if there's also a moduleRoot of
- // baz, the resolved import path is location/of/foo/baz/bar
- //
- // using strings.TrimPrefix for trimming the path is ok, since
- // pathStartsWith already checked that moduleName is a proper prefix of
- // i.importPath
- return platform.Normalize(filepath.Join(pkg, moduleRoot, strings.TrimPrefix(imported, moduleName)))
-}
-
-// pathStartsWith checks if path starts with prefix, checking each path segment,
-// so that @angular/core starts with @angular/core, but @angular/core-bananas
-// does not
-func pathStartsWith(path, prefix string) bool {
- pathParts := strings.Split(path, "/")
- prefixParts := strings.Split(prefix, "/")
-
- if len(prefixParts) > len(pathParts) {
- return false
- }
-
- for i, prefixPart := range prefixParts {
- if prefixPart != pathParts[i] {
- return false
- }
- }
-
- return true
-}
-
-// 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
deleted file mode 100644
index 5765129..0000000
--- a/ts_auto_deps/analyze/loader_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package analyze
-
-import (
- "testing"
-
- "github.com/golang/protobuf/proto"
-
- appb "github.com/bazelbuild/buildtools/build_proto"
-)
-
-func TestResolveAgainstModuleRoot(t *testing.T) {
- tests := []struct {
- ruleLiteral string
- imported string
- expected string
- }{
- {
- ruleLiteral: `name: "//a"
- rule_class: "ts_library"`,
- imported: "foo",
- expected: "foo",
- },
- {
- ruleLiteral: `name: "//b"
- rule_class: "ts_library"
- attribute: <
- type: 4
- name: "module_name"
- string_value: "foo"
- >`,
- imported: "bar",
- expected: "bar",
- },
- {
- ruleLiteral: `name: "//c"
- rule_class: "ts_library"
- attribute: <
- type: 4
- name: "module_name"
- string_value: "foo"
- >`,
- imported: "foo/bar",
- expected: "c/bar",
- },
- {
- ruleLiteral: `name: "//actual/loc:target"
- rule_class: "ts_library"
- attribute: <
- type: 4
- name: "module_name"
- string_value: "foo/bar"
- >
- attribute: <
- type: 4
- name: "module_root"
- string_value: "mod/root"
- >`,
- imported: "foo/bar/baz/bam",
- expected: "actual/loc/mod/root/baz/bam",
- },
- }
- for _, test := range tests {
- rule, err := parseRuleLiteral(test.ruleLiteral)
- if err != nil {
- t.Errorf("isRuleAnAlias(%q): failed to parse literal: %s", test.ruleLiteral, err)
- continue
- }
- if actual := resolveAgainstModuleRoot(rule, test.imported); actual != test.expected {
- t.Errorf("resolveAgainstModuleRoot(%q): got %q, want %q", rule.GetName(), actual, test.expected)
- }
- }
-}
-
-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/main.go b/ts_auto_deps/main.go
deleted file mode 100644
index 553afbc..0000000
--- a/ts_auto_deps/main.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package main
-
-import (
- "flag"
- "fmt"
- "os"
-
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/platform"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/updater"
-)
-
-var (
- isRoot = flag.Bool("root", false, "the given path is the root of a TypeScript project "+
- "(generates ts_config and ts_development_sources targets).")
- recursive = flag.Bool("recursive", false, "recursively update all packages under the given root.")
- files = flag.Bool("files", false, "treats arguments as file names. Filters .ts files, then runs on their dirnames.")
-)
-
-func usage() {
- fmt.Fprintf(os.Stderr, `ts_auto_deps: generate BUILD rules for TypeScript sources.
-
-usage: %s [flags] [path...]
-
-ts_auto_deps generates and updates BUILD rules for each of the given package paths.
-Paths are expected to reside underneath the workspace root. If none is given,
-ts_auto_deps runs on the current working directory.
-
-For each of the given package paths, ts_auto_deps finds all TypeScript sources in the
-package and adds sources that are not currently built to the appropriate
-BUILD rule (ts_library or ts_declaration).
-
-If there is no matching BUILD rule, or no BUILD file, ts_auto_deps will create either.
-
-ts_auto_deps also updates BUILD rule dependencies ('deps') based on the source imports.
-
-Flags:
-`, os.Args[0])
- flag.PrintDefaults()
-}
-
-func main() {
- flag.Usage = usage
- flag.Parse()
-
- paths, err := updater.Paths(*isRoot, *files, *recursive)
- if err != nil {
- platform.Error(err)
- }
-
- host := updater.New(false, false, updater.QueryBasedBazelAnalyze, updater.LocalUpdateFile)
- if err := updater.Execute(host, paths, *isRoot, *recursive); err != nil {
- platform.Error(err)
- }
-}
diff --git a/ts_auto_deps/platform/BUILD.bazel b/ts_auto_deps/platform/BUILD.bazel
deleted file mode 100644
index 9e351ab..0000000
--- a/ts_auto_deps/platform/BUILD.bazel
+++ /dev/null
@@ -1,11 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-go_library(
- name = "go_default_library",
- srcs = [
- "file.go",
- "log.go",
- ],
- importpath = "github.com/bazelbuild/rules_typescript/ts_auto_deps/platform",
- visibility = ["//visibility:public"],
-)
diff --git a/ts_auto_deps/platform/file.go b/ts_auto_deps/platform/file.go
deleted file mode 100644
index 7df93d0..0000000
--- a/ts_auto_deps/platform/file.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package platform
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
-)
-
-const (
- filePerms = 0666
-)
-
-var pathReplacer = strings.NewReplacer("\\", "/")
-
-// ReadFile reads the contents of name.
-func ReadFile(ctx context.Context, name string) ([]byte, error) {
- return ioutil.ReadFile(name)
-}
-
-// ReadBytesFromFile reads bytes into the buffer provided, stopping when the
-// buffer is full.
-func ReadBytesFromFile(ctx context.Context, name string, buffer []byte) (int, error) {
- f, err := os.Open(name)
- if err != nil {
- return 0, err
- }
- defer f.Close()
-
- n, err := f.Read(buffer)
- return n, err
-}
-
-// WriteFile writes data to filename.
-func WriteFile(ctx context.Context, filename string, data []byte) error {
- return ioutil.WriteFile(filename, data, filePerms)
-}
-
-// Stat reads the file system information of name.
-func Stat(ctx context.Context, name string) (interface{}, error) {
- return os.Stat(name)
-}
-
-// Glob returns all paths matching pattern.
-func Glob(ctx context.Context, pattern string) ([]string, error) {
- return filepath.Glob(pattern)
-}
-
-// Normalize converts Windows path separators into POSIX
-func Normalize(path string) string {
- return pathReplacer.Replace(path)
-}
diff --git a/ts_auto_deps/platform/log.go b/ts_auto_deps/platform/log.go
deleted file mode 100644
index 4fb889e..0000000
--- a/ts_auto_deps/platform/log.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package platform
-
-import (
- "fmt"
- "log"
- "os"
-)
-
-// Infof prints a formatted message to stdout.
-func Infof(format string, args ...interface{}) {
- fmt.Printf(format+"\n", args...)
-}
-
-// Error prints a series of args to stderr.
-func Error(args ...interface{}) {
- fmt.Fprintln(os.Stderr, args...)
-}
-
-// Fatalf prints a formatted message to stderr. Panics after printing.
-func Fatalf(format string, v ...interface{}) {
- log.Fatalf(format, v...)
-}
diff --git a/ts_auto_deps/platform/walk.go b/ts_auto_deps/platform/walk.go
deleted file mode 100644
index 05dd366..0000000
--- a/ts_auto_deps/platform/walk.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package platform
-
-import (
- "os"
- "path/filepath"
-)
-
-func Walk(root string, walkFn func(path string, typ os.FileMode) error) error {
- return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- return walkFn(path, info.Mode())
- })
-}
diff --git a/ts_auto_deps/proto/BUILD.bazel b/ts_auto_deps/proto/BUILD.bazel
deleted file mode 100644
index e7e793a..0000000
--- a/ts_auto_deps/proto/BUILD.bazel
+++ /dev/null
@@ -1,22 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-proto_library(
- name = "bazel_analyze_proto",
- srcs = ["analyze_result.proto"],
- visibility = ["//visibility:public"],
-)
-
-go_proto_library(
- name = "bazel_analyze_go_proto",
- importpath = "github.com/bazelbuild/rules_typescript/ts_auto_deps/proto",
- proto = ":bazel_analyze_proto",
- visibility = ["//visibility:public"],
-)
-
-go_library(
- name = "go_default_library",
- embed = [":bazel_analyze_go_proto"],
- importpath = "github.com/bazelbuild/rules_typescript/ts_auto_deps/proto",
- visibility = ["//visibility:public"],
-)
diff --git a/ts_auto_deps/proto/analyze_result.proto b/ts_auto_deps/proto/analyze_result.proto
deleted file mode 100644
index 49ee500..0000000
--- a/ts_auto_deps/proto/analyze_result.proto
+++ /dev/null
@@ -1,98 +0,0 @@
-syntax = "proto2";
-
-package bazel_analyze;
-
-// A DependencyGroup represents a list of alternatives for satisfying
-// a missing dependency.
-message DependencyGroup {
- // Represents the visibility status of this dependency group. All the
- // dependencies have the same visibility status indicated by the visibility
- // field. Producers of this message (i.e. checkdeps) will ensure that
- // the invariant is true.
- enum Visibility {
- // Visibility was not considered. (Default)
- UNKNOWN = 0;
- // All the alternatives are visible
- ALL_VISIBLE = 1;
- // All the alternatives are invisible
- ALL_INVISIBLE = 2;
- }
-
- repeated string dependency = 1;
- optional Visibility visibility = 2 [default = UNKNOWN];
- // The set of import paths for which any of these dependencies can provide.
- // It is legal for there to be multiple entries there (in practice it is
- // not very common). This happens if the dependencies required by one
- // import_path is a subset of the other. In that case, the smaller set of
- // dependencies is populated in dependency field, and both import_path's are
- // listed here.
- repeated string import_path = 3;
-}
-
-// Represents information about arbitrary non-deps attributes.
-message AttributeReport {
- // Name of the attribute (e.g. "runtime_deps").
- optional string attribute = 1;
- // Attribute value that is required but missing.
- repeated string missing_value = 2;
- // Attribute value that is declared but not needed.
- repeated string unnecessary_value = 3;
-}
-
-// The result of checking a single build rule (called X in comments
-// below) for missing or unnecessary dependencies. Produced by
-// java/com/google/devtools/build/checkbuilddeps (which for external
-// users means it's produced by bazel analyze).
-message DependencyReport {
- required string rule = 1; // Name of rule X, in canonical google3 format.
-
- // Names of targets that are, or should be, declared as dependencies
- // by rule X. Some need to be declared and some don't; in some
- // cases it is impossible to tell by static analysis. Each is the
- // full canonical name of the target, i.e. //x/y:z, or the shorthand
- // form if the target name equals the package name, i.e. //x/y
- // (shorthand for //x/y:y).
-
- // Declared and needed. Removing these dependencies should break the build.
- repeated string necessary_dependency = 2;
-
- // Declared but not needed. Removing these dependencies should not break
- // the build.
- repeated string unnecessary_dependency = 3;
-
- // Can't tell. These may be dependencies where no corresponding imports
- // were found, but alwayslink=1.
- repeated string ambiguous_dependency = 4;
-
- // Needed but not declared. If there are imports which can be satisfied
- // by more than one dependency declared in a best-matching package, list
- // all of them. If this list is not empty, we expect the build to break.
- repeated DependencyGroup missing_dependency_group = 8;
-
- // Files/classes that a source file imports, but for which no
- // appropriate dependency could be found. We expect the build to break
- // if this list is not empty.
- // NOTE: If there are unreseolved imports, it may indicate that
- // analysis failed to connect a declared dependency with a
- // corresponding import statement, and so you may want to have less
- // faith in the unnecessary_dependency field (though, in general,
- // rules in unnecessary_dependency are still PROBABLY unnecessary).
- repeated string unresolved_import = 7;
-
- // Source files that are inputs to the rule but cannot be found.
- // Values are absolute labels, e.g. '//foo:bar/baz.c'.
- repeated string missing_source_file = 13;
-
- // General-purpose feedback messages from the dependency checker
- // (errors, warnings, any kind of info).
- repeated string feedback = 6;
-
- // Indicates whether the dependency analysis completed without errors.
- optional bool successful = 10 [default = true];
-}
-
-// Aggregate DependencyReports for multiple analysis
-// targets - used to support bazel analyze --analysis_output=proto
-message AnalyzeResult {
- repeated DependencyReport dependency_report = 1;
-}
diff --git a/ts_auto_deps/updater/BUILD.bazel b/ts_auto_deps/updater/BUILD.bazel
deleted file mode 100644
index 53e319c..0000000
--- a/ts_auto_deps/updater/BUILD.bazel
+++ /dev/null
@@ -1,29 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-go_library(
- name = "go_default_library",
- srcs = ["updater.go"],
- importpath = "github.com/bazelbuild/rules_typescript/ts_auto_deps/updater",
- visibility = ["//visibility:public"],
- deps = [
- "//ts_auto_deps/analyze:go_default_library",
- "//ts_auto_deps/platform:go_default_library",
- "//ts_auto_deps/proto:go_default_library",
- "//ts_auto_deps/workspace:go_default_library",
- "@com_github_bazelbuild_buildtools//build:go_default_library",
- "@com_github_bazelbuild_buildtools//edit:go_default_library",
- "@com_github_golang_protobuf//proto:go_default_library",
- "@com_github_mattn_go_isatty//:go_default_library",
- ],
-)
-
-go_test(
- name = "go_default_test",
- srcs = ["updater_test.go"],
- embed = [":go_default_library"],
- deps = [
- "//ts_auto_deps/proto:go_default_library",
- "@com_github_bazelbuild_buildtools//build:go_default_library",
- "@com_github_golang_protobuf//proto:go_default_library",
- ],
-)
diff --git a/ts_auto_deps/updater/test_register.go b/ts_auto_deps/updater/test_register.go
deleted file mode 100644
index 0f51e63..0000000
--- a/ts_auto_deps/updater/test_register.go
+++ /dev/null
@@ -1,259 +0,0 @@
-package updater
-
-import (
- "context"
- "fmt"
- "path/filepath"
-
- "github.com/bazelbuild/buildtools/build"
- "github.com/bazelbuild/buildtools/edit"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/platform"
-)
-
-// isAllTestLibrary identifies testonly ts_libraries named "all_tests". Taze
-// will register tests with these rules instead of
-// ts_config/ts_development_sources rules to allow users to set up their builds
-// differently.
-func isAllTestLibrary(bld *build.File, r *build.Rule) bool {
- if !ruleMatches(bld, r, "ts_library", ruleTypeTest) {
- return false
- }
-
- if r.Name() != "all_tests" {
- return false
- }
-
- return true
-}
-
-func getAllTestLibraries(bld *build.File) []*build.Rule {
- var allTestRules []*build.Rule
- for _, r := range buildRules(bld, "ts_library") {
- if isAllTestLibrary(bld, r) {
- allTestRules = append(allTestRules, r)
- }
- }
- return allTestRules
-}
-
-// RegisterTestRules registers ts_library test targets with the project's
-// ts_config and ts_development_sources rules. It may also register the tests
-// with a testonly ts_library named "all_tests", which allows users to set up
-// their own BUILD layout. It's separated from UpdateBUILD since it's
-// non-local, multiple packages may all need to make writes to the same
-// ts_config. It returns a set of the paths for the packages that were updated.
-func (upd *Updater) RegisterTestRules(ctx context.Context, paths ...string) (bool, map[string]bool, error) {
- reg := &buildRegistry{make(map[string]*build.File), make(map[*build.File]bool)}
- var g3root string
- updatedAncestorPackages := make(map[string]bool)
- for _, path := range paths {
- // declare variables manually so that g3root doesn't get overwritten by a :=
- // declaration
- var err error
- var buildPath string
- g3root, buildPath, _, err = getBUILDPath(ctx, path)
- if err != nil {
- return false, nil, err
- }
- bld, err := reg.readBUILD(ctx, g3root, buildPath)
- if err != nil {
- return false, nil, err
- }
- for _, tr := range getRules(bld, "ts_library", ruleTypeTest) {
- // don't register all_test libraries themselves
- if isAllTestLibrary(bld, tr) {
- continue
- }
- platform.Infof("Registering test rule in closest ts_config & ts_development_sources")
- target := AbsoluteBazelTarget(bld, tr.Name())
- ancestorBuild, err := reg.registerTestRule(ctx, bld, tsConfig, g3root, target)
- if err != nil {
- return false, nil, err
- }
- if ancestorBuild != "" {
- updatedAncestorPackages[ancestorBuild] = true
- }
- // NodeJS rules should not be added to ts_development_sources automatically, because
- // they typically do not run in the browser.
- if tr.AttrString("runtime") != "nodejs" {
- ancestorBuild, err := reg.registerTestRule(ctx, bld, tsDevSrcs, g3root, target)
- if err != nil {
- return false, nil, err
- }
- if ancestorBuild != "" {
- updatedAncestorPackages[ancestorBuild] = true
- }
- }
- }
- }
-
- updated := false
- for b := range reg.filesToUpdate {
- fmt.Printf("Registered test(s) in %s\n", b.Path)
- fileChanged, err := upd.maybeWriteBUILD(ctx, filepath.Join(g3root, b.Path), b)
- if err != nil {
- return false, nil, err
- }
- updated = updated || fileChanged
- }
-
- return updated, updatedAncestorPackages, nil
-}
-
-// buildRegistry buffers reads and writes done while registering ts_libraries
-// with ts_config and ts_development_sources rules, so that registers from
-// multiple packages all get applied at once.
-type buildRegistry struct {
- bldFiles map[string]*build.File
- filesToUpdate map[*build.File]bool
-}
-
-func (reg *buildRegistry) readBUILD(ctx context.Context, workspaceRoot, buildFilePath string) (*build.File, error) {
- normalizedG3Path, err := getWorkspaceRelativePath(workspaceRoot, buildFilePath)
- if err != nil {
- return nil, err
- }
-
- if bld, ok := reg.bldFiles[normalizedG3Path]; ok {
- return bld, nil
- }
-
- bld, err := readBUILD(ctx, buildFilePath, normalizedG3Path)
- if err != nil {
- return nil, err
- }
- if bld == nil {
- // The BUILD file didn't exist, so create a new, empty one.
- bld = &build.File{Path: normalizedG3Path, Type: build.TypeBuild}
- }
-
- reg.bldFiles[normalizedG3Path] = bld
-
- return bld, nil
-}
-
-func (reg *buildRegistry) registerForPossibleUpdate(bld *build.File) {
- reg.filesToUpdate[bld] = true
-}
-
-type registerTarget int
-
-const (
- tsConfig registerTarget = iota
- tsDevSrcs
-)
-
-func (rt registerTarget) kind() string {
- if rt == tsConfig {
- return "ts_config"
- }
-
- return "ts_development_sources"
-}
-
-func (rt registerTarget) ruleType() ruleType {
- if rt == tsConfig {
- return ruleTypeAny
- }
-
- return ruleTypeTest
-}
-
-// registerTestRule searches ancestor packages for a rule matching the register
-// target and adds the given target to it. If an all_tests library is found, the
-// rule is registered with it, instead of specified register target. Prints a
-// warning if no rule is found, but only returns an error if adding the
-// dependency fails.
-func (reg *buildRegistry) registerTestRule(ctx context.Context, bld *build.File, rt registerTarget, g3root, target string) (string, error) {
- if buildHasDisableTaze(bld) {
- return "", nil
- }
-
- var ruleToRegister *build.Rule
- for _, r := range bld.Rules("") {
- if isAllTestLibrary(bld, r) {
- if hasDependency(bld, r, target) {
- return "", nil
- }
-
- // an all_tests library takes presidence over a registerTarget, and there
- // can only be one, since there can only be one rule with a given name, so
- // can just break after finding
- ruleToRegister = r
- break
- }
- if ruleMatches(bld, r, rt.kind(), rt.ruleType()) {
- if hasDependency(bld, r, target) {
- return "", nil
- }
-
- // keep overwriting ruleToRegister so the last match in the BUILD gets
- // used
- ruleToRegister = r
- }
- }
-
- if ruleToRegister != nil {
- addDep(bld, ruleToRegister, target)
- reg.registerForPossibleUpdate(bld)
- return filepath.Dir(bld.Path), nil
- }
-
- parentDir := filepath.Dir(filepath.Dir(bld.Path))
- for parentDir != "." && parentDir != "/" {
- buildFile := filepath.Join(g3root, parentDir, "BUILD")
- if _, err := platform.Stat(ctx, buildFile); err == nil {
- parent, err := reg.readBUILD(ctx, g3root, buildFile)
- if err != nil {
- return "", err
- }
- return reg.registerTestRule(ctx, parent, rt, g3root, target)
- }
- parentDir = filepath.Dir(parentDir)
- }
- fmt.Printf("WARNING: no %s rule in parent packages of %s to register with.\n",
- rt.kind(), target)
- return "", nil
-}
-
-var wellKnownBuildRules = []struct {
- name string
- attrName string
-}{
- {
- name: "karma_polymer_test",
- attrName: "test_ts_deps",
- },
- {
- name: "wct_closure_test_suite",
- attrName: "js_deps",
- },
- {
- name: "jasmine_node_test",
- attrName: "deps",
- },
- {
- name: "karma_web_test_suite",
- attrName: "deps",
- },
- {
- name: "boq_jswire_test_library",
- attrName: "deps",
- },
-}
-
-// isRegisteredWithAlternateTestRule returns true if the rule is already
-// registered with a well known test rule, such as karma_polymer_test,
-// wct_closure_test_suite or jasmine_node_test.
-func isRegisteredWithAlternateTestRule(bld *build.File, r *build.Rule, dep string) bool {
- pkg := filepath.Dir(bld.Path)
- for _, wkbr := range wellKnownBuildRules {
- if isKind(r, wkbr.name) {
- testTsDeps := r.Attr(wkbr.attrName)
- if edit.ListFind(testTsDeps, dep, pkg) != nil {
- return true
- }
- }
- }
- return false
-}
diff --git a/ts_auto_deps/updater/updater.go b/ts_auto_deps/updater/updater.go
deleted file mode 100644
index f606e5b..0000000
--- a/ts_auto_deps/updater/updater.go
+++ /dev/null
@@ -1,1340 +0,0 @@
-// Package updater implements the main logic of the ts_auto_deps command. It reads BUILD files,
-// discovers TypeScript sources, uses `bazel analyze` to update import/dependency information,
-// and then modifies the BUILD file accordingly.
-package updater
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "regexp"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "flag"
- "github.com/bazelbuild/buildtools/build"
- "github.com/bazelbuild/buildtools/edit"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/analyze"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/platform"
- "github.com/bazelbuild/rules_typescript/ts_auto_deps/workspace"
- "github.com/golang/protobuf/proto"
- "github.com/mattn/go-isatty"
-
- arpb "github.com/bazelbuild/rules_typescript/ts_auto_deps/proto"
-)
-
-var bazelErrorRE = regexp.MustCompile(`ERROR: ([^:]+):(\d+):\d+:`)
-
-// New creates a new updater from the given arguments. One updater can be used
-// to update many packages, repeatedly, but not concurrently.
-// bazelAnalyze and updateFile can be passed in to handle ts_auto_deps operation in
-// different environments and for fakes in tests.
-func New(removeUnusedDeclarations bool, updateComments bool, bazelAnalyze BazelAnalyzer, updateFile UpdateFile) *Updater {
- return &Updater{removeUnusedDeclarations, updateComments, bazelAnalyze, updateFile}
-}
-
-// UpdateFile updates the contents of filePath. Implementations may postpone or batch actually writing the given file, i.e.
-// a subsequent read may return stale contents.
-type UpdateFile func(ctx context.Context, filePath string, contents string) error
-
-// LocalUpdateFile simply writes to the given file.
-func LocalUpdateFile(ctx context.Context, filePath string, contents string) error {
- return platform.WriteFile(ctx, filePath, []byte(contents))
-}
-
-// BazelAnalyzer is a function that executes bazel analyze for the given
-// absolute build file path and bazel targets, and returns the raw analysis
-// result proto, or an error if the analyze call failed.
-// It's used to abstract over execution using rabbit vs local execution.
-// Returns the main output (serialized AnalysisResult proto), any error
-// messages, or an error.
-type BazelAnalyzer func(buildFilePath string, targets []string) ([]byte, []byte, error)
-
-// Updater encapsulates configuration for the ts_auto_deps process.
-type Updater struct {
- removeUnusedDeclarations bool
- updateComments bool
- bazelAnalyze BazelAnalyzer
- updateFile UpdateFile
-}
-
-func attrTruthy(r *build.Rule, attr string) bool {
- attrVal := r.AttrLiteral(attr)
- return attrVal == "True" || attrVal == "1"
-}
-
-// Matches the warning TypeScriptRuleChecker prints for unused ts_declarations.
-// TODO(martinprobst): in the long term, this should become the default and TypeScriptRuleChecker should no longer special case ts_declaration.
-var unusedDeclarationRE = regexp.MustCompile(
- `WARNING: [^:]+:\d+:(?:\d+:)? keeping possibly used ts_declaration '([^']+)'`)
-
-// GarbledBazelResponseError signals to callers that the proto returned by bazel
-// analyze was garbled, and couldn't be unmarshalled.
-// TODO(lucassloan): remove when b/112891536 is fixed
-// Build Rabbit rewrites paths produced by bazel, which garbles the error
-// messages from bazel analyze, since they're encoded in protobufs.
-type GarbledBazelResponseError struct {
- Message string
-}
-
-func (g *GarbledBazelResponseError) Error() string {
- return g.Message
-}
-
-// runBazelAnalyze executes the `bazel analyze` command and extracts reports.
-// It returns the dependency report with rule names referring to rules *before*
-// macro expansion, or an error. runBazelAnalyze uses the given `analyze`
-// function to actually run the `bazel analyze` operation, which allows
-// exchanging it for a different implementation in the ts_auto_deps presubmit service.
-func (upd *Updater) runBazelAnalyze(buildFilePath string, bld *build.File, rules []*build.Rule) ([]*arpb.DependencyReport, error) {
- var targets []string
- for _, r := range rules {
- fullTarget := AbsoluteBazelTarget(bld, r.Name())
- targets = append(targets, fullTarget)
- }
- out, stderr, err := upd.bazelAnalyze(buildFilePath, targets)
- if err != nil {
- return nil, err
- }
-
- var res arpb.AnalyzeResult
- if err := proto.Unmarshal(out, &res); err != nil {
- // TODO(lucassloan): remove when b/112891536 is fixed
- // Build Rabbit rewrites paths produced by bazel, which garbles the error
- // messages from bazel analyze, since they're encoded in protobufs.
- return nil, &GarbledBazelResponseError{fmt.Sprintf("failed to unmarshal analysis result: %v\nin: %q", err, string(out))}
- }
- platform.Infof("analyze result %v", res)
- reports := res.GetDependencyReport()
- if len(targets) != len(reports) {
- if len(stderr) > 0 {
- // TODO(b/73321854): pretend second rule has a syntactical error, so bazel analyze produces no
- // report for it, but also doesn't return an error code. Remove workaround once fixed.
- return nil, fmt.Errorf("parsing reports failed (%d reports for %s):\n%s",
- len(reports), targets, stderr)
- }
- return nil, fmt.Errorf("parsing reports failed (%d reports for %s) in output: %s",
- len(reports), targets, out)
- }
- if upd.removeUnusedDeclarations {
- for _, report := range reports {
- for _, fb := range report.GetFeedback() {
- m := unusedDeclarationRE.FindStringSubmatch(fb)
- if m == nil {
- continue
- }
- target := m[1]
- platform.Infof("Removing (possibly) unused ts_declaration dependency %q", target)
- // TODO(martinprobst): this warning is to educate users after changing removeUnusedDeclarations to true by default.
- // Once existing code is fixed, this constitutes normal operation, and the logging below should be dropped.
- fmt.Fprintf(os.Stderr, "WARNING: removing apparently unused ts_declaration() dependency from %q.\n", report.GetRule())
-
- report.UnnecessaryDependency = append(report.UnnecessaryDependency, target)
- }
- }
- }
- return reports, nil
-}
-
-func spin(prefix string) chan<- struct{} {
- done := make(chan struct{})
- // Check for cygwin, important for Windows compatibility
- if !isatty.IsTerminal(os.Stderr.Fd()) || !isatty.IsCygwinTerminal(os.Stdout.Fd()) {
- return done
- }
- go func() {
- // Wait a bit before starting to print to avoid flashing a spinner unnecessarily.
- time.Sleep(100 * time.Millisecond)
- ticker := time.NewTicker(100 * time.Millisecond)
- defer ticker.Stop()
- i := 0
- str := []rune(`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`)
- for {
- select {
- case <-done:
- fmt.Fprint(os.Stderr, "\r")
- for i := 0; i < len(prefix)+2; i++ {
- fmt.Fprint(os.Stderr, " ")
- }
- fmt.Fprint(os.Stderr, "\r")
- return
- case <-ticker.C:
- fmt.Fprintf(os.Stderr, "\r%s %s", prefix, string(str[i]))
- i = (i + 1) % len(str)
- }
- }
- }()
- return done
-}
-
-// readBUILD loads the BUILD file, if present, or returns a nil pointer, if not.
-// buildFilePath is relative to CWD, and workspaceRelativePath is relative to
-// the workspace root (the directory containing the WORKSPACE file).
-func readBUILD(ctx context.Context, buildFilePath, workspaceRelativePath string) (*build.File, error) {
- data, err := platform.ReadFile(ctx, buildFilePath)
- if err != nil {
- if os.IsNotExist(err) {
- return nil, nil
- }
- return nil, err
- }
- bld, err := build.ParseBuild(workspaceRelativePath, data)
- if err != nil {
- if parseErr, ok := err.(build.ParseError); ok {
- return nil, &AnalysisFailedError{
- []AnalysisFailureCause{
- AnalysisFailureCause{
- Message: parseErr.Error(),
- Path: parseErr.Filename,
- Line: parseErr.Pos.Line,
- },
- },
- }
-
- }
-
- // wasn't an error we know how to parse
- return nil, err
- }
- return bld, nil
-}
-
-type srcSet map[string]bool
-
-type globResult struct {
- srcs srcSet
- err error
-}
-
-// globSources finds sources in path with any of the given extensions.
-// It also filters out temporary files, dangling symlinks, and symlinks into bazel-bin specifically.
-// It returns file names relative to path.
-func globSources(ctx context.Context, path string, extensions []string) (srcSet, error) {
- var allSourcePaths []string
- for _, extension := range extensions {
- pattern := "*." + extension
- matched, err := platform.Glob(ctx, filepath.Join(path, pattern))
- if err != nil {
- return nil, fmt.Errorf("glob(%s): %s", pattern, err)
- }
- allSourcePaths = append(allSourcePaths, matched...)
- }
- srcs := make(srcSet)
- for _, p := range allSourcePaths {
- fileName := filepath.Base(p)
- if isTempFile(fileName) {
- continue // Ignore editor swap/backup files.
- }
- // Try platform.Stat as a fallback, for Google file systems.
- _, err := platform.Stat(ctx, p)
- if os.IsNotExist(err) {
- platform.Infof("platform.Glob returned non-existent file (dangling symlink?). Ignoring %q.", p)
- continue
- }
- if err != nil {
- return nil, fmt.Errorf("cannot stat platform.Glob result %q: %v", p, err)
- }
- isMpeg, err := IsMpegTS(ctx, p)
- if err != nil {
- return nil, err
- }
- if isMpeg {
- continue
- }
- p, err := filepath.Rel(path, p)
- if err != nil {
- return nil, fmt.Errorf("filepath.Rel(%s, %s): %v", path, p, err)
- }
- srcs[p] = true
- }
- return srcs, nil
-}
-
-// IsMpegTS checks if a ".ts" file is an MPEG transport stream. Taze shouldn't
-// treat them as TypeScript files.
-func IsMpegTS(ctx context.Context, path string) (bool, error) {
- var content [200]byte
- n, err := platform.ReadBytesFromFile(ctx, path, content[:])
- if err != nil && err != io.EOF {
- return false, err
- }
-
- // MPEG TS' frame format starts with 0x47 every 189 bytes - detect that and return.
- isMpeg := n > 188 && content[0] == 0x47 && content[188] == 0x47
- return isMpeg, nil
-}
-
-func isTempFile(fileName string) bool {
- return strings.HasPrefix(fileName, ".") || strings.HasSuffix(fileName, ".swp") ||
- strings.HasSuffix(fileName, "~")
-}
-
-// updateSources adds any srcs that are not in some rule to the last ts_* rule
-// in the package, or create a new rule for them.
-func updateSources(bld *build.File, srcs srcSet) {
- removeSourcesUsed(bld, "ts_library", "srcs", srcs)
- removeSourcesUsed(bld, "ts_declaration", "srcs", srcs)
-
- if len(srcs) == 0 {
- return
- }
-
- // Sort the remaining sources for reproducibility (sadly Go has no LinkedHashSet)
- var srcSlice []string
- for s := range srcs {
- srcSlice = append(srcSlice, s)
- }
- sort.Strings(srcSlice)
-
- pkgName := filepath.Base(filepath.Dir(bld.Path))
- platform.Infof("Adding new sources to targets in %q: %q", pkgName, srcSlice)
- for _, s := range srcSlice {
- var r *build.Rule
- ruleName := pkgName
- if strings.HasSuffix(s, ".d.ts") {
- r = getOrCreateRule(bld, ruleName+"_dts", "ts_declaration", ruleTypeRegular)
- } else {
- rt := determineRuleType(bld.Path, s)
- r = getOrCreateRule(bld, ruleName, "ts_library", rt)
- }
- addToSrcsClobbering(bld, r, s)
- }
-}
-
-// Adds the given value to the srcs attribute on the build rule. Clobbers any
-// existing values for srcs that are not a list.
-func addToSrcsClobbering(bld *build.File, r *build.Rule, s string) {
- value := r.Attr("srcs")
- switch value.(type) {
- case nil, *build.ListExpr:
- // expected - a list of files (labels) or absent.
- default:
- // Remove any glob calls, variables, etc. ts_auto_deps uses explicit source lists.
- fmt.Fprintf(os.Stderr, "WARNING: clobbering non-list srcs attribute on %s\n",
- AbsoluteBazelTarget(bld, r.Name()))
- r.DelAttr("srcs")
- }
- val := &build.StringExpr{Value: s}
- edit.AddValueToListAttribute(r, "srcs", "", val, nil)
-}
-
-var testingRegexp = regexp.MustCompile(`\btesting\b`)
-
-func determineRuleType(path, s string) ruleType {
- if strings.HasSuffix(s, "_test.ts") || strings.HasSuffix(s, "_test.tsx") {
- return ruleTypeTest
- }
-
- return ruleTypeRegular
-}
-
-// AnalysisFailedError is returned by ts_auto_deps when the underlying analyze operation
-// fails, e.g. because the BUILD files have syntactical errors.
-type AnalysisFailedError struct {
- Causes []AnalysisFailureCause
-}
-
-// AnalysisFailureCause gives (one of) the reasons analysis failed, along with
-// the path and line that caused the failure (if available).
-type AnalysisFailureCause struct {
- Message string
- // workspace path of the file on which analysis failed ie foo/bar/baz.ts, not
- // starting with google3/
- Path string
- // 1-based line on which analysis failed
- Line int
-}
-
-func (a *AnalysisFailedError) Error() string {
- var messages []string
- for _, c := range a.Causes {
- messages = append(messages, c.Message)
- }
- return strings.Join(messages, "\n")
-}
-
-// updateDeps adds missing dependencies and removes unnecessary dependencies
-// for the targets in the given DependencyReports to the build rules in bld.
-func updateDeps(bld *build.File, reports []*arpb.DependencyReport) error {
- // First, check *all* reports on whether they were successful, so that users
- // get the complete set of errors at once.
- var errors []AnalysisFailureCause
- for _, report := range reports {
- if !report.GetSuccessful() {
- for _, fb := range report.GetFeedback() {
- msg := fmt.Sprintf("dependency analysis failed for %s:\n%s",
- report.GetRule(), fb)
-
- m := bazelErrorRE.FindStringSubmatch(fb)
- if m == nil {
- // error message didn't contain file and line number, so just use the
- // path of the BUILD file that was analyzed
- errors = append(errors, AnalysisFailureCause{Message: msg, Path: bld.Path})
- continue
- }
-
- file := m[1]
- line, err := strconv.Atoi(m[2])
- if err != nil {
- return err
- }
-
- errors = append(errors, AnalysisFailureCause{msg, file, line})
- }
- }
- }
- if len(errors) > 0 {
- return &AnalysisFailedError{errors}
- }
-
- pkg := filepath.Dir(bld.Path)
- for _, report := range reports {
- platform.Infof("Applying report: %s", report.String())
- fullTarget := report.GetRule()
- targetName := fullTarget[strings.LastIndex(fullTarget, ":")+1:]
- r := edit.FindRuleByName(bld, targetName)
- if r == nil {
- return fmt.Errorf("could not find rule from report %v", targetName)
- }
- for _, md := range report.MissingDependencyGroup {
- for _, d := range md.Dependency {
- d = AbsoluteBazelTarget(bld, d)
- if d == fullTarget {
- return &AnalysisFailedError{
- []AnalysisFailureCause{
- AnalysisFailureCause{
- Message: fmt.Sprintf("target %s depends on itself. "+
- "Maybe you have an incorrect `// from %s` comment, or need to split application "+
- "entry point (main.ts) and ng_module() rule?", d, d),
- Path: bld.Path,
- },
- },
- }
- }
- platform.Infof("Adding dependency on %s to %s\n", d, fullTarget)
- addDep(bld, r, d)
- }
- }
- hadUnresolved := len(report.UnresolvedImport) > 0
- if hadUnresolved {
- return &AnalysisFailedError{
- []AnalysisFailureCause{
- AnalysisFailureCause{
- Message: fmt.Sprintf("ERROR in %s: unresolved imports %s.\nMaybe you are missing a "+
- "'// from ...'' comment, or the target BUILD files are incorrect?\n%s\n",
- fullTarget, report.UnresolvedImport, strings.Join(report.GetFeedback(), "\n")),
- Path: bld.Path,
- },
- },
- }
- }
- for _, d := range report.UnnecessaryDependency {
- platform.Infof("Removing dependency on %s from %s\n", d, fullTarget)
- edit.ListAttributeDelete(r, "deps", d, pkg)
- }
- for _, s := range report.MissingSourceFile {
- platform.Infof("Removing missing source %s from %s\n", s, fullTarget)
- edit.ListAttributeDelete(r, "srcs", s, pkg)
- }
- }
- return nil
-}
-
-// maybeWriteBUILD checks if the given file needs updating, i.e. whether the
-// canonical serialized form of bld has changed from the file contents on disk.
-// If so, writes the file and returns true, returns false otherwise.
-func (upd *Updater) maybeWriteBUILD(ctx context.Context, path string, bld *build.File) (bool, error) {
- ri := &build.RewriteInfo{}
- build.Rewrite(bld, ri)
- platform.Infof("Formatted %s: %s\n", path, ri)
- newContent := build.Format(bld)
- oldContent, err := platform.ReadFile(ctx, path)
- if err != nil {
- if !os.IsNotExist(err) {
- return false, err
- } else if len(newContent) == 0 {
- // The BUILD file does not exist, and the newly created file has no content.
- // Treat this as equivalent, and do not create a new BUILD file.
- return false, nil
- }
- // Fall through to write a new file.
- } else if bytes.Equal(oldContent, newContent) {
- // Compare contents, only update if changed.
- return false, nil
- }
- if err := upd.updateFile(ctx, path, string(newContent)); err != nil {
- return false, fmt.Errorf("failed to update %q: %v", path, err)
- }
- return true, nil
-}
-
-// getWorkspaceRelativePath takes a buildFilePath that's relative to the working
-// directory, and returns a path to the BUILD file that's relative to the
-// workspaceRoot (the absolute path of the directory containing the WORKSPACE
-// file).
-func getWorkspaceRelativePath(workspaceRoot, buildFilePath string) (string, error) {
- absPath, err := filepath.Abs(buildFilePath)
- if err != nil {
- return "", err
- }
- workspaceRelativePath, err := filepath.Rel(workspaceRoot, absPath)
- if err != nil {
- return "", err
- }
- platform.Normalize(workspaceRelativePath)
-
- return workspaceRelativePath, nil
-}
-
-// getBUILDPath takes in a package or BUILD file path, and returns the path of
-// the workspace root (the absolute path of the directory containing the
-// WORKSPACE file), the BUILD file path relative to the working directory, and
-// the BUILD file path relative to the workspace root.
-func getBUILDPath(ctx context.Context, path string) (string, string, string, error) {
- path = strings.TrimSuffix(path, "/BUILD") // Support both package paths and BUILD files
- if _, err := platform.Stat(ctx, path); os.IsNotExist(err) {
- return "", "", "", err
- }
- buildFilePath := filepath.Join(path, "BUILD")
- workspaceRoot, err := workspace.Root(buildFilePath)
- if err != nil {
- return "", "", "", err
- }
-
- workspaceRelativePath, err := getWorkspaceRelativePath(workspaceRoot, buildFilePath)
- if err != nil {
- return "", "", "", err
- }
-
- return workspaceRoot, buildFilePath, workspaceRelativePath, nil
-}
-
-// isTazeDisabledInPackage checks the BUILD file, or if the BUILD doesn't exist,
-// the nearest ancestor BUILD file for a disable_ts_auto_deps() rule.
-func isTazeDisabledInPackage(ctx context.Context, g3root, buildFilePath, workspaceRelativePath string, bld *build.File) (bool, error) {
- if bld == nil {
- // Make sure ts_auto_deps hasn't been disabled in the next closest ancestor package.
- ancestor, err := FindBUILDFile(ctx, make(map[string]*build.File), g3root, filepath.Dir(workspaceRelativePath))
- if _, ok := err.(*noAncestorBUILDError); ok {
- platform.Infof("Could not find any ancestor BUILD for %q, continuing with a new BUILD file",
- buildFilePath)
- return false, nil
- } else if err != nil {
- return false, err
- } else if buildHasDisableTaze(ancestor) {
- fmt.Printf("ts_auto_deps disabled below %q\n", ancestor.Path)
- return true, nil
- } else {
- platform.Infof("BUILD file missing and ts_auto_deps is enabled below %q. Creating new BUILD file.",
- ancestor.Path)
- return false, nil
- }
- }
-
- if buildHasDisableTaze(bld) {
- fmt.Printf("ts_auto_deps disabled on %q\n", buildFilePath)
- return true, nil
- }
-
- return false, nil
-}
-
-// SubdirectorySourcesError is returned when ts_auto_deps detects a BUILD file
-// that references sources in another directory, either in the directory
-// being ts_auto_depsd, or in a super directory.
-type SubdirectorySourcesError struct{}
-
-func (a *SubdirectorySourcesError) Error() string {
- return "ts_auto_deps doesn't handle referencing sources in another directory " +
- "- to use ts_auto_deps, migrate to having a BUILD file in every directory. " +
- "For more details, see go/ts_auto_deps#subdirectory-sources"
-}
-
-// hasSubdirectorySources checks if the BUILD file has ts_libraries that contain
-// source files from subdirectories of the directory with the BUILD. ie foo/BUILD
-// has a src foo/bar/baz.ts, in the subdirectory foo/bar.
-func hasSubdirectorySources(bld *build.File) bool {
- for _, rule := range buildRules(bld, "ts_library") {
- srcs := rule.AttrStrings("srcs")
- if srcs != nil {
- for _, s := range srcs {
- if strings.Contains(s, "/") {
- return true
- }
- }
- } else {
- // srcs wasn't a list, check for a glob over subdirectory soruces
- srcExp := rule.Attr("srcs")
- call, ok := srcExp.(*build.CallExpr)
- if ok {
- callName, ok := call.X.(*build.Ident)
- if ok {
- if callName.Name == "glob" {
- for _, arg := range call.List {
- strArg, ok := arg.(*build.StringExpr)
- if ok && strings.Contains(strArg.Value, "/") {
- return true
- }
- }
- }
- }
- }
- }
- // TODO(b/120783741):
- // This only handles a lists of files, and a single glob, there are other
- // cases such as a glob + a list of files that it doesn't handle, but that's
- // ok since, this is only meant as a caution to the user.
- }
-
- return false
-}
-
-// directoryOrAncestorHasSubdirectorySources checks for ts_libraries referencing sources in subdirectories.
-// It checks the current directory's BUILD if it exists, otherwise it checks the nearest
-// ancestor package.
-func directoryOrAncestorHasSubdirectorySources(ctx context.Context, g3root, workspaceRelativePath string, bld *build.File) (bool, error) {
- if bld == nil {
- // Make sure the next closest ancestor package doesn't reference sources in a subdirectory.
- ancestor, err := FindBUILDFile(ctx, make(map[string]*build.File), g3root, filepath.Dir(workspaceRelativePath))
- if _, ok := err.(*noAncestorBUILDError); ok {
- // couldn't find an ancestor BUILD, so there aren't an subdirectory sources
- return false, nil
- } else if err != nil {
- return false, err
- } else if hasSubdirectorySources(ancestor) {
- return true, nil
- } else {
- // there was an ancestor BUILD, but it didn't reference subdirectory sources
- return false, nil
- }
- }
-
- if hasSubdirectorySources(bld) {
- return true, nil
- }
-
- return false, nil
-}
-
-func (upd *Updater) addSourcesToBUILD(ctx context.Context, path string, buildFilePath string, bld *build.File, srcs srcSet) (bool, error) {
-
- platform.Infof("Updating sources")
- if len(srcs) == 0 && len(allTSRules(bld)) == 0 {
- // No TypeScript rules/sources, no need to update anything
- return false, nil
- }
- updateSources(bld, srcs)
-
- return upd.maybeWriteBUILD(ctx, buildFilePath, bld)
-}
-
-// updateBUILDAfterBazelAnalyze applies the BUILD file updates that depend on bazel
-// analyze's DependencyReports, most notably updating any rules' deps.
-func (upd *Updater) updateBUILDAfterBazelAnalyze(ctx context.Context, isRoot bool,
- g3root string, buildFilePath string, bld *build.File, reports []*arpb.DependencyReport) (bool, error) {
- platform.Infof("Updating deps")
- if err := updateDeps(bld, reports); err != nil {
- return false, err
- }
-
- platform.Infof("Setting library rule kinds")
- if err := setLibraryRuleKinds(ctx, buildFilePath, bld); err != nil {
- return false, err
- }
- return upd.maybeWriteBUILD(ctx, buildFilePath, bld)
-}
-
-// IsTazeDisabledForDir checks if ts_auto_deps is disabled in the BUILD file in the dir,
-// or if no BUILD file exists, in the closest ancestor BUILD
-func IsTazeDisabledForDir(ctx context.Context, dir string) (bool, error) {
- g3root, buildFilePath, workspaceRelativePath, err := getBUILDPath(ctx, dir)
- if err != nil {
- return false, err
- }
-
- bld, err := readBUILD(ctx, buildFilePath, workspaceRelativePath)
- if err != nil {
- platform.Infof("Error reading building file!")
- return false, err
- }
-
- return isTazeDisabledInPackage(ctx, g3root, buildFilePath, workspaceRelativePath, bld)
-}
-
-// CantProgressAfterWriteError reports that ts_auto_deps was run in an environment
-// where it can't make writes to the file system (such as when ts_auto_deps is running
-// as a service for cider) and the writes it made need to be visible to bazel analyze,
-// so it can continue updating the BUILD file(s). In such a case, the caller should
-// collect the writes using a custom UpdateFile function, and re-call ts_auto_deps after
-// applying the writes.
-type CantProgressAfterWriteError struct{}
-
-func (a *CantProgressAfterWriteError) Error() string {
- return "running ts_auto_deps in a non-writable environment, can't continue until writes are applied"
-}
-
-// UpdateBUILDOptions bundles options for the UpdateBUILD function.
-type UpdateBUILDOptions struct {
- // InNonWritableEnvironment boolean indicates to ts_auto_deps that the writes it makes
- // won't be immediately visible to bazel analyze, so it cannot proceed normally.
- // In this case, if it makes a write that needs to be visible to bazel analyze, it
- // will return a CantProgressAfterWriteError, which indicates that the caller
- // should apply the writes made to its UpdateFile function, and re-call UpdateBUILD
- // after the writes have been applied.
- InNonWritableEnvironment bool
- // IsRoot indicates that the directory is a project's root directory, so a tsconfig
- // rule should be created.
- IsRoot bool
-}
-
-// LatencyReport contains timing measurements of the functions that are called
-// when running the presubmit on a package without any TypeScript (since we
-// return early to avoid the latency of RAS analyze).
-type LatencyReport struct {
- GetBUILD, TazeDisabled, SubdirSrcs, AddSrcs time.Duration
-}
-
-// UpdateBUILD drives the main process of creating/updating the BUILD file
-// underneath path based on the available sources. Returns true if it modified
-// the BUILD file, false if the BUILD file was up to date already. bazelAnalyze
-// is used to run the underlying `bazel analyze` process. Returns another
-// boolean that's true iff the package doesn't contain any TypeScript (source
-// files or BUILD rules).
-func (upd *Updater) UpdateBUILD(ctx context.Context, path string, options UpdateBUILDOptions) (bool, *LatencyReport, error) {
- latencyReport := &LatencyReport{}
-
- // asynchronously glob for TS sources in the package, since it can be slow on
- // a network file system.
- globChan := make(chan globResult)
- go func() {
- platform.Infof("Globbing TS sources in %s", path)
- srcs, err := globSources(ctx, path, []string{"ts", "tsx"})
- globChan <- globResult{srcs, err}
- }()
-
- start := time.Now()
- g3root, buildFilePath, workspaceRelativePath, err := getBUILDPath(ctx, path)
- if err != nil {
- return false, nil, err
- }
-
- bld, err := readBUILD(ctx, buildFilePath, workspaceRelativePath)
- if err != nil {
- platform.Infof("Error reading building file!")
- return false, nil, err
- }
- latencyReport.GetBUILD = time.Since(start)
-
- start = time.Now()
- ts_auto_depsDisabled, err := isTazeDisabledInPackage(ctx, g3root, buildFilePath, workspaceRelativePath, bld)
- if err != nil {
- return false, nil, err
- }
- latencyReport.TazeDisabled = time.Since(start)
- if ts_auto_depsDisabled {
- return false, nil, nil
- }
-
- start = time.Now()
- hasSubdirSrcs, err := directoryOrAncestorHasSubdirectorySources(ctx, g3root, workspaceRelativePath, bld)
- latencyReport.SubdirSrcs = time.Since(start)
- if err != nil {
- return false, nil, err
- }
- if hasSubdirSrcs {
- return false, nil, &SubdirectorySourcesError{}
- }
-
- if bld == nil {
- // The BUILD file didn't exist, so create a new, empty one.
- bld = &build.File{Path: workspaceRelativePath, Type: build.TypeBuild}
- }
-
- start = time.Now()
- globRes := <-globChan
- if globRes.err != nil {
- return false, nil, globRes.err
- }
- changed, err := upd.addSourcesToBUILD(ctx, path, buildFilePath, bld, globRes.srcs)
- latencyReport.AddSrcs = time.Since(start)
- if err != nil {
- return false, nil, err
- }
- if options.InNonWritableEnvironment && changed {
- return true, nil, &CantProgressAfterWriteError{}
- }
-
- rules := allTSRules(bld)
- if len(rules) == 0 && !options.IsRoot {
- // No TypeScript rules, no need to query for dependencies etc, so just exit early.
- return changed, latencyReport, nil
- }
- rulesWithSrcs := []*build.Rule{}
- for _, r := range rules {
- srcs := r.Attr("srcs")
- if srcs != nil {
- if l, ok := srcs.(*build.ListExpr); ok && len(l.List) > 0 {
- rulesWithSrcs = append(rulesWithSrcs, r)
- }
- }
- }
- platform.Infof("analyzing...")
- reports, err := upd.runBazelAnalyze(buildFilePath, bld, rulesWithSrcs)
- if err != nil {
- return false, nil, err
- }
-
- changedAfterBazelAnalyze, err := upd.updateBUILDAfterBazelAnalyze(ctx, options.IsRoot, g3root, buildFilePath, bld, reports)
- if err != nil {
- return false, nil, err
- }
- changed = changed || changedAfterBazelAnalyze
- if options.InNonWritableEnvironment && changed {
- return true, nil, &CantProgressAfterWriteError{}
- }
-
- return changed, nil, nil
-}
-
-// buildHasDisableTaze checks if the BUILD file should be managed using ts_auto_deps.
-// Users can disable ts_auto_deps by adding a "disable_ts_auto_deps()" (or "dont_ts_auto_deps_me()") statement.
-func buildHasDisableTaze(bld *build.File) bool {
- for _, stmt := range bld.Stmt {
- if call, ok := stmt.(*build.CallExpr); ok {
- if fnName, ok := call.X.(*build.Ident); ok && (fnName.Name == "disable_ts_auto_deps" || fnName.Name == "dont_ts_auto_deps_me") {
- return true
- }
- }
- }
- return false
-}
-
-// QueryBasedBazelAnalyze uses bazel query to analyze targets. It is available under a flag or
-// an environment variable on engineer's workstations.
-func QueryBasedBazelAnalyze(buildFilePath string, targets []string) ([]byte, []byte, error) {
- root, err := workspace.Root(buildFilePath)
- if err != nil {
- return nil, nil, err
- }
- reports, err := analyze.New(analyze.NewQueryBasedTargetLoader(root, "bazel")).Analyze(context.Background(), buildFilePath, targets)
- if err != nil {
- return nil, nil, err
- }
- s, err := proto.Marshal(&arpb.AnalyzeResult{
- DependencyReport: reports,
- })
- return s, nil, err
-}
-
-type ruleType int
-
-const (
- ruleTypeAny ruleType = iota
- ruleTypeRegular
- ruleTypeTest
- ruleTypeTestSupport
-)
-
-// isKind returns true if the rule has given kind. It also accepts "ng_modules"
-// as "ts_library" kind.
-func isKind(r *build.Rule, kind string) bool {
- acceptNgModule := kind == "ts_library"
-
- return r.Kind() == kind || (acceptNgModule && r.Kind() == "ng_module")
-}
-
-func buildRules(bld *build.File, kind string) []*build.Rule {
- // Find all rules, then filter by kind.
- // This is nearly the same as just calling bld.Rules(kind), but allows to
- // retrieve ng_module and ts_library intermixed, in the order in which they
- // appear in the BUILD file. That allows ts_auto_deps to consistently always pick the
- // last build rule in the file in case multiple match, regardless of kind.
- allRules := bld.Rules("")
- var res []*build.Rule
- for _, r := range allRules {
- if isKind(r, kind) {
- res = append(res, r)
- }
- }
- return res
-}
-
-// hasDependency returns whether a build rule contains the specified dependency.
-func hasDependency(bld *build.File, r *build.Rule, dep string) bool {
- pkg := filepath.Dir(bld.Path)
- oldDeps := r.Attr("deps")
- if edit.ListFind(oldDeps, dep, pkg) != nil {
- return true
- }
- runtimeDeps := r.Attr("runtime_deps")
- return edit.ListFind(runtimeDeps, dep, pkg) != nil
-}
-
-// addDep adds a dependency to the specified build rule
-func addDep(bld *build.File, r *build.Rule, dep string) {
- pkg := filepath.Dir(bld.Path)
- dep = edit.ShortenLabel(dep, pkg)
- if dep[0] != '/' && dep[0] != ':' {
- dep = ":" + dep // ShortenLabel doesn't add the ':'
- }
- edit.AddValueToListAttribute(r, "deps", pkg, &build.StringExpr{Value: dep}, nil)
-}
-
-// AbsoluteBazelTarget converts a ruleName to an absolute target string (//foo/bar:bar).
-// It interprets ruleName relative to the given build file's package. It
-// supports plain names, names starting with colons, absolute paths, and
-// absolute paths with shorthand target syntax (i.e. "bar", ":bar", "//foo/bar",
-// "//foo/bar:bar").
-func AbsoluteBazelTarget(bld *build.File, ruleName string) string {
- if strings.HasPrefix(ruleName, "//") {
- // already absolute
- if colonIdx := strings.LastIndex(ruleName, ":"); colonIdx == -1 {
- // expand shorthand syntax
- return ruleName + ":" + ruleName[strings.LastIndex(ruleName, "/")+1:]
- }
- return ruleName
- }
- pkg := platform.Normalize(filepath.Dir(bld.Path))
- return fmt.Sprintf("//%s:%s", pkg, strings.TrimPrefix(ruleName, ":"))
-}
-
-// Finds all ts_library and ts_declaration targets in the given BUILD file.
-func allTSRules(bld *build.File) []*build.Rule {
- var res []*build.Rule
- res = append(res, buildRules(bld, "ts_library")...)
- res = append(res, buildRules(bld, "ts_declaration")...)
- return res
-}
-
-// removeSourcesUsed removes sources used by rules of kind ruleKind in attribute
-// attrName from the given set of sources.
-func removeSourcesUsed(bld *build.File, ruleKind, attrName string, srcs srcSet) {
- for _, rule := range buildRules(bld, ruleKind) {
- for s := range srcs {
- pkg := filepath.Dir(bld.Path)
- // Handles ":foo.ts" references, and concatenated lists [foo.ts] + [bar.ts]
- // TODO(martinprobst): What to do about sources that don't seem to exist?
- if edit.ListFind(rule.Attr(attrName), s, pkg) != nil {
- delete(srcs, s)
- }
- }
- }
-}
-
-const (
- tsSkylarkLabel = "@npm_bazel_typescript//:index.bzl"
- ngSkylarkLabel = "@angular//:index.bzl"
-)
-
-func removeUnusedLoad(bld *build.File, kind string) {
- if len(bld.Rules(kind)) > 0 {
- return // kind is still used somewhere.
- }
- var stmt []build.Expr
- for _, s := range bld.Stmt {
- load, ok := s.(*build.LoadStmt)
- if !ok {
- stmt = append(stmt, s)
- continue
- }
- if len(load.To) == 0 {
- // a load statement without actually loaded symbols, skip
- continue
- }
-
- var from, to []*build.Ident
- for i, ca := range load.To {
- if ca.Name != kind {
- from = append(from, load.From[i])
- to = append(to, ca)
- }
- }
- load.From = from
- load.To = to
- if len(to) > 0 {
- stmt = append(stmt, load)
- continue
- }
- }
- bld.Stmt = stmt
-}
-
-// setLibraryRuleKinds sets the kinds for recognized library rules. That is, it
-// determines if a rule should be an ng_module, and sets the
-// rule kind if so. It also takes care of having the appropriate load calls.
-func setLibraryRuleKinds(ctx context.Context, buildFilePath string, bld *build.File) error {
- hasNgModule := false
- changed := false
- for _, r := range buildRules(bld, "ts_library") {
- shouldBeNgModule := false
- isNgModule := r.Call.X.(*build.Ident).Name == "ng_module"
- if hasAngularDependency(r) {
- shouldBeNgModule = true
- hasNgModule = true
- }
- if isNgModule && !shouldBeNgModule {
- platform.Infof("Changing rule %s to ts_library()", r.AttrString("name"))
- r.Call.X.(*build.Ident).Name = "ts_library"
- r.DelAttr("assets")
- changed = true
- } else if !isNgModule && shouldBeNgModule {
- platform.Infof("Changing rule %s to ng_module()", r.AttrString("name"))
- r.Call.X.(*build.Ident).Name = "ng_module"
- changed = true
- }
- }
- if changed {
- bld.Stmt = edit.InsertLoad(bld.Stmt, ngSkylarkLabel,
- []string{"ng_module"}, []string{"ng_module"})
- bld.Stmt = edit.InsertLoad(bld.Stmt, tsSkylarkLabel,
- []string{"ts_library"}, []string{"ts_library"})
- removeUnusedLoad(bld, "ts_library")
- removeUnusedLoad(bld, "ng_module")
- }
- if !hasNgModule {
- return nil
- }
- return updateWebAssets(ctx, buildFilePath, bld)
-}
-
-// hasAngularDependency returns true if the given rule depends on a Angular
-// build rule.
-func hasAngularDependency(r *build.Rule) bool {
- e := r.Attr("deps")
- for _, li := range edit.AllLists(e) {
- for _, elem := range li.List {
- str, ok := elem.(*build.StringExpr)
- if ok && strings.HasPrefix(str.Value, "//third_party/javascript/angular2") {
- return true
- }
- }
- }
- return false
-}
-
-// updateWebAssets finds web assets in the package of the BUILD file and adds
-// them to the "assets" attribute of the ng_module rules.
-func updateWebAssets(ctx context.Context, buildFilePath string, bld *build.File) error {
- // TODO(martinprobst): should this be merged with updateSources above? Difference is that
- // creates new rules, this just distributes assets across them.
- // This must use buildFilePath, the absolute path to the directory, as our cwd
- // might not be the workspace root.
- absolutePkgPath := filepath.Dir(buildFilePath)
- assetFiles, err := globSources(ctx, absolutePkgPath, []string{"html", "css"})
- if err != nil {
- return err
- }
- platform.Infof("Found asset files in %s: %v", absolutePkgPath, assetFiles)
-
- pkg := filepath.Dir(bld.Path)
- for _, r := range bld.Rules("ng_module") {
- srcs := r.Attr("assets")
- if call, ok := srcs.(*build.CallExpr); ok && call.X.(*build.Ident).Name == "glob" {
- // Remove any glob calls, ts_auto_deps uses explicit source lists.
- r.DelAttr("assets")
- }
-
- for _, s := range r.AttrStrings("assets") {
- if strings.HasPrefix(s, ":") || strings.HasPrefix(s, "//") {
- continue // keep rule references
- }
- if _, ok := assetFiles[s]; !ok {
- edit.ListAttributeDelete(r, "assets", s, pkg)
- }
- }
- }
-
- removeSourcesUsed(bld, "ng_module", "assets", assetFiles)
- if len(assetFiles) == 0 {
- return nil
- }
-
- // Add to the last rule, to match behaviour with *.ts sources.
- lastModule := getLastRule(bld, "ng_module", ruleTypeRegular)
- if lastModule == nil {
- // Fall back to using any ng_module
- lastModule = getLastRule(bld, "ng_module", ruleTypeAny)
- }
- if lastModule == nil {
- // Should not happen by preconditions of this function.
- return fmt.Errorf("no ng_module rules in BUILD?")
- }
-
- for newAsset := range assetFiles {
- val := &build.StringExpr{Value: newAsset}
- edit.AddValueToListAttribute(lastModule, "assets", pkg, val, nil)
- }
- return nil
-}
-
-// getOrCreateRule returns or creates a rule of the given kind, with testonly = 1 or 0 depending on
-// rt. If there's no such rule, it creates a new rule with the given ruleName.
-// If there is more than one rule matching, it returns the *last* rule.
-func getOrCreateRule(bld *build.File, ruleName, ruleKind string, rt ruleType) *build.Rule {
- if r := getLastRule(bld, ruleKind, rt); r != nil {
- return r
- }
-
- // TODO(calebegg): Switch this to "_test" but still support "_tests"
- if rt == ruleTypeTest {
- ruleName += "_tests"
- }
-
- loadArgs := []string{ruleKind}
- bld.Stmt = edit.InsertLoad(bld.Stmt, tsSkylarkLabel, loadArgs, loadArgs)
-
- r := &build.Rule{&build.CallExpr{X: &build.Ident{Name: ruleKind}}, ""}
- // Rename to *_ts if there's a name collision. This leaves open a collision with another rule
- // called _ts, but that failure mode is unlikely to happen accidentally.
- if edit.FindRuleByName(bld, ruleName) != nil {
- ruleName = ruleName + "_ts"
- } else if filepath.Base(filepath.Dir(bld.Path)) == ruleName {
- // The various *_ajd macros do not have a "name" attribute, but implicitly use the package name.
- // Make sure not to use the package name if there is a *_ajd rule.
- for _, r := range bld.Rules("") {
- if r.Name() == "" && strings.HasSuffix(r.Kind(), "_ajd") {
- ruleName = ruleName + "_ts"
- break
- }
- }
- }
- r.SetAttr("name", &build.StringExpr{Value: ruleName})
- if rt == ruleTypeTest || rt == ruleTypeTestSupport {
- r.SetAttr("testonly", &build.Ident{Name: "True"})
- }
- bld.Stmt = append(bld.Stmt, r.Call)
- return r
-}
-
-// ruleMatches return whether a rule matches the specified kind and rt value.
-func ruleMatches(bld *build.File, r *build.Rule, kind string, rt ruleType) bool {
- if !isKind(r, kind) {
- return false
- }
- inTestingDir := determineRuleType(bld.Path, "somefile.ts") == ruleTypeTestSupport
- hasTestsName := strings.HasSuffix(r.Name(), "_tests")
- // Accept the rule if it matches the testonly attribute.
- if rt == ruleTypeAny {
- return true
- }
- if attrTruthy(r, "testonly") {
- if inTestingDir && ((hasTestsName && rt == ruleTypeTest) || (!hasTestsName && rt == ruleTypeTestSupport)) {
- return true
- }
- if !inTestingDir && rt == ruleTypeTest {
- return true
- }
- }
- return rt == ruleTypeRegular && !attrTruthy(r, "testonly")
-}
-
-// targetRegisteredInRule returns whether a target has been registered in a rule that
-// matches a specified ruleKind and ruleType in current build file
-func targetRegisteredInRule(bld *build.File, ruleKind string, rt ruleType, target string) bool {
- for _, r := range bld.Rules("") {
- if ruleMatches(bld, r, ruleKind, rt) && hasDependency(bld, r, target) {
- return true
- }
- }
- return false
-}
-
-// getRule returns the last rule in bld that has the given ruleKind and matches
-// the specified rt value.
-func getLastRule(bld *build.File, ruleKind string, rt ruleType) *build.Rule {
- rules := getRules(bld, ruleKind, rt)
-
- if len(rules) == 0 {
- return nil
- }
-
- return rules[len(rules)-1]
-}
-
-// getRules returns all the rules in bld that have the given ruleKind and
-// matches the specified rt value.
-func getRules(bld *build.File, ruleKind string, rt ruleType) []*build.Rule {
- var rules []*build.Rule
- for _, r := range bld.Rules("") {
- if ruleMatches(bld, r, ruleKind, rt) {
- rules = append(rules, r)
- }
- }
-
- return rules
-}
-
-// FilterPaths filters the given paths, returning the deduplicated set of
-// folders that contain TypeScript sources (.ts and .tsx) or BUILD files.
-func FilterPaths(paths []string) []string {
- fileSet := make(map[string]bool)
- for _, p := range paths {
- if !strings.HasSuffix(p, ".ts") && !strings.HasSuffix(p, ".tsx") && filepath.Base(p) != "BUILD" {
- continue
- }
- fileSet[filepath.Dir(p)] = true
- }
- var newPaths []string
- for k := range fileSet {
- newPaths = append(newPaths, platform.Normalize(k))
- }
- return newPaths
-}
-
-// ResolvePackages resolves package paths, i.e. paths starting with '//',
-// against the workspace root folder closest to the current working directory.
-// It updates paths in place.
-// It returns an error if it cannot find a workspace root or working directory.
-func ResolvePackages(paths []string) error {
- for i, p := range paths {
- if strings.HasPrefix(p, "//") {
- wd, err := os.Getwd()
- if err != nil {
- return fmt.Errorf("failed to get working directory: %v", err)
- }
- g3root, err := workspace.Root(wd)
- if err != nil {
- return fmt.Errorf("failed to find workspace root under %q: %v", wd, err)
- }
- paths[i] = filepath.Join(g3root, p)
- }
- }
- return nil
-}
-
-type noAncestorBUILDError struct{}
-
-func (nabe *noAncestorBUILDError) Error() string {
- return "no ancestor BUILD file found"
-}
-
-// FindBUILDFile searches for the closest parent BUILD file above pkg. It
-// returns the parsed BUILD file, or an error if none can be found.
-func FindBUILDFile(ctx context.Context, pkgToBUILD map[string]*build.File,
- workspaceRoot string, packagePath string) (*build.File, error) {
- if packagePath == "." || packagePath == "/" {
- return nil, &noAncestorBUILDError{}
- }
- if bld, ok := pkgToBUILD[packagePath]; ok {
- return bld, nil
- }
- buildPath := filepath.Join(workspaceRoot, packagePath, "BUILD")
- bld, err := readBUILD(ctx, buildPath, filepath.Join(packagePath, "BUILD"))
- if err != nil {
- return nil, err
- } else if bld == nil {
- // Recursively search parent package and cache its location below if found.
- bld, err = FindBUILDFile(ctx, pkgToBUILD, workspaceRoot, filepath.Dir(packagePath))
- }
- if err == nil {
- // NB: The cache key is packagePath ('foo/bar/baz'), even if build file was
- // found at a higher location ('foo/BUILD'). This avoids re-testing for file
- // existence.
- pkgToBUILD[packagePath] = bld
- }
- return bld, err
-}
-
-// Paths gets the list of paths for the current execution of ts_auto_deps.
-func Paths(isRoot bool, files bool, recursive bool) ([]string, error) {
- paths := flag.Args()
- if len(paths) == 0 {
- wd, err := os.Getwd()
- if err != nil {
- return nil, fmt.Errorf("failed to get working directory: %v", err)
- }
- paths = []string{wd}
- }
-
- if len(paths) > 1 && isRoot {
- return nil, fmt.Errorf("can only take exactly one path with -root")
- }
-
- if files {
- paths = FilterPaths(paths)
- if len(paths) == 0 {
- return nil, fmt.Errorf("WARNING: found no TypeScript files in %s", paths)
- }
- }
-
- if err := ResolvePackages(paths); err != nil {
- return nil, fmt.Errorf("failed to resolve packages: %s", err)
- }
-
- if recursive {
- var lock sync.Mutex // guards allPaths
- var allPaths []string
- for _, p := range paths {
- err := platform.Walk(p, func(path string, info os.FileMode) error {
- if info.IsDir() {
- lock.Lock()
- allPaths = append(allPaths, path)
- lock.Unlock()
- }
- return nil
- })
- if err != nil {
- return nil, fmt.Errorf("ts_auto_deps -recursive failed: %s", err)
- }
- }
- sort.Sort(byLengthInverted(allPaths))
- paths = allPaths
- }
-
- return paths, nil
-}
-
-// Execute runs ts_auto_deps on paths using host.
-func Execute(host *Updater, paths []string, isRoot, recursive bool) error {
- ctx := context.Background()
- for i, p := range paths {
- isLastAndRoot := isRoot && i == len(paths)-1
- changed, _, err := host.UpdateBUILD(ctx, p, UpdateBUILDOptions{InNonWritableEnvironment: false, IsRoot: isLastAndRoot})
- if err != nil {
- if recursive {
- return fmt.Errorf("ts_auto_deps failed on %s/BUILD: %s", p, err)
- }
- return fmt.Errorf("ts_auto_deps failed: %s", err)
- }
- if changed {
- if filepath.Base(p) == "BUILD" {
- fmt.Printf("Wrote %s\n", p)
- } else {
- fmt.Printf("Wrote %s\n", filepath.Join(p, "BUILD"))
- }
- }
- }
- host.RegisterTestRules(ctx, paths...)
- return nil
-}
-
-// allPaths walks the file system and returns a list of all directories under
-// all paths.
-func allPaths(paths []string) ([]string, error) {
- var allPaths []string
- for _, p := range paths {
- err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
- if err == nil && info.IsDir() {
- allPaths = append(allPaths, path)
- }
- return err
- })
- if err != nil {
- return nil, err
- }
- }
- sort.Sort(byLengthInverted(allPaths))
- return allPaths, nil
-}
-
-type byLengthInverted []string
-
-func (s byLengthInverted) Len() int { return len(s) }
-func (s byLengthInverted) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s byLengthInverted) Less(i, j int) bool { return len(s[i]) > len(s[j]) }
diff --git a/ts_auto_deps/updater/updater_test.go b/ts_auto_deps/updater/updater_test.go
deleted file mode 100644
index 613b99f..0000000
--- a/ts_auto_deps/updater/updater_test.go
+++ /dev/null
@@ -1,563 +0,0 @@
-package updater
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "reflect"
- "strings"
- "testing"
-
- "github.com/bazelbuild/buildtools/build"
- "github.com/golang/protobuf/proto"
-
- arpb "github.com/bazelbuild/rules_typescript/ts_auto_deps/proto"
-)
-
-var (
- testTmpDir = os.Getenv("TEST_TMPDIR")
-)
-
-func mktmp(fn string, content []byte) (string, error) {
- p := fn
- if !filepath.IsAbs(p) {
- p = filepath.Join(testTmpDir, fn)
- }
- if err := os.MkdirAll(filepath.Dir(p), 0777); err != nil {
- return "", err
- }
- return p, ioutil.WriteFile(p, content, 0666)
-}
-
-func TestReadBuild(t *testing.T) {
- p, err := mktmp("google3/foo/bar/BUILD", []byte(`
-ts_library(name = 'a', srcs = ['b.ts'])
-`))
- if err != nil {
- t.Fatal(err)
- }
- bld, err := readBUILD(context.Background(), p, "foo/bar/BUILD")
- if err != nil {
- t.Fatal(err)
- }
- if bld.Path != "foo/bar/BUILD" {
- t.Errorf("bld.Path: got %s, expected %s", bld.Path, "foo/bar/BUILD")
- }
-}
-
-func TestGlobSources(t *testing.T) {
- for _, f := range []string{"a.ts", "a/b.ts", "c.tsx", "whatever", "foo.cpp", "d.d.ts", "._e.ts"} {
- if _, err := mktmp(f, []byte("// content")); err != nil {
- t.Fatal(err)
- }
- }
- if err := os.Symlink("../bazel-bin/symlink.d.ts", filepath.Join(testTmpDir, "symlink.d.ts")); err != nil {
- t.Fatal(err)
- }
- if err := os.Symlink("whatever", filepath.Join(testTmpDir, "whatever.d.ts")); err != nil {
- t.Fatal(err)
- }
- srcs, err := globSources(context.Background(), testTmpDir, []string{"ts", "tsx"})
- if err != nil {
- t.Fatal(err)
- }
- expected := srcSet(map[string]bool{
- "a.ts": true,
- "c.tsx": true,
- "d.d.ts": true,
- "whatever.d.ts": true,
- })
- if !reflect.DeepEqual(srcs, expected) {
- t.Errorf("globSources: got %v, want %v", srcs, expected)
- }
-}
-
-func TestDetermineRuleType(t *testing.T) {
- tests := []struct {
- path string
- source string
- rt ruleType
- }{
- {"java/com/google/myapp/BUILD", "foo.ts", ruleTypeRegular},
- {"java/com/google/myapp/BUILD", "foo_test.ts", ruleTypeTest},
- {"java/com/google/myapp/BUILD", "foo_test.tsx", ruleTypeTest},
-
- {"java/com/google/testing/mytesttool/BUILD", "foo.ts", ruleTypeRegular},
- {"testing/mytesttool/BUILD", "foo.ts", ruleTypeRegular},
- {"testing/mytesttool/BUILD", "foo_test.ts", ruleTypeTest},
- {"testing/mytesttool/BUILD", "foo_test.ts", ruleTypeTest},
- }
- for _, tst := range tests {
- rt := determineRuleType(tst.path, tst.source)
- if rt != tst.rt {
- t.Errorf("determineRuleType(%q, %q): got %v, expected %v", tst.path, tst.source, rt, tst.rt)
- }
- }
-}
-
-func parseReport(t *testing.T, input string) *arpb.DependencyReport {
- report := &arpb.DependencyReport{}
- if err := proto.UnmarshalText(input, report); err != nil {
- t.Error(err)
- }
- return report
-}
-
-func TestBazelAnalyzeError(t *testing.T) {
- bld, err := build.ParseBuild("rules/BUILD", []byte(`
-ts_library(
- name = "firstrule",
- srcs = [],
-)
-ts_library(
- name = "secondrule",
- srcs = [],
-)
- `))
- if err != nil {
- t.Fatal(err)
- }
- mockAnalyze := func(_ string, targets []string) ([]byte, []byte, error) {
- data, err := proto.Marshal(&arpb.AnalyzeResult{
- DependencyReport: []*arpb.DependencyReport{&arpb.DependencyReport{
- Rule: proto.String("//rules:firstrule"),
- }},
- })
- return data, []byte(`Here's the actual error`), err
- }
- upd := &Updater{}
- upd.bazelAnalyze = mockAnalyze
- report, err := upd.runBazelAnalyze("firstrule/BUILD", bld, bld.Rules("ts_library"))
- if err == nil {
- t.Fatalf("expected an error, got a report: %v", report)
- }
- expected := `parsing reports failed (1 reports for [//rules:firstrule //rules:secondrule]):
-Here's the actual error`
- if err.Error() != expected {
- t.Errorf("runBazelAnalyze: got %q, expected %q", err.Error(), expected)
- }
-}
-
-func TestUpdateDeps(t *testing.T) {
- report := parseReport(t, `
- rule: "//foo:bar"
- unnecessary_dependency: "//unnecessary_dep"
- missing_dependency_group: {
- dependency: "//missing_dep"
- }
- missing_source_file: "missing_file.ts"`)
-
- tests := []struct {
- name string
- before string
- after string
- changed bool
- }{
- {
- "Add missing dependency",
- `ts_library(
- name = "bar",
- deps = [],
- )`,
- `ts_library(
- name = "bar",
- deps = ["//missing_dep"],
- )`,
- true,
- },
- {
- "Remove + Add dependency",
- `ts_library(
- name = "bar",
- deps = ["//unnecessary_dep"],
- )`,
- `ts_library(
- name = "bar",
- deps = ["//missing_dep"],
- )`,
- true,
- },
- {
- "Remove nonexistent dep (e.g. due to macro)",
- `ts_library(
- name = "bar",
- deps = ["//missing_dep"],
- )`,
- `ts_library(
- name = "bar",
- deps = ["//missing_dep"],
- )`,
- false, // Unchanged!
- },
- {
- "Remove nonexistent src",
- `ts_library(
- name = "bar",
- srcs = ["hello.ts"],
- deps = ["//missing_dep"],
- )`,
- `ts_library(
- name = "bar",
- srcs = ["hello.ts"],
- deps = ["//missing_dep"],
- )`,
- false, // Unchanged!
- },
- }
- for _, tst := range tests {
- bld, err := build.ParseBuild("foo/BUILD", []byte(tst.before))
- if err != nil {
- t.Errorf("parse %s failed: %s in %s", tst.name, err, tst.before)
- }
- bldAft, err := build.ParseBuild("foo/BUILD", []byte(tst.after))
- if err != nil {
- t.Errorf("parse %s after failed: %s", tst.name, err)
- }
- if err := updateDeps(bld, []*arpb.DependencyReport{report}); err != nil {
- t.Errorf("update %s failed: %s", tst.name, err)
- }
- updated := string(build.Format(bld))
- after := string(build.Format(bldAft))
- if updated != after {
- t.Errorf("update(%s), got:\n%s\n\nexpected:\n%s", tst.name, updated, after)
- }
- }
-}
-
-func TestUnresolvedImportError(t *testing.T) {
- report := parseReport(t, `
- rule: "//foo:bar"
- unresolved_import: "unresolved/import"`)
-
- bld, err := build.ParseBuild("foo/BUILD", []byte(`ts_library(
- name = "bar",
- srcs = ["hello.ts"],
- )`))
- if err != nil {
- t.Fatal(err)
- }
-
- expectedErr := "'// from ...'' comment, or the target BUILD files are incorrect?"
-
- err = updateDeps(bld, []*arpb.DependencyReport{report})
- if !strings.Contains(err.Error(), expectedErr) {
- t.Errorf("returned error %s: expected it to contain %s", err, expectedErr)
- }
-}
-
-func TestDottedCall(t *testing.T) {
- // Repro for a crash, b/35389044
- buildText := `foo.bar("baz")`
- bld, err := build.ParseBuild("test", []byte(buildText))
- if err != nil {
- t.Error(err)
- }
- removeUnusedLoad(bld, "ignored")
-}
-
-func TestFilterPaths(t *testing.T) {
- tests := []struct {
- in []string
- expected []string
- }{
- {[]string{"foo/bar.txt", "foo/baz.ts"}, []string{"foo"}},
- {[]string{"bam.ts"}, []string{"."}},
- {[]string{"foo/BUILD"}, []string{"foo"}},
- {[]string{"r/foo.tsx"}, []string{"r"}},
- {[]string{"../../x.ts"}, []string{"../.."}},
- {[]string{"a.txt", "foo/b.txt"}, []string(nil)},
- }
- for _, tst := range tests {
- res := FilterPaths(tst.in)
- if !reflect.DeepEqual(res, tst.expected) {
- t.Errorf("FilterPaths(%v): got %v, expected %v", tst.in, res, tst.expected)
- }
- }
-}
-
-func TestAddDep(t *testing.T) {
- tests := []struct {
- buildFile string
- newDep string
- expected string
- }{
- {`ts_library(name = "lib", deps = ["//a", "//b", "//c"])`,
- "//b",
- `ts_library(name = "lib", deps = ["//a", "//b", "//c"])`},
- {`ts_library(name = "lib", deps = ["//a", "//b", "//c"])`,
- "//d",
- `ts_library(name = "lib", deps = ["//a", "//b", "//c", "//d"])`},
- {`ts_library(name = "lib", deps = ["//a", ":b", "//c"])`,
- ":b",
- `ts_library(name = "lib", deps = ["//a", ":b", "//c"])`},
- {`ts_library(name = "lib", deps = ["//a", ":b", "//c"])`,
- "//buildloc:b",
- `ts_library(name = "lib", deps = ["//a", ":b", "//c"])`},
- {`ts_library(name = "lib", deps = ["//a", "//buildloc:b", "//c"])`,
- ":b",
- `ts_library(name = "lib", deps = ["//a", "//buildloc:b", "//c"])`},
- {`ts_library(name = "lib", deps = ["//a", "//other:b", "//c"])`,
- ":b",
- `ts_library(name = "lib", deps = [":b", "//a", "//other:b", "//c"])`},
- {`ts_library(name = "lib", deps = ["//a", "//other:b", "//c"])`,
- "//a:a",
- `ts_library(name = "lib", deps = ["//a", "//other:b", "//c"])`},
- }
- for _, tst := range tests {
- bld, err := build.ParseBuild("buildloc/BUILD", []byte(tst.buildFile))
- if err != nil {
- t.Fatalf("parse failure: %s - %v", tst.buildFile, err)
- }
- addDep(bld, bld.Rules("ts_library")[0], tst.newDep)
- newContent := string(build.Format(bld))
- expectedBld, err := build.ParseBuild("buildloc/BUILD", []byte(tst.expected))
- if err != nil {
- t.Fatalf("parse failure: %s - %v", tst.expected, err)
- }
- expected := string(build.Format(expectedBld))
- if newContent != expected {
- t.Errorf("addDep(%s, %s): got %v, expected %v", tst.buildFile, tst.newDep, newContent, tst.expected)
- }
- }
-}
-
-func TestRemoveSourcesUsed(t *testing.T) {
- tests := []struct {
- name string
- buildFile string
- ruleKind string
- attrName string
- srcs srcSet
- expectedSrcs srcSet
- }{
- {
- name: "RemovesSources",
- buildFile: `ts_library(name = "lib", srcs = ["foo.ts", "bar.ts"])`,
- ruleKind: "ts_library",
- attrName: "srcs",
- srcs: map[string]bool{"foo.ts": true},
- expectedSrcs: map[string]bool{},
- },
- {
- name: "WrongRuleKind",
- buildFile: `ts_library(name = "lib", srcs = ["foo.ts", "bar.ts"])`,
- ruleKind: "ng_module",
- attrName: "srcs",
- srcs: map[string]bool{"foo.ts": true},
- expectedSrcs: map[string]bool{"foo.ts": true},
- },
- {
- name: "WrongAttrName",
- buildFile: `ts_library(name = "lib", srcs = ["foo.ts", "bar.ts"])`,
- ruleKind: "ts_library",
- attrName: "deps",
- srcs: map[string]bool{"foo.ts": true},
- expectedSrcs: map[string]bool{"foo.ts": true},
- },
- {
- name: "MultipleRules",
- buildFile: `ts_library(name = "lib", srcs = ["foo.ts"])
- ts_library(name = "lib2", srcs = ["bar.ts"])`,
- ruleKind: "ts_library",
- attrName: "srcs",
- srcs: map[string]bool{"foo.ts": true, "bar.ts": true},
- expectedSrcs: map[string]bool{},
- },
- {
- name: "ConcatenatedLists",
- buildFile: `ts_library(name = "lib", srcs = ["foo.ts"] + ["bar.ts"])`,
- ruleKind: "ts_library",
- attrName: "srcs",
- srcs: map[string]bool{"foo.ts": true, "bar.ts": true},
- expectedSrcs: map[string]bool{},
- },
- {
- name: "ColonReferences",
- buildFile: `ts_library(name = "lib", srcs = [":foo.ts", "bar.ts"])`,
- ruleKind: "ts_library",
- attrName: "srcs",
- srcs: map[string]bool{"foo.ts": true},
- expectedSrcs: map[string]bool{},
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- bld, err := build.ParseBuild("foo/bar/BUILD",
- []byte(test.buildFile))
- if err != nil {
- t.Fatalf("parse failure: %v", err)
- }
-
- removeSourcesUsed(bld, test.ruleKind, test.attrName, test.srcs)
- if !reflect.DeepEqual(test.srcs, test.expectedSrcs) {
- t.Errorf("expected removeSourcesUsed() = %v, expected %v", test.srcs, test.expectedSrcs)
- }
- })
- }
-}
-
-func TestUpdateWebAssets(t *testing.T) {
- ctx := context.Background()
- bld, err := build.ParseBuild("foo/bar/BUILD",
- []byte(`ng_module(name = "m", assets = [":rule", "gone.html"])`))
- if err != nil {
- t.Fatalf("parse failure: %v", err)
- }
- testHTML, err := mktmp("google3/foo/bar/test.html", []byte(`<p>test</p>`))
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- if err := os.Remove(testHTML); err != nil {
- t.Error(err)
- }
- }()
- testCSS, err := mktmp("google3/foo/bar/test.css", []byte(`.test {}`))
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- if err := os.Remove(testCSS); err != nil {
- t.Error(err)
- }
- }()
- absoluteBuildPath := filepath.Join(filepath.Dir(testCSS), "BUILD")
- if err := updateWebAssets(ctx, absoluteBuildPath, bld); err != nil {
- t.Fatal(err)
- }
- data := string(build.Format(bld))
- expected := `ng_module(
- name = "m",
- assets = [
- ":rule",
- "test.css",
- "test.html",
- ],
-)
-`
- if data != expected {
- t.Errorf("build file mismatch, got %s, expected %s", data, expected)
- }
-}
-
-func TestWebAssetReferredByColon(t *testing.T) {
- ctx := context.Background()
- bld, err := build.ParseBuild("foo/bar/BUILD",
- []byte(`ng_module(name = "m", assets = [":colon.html"])`))
- if err != nil {
- t.Fatalf("parse failure: %v", err)
- }
- colon, err := mktmp("google3/foo/bar/colon.html", []byte(`<p>test</p>`))
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- if err := os.Remove(colon); err != nil {
- t.Error(err)
- }
- }()
- absolutBuildPath := filepath.Join(filepath.Dir(colon), "BUILD")
- if err := updateWebAssets(ctx, absolutBuildPath, bld); err != nil {
- t.Error(err)
- }
- data := string(build.Format(bld))
- expected := `ng_module(
- name = "m",
- assets = [":colon.html"],
-)
-`
- if data != expected {
- t.Errorf("build file mismatch, got %s, expected %s", data, expected)
- }
-}
-
-func TestAbsoluteBazelTarget(t *testing.T) {
- bld := &build.File{Path: "foo/bar/BUILD", Type: build.TypeBuild}
- tests := []struct{ target, expected string }{
- {"//foo/bar:bar", "//foo/bar:bar"},
- {":bar", "//foo/bar:bar"},
- {"bar", "//foo/bar:bar"},
- {"//foo/bar", "//foo/bar:bar"},
- }
- for _, tst := range tests {
- abs := AbsoluteBazelTarget(bld, tst.target)
- if abs != tst.expected {
- t.Errorf("AbsoluteBazelTarget(%q): got %q, expected %q", tst.target, abs, tst.expected)
- }
- }
-}
-
-func TestFindBUILDFileCacheOnError(t *testing.T) {
- ctx := context.Background()
- cache := make(map[string]*build.File)
- p, err := mktmp("google3/pkg/file", []byte(""))
- if err != nil {
- t.Fatal(err)
- }
- g3root := filepath.Dir(filepath.Dir(p))
- if filepath.Base(g3root) != "google3" {
- t.Errorf("g3root should be called google3, got %q", g3root)
- }
- // No BUILD file was created in the file system so FindBUILDFile should
- // return an error.
- if _, err = FindBUILDFile(ctx, cache, g3root, "pkg"); err == nil {
- t.Fatalf("returned no error, expected some error to occur")
- }
- if _, ok := cache["pkg"]; ok {
- t.Fatalf("cache contained BUILD file for package")
- }
-}
-
-func TestHasSubdirectorySources(t *testing.T) {
- tests := []struct {
- name string
- buildFile string
- expected bool
- }{
- {
- name: "LocalSources",
- buildFile: `ts_library(name = "lib", srcs = ["foo.ts", "bar.ts"])`,
- expected: false,
- },
- {
- name: "SubdirectorySources",
- buildFile: `ts_library(name = "lib", srcs = ["subdir/foo.ts", "subdir/bar.ts"])`,
- expected: true,
- },
- {
- name: "LocalNgModuleSources",
- buildFile: `ng_module(name = "lib", srcs = ["foo.ts", "bar.ts"])`,
- expected: false,
- },
- {
- name: "SubdirectoryNgModuleSources",
- buildFile: `ng_module(name = "lib", srcs = ["subdir/foo.ts", "subdir/bar.ts"])`,
- expected: true,
- },
- {
- name: "LocalGlob",
- buildFile: `ts_library(name = "lib", srcs = glob("*.ts"))`,
- expected: false,
- },
- {
- name: "SubdirectoryGlob",
- buildFile: `ts_library(name = "lib", srcs = glob("**/*.ts"))`,
- expected: true,
- },
- }
- for _, test := range tests {
- t.Run(test.name, func(t *testing.T) {
- bld, err := build.ParseBuild("foo/bar/BUILD",
- []byte(test.buildFile))
- if err != nil {
- t.Fatalf("parse failure: %v", err)
- }
-
- actual := hasSubdirectorySources(bld)
- if actual != test.expected {
- t.Errorf("got hasSubdirectorySouces() = %v, expected %v", actual, test.expected)
- }
- })
- }
-}
diff --git a/ts_auto_deps/workspace/BUILD.bazel b/ts_auto_deps/workspace/BUILD.bazel
deleted file mode 100644
index 38f8123..0000000
--- a/ts_auto_deps/workspace/BUILD.bazel
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-go_library(
- name = "go_default_library",
- srcs = ["workspace.go"],
- importpath = "github.com/bazelbuild/rules_typescript/ts_auto_deps/workspace",
- visibility = ["//visibility:public"],
- deps = ["@com_github_bazelbuild_buildtools//wspace:go_default_library"],
-)
diff --git a/ts_auto_deps/workspace/workspace.go b/ts_auto_deps/workspace/workspace.go
deleted file mode 100644
index c7f1ac2..0000000
--- a/ts_auto_deps/workspace/workspace.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package workspace
-
-import "github.com/bazelbuild/buildtools/wspace"
-
-// Root finds the closest directory containing a WORKSPACE file from p.
-func Root(p string) (string, error) {
- return wspace.Find(p)
-}
-
-// Name returns the name of the workspace.
-func Name() string {
- return "TODO"
-}