Skip to content

Commit

Permalink
Add gazelle:resolve_file_symbol_name directive
Browse files Browse the repository at this point in the history
  • Loading branch information
pcj committed Dec 3, 2023
1 parent 7bd32ba commit 995db88
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 12 deletions.
7 changes: 4 additions & 3 deletions language/scala/language.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,13 @@ func (sl *scalaLang) Name() string { return scalaLangName }
// KnownDirectives implements part of the language.Language interface
func (*scalaLang) KnownDirectives() []string {
return []string{
scalaRuleDirective,
resolveConflictsDirective,
resolveFileSymbolName,
resolveGlobDirective,
resolveKindRewriteNameDirective,
resolveWithDirective,
resolveConflictsDirective,
scalaAnnotateDirective,
resolveKindRewriteNameDirective,
scalaRuleDirective,
}
}

Expand Down
67 changes: 58 additions & 9 deletions language/scala/scala_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/bazelbuild/bazel-gazelle/resolve"
"github.com/bazelbuild/bazel-gazelle/rule"
"github.com/bazelbuild/buildtools/build"
"github.com/bmatcuk/doublestar/v4"

"github.com/stackb/scala-gazelle/pkg/collections"
"github.com/stackb/scala-gazelle/pkg/resolver"
Expand All @@ -31,20 +32,22 @@ const (
resolveGlobDirective = "resolve_glob"
resolveConflictsDirective = "resolve_conflicts"
resolveWithDirective = "resolve_with"
resolveFileSymbolName = "resolve_file_symbol_name"
resolveKindRewriteNameDirective = "resolve_kind_rewrite_name"
)

// scalaConfig represents the config extension for the a scala package.
type scalaConfig struct {
config *config.Config
rel string
universe resolver.Universe
overrides []*overrideSpec
implicitImports []*implicitImportSpec
rules map[string]*scalarule.Config
labelNameRewrites map[string]resolver.LabelNameRewriteSpec
annotations map[annotation]interface{}
conflictResolvers []resolver.ConflictResolver
config *config.Config
rel string
universe resolver.Universe
overrides []*overrideSpec
implicitImports []*implicitImportSpec
resolveFileSymbolNames []*resolveFileSymbolNameSpec
rules map[string]*scalarule.Config
labelNameRewrites map[string]resolver.LabelNameRewriteSpec
annotations map[annotation]interface{}
conflictResolvers []resolver.ConflictResolver
}

// newScalaConfig initializes a new scalaConfig.
Expand Down Expand Up @@ -102,6 +105,9 @@ func (c *scalaConfig) clone(config *config.Config, rel string) *scalaConfig {
if c.conflictResolvers != nil {
clone.conflictResolvers = c.conflictResolvers[:]
}
if c.resolveFileSymbolNames != nil {
clone.resolveFileSymbolNames = c.resolveFileSymbolNames[:]
}
return clone
}

Expand Down Expand Up @@ -149,6 +155,8 @@ func (c *scalaConfig) parseDirectives(directives []rule.Directive) (err error) {
c.parseResolveGlobDirective(d)
case resolveWithDirective:
c.parseResolveWithDirective(d)
case resolveFileSymbolName:
c.parseResolveFileSymbolNames(d)
case resolveKindRewriteNameDirective:
c.parseResolveKindRewriteNameDirective(d)
case resolveConflictsDirective:
Expand Down Expand Up @@ -215,6 +223,23 @@ func (c *scalaConfig) parseResolveWithDirective(d rule.Directive) {
})
}

func (c *scalaConfig) parseResolveFileSymbolNames(d rule.Directive) {
parts := strings.Fields(d.Value)
if len(parts) < 2 {
log.Printf("invalid gazelle:%s directive: expected [FILENAME_PATTERN [+|-]SYMBOLS...], got %v", resolveKindRewriteNameDirective, parts)
return
}
pattern := parts[0]

for _, part := range parts[1:] {
intent := collections.ParseIntent(part)
c.resolveFileSymbolNames = append(c.resolveFileSymbolNames, &resolveFileSymbolNameSpec{
pattern: pattern,
symbolName: *intent,
})
}
}

func (c *scalaConfig) parseResolveKindRewriteNameDirective(d rule.Directive) {
parts := strings.Fields(d.Value)
if len(parts) != 3 {
Expand Down Expand Up @@ -318,6 +343,23 @@ func (c *scalaConfig) shouldAnnotateExports() bool {
return ok
}

// ShouldResolveFileSymbolName tests whether the given symbol name pattern
// should be resolved within the scope of the given filename pattern.
// resolveFileSymbolNameSpecs represent a whitelist; if no patterns match, false
// is returned.
func (c *scalaConfig) ShouldResolveFileSymbolName(filename, name string) bool {
for _, spec := range c.resolveFileSymbolNames {
if ok, _ := doublestar.Match(spec.pattern, filename); !ok {
continue
}
if ok, _ := doublestar.Match(spec.symbolName.Value, name); !ok {
continue
}
return spec.symbolName.Want
}
return false
}

func (c *scalaConfig) Comment() build.Comment {
return build.Comment{Token: "# " + c.String()}
}
Expand Down Expand Up @@ -487,6 +529,13 @@ type implicitImportSpec struct {
deps []string
}

type resolveFileSymbolNameSpec struct {
// pattern is the filename glob pattern to test
pattern string
// symbol is the symbol name to resolve
symbolName collections.Intent
}

func parseAnnotation(val string) annotation {
switch val {
case "imports":
Expand Down
52 changes: 52 additions & 0 deletions language/scala/scala_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,58 @@ func TestScalaConfigParseImplicitImportDirective(t *testing.T) {
}
}

func TestScalaConfigParseResolveFileSymbolName(t *testing.T) {
for name, tc := range map[string]struct {
directives []rule.Directive
filename string
names []string
want []bool
wantErr error
}{
"degenerate": {
want: []bool{},
},
"exact matches": {
directives: []rule.Directive{
{Key: resolveFileSymbolName, Value: "filename.scala +foo -bar"},
},
filename: "filename.scala",
names: []string{"foo", "bar"},
want: []bool{true, false},
},
"glob matches": {
directives: []rule.Directive{
{Key: resolveFileSymbolName, Value: "*.scala +foo* -bar*"},
},
filename: "filename.scala",
names: []string{"foo", "foox", "bar", "barx"},
want: []bool{true, true, false, false},
},
"no match": {
directives: []rule.Directive{
{Key: resolveFileSymbolName, Value: "*.scala +foo* -bar*"},
},
filename: "filename.scala",
names: []string{"baz"},
want: []bool{false},
},
} {
t.Run(name, func(t *testing.T) {
sc, err := newTestScalaConfig(t, mocks.NewUniverse(t), "", tc.directives...)
if testutil.ExpectError(t, tc.wantErr, err) {
return
}
got := []bool{}
for _, name := range tc.names {
got = append(got, sc.ShouldResolveFileSymbolName(tc.filename, name))
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("(-want +got):\n%s", diff)
}
})
}
}

func TestScalaConfigParseScalaAnnotate(t *testing.T) {
for name, tc := range map[string]struct {
directives []rule.Directive
Expand Down

0 comments on commit 995db88

Please sign in to comment.