From f6b2319a90a75260ad571b5cde89010abfe06fca Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 10 May 2024 13:41:30 +0100 Subject: [PATCH] gazelle: Populate plugins attributes with annotation processors (#276) --- java/gazelle/configure.go | 17 +++ java/gazelle/generate.go | 32 ++-- java/gazelle/generate_test.go | 4 +- java/gazelle/javaconfig/BUILD.bazel | 2 + java/gazelle/javaconfig/config.go | 49 +++++-- java/gazelle/private/java/package.go | 19 ++- java/gazelle/private/javaparser/javaparser.go | 30 +++- .../java/javaparser/v0/javaparser.proto | 6 + .../private/sorted_multiset/multiset.go | 24 ++- java/gazelle/private/types/types.go | 1 + java/gazelle/resolve.go | 106 ++++++++------ .../testdata/annotation_processor/BUILD.in | 1 + .../testdata/annotation_processor/WORKSPACE | 38 +++++ .../annotation_processor/maven_install.json | 138 ++++++++++++++++++ .../src/main/java/com/example/BUILD.out | 16 ++ .../src/main/java/com/example/Main.java | 21 +++ .../generators/ClasspathParser.java | 133 ++++++++++++++--- .../javaparser/generators/GrpcServer.java | 9 ++ .../generators/ClasspathParserTest.java | 62 +++++++- .../generators/AnnotationOnField.java | 26 ++++ .../generators/NestedClassAnnotations.java | 8 + 21 files changed, 644 insertions(+), 98 deletions(-) create mode 100644 java/gazelle/testdata/annotation_processor/BUILD.in create mode 100644 java/gazelle/testdata/annotation_processor/WORKSPACE create mode 100644 java/gazelle/testdata/annotation_processor/maven_install.json create mode 100644 java/gazelle/testdata/annotation_processor/src/main/java/com/example/BUILD.out create mode 100644 java/gazelle/testdata/annotation_processor/src/main/java/com/example/Main.java create mode 100644 java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/workspace/com/gazelle/java/javaparser/generators/AnnotationOnField.java create mode 100644 java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/workspace/com/gazelle/java/javaparser/generators/NestedClassAnnotations.java diff --git a/java/gazelle/configure.go b/java/gazelle/configure.go index 19006343..e2b405b3 100644 --- a/java/gazelle/configure.go +++ b/java/gazelle/configure.go @@ -8,6 +8,7 @@ import ( "github.com/bazel-contrib/rules_jvm/java/gazelle/javaconfig" "github.com/bazel-contrib/rules_jvm/java/gazelle/private/javaparser" "github.com/bazel-contrib/rules_jvm/java/gazelle/private/maven" + "github.com/bazel-contrib/rules_jvm/java/gazelle/private/types" "github.com/bazelbuild/bazel-gazelle/config" "github.com/bazelbuild/bazel-gazelle/rule" bzl "github.com/bazelbuild/buildtools/build" @@ -64,6 +65,7 @@ func (jc *Configurer) KnownDirectives() []string { javaconfig.JavaTestMode, javaconfig.JavaGenerateProto, javaconfig.JavaMavenRepositoryName, + javaconfig.JavaAnnotationProcessorPlugin, } } @@ -129,6 +131,21 @@ func (jc *Configurer) Configure(c *config.Config, rel string, f *rule.File) { jc.lang.logger.Fatal().Msgf("invalid value for directive %q: %s: possible values are true/false", javaconfig.JavaGenerateProto, d.Value) } + case javaconfig.JavaAnnotationProcessorPlugin: + // Format: # gazelle:java_annotation_processor_plugin com.example.AnnotationName com.example.AnnotationProcessorImpl + parts := strings.Split(d.Value, " ") + if len(parts) != 2 { + jc.lang.logger.Fatal().Msgf("invalid value for directive %q: %s: expected an annotation class-name followed by a processor class-name", javaconfig.JavaAnnotationProcessorPlugin, d.Value) + } + annotationClassName, err := types.ParseClassName(parts[0]) + if err != nil { + jc.lang.logger.Fatal().Msgf("invalid value for directive %q: %q: couldn't parse annotation processor annotation class-name: %v", javaconfig.JavaAnnotationProcessorPlugin, parts[0], err) + } + processorClassName, err := types.ParseClassName(parts[1]) + if err != nil { + jc.lang.logger.Fatal().Msgf("invalid value for directive %q: %q: couldn't parse annotation processor class-name: %v", javaconfig.JavaAnnotationProcessorPlugin, parts[1], err) + } + cfg.AddAnnotationProcessorPlugin(*annotationClassName, *processorClassName) } } } diff --git a/java/gazelle/generate.go b/java/gazelle/generate.go index 7819fc2b..babd90ee 100644 --- a/java/gazelle/generate.go +++ b/java/gazelle/generate.go @@ -127,6 +127,8 @@ func (l javaLang) GenerateRules(args language.GenerateArgs) language.GenerateRes // All java packages present in this bazel package. allPackageNames := sorted_set.NewSortedSetFn([]types.PackageName{}, types.PackageNameLess) + annotationProcessorClasses := sorted_set.NewSortedSetFn(nil, types.ClassNameLess) + if isModule { for mRel, mJavaPkg := range l.javaPackageCache { if !strings.HasPrefix(mRel, args.Rel) { @@ -152,6 +154,9 @@ func (l javaLang) GenerateRules(args language.GenerateArgs) language.GenerateRes accumulateJavaFile(cfg, testJavaFiles, testHelperJavaFiles, separateTestJavaFiles, file, mJavaPkg.PerClassMetadata, log) } } + for _, annotationClass := range mJavaPkg.AllAnnotations().SortedSlice() { + annotationProcessorClasses.AddAll(cfg.GetAnnotationProcessorPluginClasses(annotationClass)) + } } } else { allPackageNames.Add(javaPkg.Name) @@ -174,6 +179,9 @@ func (l javaLang) GenerateRules(args language.GenerateArgs) language.GenerateRes productionJavaFiles.Add(path) } } + for _, annotationClass := range javaPkg.AllAnnotations().SortedSlice() { + annotationProcessorClasses.AddAll(cfg.GetAnnotationProcessorPluginClasses(annotationClass)) + } } allPackageNamesSlice := allPackageNames.SortedSlice() @@ -192,7 +200,7 @@ func (l javaLang) GenerateRules(args language.GenerateArgs) language.GenerateRes } if productionJavaFiles.Len() > 0 { - l.generateJavaLibrary(args.File, args.Rel, filepath.Base(args.Rel), productionJavaFiles.SortedSlice(), allPackageNames, nonLocalProductionJavaImports, nonLocalJavaExports, false, javaLibraryKind, &res) + l.generateJavaLibrary(args.File, args.Rel, filepath.Base(args.Rel), productionJavaFiles.SortedSlice(), allPackageNames, nonLocalProductionJavaImports, nonLocalJavaExports, annotationProcessorClasses, false, javaLibraryKind, &res) } var testHelperJavaClasses *sorted_set.SortedSet[types.ClassName] @@ -228,7 +236,7 @@ func (l javaLang) GenerateRules(args language.GenerateArgs) language.GenerateRes testJavaImportsWithHelpers.Add(tf.pkg) srcs = append(srcs, tf.pathRelativeToBazelWorkspaceRoot) } - l.generateJavaLibrary(args.File, args.Rel, filepath.Base(args.Rel), srcs, packages, testJavaImports, nonLocalJavaExports, true, javaLibraryKind, &res) + l.generateJavaLibrary(args.File, args.Rel, filepath.Base(args.Rel), srcs, packages, testJavaImports, nonLocalJavaExports, annotationProcessorClasses, true, javaLibraryKind, &res) } } @@ -240,7 +248,7 @@ func (l javaLang) GenerateRules(args language.GenerateArgs) language.GenerateRes case "file": for _, tf := range testJavaFiles.SortedSlice() { separateJavaTestReasons := separateTestJavaFiles[tf] - l.generateJavaTest(args.File, args.Rel, cfg.MavenRepositoryName(), tf, isModule, testJavaImportsWithHelpers, nil, separateJavaTestReasons.wrapper, separateJavaTestReasons.attributes, &res) + l.generateJavaTest(args.File, args.Rel, cfg.MavenRepositoryName(), tf, isModule, testJavaImportsWithHelpers, annotationProcessorClasses, nil, separateJavaTestReasons.wrapper, separateJavaTestReasons.attributes, &res) } case "suite": @@ -268,6 +276,7 @@ func (l javaLang) GenerateRules(args language.GenerateArgs) language.GenerateRes packageNames, cfg.MavenRepositoryName(), testJavaImportsWithHelpers, + annotationProcessorClasses, cfg.GetCustomJavaTestFileSuffixes(), testHelperJavaFiles.Len() > 0, &res, @@ -284,7 +293,7 @@ func (l javaLang) GenerateRules(args language.GenerateArgs) language.GenerateRes testHelperDep = ptr(testHelperLibname(suiteName)) } separateJavaTestReasons := separateTestJavaFiles[src] - l.generateJavaTest(args.File, args.Rel, cfg.MavenRepositoryName(), src, isModule, testJavaImportsWithHelpers, testHelperDep, separateJavaTestReasons.wrapper, separateJavaTestReasons.attributes, &res) + l.generateJavaTest(args.File, args.Rel, cfg.MavenRepositoryName(), src, isModule, testJavaImportsWithHelpers, annotationProcessorClasses, testHelperDep, separateJavaTestReasons.wrapper, separateJavaTestReasons.attributes, &res) } } } @@ -415,7 +424,7 @@ func addFilteringOutOwnPackage(to *sorted_set.SortedSet[types.PackageName], from func accumulateJavaFile(cfg *javaconfig.Config, testJavaFiles, testHelperJavaFiles *sorted_set.SortedSet[javaFile], separateTestJavaFiles map[javaFile]separateJavaTestReasons, file javaFile, perClassMetadata map[string]java.PerClassMetadata, log zerolog.Logger) { if cfg.IsJavaTestFile(filepath.Base(file.pathRelativeToBazelWorkspaceRoot)) { - annotationClassNames := sorted_set.NewSortedSet[string](nil) + annotationClassNames := sorted_set.NewSortedSetFn[types.ClassName](nil, types.ClassNameLess) metadataForClass := perClassMetadata[file.ClassName().FullyQualifiedClassName()] annotationClassNames.AddAll(metadataForClass.AnnotationClassNames) for _, key := range metadataForClass.MethodAnnotationClassNames.Keys() { @@ -425,7 +434,7 @@ func accumulateJavaFile(cfg *javaconfig.Config, testJavaFiles, testHelperJavaFil perFileAttrs := make(map[string]bzl.Expr) wrapper := "" for _, annotationClassName := range annotationClassNames.SortedSlice() { - if attrs, ok := cfg.AttributesForAnnotation(annotationClassName); ok { + if attrs, ok := cfg.AttributesForAnnotation(annotationClassName.FullyQualifiedClassName()); ok { for k, v := range attrs { if old, ok := perFileAttrs[k]; ok { log.Error().Str("file", file.pathRelativeToBazelWorkspaceRoot).Msgf("Saw conflicting attr overrides from annotations for attribute %v: %v and %v. Picking one at random.", k, old, v) @@ -433,7 +442,7 @@ func accumulateJavaFile(cfg *javaconfig.Config, testJavaFiles, testHelperJavaFil perFileAttrs[k] = v } } - newWrapper, ok := cfg.WrapperForAnnotation(annotationClassName) + newWrapper, ok := cfg.WrapperForAnnotation(annotationClassName.FullyQualifiedClassName()) if ok { if wrapper != "" { log.Error().Str("file", file.pathRelativeToBazelWorkspaceRoot).Msgf("Saw conflicting wrappers from annotations: %v and %v. Picking one at random.", wrapper, newWrapper) @@ -453,7 +462,7 @@ func accumulateJavaFile(cfg *javaconfig.Config, testJavaFiles, testHelperJavaFil } } -func (l javaLang) generateJavaLibrary(file *rule.File, pathToPackageRelativeToBazelWorkspace string, name string, srcsRelativeToBazelWorkspace []string, packages, imports *sorted_set.SortedSet[types.PackageName], exports *sorted_set.SortedSet[types.PackageName], testonly bool, javaLibraryRuleKind string, res *language.GenerateResult) { +func (l javaLang) generateJavaLibrary(file *rule.File, pathToPackageRelativeToBazelWorkspace string, name string, srcsRelativeToBazelWorkspace []string, packages, imports *sorted_set.SortedSet[types.PackageName], exports *sorted_set.SortedSet[types.PackageName], annotationProcessorClasses *sorted_set.SortedSet[types.ClassName], testonly bool, javaLibraryRuleKind string, res *language.GenerateResult) { const ruleKind = "java_library" r := rule.NewRule(ruleKind, name) @@ -487,6 +496,7 @@ func (l javaLang) generateJavaLibrary(file *rule.File, pathToPackageRelativeToBa PackageNames: packages, ImportedPackageNames: imports, ExportedPackageNames: exports, + AnnotationProcessors: annotationProcessorClasses, } res.Imports = append(res.Imports, resolveInput) } @@ -511,7 +521,7 @@ func (l javaLang) generateJavaBinary(file *rule.File, m types.ClassName, libName }) } -func (l javaLang) generateJavaTest(file *rule.File, pathToPackageRelativeToBazelWorkspace string, mavenRepositoryName string, f javaFile, includePackageInName bool, imports *sorted_set.SortedSet[types.PackageName], depOnTestHelpers *string, wrapper string, extraAttributes map[string]bzl.Expr, res *language.GenerateResult) { +func (l javaLang) generateJavaTest(file *rule.File, pathToPackageRelativeToBazelWorkspace string, mavenRepositoryName string, f javaFile, includePackageInName bool, imports *sorted_set.SortedSet[types.PackageName], annotationProcessorClasses *sorted_set.SortedSet[types.ClassName], depOnTestHelpers *string, wrapper string, extraAttributes map[string]bzl.Expr, res *language.GenerateResult) { className := f.ClassName() fullyQualifiedTestClass := className.FullyQualifiedClassName() var testName string @@ -571,6 +581,7 @@ func (l javaLang) generateJavaTest(file *rule.File, pathToPackageRelativeToBazel resolveInput := types.ResolveInput{ PackageNames: sorted_set.NewSortedSetFn([]types.PackageName{f.pkg}, types.PackageNameLess), ImportedPackageNames: testImports, + AnnotationProcessors: annotationProcessorClasses, } res.Imports = append(res.Imports, resolveInput) } @@ -598,7 +609,7 @@ var junit5RuntimeDeps = []string{ "org.junit.platform:junit-platform-reporting", } -func (l javaLang) generateJavaTestSuite(file *rule.File, name string, srcs []string, packageNames *sorted_set.SortedSet[types.PackageName], mavenRepositoryName string, imports *sorted_set.SortedSet[types.PackageName], customTestSuffixes *[]string, hasHelpers bool, res *language.GenerateResult) { +func (l javaLang) generateJavaTestSuite(file *rule.File, name string, srcs []string, packageNames *sorted_set.SortedSet[types.PackageName], mavenRepositoryName string, imports *sorted_set.SortedSet[types.PackageName], annotationProcessorClasses *sorted_set.SortedSet[types.ClassName], customTestSuffixes *[]string, hasHelpers bool, res *language.GenerateResult) { const ruleKind = "java_test_suite" r := rule.NewRule(ruleKind, name) r.SetAttr("srcs", srcs) @@ -636,6 +647,7 @@ func (l javaLang) generateJavaTestSuite(file *rule.File, name string, srcs []str resolveInput := types.ResolveInput{ PackageNames: packageNames, ImportedPackageNames: suiteImports, + AnnotationProcessors: annotationProcessorClasses, } res.Imports = append(res.Imports, resolveInput) } diff --git a/java/gazelle/generate_test.go b/java/gazelle/generate_test.go index 1dcc28a0..418f280c 100644 --- a/java/gazelle/generate_test.go +++ b/java/gazelle/generate_test.go @@ -159,7 +159,7 @@ func TestSingleJavaTestFile(t *testing.T) { var res language.GenerateResult l := newTestJavaLang(t) - l.generateJavaTest(nil, "", "maven", f, tc.includePackageInName, stringsToPackageNames(tc.importedPackages), nil, tc.wrapper, nil, &res) + l.generateJavaTest(nil, "", "maven", f, tc.includePackageInName, stringsToPackageNames(tc.importedPackages), nil, nil, tc.wrapper, nil, &res) require.Len(t, res.Gen, 1, "want 1 generated rule") @@ -252,7 +252,7 @@ func TestSuite(t *testing.T) { var res language.GenerateResult l := newTestJavaLang(t) - l.generateJavaTestSuite(nil, "blah", []string{src}, stringsToPackageNames([]string{pkg}), "maven", stringsToPackageNames(tc.importedPackages), nil, false, &res) + l.generateJavaTestSuite(nil, "blah", []string{src}, stringsToPackageNames([]string{pkg}), "maven", stringsToPackageNames(tc.importedPackages), nil, nil, false, &res) require.Len(t, res.Gen, 1, "want 1 generated rule") diff --git a/java/gazelle/javaconfig/BUILD.bazel b/java/gazelle/javaconfig/BUILD.bazel index d8b7accc..bb2b756f 100644 --- a/java/gazelle/javaconfig/BUILD.bazel +++ b/java/gazelle/javaconfig/BUILD.bazel @@ -9,6 +9,8 @@ go_library( importpath = "github.com/bazel-contrib/rules_jvm/java/gazelle/javaconfig", visibility = ["//visibility:public"], deps = [ + "//java/gazelle/private/sorted_set", + "//java/gazelle/private/types", "@com_github_bazelbuild_buildtools//build", ], ) diff --git a/java/gazelle/javaconfig/config.go b/java/gazelle/javaconfig/config.go index 66a9a852..a3cc492c 100644 --- a/java/gazelle/javaconfig/config.go +++ b/java/gazelle/javaconfig/config.go @@ -6,6 +6,8 @@ import ( "path/filepath" "strings" + "github.com/bazel-contrib/rules_jvm/java/gazelle/private/sorted_set" + "github.com/bazel-contrib/rules_jvm/java/gazelle/private/types" bzl "github.com/bazelbuild/buildtools/build" ) @@ -47,6 +49,10 @@ const ( // JavaMavenRepositoryName tells the code generator what the repository name that contains all maven dependencies is. // Defaults to "maven" JavaMavenRepositoryName = "java_maven_repository_name" + + // JavaAnnotationProcessorPlugin tells the code generator about specific java_plugin targets needed to process + // specific annotations. + JavaAnnotationProcessorPlugin = "java_annotation_processor_plugin" ) // Configs is an extension of map[string]*Config. It provides finding methods @@ -60,6 +66,10 @@ func (c *Config) NewChild() *Config { for key, value := range c.excludedArtifacts { clonedExcludedArtifacts[key] = value } + annotationProcessorFullQualifiedClassToPluginClass := make(map[string]*sorted_set.SortedSet[types.ClassName]) + for key, value := range c.annotationProcessorFullQualifiedClassToPluginClass { + annotationProcessorFullQualifiedClassToPluginClass[key] = value.Clone() + } return &Config{ parent: c, extensionEnabled: c.extensionEnabled, @@ -74,6 +84,7 @@ func (c *Config) NewChild() *Config { annotationToWrapper: c.annotationToWrapper, excludedArtifacts: clonedExcludedArtifacts, mavenRepositoryName: c.mavenRepositoryName, + annotationProcessorFullQualifiedClassToPluginClass: annotationProcessorFullQualifiedClassToPluginClass, } } @@ -91,18 +102,19 @@ func (c *Configs) ParentForPackage(pkg string) *Config { type Config struct { parent *Config - extensionEnabled bool - isModuleRoot bool - generateProto bool - mavenInstallFile string - moduleGranularity string - repoRoot string - testMode string - customTestFileSuffixes *[]string - excludedArtifacts map[string]struct{} - annotationToAttribute map[string]map[string]bzl.Expr - annotationToWrapper map[string]string - mavenRepositoryName string + extensionEnabled bool + isModuleRoot bool + generateProto bool + mavenInstallFile string + moduleGranularity string + repoRoot string + testMode string + customTestFileSuffixes *[]string + excludedArtifacts map[string]struct{} + annotationToAttribute map[string]map[string]bzl.Expr + annotationToWrapper map[string]string + mavenRepositoryName string + annotationProcessorFullQualifiedClassToPluginClass map[string]*sorted_set.SortedSet[types.ClassName] } type LoadInfo struct { @@ -125,6 +137,7 @@ func New(repoRoot string) *Config { annotationToAttribute: make(map[string]map[string]bzl.Expr), annotationToWrapper: make(map[string]string), mavenRepositoryName: "maven", + annotationProcessorFullQualifiedClassToPluginClass: make(map[string]*sorted_set.SortedSet[types.ClassName]), } } @@ -269,6 +282,18 @@ func (c *Config) IsTestRule(ruleKind string) bool { return false } +func (c *Config) GetAnnotationProcessorPluginClasses(annotationClass types.ClassName) *sorted_set.SortedSet[types.ClassName] { + return c.annotationProcessorFullQualifiedClassToPluginClass[annotationClass.FullyQualifiedClassName()] +} + +func (c *Config) AddAnnotationProcessorPlugin(annotationClass types.ClassName, processorClass types.ClassName) { + fullyQualifiedAnnotationClass := annotationClass.FullyQualifiedClassName() + if _, ok := c.annotationProcessorFullQualifiedClassToPluginClass[fullyQualifiedAnnotationClass]; !ok { + c.annotationProcessorFullQualifiedClassToPluginClass[fullyQualifiedAnnotationClass] = sorted_set.NewSortedSetFn[types.ClassName](nil, types.ClassNameLess) + } + c.annotationProcessorFullQualifiedClassToPluginClass[fullyQualifiedAnnotationClass].Add(processorClass) +} + func equalStringSlices(l, r []string) bool { if len(l) != len(r) { return false diff --git a/java/gazelle/private/java/package.go b/java/gazelle/private/java/package.go index e09072a8..ba49726e 100644 --- a/java/gazelle/private/java/package.go +++ b/java/gazelle/private/java/package.go @@ -21,7 +21,22 @@ type Package struct { PerClassMetadata map[string]PerClassMetadata } +func (p *Package) AllAnnotations() *sorted_set.SortedSet[types.ClassName] { + annotations := sorted_set.NewSortedSetFn(nil, types.ClassNameLess) + for _, pcm := range p.PerClassMetadata { + annotations.AddAll(pcm.AnnotationClassNames) + for _, method := range pcm.MethodAnnotationClassNames.Keys() { + annotations.AddAll(pcm.MethodAnnotationClassNames.Values(method)) + } + for _, field := range pcm.FieldAnnotationClassNames.Keys() { + annotations.AddAll(pcm.FieldAnnotationClassNames.Values(field)) + } + } + return annotations +} + type PerClassMetadata struct { - AnnotationClassNames *sorted_set.SortedSet[string] - MethodAnnotationClassNames *sorted_multiset.SortedMultiSet[string, string] + AnnotationClassNames *sorted_set.SortedSet[types.ClassName] + MethodAnnotationClassNames *sorted_multiset.SortedMultiSet[string, types.ClassName] + FieldAnnotationClassNames *sorted_multiset.SortedMultiSet[string, types.ClassName] } diff --git a/java/gazelle/private/javaparser/javaparser.go b/java/gazelle/private/javaparser/javaparser.go index a93ad06f..2e07d3e6 100644 --- a/java/gazelle/private/javaparser/javaparser.go +++ b/java/gazelle/private/javaparser/javaparser.go @@ -65,15 +65,39 @@ func (r Runner) ParsePackage(ctx context.Context, in *ParsePackageRequest) (*jav perClassMetadata := make(map[string]java.PerClassMetadata, len(resp.GetPerClassMetadata())) for k, v := range resp.GetPerClassMetadata() { - methodAnnotationClassNames := sorted_multiset.NewSortedMultiSet[string, string]() + annotationClassNames := sorted_set.NewSortedSetFn(nil, types.ClassNameLess) + for _, annotation := range v.GetAnnotationClassNames() { + annotationClassName, err := types.ParseClassName(annotation) + if err != nil { + return nil, fmt.Errorf("failed to parse annotation name %q as a class name in %s: %w", k, annotation, err) + } + annotationClassNames.Add(*annotationClassName) + } + + methodAnnotationClassNames := sorted_multiset.NewSortedMultiSetFn[string, types.ClassName](types.ClassNameLess) for method, perMethod := range v.GetPerMethodMetadata() { for _, annotation := range perMethod.AnnotationClassNames { - methodAnnotationClassNames.Add(method, annotation) + annotationClassName, err := types.ParseClassName(annotation) + if err != nil { + return nil, fmt.Errorf("failed to parse annotation name %q as a class name in %s: %w", k, annotation, err) + } + methodAnnotationClassNames.Add(method, *annotationClassName) + } + } + fieldAnnotationClassNames := sorted_multiset.NewSortedMultiSetFn[string, types.ClassName](types.ClassNameLess) + for field, perField := range v.GetPerFieldMetadata() { + for _, annotation := range perField.AnnotationClassNames { + annotationClassName, err := types.ParseClassName(annotation) + if err != nil { + return nil, fmt.Errorf("failed to parse annotation name %q as a class name in %s: %w", k, annotation, err) + } + fieldAnnotationClassNames.Add(field, *annotationClassName) } } metadata := java.PerClassMetadata{ - AnnotationClassNames: sorted_set.NewSortedSet(v.GetAnnotationClassNames()), + AnnotationClassNames: annotationClassNames, MethodAnnotationClassNames: methodAnnotationClassNames, + FieldAnnotationClassNames: fieldAnnotationClassNames, } perClassMetadata[k] = metadata } diff --git a/java/gazelle/private/javaparser/proto/gazelle/java/javaparser/v0/javaparser.proto b/java/gazelle/private/javaparser/proto/gazelle/java/javaparser/v0/javaparser.proto index 7d8162f1..876683af 100644 --- a/java/gazelle/private/javaparser/proto/gazelle/java/javaparser/v0/javaparser.proto +++ b/java/gazelle/private/javaparser/proto/gazelle/java/javaparser/v0/javaparser.proto @@ -54,12 +54,18 @@ message PerClassMetadata { repeated string annotation_class_names = 1; // Not all methods will be present here, only those with something interesting to report. map per_method_metadata = 2; + // Not all fields will be present here, only those with something interesting to report. + map per_field_metadata = 3; } message PerMethodMetadata { repeated string annotation_class_names = 1; } +message PerFieldMetadata { + repeated string annotation_class_names = 1; +} + service Lifecycle { rpc Shutdown(ShutdownRequest) returns (ShutdownResponse) {} } diff --git a/java/gazelle/private/sorted_multiset/multiset.go b/java/gazelle/private/sorted_multiset/multiset.go index 314b26bf..2739b3ca 100644 --- a/java/gazelle/private/sorted_multiset/multiset.go +++ b/java/gazelle/private/sorted_multiset/multiset.go @@ -5,22 +5,36 @@ import ( "github.com/google/btree" ) -type SortedMultiSet[K btree.Ordered, V btree.Ordered] struct { - ms map[K]*sorted_set.SortedSet[V] - keys *sorted_set.SortedSet[K] +type SortedMultiSet[K btree.Ordered, V any] struct { + ms map[K]*sorted_set.SortedSet[V] + keys *sorted_set.SortedSet[K] + valueSetCreator func() *sorted_set.SortedSet[V] } func NewSortedMultiSet[K btree.Ordered, V btree.Ordered]() *SortedMultiSet[K, V] { return &SortedMultiSet[K, V]{ ms: make(map[K]*sorted_set.SortedSet[V]), keys: sorted_set.NewSortedSet([]K{}), + valueSetCreator: func() *sorted_set.SortedSet[V] { + return sorted_set.NewSortedSet([]V{}) + }, + } +} + +func NewSortedMultiSetFn[K btree.Ordered, V any](less btree.LessFunc[V]) *SortedMultiSet[K, V] { + return &SortedMultiSet[K, V]{ + ms: make(map[K]*sorted_set.SortedSet[V]), + keys: sorted_set.NewSortedSet([]K{}), + valueSetCreator: func() *sorted_set.SortedSet[V] { + return sorted_set.NewSortedSetFn([]V{}, less) + }, } } func (s *SortedMultiSet[K, V]) Add(key K, value V) { if !s.keys.Contains(key) { s.keys.Add(key) - s.ms[key] = sorted_set.NewSortedSet([]V{}) + s.ms[key] = s.valueSetCreator() } s.ms[key].Add(value) } @@ -35,7 +49,7 @@ func (s *SortedMultiSet[K, V]) Keys() []K { func (s *SortedMultiSet[K, V]) Values(key K) *sorted_set.SortedSet[V] { if s == nil { - return sorted_set.NewSortedSet[V](nil) + return nil } return s.ms[key] diff --git a/java/gazelle/private/types/types.go b/java/gazelle/private/types/types.go index ea2d05bf..6a010eca 100644 --- a/java/gazelle/private/types/types.go +++ b/java/gazelle/private/types/types.go @@ -97,6 +97,7 @@ type ResolveInput struct { PackageNames *sorted_set.SortedSet[PackageName] ImportedPackageNames *sorted_set.SortedSet[PackageName] ExportedPackageNames *sorted_set.SortedSet[PackageName] + AnnotationProcessors *sorted_set.SortedSet[ClassName] } type ResolvableJavaPackage struct { diff --git a/java/gazelle/resolve.go b/java/gazelle/resolve.go index 80427a26..7d0e6c82 100644 --- a/java/gazelle/resolve.go +++ b/java/gazelle/resolve.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "sort" + "strings" "github.com/bazel-contrib/rules_jvm/java/gazelle/javaconfig" "github.com/bazel-contrib/rules_jvm/java/gazelle/private/java" @@ -42,11 +43,11 @@ func NewResolver(lang *javaLang) *Resolver { } } -func (Resolver) Name() string { +func (*Resolver) Name() string { return languageName } -func (jr Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec { +func (jr *Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec { log := jr.lang.logger.With().Str("step", "Imports").Str("rel", f.Pkg).Str("rule", r.Name()).Logger() if !isJavaLibrary(r.Kind()) && r.Kind() != "java_test_suite" { @@ -64,7 +65,7 @@ func (jr Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resol return out } -func (Resolver) Embeds(r *rule.Rule, from label.Label) []label.Label { +func (*Resolver) Embeds(r *rule.Rule, from label.Label) []label.Label { embedStrings := r.AttrStrings("embed") if isJavaProtoLibrary(r.Kind()) { embedStrings = append(embedStrings, r.AttrString("proto")) @@ -81,7 +82,7 @@ func (Resolver) Embeds(r *rule.Rule, from label.Label) []label.Label { return embedLabels } -func (jr Resolver) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, r *rule.Rule, imports interface{}, from label.Label) { +func (jr *Resolver) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.RemoteCache, r *rule.Rule, imports interface{}, from label.Label) { resolveInput := imports.(types.ResolveInput) packageConfig := c.Exts[languageName].(javaconfig.Configs)[from.Pkg] @@ -97,25 +98,15 @@ func (jr Resolver) Resolve(c *config.Config, ix *resolve.RuleIndex, rc *repo.Rem jr.populateAttr(c, packageConfig, r, "deps", resolveInput.ImportedPackageNames, ix, isTestRule, from, resolveInput.PackageNames) jr.populateAttr(c, packageConfig, r, "exports", resolveInput.ExportedPackageNames, ix, isTestRule, from, resolveInput.PackageNames) + + jr.populatePluginsAttr(c, ix, resolveInput, packageConfig, from, isTestRule, r) } -func (jr Resolver) populateAttr(c *config.Config, pc *javaconfig.Config, r *rule.Rule, attrName string, requiredPackageNames *sorted_set.SortedSet[types.PackageName], ix *resolve.RuleIndex, isTestRule bool, from label.Label, ownPackageNames *sorted_set.SortedSet[types.PackageName]) { +func (jr *Resolver) populateAttr(c *config.Config, pc *javaconfig.Config, r *rule.Rule, attrName string, requiredPackageNames *sorted_set.SortedSet[types.PackageName], ix *resolve.RuleIndex, isTestRule bool, from label.Label, ownPackageNames *sorted_set.SortedSet[types.PackageName]) { labels := sorted_set.NewSortedSetFn[label.Label]([]label.Label{}, labelLess) - for _, implicitDep := range r.AttrStrings(attrName) { - l, err := label.Parse(implicitDep) - if err != nil { - panic(fmt.Sprintf("error converting implicit %s %q to label: %v", attrName, implicitDep, err)) - } - labels.Add(l) - } - for _, imp := range requiredPackageNames.SortedSlice() { - dep, err := jr.resolveSinglePackage(c, pc, imp, ix, from, isTestRule, ownPackageNames) - if err != nil { - jr.lang.logger.Error().Str("import", dep.String()).Err(err).Msg("error converting import") - panic(fmt.Sprintf("error converting import: %s", err)) - } + dep := jr.resolveSinglePackage(c, pc, imp, ix, from, isTestRule, ownPackageNames) if dep == label.NoLabel { continue } @@ -123,18 +114,26 @@ func (jr Resolver) populateAttr(c *config.Config, pc *javaconfig.Config, r *rule labels.Add(simplifyLabel(c.RepoName, dep, from)) } - var exprs []build.Expr - if labels.Len() > 0 { - for _, l := range labels.SortedSlice() { - if l.Relative && l.Name == from.Name { - continue - } - exprs = append(exprs, &build.StringExpr{Value: l.String()}) + setLabelAttrIncludingExistingValues(r, attrName, labels) +} + +func (jr *Resolver) populatePluginsAttr(c *config.Config, ix *resolve.RuleIndex, resolveInput types.ResolveInput, packageConfig *javaconfig.Config, from label.Label, isTestRule bool, r *rule.Rule) { + pluginLabels := sorted_set.NewSortedSetFn[label.Label]([]label.Label{}, labelLess) + for _, annotationProcessor := range resolveInput.AnnotationProcessors.SortedSlice() { + dep := jr.resolveSinglePackage(c, packageConfig, annotationProcessor.PackageName(), ix, from, isTestRule, resolveInput.PackageNames) + if dep == label.NoLabel { + continue } + + // Use the naming scheme for plugins as per https://github.com/bazelbuild/rules_jvm_external/pull/1102 + // In the case of overrides (i.e. # gazelle:resolve targets) we require that they follow the same name-mangling scheme for the java_plugin target as rules_jvm_external uses. + // Ideally this would be a call to `java_plugin_artifact(dep.String(), annotationProcessor.FullyQualifiedClassName())` but we don't have function calls working in attributes. + dep.Name += "__java_plugin__" + strings.NewReplacer(".", "_", "$", "_").Replace(annotationProcessor.FullyQualifiedClassName()) + + pluginLabels.Add(simplifyLabel(c.RepoName, dep, from)) } - if len(exprs) > 0 { - r.SetAttr(attrName, exprs) - } + + setLabelAttrIncludingExistingValues(r, "plugins", pluginLabels) } func labelLess(l, r label.Label) bool { @@ -163,16 +162,40 @@ func simplifyLabel(repoName string, l label.Label, from label.Label) label.Label return l } -func (jr *Resolver) resolveSinglePackage(c *config.Config, pc *javaconfig.Config, imp types.PackageName, ix *resolve.RuleIndex, from label.Label, isTestRule bool, ownPackageNames *sorted_set.SortedSet[types.PackageName]) (out label.Label, err error) { +// Note: This function may modify labels. +func setLabelAttrIncludingExistingValues(r *rule.Rule, attrName string, labels *sorted_set.SortedSet[label.Label]) { + for _, implicitDep := range r.AttrStrings(attrName) { + l, err := label.Parse(implicitDep) + if err != nil { + panic(fmt.Sprintf("error converting implicit %s %q to label: %v", attrName, implicitDep, err)) + } + labels.Add(l) + } + + var exprs []build.Expr + if labels.Len() > 0 { + for _, l := range labels.SortedSlice() { + if l.Relative && l.Name == r.Name() { + continue + } + exprs = append(exprs, &build.StringExpr{Value: l.String()}) + } + } + if len(exprs) > 0 { + r.SetAttr(attrName, exprs) + } +} + +func (jr *Resolver) resolveSinglePackage(c *config.Config, pc *javaconfig.Config, imp types.PackageName, ix *resolve.RuleIndex, from label.Label, isTestRule bool, ownPackageNames *sorted_set.SortedSet[types.PackageName]) (out label.Label) { cacheKey := types.NewResolvableJavaPackage(imp, false, false) importSpec := resolve.ImportSpec{Lang: languageName, Imp: cacheKey.String()} if ol, found := resolve.FindRuleWithOverride(c, importSpec, languageName); found { - return ol, nil + return ol } matches := ix.FindRulesByImportWithConfig(c, importSpec, languageName) if len(matches) == 1 { - return matches[0].Label, nil + return matches[0].Label } if len(matches) > 1 { @@ -189,19 +212,19 @@ func (jr *Resolver) resolveSinglePackage(c *config.Config, pc *javaconfig.Config } if v, ok := jr.internalCache.Get(cacheKey); ok { - return simplifyLabel(c.RepoName, v.(label.Label), from), nil + return simplifyLabel(c.RepoName, v.(label.Label), from) } jr.lang.logger.Debug().Str("parsedImport", imp.Name).Stringer("from", from).Msg("not found yet") defer func() { - if err == nil && out != label.NoLabel { + if out != label.NoLabel { jr.internalCache.Add(cacheKey, out) } }() if java.IsStdlib(imp) { - return label.NoLabel, nil + return label.NoLabel } // As per https://github.com/bazelbuild/bazel/blob/347407a88fd480fc5e0fbd42cc8196e4356a690b/tools/java/runfiles/Runfiles.java#L41 @@ -209,9 +232,10 @@ func (jr *Resolver) resolveSinglePackage(c *config.Config, pc *javaconfig.Config runfilesLabel := "@bazel_tools//tools/java/runfiles" l, err := label.Parse(runfilesLabel) if err != nil { - return label.NoLabel, fmt.Errorf("failed to parse known-good runfiles label %s: %w", runfilesLabel, err) + jr.lang.logger.Fatal().Str("label", runfilesLabel).Err(err).Msg("failed to parse known-good runfiles label") + return label.NoLabel } - return l, nil + return l } if l, err := jr.lang.mavenResolver.Resolve(imp, pc.ExcludedArtifacts(), pc.MavenRepositoryName()); err != nil { @@ -230,7 +254,7 @@ func (jr *Resolver) resolveSinglePackage(c *config.Config, pc *javaconfig.Config jr.lang.logger.Fatal().Err(err).Msg("maven resolver error") } } else { - return l, nil + return l } if isTestRule { @@ -240,7 +264,7 @@ func (jr *Resolver) resolveSinglePackage(c *config.Config, pc *javaconfig.Config testonlyMatches := ix.FindRulesByImportWithConfig(c, testonlyImportSpec, languageName) if len(testonlyMatches) == 1 { cacheKey = testonlyCacheKey - return simplifyLabel(c.RepoName, testonlyMatches[0].Label, from), nil + return simplifyLabel(c.RepoName, testonlyMatches[0].Label, from) } // If there's exactly one testonly match, use it @@ -252,14 +276,14 @@ func (jr *Resolver) resolveSinglePackage(c *config.Config, pc *javaconfig.Config l := testsuiteMatches[0].Label if l != from { l.Name += "-test-lib" - return simplifyLabel(c.RepoName, l, from), nil + return simplifyLabel(c.RepoName, l, from) } } } if isTestRule && ownPackageNames.Contains(imp) { // Tests may have unique packages which don't exist outside of those tests - don't treat this as an error. - return label.NoLabel, nil + return label.NoLabel } jr.lang.logger.Warn(). @@ -268,7 +292,7 @@ func (jr *Resolver) resolveSinglePackage(c *config.Config, pc *javaconfig.Config Msg("Unable to find package for import in any dependency") jr.lang.hasHadErrors = true - return label.NoLabel, nil + return label.NoLabel } func isJavaLibrary(kind string) bool { diff --git a/java/gazelle/testdata/annotation_processor/BUILD.in b/java/gazelle/testdata/annotation_processor/BUILD.in new file mode 100644 index 00000000..0dbbc2cf --- /dev/null +++ b/java/gazelle/testdata/annotation_processor/BUILD.in @@ -0,0 +1 @@ +# gazelle:java_annotation_processor_plugin com.google.auto.value.AutoValue com.google.auto.value.processor.AutoValueProcessor diff --git a/java/gazelle/testdata/annotation_processor/WORKSPACE b/java/gazelle/testdata/annotation_processor/WORKSPACE new file mode 100644 index 00000000..a5cd8583 --- /dev/null +++ b/java/gazelle/testdata/annotation_processor/WORKSPACE @@ -0,0 +1,38 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +RULES_JVM_EXTERNAL_TAG = "6.1" + +RULES_JVM_EXTERNAL_SHA = "08ea921df02ffe9924123b0686dc04fd0ff875710bfadb7ad42badb931b0fd50" + +http_archive( + name = "rules_jvm_external", + sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, + url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/%s/rules_jvm_external-%s.tar.gz" % (RULES_JVM_EXTERNAL_TAG, RULES_JVM_EXTERNAL_TAG), +) + +load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") + +rules_jvm_external_deps() + +load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") + +rules_jvm_external_setup() + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + name = "maven", + artifacts = [ + "com.google.auto.value:auto-value:1.10.4", + "com.google.auto.value:auto-value-annotations:1.10.4", + ], + maven_install_json = "@//:maven_install.json", + repositories = [ + "https://repo1.maven.org/maven2", + ], +) + +load("@maven//:defs.bzl", "pinned_maven_install") + +pinned_maven_install() diff --git a/java/gazelle/testdata/annotation_processor/maven_install.json b/java/gazelle/testdata/annotation_processor/maven_install.json new file mode 100644 index 00000000..1ee5d42d --- /dev/null +++ b/java/gazelle/testdata/annotation_processor/maven_install.json @@ -0,0 +1,138 @@ +{ + "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", + "__INPUT_ARTIFACTS_HASH": -702707572, + "__RESOLVED_ARTIFACTS_HASH": 1474543296, + "artifacts": { + "com.google.auto.value:auto-value": { + "shasums": { + "jar": "f3c438d1f82904bbcb452084d488b660f3c7488e9274c3a58f049e121632d434" + }, + "version": "1.10.4" + }, + "com.google.auto.value:auto-value-annotations": { + "shasums": { + "jar": "e1c45e6beadaef9797cb0d9afd5a45621ad061cd8632012f85582853a3887825" + }, + "version": "1.10.4" + } + }, + "dependencies": {}, + "packages": { + "com.google.auto.value:auto-value": [ + "autovalue.shaded.com.google.auto.common", + "autovalue.shaded.com.google.auto.service", + "autovalue.shaded.com.google.common.annotations", + "autovalue.shaded.com.google.common.base", + "autovalue.shaded.com.google.common.cache", + "autovalue.shaded.com.google.common.collect", + "autovalue.shaded.com.google.common.escape", + "autovalue.shaded.com.google.common.eventbus", + "autovalue.shaded.com.google.common.graph", + "autovalue.shaded.com.google.common.hash", + "autovalue.shaded.com.google.common.html", + "autovalue.shaded.com.google.common.io", + "autovalue.shaded.com.google.common.math", + "autovalue.shaded.com.google.common.net", + "autovalue.shaded.com.google.common.primitives", + "autovalue.shaded.com.google.common.reflect", + "autovalue.shaded.com.google.common.util.concurrent", + "autovalue.shaded.com.google.common.xml", + "autovalue.shaded.com.google.errorprone.annotations", + "autovalue.shaded.com.google.errorprone.annotations.concurrent", + "autovalue.shaded.com.google.escapevelocity", + "autovalue.shaded.com.google.j2objc.annotations", + "autovalue.shaded.com.squareup.javapoet", + "autovalue.shaded.kotlin", + "autovalue.shaded.kotlin.annotation", + "autovalue.shaded.kotlin.collections", + "autovalue.shaded.kotlin.collections.builders", + "autovalue.shaded.kotlin.collections.unsigned", + "autovalue.shaded.kotlin.comparisons", + "autovalue.shaded.kotlin.contracts", + "autovalue.shaded.kotlin.coroutines", + "autovalue.shaded.kotlin.coroutines.intrinsics", + "autovalue.shaded.kotlin.coroutines.jvm.internal", + "autovalue.shaded.kotlin.enums", + "autovalue.shaded.kotlin.experimental", + "autovalue.shaded.kotlin.internal", + "autovalue.shaded.kotlin.internal.jdk7", + "autovalue.shaded.kotlin.internal.jdk8", + "autovalue.shaded.kotlin.jvm", + "autovalue.shaded.kotlin.jvm.functions", + "autovalue.shaded.kotlin.jvm.internal", + "autovalue.shaded.kotlin.jvm.internal.markers", + "autovalue.shaded.kotlin.random", + "autovalue.shaded.kotlin.random.jdk8", + "autovalue.shaded.kotlin.ranges", + "autovalue.shaded.kotlin.reflect", + "autovalue.shaded.kotlin.sequences", + "autovalue.shaded.kotlin.text", + "autovalue.shaded.kotlinx.metadata", + "autovalue.shaded.kotlinx.metadata.internal", + "autovalue.shaded.kotlinx.metadata.internal.common", + "autovalue.shaded.kotlinx.metadata.internal.extensions", + "autovalue.shaded.kotlinx.metadata.internal.metadata", + "autovalue.shaded.kotlinx.metadata.internal.metadata.deserialization", + "autovalue.shaded.kotlinx.metadata.internal.metadata.jvm", + "autovalue.shaded.kotlinx.metadata.internal.metadata.jvm.deserialization", + "autovalue.shaded.kotlinx.metadata.internal.metadata.jvm.serialization", + "autovalue.shaded.kotlinx.metadata.internal.metadata.serialization", + "autovalue.shaded.kotlinx.metadata.internal.protobuf", + "autovalue.shaded.kotlinx.metadata.jvm", + "autovalue.shaded.kotlinx.metadata.jvm.internal", + "autovalue.shaded.net.ltgt.gradle.incap", + "autovalue.shaded.org.checkerframework.checker.nullness.qual", + "autovalue.shaded.org.checkerframework.framework.qual", + "autovalue.shaded.org.jetbrains.annotations", + "autovalue.shaded.org.objectweb.asm", + "com.google.auto.value.extension", + "com.google.auto.value.extension.memoized.processor", + "com.google.auto.value.extension.serializable.processor", + "com.google.auto.value.extension.serializable.serializer", + "com.google.auto.value.extension.serializable.serializer.impl", + "com.google.auto.value.extension.serializable.serializer.interfaces", + "com.google.auto.value.extension.serializable.serializer.runtime", + "com.google.auto.value.extension.toprettystring.processor", + "com.google.auto.value.processor" + ], + "com.google.auto.value:auto-value-annotations": [ + "com.google.auto.value", + "com.google.auto.value.extension.memoized", + "com.google.auto.value.extension.serializable", + "com.google.auto.value.extension.toprettystring" + ] + }, + "repositories": { + "https://repo1.maven.org/maven2/": [ + "com.google.auto.value:auto-value", + "com.google.auto.value:auto-value-annotations" + ] + }, + "services": { + "com.google.auto.value:auto-value": { + "autovalue.shaded.kotlinx.metadata.internal.extensions.MetadataExtensions": [ + "autovalue.shaded.kotlinx.metadata.jvm.internal.JvmMetadataExtensions" + ], + "com.google.auto.value.extension.AutoValueExtension": [ + "com.google.auto.value.extension.memoized.processor.MemoizeExtension", + "com.google.auto.value.extension.serializable.processor.SerializableAutoValueExtension", + "com.google.auto.value.extension.toprettystring.processor.ToPrettyStringExtension" + ], + "com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension": [ + "com.google.auto.value.extension.serializable.serializer.impl.ImmutableListSerializerExtension", + "com.google.auto.value.extension.serializable.serializer.impl.ImmutableMapSerializerExtension", + "com.google.auto.value.extension.serializable.serializer.impl.OptionalSerializerExtension" + ], + "javax.annotation.processing.Processor": [ + "com.google.auto.value.extension.memoized.processor.MemoizedValidator", + "com.google.auto.value.extension.toprettystring.processor.ToPrettyStringValidator", + "com.google.auto.value.processor.AutoAnnotationProcessor", + "com.google.auto.value.processor.AutoBuilderProcessor", + "com.google.auto.value.processor.AutoOneOfProcessor", + "com.google.auto.value.processor.AutoValueBuilderProcessor", + "com.google.auto.value.processor.AutoValueProcessor" + ] + } + }, + "version": "2" +} diff --git a/java/gazelle/testdata/annotation_processor/src/main/java/com/example/BUILD.out b/java/gazelle/testdata/annotation_processor/src/main/java/com/example/BUILD.out new file mode 100644 index 00000000..0d61c04e --- /dev/null +++ b/java/gazelle/testdata/annotation_processor/src/main/java/com/example/BUILD.out @@ -0,0 +1,16 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +java_library( + name = "example", + srcs = ["Main.java"], + plugins = ["@maven//:com_google_auto_value_auto_value__java_plugin__com_google_auto_value_processor_AutoValueProcessor"], + visibility = ["//:__subpackages__"], + deps = ["@maven//:com_google_auto_value_auto_value_annotations"], +) + +java_binary( + name = "Main", + main_class = "com.example.Main", + visibility = ["//visibility:public"], + runtime_deps = [":example"], +) diff --git a/java/gazelle/testdata/annotation_processor/src/main/java/com/example/Main.java b/java/gazelle/testdata/annotation_processor/src/main/java/com/example/Main.java new file mode 100644 index 00000000..3ea1ad03 --- /dev/null +++ b/java/gazelle/testdata/annotation_processor/src/main/java/com/example/Main.java @@ -0,0 +1,21 @@ +package com.example; + +import com.google.auto.value.AutoValue; + +class Main { + public static void main(String[] args) { + Animal pig = Animal.create("pig", 4); + Animal chicken = Animal.create("chicken", 2); + System.out.printf("Checking if %s has same legs as %s: %s%n", pig, chicken, pig.numberOfLegs() == chicken.numberOfLegs()); + } + + @AutoValue + public abstract static class Animal { + static Animal create(String name, int numberOfLegs) { + return new AutoValue_Main_Animal(name, numberOfLegs); + } + + abstract String name(); + abstract int numberOfLegs(); + } +} diff --git a/java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParser.java b/java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParser.java index 6b770573..c2e25abd 100644 --- a/java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParser.java +++ b/java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParser.java @@ -4,6 +4,7 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; +import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; @@ -15,6 +16,7 @@ import com.sun.source.tree.ImportTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.PackageTree; import com.sun.source.tree.ParameterizedTypeTree; @@ -26,7 +28,11 @@ import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -69,7 +75,7 @@ public ClasspathParser() { static class PerClassData { public PerClassData() { - this(new TreeSet<>(), new TreeMap<>()); + this(new TreeSet<>(), new TreeMap<>(), new TreeMap<>()); } @Override @@ -79,18 +85,24 @@ public String toString() { + annotations + ", perMethodAnnotations=" + perMethodAnnotations + + ", perFieldAnnotations=" + + perFieldAnnotations + '}'; } public PerClassData( - SortedSet annotations, SortedMap> perMethodAnnotations) { + SortedSet annotations, + SortedMap> perMethodAnnotations, + SortedMap> perFieldAnnotations) { this.annotations = annotations; this.perMethodAnnotations = perMethodAnnotations; + this.perFieldAnnotations = perFieldAnnotations; } final SortedSet annotations; final SortedMap> perMethodAnnotations; + final SortedMap> perFieldAnnotations; @Override public boolean equals(Object o) { @@ -98,12 +110,13 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; PerClassData that = (PerClassData) o; return Objects.equals(annotations, that.annotations) - && Objects.equals(perMethodAnnotations, that.perMethodAnnotations); + && Objects.equals(perMethodAnnotations, that.perMethodAnnotations) + && Objects.equals(perFieldAnnotations, that.perFieldAnnotations); } @Override public int hashCode() { - return Objects.hash(annotations, perMethodAnnotations); + return Objects.hash(annotations, perMethodAnnotations, perFieldAnnotations); } } @@ -171,17 +184,28 @@ private void parseFileGatherDependencies(Iterable comp class ClassScanner extends TreeScanner { private CompilationUnitTree compileUnit; private String fileName; - @Nullable private String currentPackage; - @Nullable private String currentClassName; + + // Stack of possibly-nested contexts we may currently be in. + // First element is the outer-most context (e.g. top-level class), last element is the + // inner-most context (e.g. inner class). + // Currently tracks classes, so that we can know what outer and inner classes we may be in. + private final Deque stack = new ArrayDeque<>(); @Nullable private Map currentFileImports; + void popOrThrow(Tree expected) { + Tree popped = stack.removeLast(); + if (!expected.equals(popped)) { + throw new IllegalStateException( + String.format("Expected to pop %s but got %s", expected, popped)); + } + } + @Override public Void visitCompilationUnit(CompilationUnitTree t, Void v) { compileUnit = t; fileName = Paths.get(compileUnit.getSourceFile().toUri()).getFileName().toString(); - currentClassName = null; currentFileImports = new HashMap<>(); return super.visitCompilationUnit(t, v); @@ -225,25 +249,25 @@ public Void visitImport(ImportTree i, Void v) { @Override public Void visitClass(ClassTree t, Void v) { - // Set class name for the top level classes only - if (currentClassName == null) { - currentClassName = t.getSimpleName().toString(); - } + stack.addLast(t); for (AnnotationTree annotation : t.getModifiers().getAnnotations()) { String annotationClassName = annotation.getAnnotationType().toString(); String importedFullyQualified = currentFileImports.get(annotationClassName); - String currentFullyQualifiedClass = fullyQualify(currentPackage, currentClassName); + String currentFullyQualifiedClass = currentFullyQualifiedClassName(); if (importedFullyQualified != null) { noteAnnotatedClass(currentFullyQualifiedClass, importedFullyQualified); } else { noteAnnotatedClass(currentFullyQualifiedClass, annotationClassName); } } - return super.visitClass(t, v); + Void ret = super.visitClass(t, v); + popOrThrow(t); + return ret; } @Override public Void visitMethod(com.sun.source.tree.MethodTree m, Void v) { + stack.addLast(m); boolean isVoidReturn = false; // Check the return type on the method. @@ -267,6 +291,7 @@ public Void visitMethod(com.sun.source.tree.MethodTree m, Void v) { if (m.getName().toString().equals("main") && m.getModifiers().getFlags().containsAll(Set.of(STATIC, PUBLIC)) && isVoidReturn) { + String currentClassName = currentNestedClassNameWithoutPackage(); logger.debug("JavaTools: Found main method for {}", currentClassName); mainClasses.add(currentClassName); } @@ -280,7 +305,7 @@ public Void visitMethod(com.sun.source.tree.MethodTree m, Void v) { for (AnnotationTree annotation : m.getModifiers().getAnnotations()) { String annotationClassName = annotation.getAnnotationType().toString(); String importedFullyQualified = currentFileImports.get(annotationClassName); - String currentFullyQualifiedClass = fullyQualify(currentPackage, currentClassName); + String currentFullyQualifiedClass = currentFullyQualifiedClassName(); if (importedFullyQualified != null) { noteAnnotatedMethod( currentFullyQualifiedClass, m.getName().toString(), importedFullyQualified); @@ -290,7 +315,9 @@ public Void visitMethod(com.sun.source.tree.MethodTree m, Void v) { } } - return super.visitMethod(m, v); + Void ret = super.visitMethod(m, v); + popOrThrow(m); + return ret; } private void handleAnnotations(List annotations) { @@ -343,6 +370,23 @@ public Void visitVariable(VariableTree node, Void unused) { if (node.getType() != null) { checkFullyQualifiedType(node.getType()); } + + // Local variables inside methods shouldn't be treated as fields. + if (isDirectlyInClass()) { + for (AnnotationTree annotation : node.getModifiers().getAnnotations()) { + String annotationClassName = annotation.getAnnotationType().toString(); + String importedFullyQualified = currentFileImports.get(annotationClassName); + String currentFullyQualifiedClass = currentFullyQualifiedClassName(); + if (importedFullyQualified != null) { + noteAnnotatedField( + currentFullyQualifiedClass, node.getName().toString(), importedFullyQualified); + } else { + noteAnnotatedField( + currentFullyQualifiedClass, node.getName().toString(), annotationClassName); + } + } + } + return super.visitVariable(node, unused); } @@ -374,6 +418,50 @@ private Set checkFullyQualifiedType(Tree identifier) { } return types; } + + private boolean isDirectlyInClass() { + Iterator treeIterator = stack.descendingIterator(); + while (treeIterator.hasNext()) { + Tree tree = treeIterator.next(); + if (tree instanceof ClassTree) { + return true; + } + if (tree instanceof MethodTree) { + return false; + } + } + return false; + } + + @Nullable + private String currentNestedClassNameWithoutPackage() { + List parts = new ArrayList<>(); + boolean sawClass = false; + for (Tree tree : stack) { + if (tree instanceof ClassTree) { + sawClass = true; + parts.add(((ClassTree) tree).getSimpleName().toString()); + } + } + if (!sawClass) { + return null; + } + return Joiner.on('.').join(parts); + } + + @Nullable + private String currentFullyQualifiedClassName() { + String nestedClassName = currentNestedClassNameWithoutPackage(); + if (nestedClassName == null) { + return null; + } + List parts = new ArrayList<>(); + if (currentPackage != null) { + parts.add(currentPackage); + } + parts.add(nestedClassName); + return Joiner.on('.').join(parts); + } } private void noteAnnotatedClass( @@ -401,10 +489,17 @@ private void noteAnnotatedMethod( data.perMethodAnnotations.get(methodName).add(annotationFullyQualifiedClassName); } - private String fullyQualify(String packageName, String className) { - if (packageName == null || packageName.isEmpty()) { - return className; + private void noteAnnotatedField( + String annotatedFullyQualifiedClassName, + String fieldName, + String annotationFullyQualifiedClassName) { + if (!perClassData.containsKey(annotatedFullyQualifiedClassName)) { + perClassData.put(annotatedFullyQualifiedClassName, new PerClassData()); + } + PerClassData data = perClassData.get(annotatedFullyQualifiedClassName); + if (!data.perFieldAnnotations.containsKey(fieldName)) { + data.perFieldAnnotations.put(fieldName, new TreeSet<>()); } - return packageName + "." + className; + data.perFieldAnnotations.get(fieldName).add(annotationFullyQualifiedClassName); } } diff --git a/java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/GrpcServer.java b/java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/GrpcServer.java index 660754c7..62f85c7b 100644 --- a/java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/GrpcServer.java +++ b/java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/GrpcServer.java @@ -7,6 +7,7 @@ import com.gazelle.java.javaparser.v0.Package.Builder; import com.gazelle.java.javaparser.v0.ParsePackageRequest; import com.gazelle.java.javaparser.v0.PerClassMetadata; +import com.gazelle.java.javaparser.v0.PerFieldMetadata; import com.gazelle.java.javaparser.v0.PerMethodMetadata; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; @@ -168,6 +169,14 @@ private Package getImports(ParsePackageRequest request) { .addAllAnnotationClassNames(methodEntry.getValue()) .build()); } + for (Map.Entry> fieldEntry : + classEntry.getValue().perFieldAnnotations.entrySet()) { + perClassMetadata.putPerFieldMetadata( + fieldEntry.getKey(), + PerFieldMetadata.newBuilder() + .addAllAnnotationClassNames(fieldEntry.getValue()) + .build()); + } packageBuilder.putPerClassMetadata(classEntry.getKey(), perClassMetadata.build()); } return packageBuilder.build(); diff --git a/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParserTest.java b/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParserTest.java index ee7a26bc..c8be7ded 100644 --- a/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParserTest.java +++ b/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParserTest.java @@ -201,10 +201,61 @@ public void testAnnotationAfterImport() throws IOException { assertEquals( Map.of( "workspace.com.gazelle.java.javaparser.generators.AnnotationAfterImport", - new ClasspathParser.PerClassData(treeSet("com.example.FlakyTest"), new TreeMap<>())), + new ClasspathParser.PerClassData( + treeSet("com.example.FlakyTest"), new TreeMap<>(), new TreeMap<>())), parser.perClassData); } + @Test + public void testAnnotationAfterImportOnNestedClass() throws IOException { + List files = + List.of( + testFiles.get( + "/workspace/com/gazelle/java/javaparser/generators/NestedClassAnnotations.java")); + parser.parseClasses(files); + + assertEquals( + Map.of( + "workspace.com.gazelle.java.javaparser.generators.NestedClassAnnotations.Inner", + new ClasspathParser.PerClassData( + treeSet("com.example.FlakyTest"), new TreeMap<>(), new TreeMap<>())), + parser.perClassData); + } + + @Test + public void testAnnotationOnField() throws IOException { + List files = + List.of( + testFiles.get( + "/workspace/com/gazelle/java/javaparser/generators/AnnotationOnField.java")); + parser.parseClasses(files); + + TreeMap> expectedOuterClassFieldAnnotations = new TreeMap<>(); + expectedOuterClassFieldAnnotations.put("someField", treeSet("lombok.Getter")); + + TreeMap> expectedInnerClassFieldAnnotations = new TreeMap<>(); + expectedInnerClassFieldAnnotations.put("canBeSet", treeSet("lombok.Setter")); + + TreeMap> expectedInnerEnumFieldAnnotations = new TreeMap<>(); + expectedInnerEnumFieldAnnotations.put("size", treeSet("lombok.Getter")); + + TreeMap expected = new TreeMap<>(); + expected.put( + "workspace.com.gazelle.java.javaparser.generators.AnnotationOnField", + new ClasspathParser.PerClassData( + new TreeSet<>(), new TreeMap<>(), expectedOuterClassFieldAnnotations)); + expected.put( + "workspace.com.gazelle.java.javaparser.generators.AnnotationOnField.InnerClass", + new ClasspathParser.PerClassData( + new TreeSet<>(), new TreeMap<>(), expectedInnerClassFieldAnnotations)); + expected.put( + "workspace.com.gazelle.java.javaparser.generators.AnnotationOnField.InnerEnum", + new ClasspathParser.PerClassData( + new TreeSet<>(), new TreeMap<>(), expectedInnerEnumFieldAnnotations)); + + assertEquals(expected, parser.perClassData); + } + @Test public void testAnnotationAfterImportOnMethod() throws IOException { List files = @@ -219,7 +270,8 @@ public void testAnnotationAfterImportOnMethod() throws IOException { assertEquals( Map.of( "workspace.com.gazelle.java.javaparser.generators.AnnotationAfterImportOnMethod", - new ClasspathParser.PerClassData(new TreeSet<>(), expectedPerMethodAnnotations)), + new ClasspathParser.PerClassData( + new TreeSet<>(), expectedPerMethodAnnotations, new TreeMap<>())), parser.perClassData); } @@ -236,7 +288,8 @@ public void testAnnotationFromJavaStandardLibrary() throws IOException { assertEquals( Map.of( "workspace.com.gazelle.java.javaparser.generators.AnnotationFromJavaStandardLibrary", - new ClasspathParser.PerClassData(treeSet("Deprecated"), new TreeMap<>())), + new ClasspathParser.PerClassData( + treeSet("Deprecated"), new TreeMap<>(), new TreeMap<>())), parser.perClassData); } @@ -253,7 +306,8 @@ public void testAnnotationWithoutImport() throws IOException { assertEquals( Map.of( "workspace.com.gazelle.java.javaparser.generators.AnnotationWithoutImport", - new ClasspathParser.PerClassData(treeSet("WhoKnowsWhereIAmFrom"), new TreeMap<>())), + new ClasspathParser.PerClassData( + treeSet("WhoKnowsWhereIAmFrom"), new TreeMap<>(), new TreeMap<>())), parser.perClassData); } diff --git a/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/workspace/com/gazelle/java/javaparser/generators/AnnotationOnField.java b/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/workspace/com/gazelle/java/javaparser/generators/AnnotationOnField.java new file mode 100644 index 00000000..3762ad34 --- /dev/null +++ b/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/workspace/com/gazelle/java/javaparser/generators/AnnotationOnField.java @@ -0,0 +1,26 @@ +package workspace.com.gazelle.java.javaparser.generators; + +import lombok.Getter; +import lombok.Setter; +import com.example.NonNull; + +public class AnnotationOnField { + @Getter + private final String someField; + + public void doSomething() { + @NonNull String variable = "hello"; + } + + private static class InnerClass { + @Setter + private String canBeSet; + } + + public enum InnerEnum { + VARIANT; + + @Getter + private final int size; + } +} diff --git a/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/workspace/com/gazelle/java/javaparser/generators/NestedClassAnnotations.java b/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/workspace/com/gazelle/java/javaparser/generators/NestedClassAnnotations.java new file mode 100644 index 00000000..65954662 --- /dev/null +++ b/java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/workspace/com/gazelle/java/javaparser/generators/NestedClassAnnotations.java @@ -0,0 +1,8 @@ +package workspace.com.gazelle.java.javaparser.generators; + +import com.example.FlakyTest; + +public interface NestedClassAnnotations { + @FlakyTest + public static class Inner {} +}