From f354e5bb109cb531d26b76eb15a1b5c6250f1fa6 Mon Sep 17 00:00:00 2001 From: Logan Shire Date: Tue, 29 Aug 2023 11:22:16 -0400 Subject: [PATCH] feat: implement support for PascalCase module names with snake_case directory names (#534) This PR introduces an optional Gazelle directive to convert module names from snake_case to PascalCase for consistency with platform idioms. Currently the module name is the same as the target name, derived typically from the directory name. Secondly, the current heuristic for detecting whether a directory is a swift_test or swift_library / swift_binary checks for case sensitive, "Test" or "Tests" directory names or "Test.swift" / "Tests.swift" file names. This breaks if the directory name is in snake_case. To address this issue I just made the checks case insensitive. I also unified the DirSuffixes and fileSuffixes types into a PathSuffixes type and added tests for both files and paths with both casings. --- .bazelrc | 4 +- MODULE.bazel | 1 + examples/grpc_example/BUILD.bazel | 1 + examples/grpc_example/protos/BUILD.bazel | 0 .../grpc_example/sources/client/BUILD.bazel | 2 +- .../grpc_example/sources/server/BUILD.bazel | 2 +- .../grpc_example/sources/test/BUILD.bazel | 3 +- gazelle/BUILD.bazel | 2 + gazelle/config.go | 12 +++- gazelle/generate.go | 58 ++++++++++++------- gazelle/internal/swift/rules_common.go | 13 +++-- gazelle/internal/swift/rules_from_srcs.go | 19 +++--- gazelle/internal/swift/test_rules_common.go | 4 +- gazelle/internal/swiftcfg/swift_config.go | 7 +++ gazelle/internal/swiftpkg/swift_file_info.go | 32 ++++------ .../internal/swiftpkg/swift_file_info_test.go | 54 ++++++++++++++--- go.mod | 1 + go.sum | 1 + 18 files changed, 145 insertions(+), 71 deletions(-) delete mode 100644 examples/grpc_example/protos/BUILD.bazel diff --git a/.bazelrc b/.bazelrc index 06b91fbb8..4b087bbb8 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,7 +1,7 @@ # To update these lines, execute # `bazel run @rules_bazel_integration_test//tools:update_deleted_packages` -build --deleted_packages=bzlmod/workspace,bzlmod/workspace/Sources/MyExecutable,bzlmod/workspace/Sources/MyLibrary,bzlmod/workspace/Tests/MyLibraryTests,examples/firebase_example,examples/firebase_example/abtesting,examples/firebase_example/abtesting/SharedApp,examples/firebase_example/analytics/AnalyticsExample,examples/firebase_example/appdistribution,examples/firebase_example/appdistribution/AppDistributionExample,examples/firebase_example/appdistribution/AppDistributionTests,examples/firebase_example/crashlytics,examples/grpc_example,examples/grpc_example/protos,examples/grpc_example/protos/echoservice,examples/grpc_example/protos/echoservice/messages,examples/grpc_example/sources,examples/grpc_example/sources/client,examples/grpc_example/sources/server,examples/grpc_example/sources/test,examples/http_archive_ext_deps,examples/http_archive_ext_deps/Sources/MyDequeModule,examples/http_archive_ext_deps/Sources/PrintStuff,examples/http_archive_ext_deps/Tests/MyDequeModuleTests,examples/http_archive_ext_deps/third_party,examples/interesting_deps,examples/ios_sim,examples/ios_sim/Sources/Foo,examples/ios_sim/Tests/FooTests,examples/ios_sim/third-party/swift-cmark,examples/nimble_example,examples/nimble_example/Sources/NimbleExample,examples/objc_code,examples/phone_number_kit,examples/phone_number_kit/Tests/PhoneNumberKitTests,examples/pkg_manifest_minimal,examples/pkg_manifest_minimal/Sources/MyExecutable,examples/pkg_manifest_minimal/Sources/MyLibrary,examples/pkg_manifest_minimal/Tests/MyLibraryTests,examples/pkg_manifest_minimal/third_party,examples/resources_example,examples/resources_example/Sources/MyApp,examples/resources_example/Tests/MyAppTests,examples/resources_example/third_party,examples/snapkit_example,examples/soto_example,examples/soto_example/Tests/SotoTests,examples/stripe_example,examples/stripe_example/PaymentSheet/PaymentSheetExample,examples/stripe_example/PaymentSheet/PaymentSheetUITest,examples/tca_example,examples/tca_example/SwiftUICaseStudies,examples/tca_example/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads,examples/tca_example/SwiftUICaseStudies/Internal,examples/tca_example/SwiftUICaseStudiesTests,examples/vapor_example,examples/vapor_example/Sources/App,examples/vapor_example/Sources/Run,examples/vapor_example/Tests/AppTests,examples/vapor_example/swift,examples/xcmetrics_example -query --deleted_packages=bzlmod/workspace,bzlmod/workspace/Sources/MyExecutable,bzlmod/workspace/Sources/MyLibrary,bzlmod/workspace/Tests/MyLibraryTests,examples/firebase_example,examples/firebase_example/abtesting,examples/firebase_example/abtesting/SharedApp,examples/firebase_example/analytics/AnalyticsExample,examples/firebase_example/appdistribution,examples/firebase_example/appdistribution/AppDistributionExample,examples/firebase_example/appdistribution/AppDistributionTests,examples/firebase_example/crashlytics,examples/grpc_example,examples/grpc_example/protos,examples/grpc_example/protos/echoservice,examples/grpc_example/protos/echoservice/messages,examples/grpc_example/sources,examples/grpc_example/sources/client,examples/grpc_example/sources/server,examples/grpc_example/sources/test,examples/http_archive_ext_deps,examples/http_archive_ext_deps/Sources/MyDequeModule,examples/http_archive_ext_deps/Sources/PrintStuff,examples/http_archive_ext_deps/Tests/MyDequeModuleTests,examples/http_archive_ext_deps/third_party,examples/interesting_deps,examples/ios_sim,examples/ios_sim/Sources/Foo,examples/ios_sim/Tests/FooTests,examples/ios_sim/third-party/swift-cmark,examples/nimble_example,examples/nimble_example/Sources/NimbleExample,examples/objc_code,examples/phone_number_kit,examples/phone_number_kit/Tests/PhoneNumberKitTests,examples/pkg_manifest_minimal,examples/pkg_manifest_minimal/Sources/MyExecutable,examples/pkg_manifest_minimal/Sources/MyLibrary,examples/pkg_manifest_minimal/Tests/MyLibraryTests,examples/pkg_manifest_minimal/third_party,examples/resources_example,examples/resources_example/Sources/MyApp,examples/resources_example/Tests/MyAppTests,examples/resources_example/third_party,examples/snapkit_example,examples/soto_example,examples/soto_example/Tests/SotoTests,examples/stripe_example,examples/stripe_example/PaymentSheet/PaymentSheetExample,examples/stripe_example/PaymentSheet/PaymentSheetUITest,examples/tca_example,examples/tca_example/SwiftUICaseStudies,examples/tca_example/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads,examples/tca_example/SwiftUICaseStudies/Internal,examples/tca_example/SwiftUICaseStudiesTests,examples/vapor_example,examples/vapor_example/Sources/App,examples/vapor_example/Sources/Run,examples/vapor_example/Tests/AppTests,examples/vapor_example/swift,examples/xcmetrics_example +build --deleted_packages=bzlmod/workspace,bzlmod/workspace/Sources/MyExecutable,bzlmod/workspace/Sources/MyLibrary,bzlmod/workspace/Tests/MyLibraryTests,examples/firebase_example,examples/firebase_example/abtesting,examples/firebase_example/abtesting/SharedApp,examples/firebase_example/analytics/AnalyticsExample,examples/firebase_example/appdistribution,examples/firebase_example/appdistribution/AppDistributionExample,examples/firebase_example/appdistribution/AppDistributionTests,examples/firebase_example/crashlytics,examples/grpc_example,examples/grpc_example/protos/echoservice,examples/grpc_example/protos/echoservice/messages,examples/grpc_example/sources,examples/grpc_example/sources/client,examples/grpc_example/sources/server,examples/grpc_example/sources/test,examples/http_archive_ext_deps,examples/http_archive_ext_deps/Sources/MyDequeModule,examples/http_archive_ext_deps/Sources/PrintStuff,examples/http_archive_ext_deps/Tests/MyDequeModuleTests,examples/http_archive_ext_deps/third_party,examples/interesting_deps,examples/ios_sim,examples/ios_sim/Sources/Foo,examples/ios_sim/Tests/FooTests,examples/ios_sim/third-party/swift-cmark,examples/nimble_example,examples/nimble_example/Sources/NimbleExample,examples/objc_code,examples/phone_number_kit,examples/phone_number_kit/Tests/PhoneNumberKitTests,examples/pkg_manifest_minimal,examples/pkg_manifest_minimal/Sources/MyExecutable,examples/pkg_manifest_minimal/Sources/MyLibrary,examples/pkg_manifest_minimal/Tests/MyLibraryTests,examples/pkg_manifest_minimal/third_party,examples/resources_example,examples/resources_example/Sources/MyApp,examples/resources_example/Tests/MyAppTests,examples/resources_example/third_party,examples/snapkit_example,examples/soto_example,examples/soto_example/Tests/SotoTests,examples/stripe_example,examples/stripe_example/PaymentSheet/PaymentSheetExample,examples/stripe_example/PaymentSheet/PaymentSheetUITest,examples/tca_example,examples/tca_example/SwiftUICaseStudies,examples/tca_example/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads,examples/tca_example/SwiftUICaseStudies/Internal,examples/tca_example/SwiftUICaseStudiesTests,examples/vapor_example,examples/vapor_example/Sources/App,examples/vapor_example/Sources/Run,examples/vapor_example/Tests/AppTests,examples/vapor_example/swift,examples/xcmetrics_example +query --deleted_packages=bzlmod/workspace,bzlmod/workspace/Sources/MyExecutable,bzlmod/workspace/Sources/MyLibrary,bzlmod/workspace/Tests/MyLibraryTests,examples/firebase_example,examples/firebase_example/abtesting,examples/firebase_example/abtesting/SharedApp,examples/firebase_example/analytics/AnalyticsExample,examples/firebase_example/appdistribution,examples/firebase_example/appdistribution/AppDistributionExample,examples/firebase_example/appdistribution/AppDistributionTests,examples/firebase_example/crashlytics,examples/grpc_example,examples/grpc_example/protos/echoservice,examples/grpc_example/protos/echoservice/messages,examples/grpc_example/sources,examples/grpc_example/sources/client,examples/grpc_example/sources/server,examples/grpc_example/sources/test,examples/http_archive_ext_deps,examples/http_archive_ext_deps/Sources/MyDequeModule,examples/http_archive_ext_deps/Sources/PrintStuff,examples/http_archive_ext_deps/Tests/MyDequeModuleTests,examples/http_archive_ext_deps/third_party,examples/interesting_deps,examples/ios_sim,examples/ios_sim/Sources/Foo,examples/ios_sim/Tests/FooTests,examples/ios_sim/third-party/swift-cmark,examples/nimble_example,examples/nimble_example/Sources/NimbleExample,examples/objc_code,examples/phone_number_kit,examples/phone_number_kit/Tests/PhoneNumberKitTests,examples/pkg_manifest_minimal,examples/pkg_manifest_minimal/Sources/MyExecutable,examples/pkg_manifest_minimal/Sources/MyLibrary,examples/pkg_manifest_minimal/Tests/MyLibraryTests,examples/pkg_manifest_minimal/third_party,examples/resources_example,examples/resources_example/Sources/MyApp,examples/resources_example/Tests/MyAppTests,examples/resources_example/third_party,examples/snapkit_example,examples/soto_example,examples/soto_example/Tests/SotoTests,examples/stripe_example,examples/stripe_example/PaymentSheet/PaymentSheetExample,examples/stripe_example/PaymentSheet/PaymentSheetUITest,examples/tca_example,examples/tca_example/SwiftUICaseStudies,examples/tca_example/SwiftUICaseStudies/04-HigherOrderReducers-ResuableOfflineDownloads,examples/tca_example/SwiftUICaseStudies/Internal,examples/tca_example/SwiftUICaseStudiesTests,examples/vapor_example,examples/vapor_example/Sources/App,examples/vapor_example/Sources/Run,examples/vapor_example/Tests/AppTests,examples/vapor_example/swift,examples/xcmetrics_example # Import Shared settings import %workspace%/shared.bazelrc diff --git a/MODULE.bazel b/MODULE.bazel index 71dd6f36e..59ac8ae86 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -43,6 +43,7 @@ use_repo( "com_github_stretchr_testify", "in_gopkg_yaml_v3", "org_golang_x_exp", + "org_golang_x_text", ) # MARK: - Dev Dependencies diff --git a/examples/grpc_example/BUILD.bazel b/examples/grpc_example/BUILD.bazel index abbffc704..539812f84 100644 --- a/examples/grpc_example/BUILD.bazel +++ b/examples/grpc_example/BUILD.bazel @@ -14,6 +14,7 @@ tidy( # Ignore the Swift build folder # gazelle:exclude .build +# gazelle:swift_module_naming_convention pascal_case gazelle_binary( name = "gazelle_bin", diff --git a/examples/grpc_example/protos/BUILD.bazel b/examples/grpc_example/protos/BUILD.bazel deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/grpc_example/sources/client/BUILD.bazel b/examples/grpc_example/sources/client/BUILD.bazel index 6fe7287fb..bdb2c8193 100644 --- a/examples/grpc_example/sources/client/BUILD.bazel +++ b/examples/grpc_example/sources/client/BUILD.bazel @@ -3,7 +3,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_binary") swift_binary( name = "client", srcs = ["client_main.swift"], - module_name = "client", + module_name = "Client", visibility = ["//visibility:public"], deps = [ "//protos/echoservice:echoservice_client_swift_grpc", diff --git a/examples/grpc_example/sources/server/BUILD.bazel b/examples/grpc_example/sources/server/BUILD.bazel index 8f4d83d1d..670f57458 100644 --- a/examples/grpc_example/sources/server/BUILD.bazel +++ b/examples/grpc_example/sources/server/BUILD.bazel @@ -3,7 +3,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_binary") swift_binary( name = "server", srcs = ["server_main.swift"], - module_name = "server", + module_name = "Server", visibility = ["//visibility:public"], deps = [ "//protos/echoservice:echoservice_server_swift_grpc", diff --git a/examples/grpc_example/sources/test/BUILD.bazel b/examples/grpc_example/sources/test/BUILD.bazel index d514f6c3a..3175a5a5f 100644 --- a/examples/grpc_example/sources/test/BUILD.bazel +++ b/examples/grpc_example/sources/test/BUILD.bazel @@ -6,8 +6,7 @@ swift_test( "client_unit_test.swift", "main.swift", ], - module_name = "test", - visibility = ["//visibility:public"], + module_name = "Test", deps = [ "//protos/echoservice:echoservice_client_swift_grpc", "//protos/echoservice:echoservice_server_swift_grpc", diff --git a/gazelle/BUILD.bazel b/gazelle/BUILD.bazel index d8eba6819..bffad5d58 100644 --- a/gazelle/BUILD.bazel +++ b/gazelle/BUILD.bazel @@ -44,6 +44,8 @@ go_library( "@bazel_gazelle//resolve:go_default_library", "@bazel_gazelle//rule:go_default_library", "@org_golang_x_exp//slices:go_default_library", + "@org_golang_x_text//cases:go_default_library", + "@org_golang_x_text//language:go_default_library", ], ) diff --git a/gazelle/config.go b/gazelle/config.go index 2ed2879e2..6e7dacab4 100644 --- a/gazelle/config.go +++ b/gazelle/config.go @@ -125,10 +125,14 @@ func (sl *swiftLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error { // Directives +const moduleNamingConventionDirective = "swift_module_naming_convention" const defaultModuleNameDirective = "swift_default_module_name" func (*swiftLang) KnownDirectives() []string { - return []string{defaultModuleNameDirective} + return []string{ + moduleNamingConventionDirective, + defaultModuleNameDirective, + } } func (*swiftLang) Configure(c *config.Config, rel string, f *rule.File) { @@ -138,6 +142,12 @@ func (*swiftLang) Configure(c *config.Config, rel string, f *rule.File) { sc := swiftcfg.GetSwiftConfig(c) for _, d := range f.Directives { switch d.Key { + case moduleNamingConventionDirective: + if d.Value == swiftcfg.PascalCaseModuleNamingConvention { + sc.ModuleNamingConvention = swiftcfg.PascalCaseModuleNamingConvention + } else { + sc.ModuleNamingConvention = swiftcfg.MatchCaseModuleNamingConvention + } case defaultModuleNameDirective: sc.DefaultModuleNames[rel] = d.Value } diff --git a/gazelle/generate.go b/gazelle/generate.go index e44760514..e80d0d455 100644 --- a/gazelle/generate.go +++ b/gazelle/generate.go @@ -12,6 +12,8 @@ import ( "github.com/cgrindel/rules_swift_package_manager/gazelle/internal/swift" "github.com/cgrindel/rules_swift_package_manager/gazelle/internal/swiftcfg" "golang.org/x/exp/slices" + "golang.org/x/text/cases" + lang "golang.org/x/text/language" ) func (l *swiftLang) GenerateRules(args language.GenerateArgs) language.GenerateResult { @@ -62,8 +64,8 @@ func genRulesFromSrcFiles(sc *swiftcfg.SwiftConfig, args language.GenerateArgs) sort.Strings(srcs) // Generate the rules from sources: - defaultModuleName := defaultModuleName(args) - rules = swift.RulesFromSrcs(args, srcs, defaultModuleName) + defaultName, defaultModuleName := defaultNameAndModuleName(args) + rules = swift.RulesFromSrcs(args, srcs, defaultName, defaultModuleName) result.Gen = append(result.Gen, rules...) result.Imports = swift.Imports(result.Gen) result.Empty = generateEmpty(args, srcs) @@ -71,35 +73,47 @@ func genRulesFromSrcFiles(sc *swiftcfg.SwiftConfig, args language.GenerateArgs) return result } -func defaultModuleName(args language.GenerateArgs) string { - // Order of names to use - // 1. Value specified via directive. - // 2. Directory name. - // 3. Repository name. - // 4. "DefaultModule" +func defaultNameAndModuleName(args language.GenerateArgs) (string, string) { - // Check for a value configured via directive + // If the name is specified by a directive, short cirucit and return that: sc := swiftcfg.GetSwiftConfig(args.Config) - var defaultModuleName string - var ok bool - if defaultModuleName, ok = sc.DefaultModuleNames[args.Rel]; ok { - return defaultModuleName + if defaultModuleName, ok := sc.DefaultModuleNames[args.Rel]; ok { + return defaultModuleName, defaultModuleName } + + // Otherwise, derive the name from the: + // 1. Directory name. + // 2. Repository name. + // 3. "DefaultModule" + var defaultName string if args.Rel == "" { - defaultModuleName = filepath.Base(args.Config.WorkDir) + defaultName = filepath.Base(args.Config.WorkDir) } else { - defaultModuleName = filepath.Base(args.Rel) + defaultName = filepath.Base(args.Rel) } - if ext := filepath.Ext(defaultModuleName); ext != "" { - defaultModuleName = strings.TrimSuffix(defaultModuleName, ext) + if ext := filepath.Ext(defaultName); ext != "" { + defaultName = strings.TrimSuffix(defaultName, ext) } - if defaultModuleName == "." || defaultModuleName == "" { - defaultModuleName = args.Config.RepoName + if defaultName == "." || defaultName == "" { + defaultName = args.Config.RepoName } - if defaultModuleName == "" { - defaultModuleName = "DefaultModule" + if defaultName == "" { + defaultName = "DefaultModule" } - return defaultModuleName + + // If configured to use PascalCase for module names, convert to that naming convention: + defaultModuleName := defaultName + if sc.ModuleNamingConvention == swiftcfg.PascalCaseModuleNamingConvention { + moduleNameComponents := strings.Split(defaultModuleName, "_") + caser := cases.Title(lang.English) + pascalCaseModuleName := "" + for _, component := range moduleNameComponents { + pascalCaseModuleName += caser.String(component) + } + defaultModuleName = pascalCaseModuleName + } + + return defaultName, defaultModuleName } // Look for any rules in the existing BUILD file that do not reference one of the source files. If diff --git a/gazelle/internal/swift/rules_common.go b/gazelle/internal/swift/rules_common.go index 70c23cb7d..e6b556a93 100644 --- a/gazelle/internal/swift/rules_common.go +++ b/gazelle/internal/swift/rules_common.go @@ -9,13 +9,14 @@ import ( // Rule Creation func rulesForLibraryModule( + defaultName string, defaultModuleName string, srcs []string, swiftImports []string, shouldSetVis bool, buildFile *rule.File, ) []*rule.Rule { - name, moduleName := ruleNameAndModuleName(buildFile, LibraryRuleKind, defaultModuleName) + name, moduleName := ruleNameAndModuleName(buildFile, LibraryRuleKind, defaultName, defaultModuleName) r := rule.NewRule(LibraryRuleKind, name) setCommonSwiftAttrs(r, moduleName, srcs, swiftImports) setVisibilityAttr(r, shouldSetVis, []string{"//visibility:public"}) @@ -23,13 +24,14 @@ func rulesForLibraryModule( } func rulesForBinaryModule( + defaultName string, defaultModuleName string, srcs []string, swiftImports []string, shouldSetVis bool, buildFile *rule.File, ) []*rule.Rule { - name, moduleName := ruleNameAndModuleName(buildFile, BinaryRuleKind, defaultModuleName) + name, moduleName := ruleNameAndModuleName(buildFile, BinaryRuleKind, defaultName, defaultModuleName) r := rule.NewRule(BinaryRuleKind, name) setCommonSwiftAttrs(r, moduleName, srcs, swiftImports) setVisibilityAttr(r, shouldSetVis, []string{"//visibility:public"}) @@ -37,6 +39,7 @@ func rulesForBinaryModule( } func rulesForTestModule( + defaultName string, defaultModuleName string, srcs []string, swiftImports []string, @@ -44,7 +47,7 @@ func rulesForTestModule( buildFile *rule.File, ) []*rule.Rule { // Detect the type of rule that should be used to build the Swift sources. - r := buildRuleForTestSrcs(buildFile, defaultModuleName) + r := buildRuleForTestSrcs(buildFile, defaultName, defaultModuleName) setCommonSwiftAttrs(r, defaultModuleName, srcs, swiftImports) return []*rule.Rule{r} } @@ -79,7 +82,7 @@ func setVisibilityAttr(r *rule.Rule, shouldSetVis bool, visibility []string) { // Name and Module Name // Determine the rule name and module name from existing rules -func ruleNameAndModuleName(buildFile *rule.File, kind, defaultModuleName string) (string, string) { +func ruleNameAndModuleName(buildFile *rule.File, kind, defaultName, defaultModuleName string) (string, string) { var existingRules []*rule.Rule if buildFile != nil { existingRules = findRulesByKind(buildFile.Rules, kind) @@ -91,7 +94,7 @@ func ruleNameAndModuleName(buildFile *rule.File, kind, defaultModuleName string) name = first.Name() moduleName = first.AttrString(ModuleNameAttrName) } else { - name = defaultModuleName + name = defaultName moduleName = defaultModuleName } return name, moduleName diff --git a/gazelle/internal/swift/rules_from_srcs.go b/gazelle/internal/swift/rules_from_srcs.go index 443ab8701..1bf8d6e87 100644 --- a/gazelle/internal/swift/rules_from_srcs.go +++ b/gazelle/internal/swift/rules_from_srcs.go @@ -13,6 +13,7 @@ import ( func RulesFromSrcs( args language.GenerateArgs, srcs []string, + defaultName string, defaultModuleName string, ) []*rule.Rule { fileInfos := swiftpkg.NewSwiftFileInfosFromRelPaths(args.Dir, srcs) @@ -23,11 +24,11 @@ func RulesFromSrcs( var rules []*rule.Rule switch moduleType { case LibraryModuleType: - rules = rulesForLibraryModule(defaultModuleName, srcs, swiftImports, shouldSetVis, args.File) + rules = rulesForLibraryModule(defaultName, defaultModuleName, srcs, swiftImports, shouldSetVis, args.File) case BinaryModuleType: - rules = rulesForBinaryModule(defaultModuleName, srcs, swiftImports, shouldSetVis, args.File) + rules = rulesForBinaryModule(defaultName, defaultModuleName, srcs, swiftImports, shouldSetVis, args.File) case TestModuleType: - rules = rulesForTestModule(defaultModuleName, srcs, swiftImports, shouldSetVis, args.File) + rules = rulesForTestModule(defaultName, defaultModuleName, srcs, swiftImports, shouldSetVis, args.File) } return rules } @@ -61,10 +62,14 @@ func collectSwiftInfo(fileInfos []*swiftpkg.SwiftFileInfo) ([]string, ModuleType } } - // Adjust the rule kind, if necessary - // Check for test files first. On Linux, a main.swift is necessary for swift_test rules. - // GUI applications can use the @main directive. So, we need to see if the module contains any - // of the GUI related modules. If no GUI modules and it has a main, then create a swift_binary. + // Adjust the rule kind, if necessary. + // Check if this is a test module first. On Linux, a main.swift is necessary for swift_test rules. + // rules_swift_package_manager does not currently support generating rules_apple targets. + // However, applications using Apple's UI frameworks can use the @main directive. + // To build these with rules_apple, we must first compile a swift_library target, + // and then pass this as a dependency to the application or extension target. + // So, we need to see if the module contains a main function and imports any of the GUI related modules. + // If it does not import any GUI modules and it has a main, then create a swift_binary. if hasTestFiles { moduleType = TestModuleType } else if hasMain && !importsGUIModules { diff --git a/gazelle/internal/swift/test_rules_common.go b/gazelle/internal/swift/test_rules_common.go index c3d532520..4a4980371 100644 --- a/gazelle/internal/swift/test_rules_common.go +++ b/gazelle/internal/swift/test_rules_common.go @@ -9,10 +9,10 @@ import ( // Determines which Swift rule should be used to build the sources. If the build file contains a // rule kind that ends in _test except swift_test, we assume that it will consume a swift_library. -func buildRuleForTestSrcs(buildFile *rule.File, moduleName string) *rule.Rule { +func buildRuleForTestSrcs(buildFile *rule.File, name, moduleName string) *rule.Rule { var libName string testKind := TestRuleKind - testName := moduleName + testName := name // Look for existing test rules and libraries if buildFile != nil { diff --git a/gazelle/internal/swiftcfg/swift_config.go b/gazelle/internal/swiftcfg/swift_config.go index e76193074..6793cc9a1 100644 --- a/gazelle/internal/swiftcfg/swift_config.go +++ b/gazelle/internal/swiftcfg/swift_config.go @@ -15,6 +15,11 @@ const SwiftConfigName = "swift" const DefaultDependencyIndexBasename = "swift_deps_index.json" const dependencyIndexPerms = 0666 +const ( + MatchCaseModuleNamingConvention string = "match_case" + PascalCaseModuleNamingConvention string = "pascal_case" +) + // A SwiftConfig represents the Swift-specific configuration for the Gazelle extension. type SwiftConfig struct { SwiftBinPath string @@ -37,6 +42,8 @@ type SwiftConfig struct { GenerateSwiftDepsForWorkspace bool + ModuleNamingConvention string + // Mapping of relative path to default module name. These values are populated from directives // that can be applied to DefaultModuleNames map[string]string diff --git a/gazelle/internal/swiftpkg/swift_file_info.go b/gazelle/internal/swiftpkg/swift_file_info.go index 5201a08ea..a4d1b773b 100644 --- a/gazelle/internal/swiftpkg/swift_file_info.go +++ b/gazelle/internal/swiftpkg/swift_file_info.go @@ -23,10 +23,11 @@ type SwiftFileInfo struct { // NewSwiftFileInfoFromReader returns file info for a source file. func NewSwiftFileInfoFromReader(rel, abs string, reader io.Reader) *SwiftFileInfo { + lowercaseRelativePath := strings.ToLower(rel) fi := SwiftFileInfo{ Rel: rel, Abs: abs, - IsTest: testSuffixes.HasSuffix(rel) || TestDirSuffixes.IsUnderDirWithSuffix(rel), + IsTest: TestFilePathSuffixes.HasSuffix(lowercaseRelativePath) || TestDirectoryPathSuffixes.IsUnderDirWithSuffix(lowercaseRelativePath), // There are several ways to detect a main. // 1. A file named "main.swift" // 2. @main annotation @@ -110,24 +111,11 @@ const ( objcDirReSubexpIdx = 5 ) -type fileSuffixes []string - -func (fs fileSuffixes) HasSuffix(path string) bool { - for _, suffix := range fs { - if strings.HasSuffix(path, suffix) { - return true - } - } - return false -} - -var testSuffixes = fileSuffixes{"Tests.swift", "Test.swift"} - -// DirSuffixes provides a means for testing a path having one of the listed suffixes. -type DirSuffixes []string +// PathSuffixes provides a means for testing a path having one of the listed suffixes. +type PathSuffixes []string // HasSuffix checks if the path has one of the suffixes. -func (ds DirSuffixes) HasSuffix(path string) bool { +func (ds PathSuffixes) HasSuffix(path string) bool { for _, suffix := range ds { if strings.HasSuffix(path, suffix) { return true @@ -137,19 +125,23 @@ func (ds DirSuffixes) HasSuffix(path string) bool { } // IsUnderDirWithSuffix checks if the path has a directory that includes one of the suffixes. -func (ds DirSuffixes) IsUnderDirWithSuffix(path string) bool { +func (ds PathSuffixes) IsUnderDirWithSuffix(path string) bool { if path == "." || path == "" || path == "/" { return false } dir := filepath.Dir(path) + dir = strings.ToLower(dir) if ds.HasSuffix(dir) { return true } return ds.IsUnderDirWithSuffix(dir) } -// TestDirSuffixes lists the suffixes used for Swift test directories. -var TestDirSuffixes = DirSuffixes{"Tests", "Test"} +// TestFilePathSuffixes lists the suffixes used for Swift test files. +var TestFilePathSuffixes = PathSuffixes{"tests.swift", "test.swift"} + +// TestDirectoryPathSuffixes lists the suffixes used for Swift test directories. +var TestDirectoryPathSuffixes = PathSuffixes{"tests", "test"} // SwiftFileInfos represents a collection of SwiftFileInfo instances. type SwiftFileInfos []*SwiftFileInfo diff --git a/gazelle/internal/swiftpkg/swift_file_info_test.go b/gazelle/internal/swiftpkg/swift_file_info_test.go index 1af28e337..a825ab8e2 100644 --- a/gazelle/internal/swiftpkg/swift_file_info_test.go +++ b/gazelle/internal/swiftpkg/swift_file_info_test.go @@ -73,25 +73,63 @@ func TestNewSwiftFileInfoFromSrc(t *testing.T) { }) } -func TestDirSuffixes(t *testing.T) { - actual := swiftpkg.TestDirSuffixes.IsUnderDirWithSuffix("Sources/Foo/Bar.swift") +func TestDirectoryPathSuffixes(t *testing.T) { + actual := swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("Sources/Foo/Bar.swift") assert.False(t, actual) - actual = swiftpkg.TestDirSuffixes.IsUnderDirWithSuffix("Tests/FooTests/Bar.swift") + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("sources/foo/bar.swift") + assert.False(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("Tests/FooTests/Bar.swift") + assert.True(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("tests/foo_tests/bar.swift") + assert.True(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("Tests/FooTests/Chicken/Bar.swift") + assert.True(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("tests/foo_tests/chicken/bar.swift") + assert.True(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("Tests/Bar.swift") + assert.True(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("tests/bar.swift") + assert.True(t, actual) +} + +func TestFilePathSuffixes(t *testing.T) { + actual := swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("Sources/Foo/Bar.swift") + assert.False(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("sources/foo/bar.swift") + assert.False(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("Tests/FooTests/Bar.swift") + assert.True(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("tests/foo_tests/bar.swift") + assert.True(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("Tests/FooTests/Chicken/Bar.swift") + assert.True(t, actual) + + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("tests/foo_tests/chicken/bar.swift") assert.True(t, actual) - actual = swiftpkg.TestDirSuffixes.IsUnderDirWithSuffix("Tests/FooTests/Chicken/Bar.swift") + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("Tests/Bar.swift") assert.True(t, actual) - actual = swiftpkg.TestDirSuffixes.IsUnderDirWithSuffix("Tests/Bar.swift") + actual = swiftpkg.TestDirectoryPathSuffixes.IsUnderDirWithSuffix("tests/bar.swift") assert.True(t, actual) } func TestSwiftFileInfos(t *testing.T) { tests := []struct { - msg string - fileInfos swiftpkg.SwiftFileInfos - exp bool + msg string + fileInfos swiftpkg.SwiftFileInfos + exp bool }{ { msg: "no files have objc directive", diff --git a/go.mod b/go.mod index 916bb3b1f..6db2c7070 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/deckarep/golang-set/v2 v2.3.1 github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 + golang.org/x/text v0.3.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 61571e1fa..84456aba0 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=