diff --git a/language/js/config.go b/language/js/config.go index 79fb3efff..d7f0a7af2 100644 --- a/language/js/config.go +++ b/language/js/config.go @@ -60,6 +60,9 @@ const ( Directive_TestFiles = "js_test_files" // The directive controlling whether asset collection is enabled for import types. Directive_Assets = "js_assets" + // Directive_RuleKind overrides the generated rule kind for a specific target group. + // Syntax: # gazelle:js_rule_kind [kind_name] + Directive_RuleKind = "js_rule_kind" // TODO(deprecated): remove - replaced with js_files [group] Directive_CustomTargetFiles = "js_custom_files" @@ -111,6 +114,10 @@ type TargetGroup struct { // If the targets are always testonly testonly bool + + // Per-group rule kind override. Empty string means use the default + // (ts_project or js_library based on source content). + ruleKind string } var DefaultSourceGlobs = []*TargetGroup{ @@ -235,6 +242,7 @@ func (g *TargetGroup) newChild() *TargetGroup { defaultSources: sources, testonly: g.testonly, visibility: g.visibility, + ruleKind: g.ruleKind, } } @@ -287,6 +295,25 @@ func (c *JsGazelleConfig) SetVisibility(groupName string, visLabels []string) er return nil } +func (c *JsGazelleConfig) SetRuleKind(groupName, kindName string) error { + target := c.GetSourceTarget(groupName) + if target == nil { + return fmt.Errorf("Target group %q not found in %q", groupName, c.rel) + } + + if kindName != "" { + switch kindName { + case TsProjectKind, JsLibraryKind, JsTestKind: + // valid source target kinds + default: + return fmt.Errorf("unknown rule kind %q; must be one of: ts_project, js_library, js_test. Use map_kind to remap to a custom rule", kindName) + } + } + + target.ruleKind = kindName + return nil +} + // SetGenerationEnabled sets whether the extension is enabled or not. func (c *JsGazelleConfig) SetGenerationEnabled(enabled bool) { c.generationEnabled = enabled diff --git a/language/js/configure.go b/language/js/configure.go index e85def298..76b14408c 100644 --- a/language/js/configure.go +++ b/language/js/configure.go @@ -56,6 +56,7 @@ func (ts *typeScriptLang) KnownDirectives() []string { Directive_LibraryFiles, Directive_TestFiles, Directive_Assets, + Directive_RuleKind, // TODO(deprecated): remove Directive_CustomTargetFiles, @@ -257,6 +258,27 @@ func (ts *typeScriptLang) readDirectives(c *config.Config, rel string, f *rule.F // Overwrite any inherited configuration for asset collection. config.SetCollectAssetsFrom(assetKinds...) + case Directive_RuleKind: + parts := strings.Fields(value) + if len(parts) == 1 { + // Reset to default (auto-detect from source content) + err := config.SetRuleKind(parts[0], "") + if err != nil { + common.MisconfiguredErrorf(c, "Invalid %s: %v", Directive_RuleKind, err) + return + } + } else if len(parts) == 2 { + err := config.SetRuleKind(parts[0], parts[1]) + if err != nil { + common.MisconfiguredErrorf(c, "Invalid %s: %v", Directive_RuleKind, err) + return + } + } else { + common.MisconfiguredErrorf(c, "invalid value for directive %q: %s: expected 'group_name [kind_name]'", + Directive_RuleKind, d.Value) + return + } + // TODO: remove, deprecated case Directive_CustomTargetFiles: fmt.Fprintf(os.Stderr, "DEPRECATED: %s is deprecated, use %s\n", Directive_CustomTargetFiles, Directive_LibraryFiles) diff --git a/language/js/generate.go b/language/js/generate.go index ad05d87d9..f356822bf 100644 --- a/language/js/generate.go +++ b/language/js/generate.go @@ -484,7 +484,11 @@ func hasTranspiledSources(sourceFiles *treeset.Set[string]) bool { func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel string, tsconfig *typescript.TsConfig, args language.GenerateArgs, group *TargetGroup, targetName string, sourceFiles, genFiles, dataFiles []string, result *language.GenerateResult) (*rule.Rule, error) { // Check for name-collisions with the rule being generated. - colError := ruleUtils.CheckCollisionErrors(targetName, TsProjectKind, sourceRuleKinds, args) + expectedKind := TsProjectKind + if group.ruleKind != "" { + expectedKind = group.ruleKind + } + colError := ruleUtils.CheckCollisionErrors(targetName, expectedKind, sourceRuleKinds, args) if colError != nil { return nil, fmt.Errorf("%v "+ "Use the '# aspect:%s' directive to change the naming convention.\n\n"+ @@ -569,9 +573,14 @@ func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel strin // A rule of the same name might already exist existing := ruleUtils.GetFileRuleByName(args, targetName) - ruleKind := TsProjectKind + defaultKind := TsProjectKind if !hasTranspiledSources(info.sources) { - ruleKind = JsLibraryKind + defaultKind = JsLibraryKind + } + + ruleKind := defaultKind + if group.ruleKind != "" { + ruleKind = group.ruleKind } sourceRule := rule.NewRule(ruleKind, targetName) @@ -604,7 +613,7 @@ func (ts *typeScriptLang) addProjectRule(cfg *JsGazelleConfig, tsconfigRel strin sourceRule.DelAttr("assets") } - if group.testonly { + if group.testonly && ruleKind != JsTestKind { sourceRule.SetAttr("testonly", true) } diff --git a/language/js/kinds.go b/language/js/kinds.go index fc3a48fcb..e608ea7fc 100644 --- a/language/js/kinds.go +++ b/language/js/kinds.go @@ -11,6 +11,7 @@ const ( TsProjectKind = "ts_project" TsProtoLibraryKind = "ts_proto_library" JsLibraryKind = "js_library" + JsTestKind = "js_test" JsBinaryKind = "js_binary" JsRunBinaryKind = "js_run_binary" TsConfigKind = "ts_config" @@ -23,7 +24,7 @@ const ( NpmRepositoryName = "npm" ) -var sourceRuleKinds = treeset.NewWith(strings.Compare, TsProjectKind, JsLibraryKind, TsProtoLibraryKind) +var sourceRuleKinds = treeset.NewWith(strings.Compare, TsProjectKind, JsLibraryKind, JsTestKind, TsProtoLibraryKind) // Kinds returns a map that maps rule names (kinds) and information on how to // match and merge attributes that may be found in rules of those kinds. @@ -78,6 +79,19 @@ var tsKinds = map[string]rule.KindInfo{ "deps": true, }, }, + JsTestKind: { + MatchAny: false, + NonEmptyAttrs: map[string]bool{ + "srcs": true, + }, + SubstituteAttrs: map[string]bool{}, + MergeableAttrs: map[string]bool{ + "srcs": true, + }, + ResolveAttrs: map[string]bool{ + "deps": true, + }, + }, JsBinaryKind: { MatchAny: false, NonEmptyAttrs: map[string]bool{ @@ -183,7 +197,7 @@ func (h *typeScriptLang) ApparentLoads(moduleToApparentName func(string) string) { Name: "@" + jsModName + "//js:defs.bzl", Symbols: []string{ - JsLibraryKind, JsBinaryKind, JsRunBinaryKind, + JsLibraryKind, JsTestKind, JsBinaryKind, JsRunBinaryKind, }, }, diff --git a/language/js/resolve.go b/language/js/resolve.go index 78ca5982f..ea6ac4511 100644 --- a/language/js/resolve.go +++ b/language/js/resolve.go @@ -48,9 +48,7 @@ func (ts *typeScriptLang) Imports(c *config.Config, r *rule.Rule, f *rule.File) return ts.protoLibraryImports(r, f) case TsConfigKind: return ts.tsconfigImports(r, f) - case TsProjectKind: - fallthrough - case JsLibraryKind: + case TsProjectKind, JsLibraryKind, JsTestKind: return ts.sourceFileImports(c, r, f) } return nil @@ -296,7 +294,7 @@ func (ts *typeScriptLang) Resolve( // TsProject imports are resolved as deps switch r.Kind() { - case TsProjectKind, JsLibraryKind, TsConfigKind, TsProtoLibraryKind: + case TsProjectKind, JsLibraryKind, JsTestKind, TsConfigKind, TsProtoLibraryKind: deps := common.NewLabelSet(from) // Support this target representing a project or a package diff --git a/language/js/tests/rule_kind_custom_group/BUILD.in b/language/js/tests/rule_kind_custom_group/BUILD.in new file mode 100644 index 000000000..9960b27ae --- /dev/null +++ b/language/js/tests/rule_kind_custom_group/BUILD.in @@ -0,0 +1,3 @@ +# gazelle:js_test_files e2e *.e2e.ts +# gazelle:js_rule_kind e2e js_test +# gazelle:map_kind js_test jest_test //:defs.bzl diff --git a/language/js/tests/rule_kind_custom_group/BUILD.out b/language/js/tests/rule_kind_custom_group/BUILD.out new file mode 100644 index 000000000..3aa108a7b --- /dev/null +++ b/language/js/tests/rule_kind_custom_group/BUILD.out @@ -0,0 +1,24 @@ +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") +load("//:defs.bzl", "jest_test") + +# gazelle:js_test_files e2e *.e2e.ts +# gazelle:js_rule_kind e2e js_test +# gazelle:map_kind js_test jest_test //:defs.bzl + +ts_project( + name = "rule_kind_custom_group", + srcs = ["main.ts"], +) + +ts_project( + name = "rule_kind_custom_group_tests", + testonly = True, + srcs = ["main.spec.ts"], + deps = [":rule_kind_custom_group"], +) + +jest_test( + name = "e2e", + srcs = ["main.e2e.ts"], + deps = [":rule_kind_custom_group"], +) diff --git a/language/js/tests/rule_kind_custom_group/WORKSPACE b/language/js/tests/rule_kind_custom_group/WORKSPACE new file mode 100644 index 000000000..4c990c0d4 --- /dev/null +++ b/language/js/tests/rule_kind_custom_group/WORKSPACE @@ -0,0 +1,2 @@ +# This is a Bazel workspace for the Gazelle test data. +workspace(name = "rule_kind_custom_group") diff --git a/language/js/tests/rule_kind_custom_group/main.e2e.ts b/language/js/tests/rule_kind_custom_group/main.e2e.ts new file mode 100644 index 000000000..e82a9533d --- /dev/null +++ b/language/js/tests/rule_kind_custom_group/main.e2e.ts @@ -0,0 +1,3 @@ +import { A } from './main'; + +console.log('e2e', A); diff --git a/language/js/tests/rule_kind_custom_group/main.spec.ts b/language/js/tests/rule_kind_custom_group/main.spec.ts new file mode 100644 index 000000000..ffb2f8ed6 --- /dev/null +++ b/language/js/tests/rule_kind_custom_group/main.spec.ts @@ -0,0 +1,3 @@ +import { A } from './main'; + +console.log(A); diff --git a/language/js/tests/rule_kind_custom_group/main.ts b/language/js/tests/rule_kind_custom_group/main.ts new file mode 100644 index 000000000..dc9f4a98d --- /dev/null +++ b/language/js/tests/rule_kind_custom_group/main.ts @@ -0,0 +1 @@ +export const A = 'hello'; diff --git a/language/js/tests/rule_kind_override/BUILD.in b/language/js/tests/rule_kind_override/BUILD.in new file mode 100644 index 000000000..dec8d6225 --- /dev/null +++ b/language/js/tests/rule_kind_override/BUILD.in @@ -0,0 +1 @@ +# gazelle:js_rule_kind {dirname}_tests js_test diff --git a/language/js/tests/rule_kind_override/BUILD.out b/language/js/tests/rule_kind_override/BUILD.out new file mode 100644 index 000000000..43d19766d --- /dev/null +++ b/language/js/tests/rule_kind_override/BUILD.out @@ -0,0 +1,15 @@ +load("@aspect_rules_js//js:defs.bzl", "js_test") +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") + +# gazelle:js_rule_kind {dirname}_tests js_test + +ts_project( + name = "rule_kind_override", + srcs = ["main.ts"], +) + +js_test( + name = "rule_kind_override_tests", + srcs = ["main.spec.ts"], + deps = [":rule_kind_override"], +) diff --git a/language/js/tests/rule_kind_override/WORKSPACE b/language/js/tests/rule_kind_override/WORKSPACE new file mode 100644 index 000000000..0c30bf5bd --- /dev/null +++ b/language/js/tests/rule_kind_override/WORKSPACE @@ -0,0 +1,2 @@ +# This is a Bazel workspace for the Gazelle test data. +workspace(name = "rule_kind_override") diff --git a/language/js/tests/rule_kind_override/main.spec.ts b/language/js/tests/rule_kind_override/main.spec.ts new file mode 100644 index 000000000..ffb2f8ed6 --- /dev/null +++ b/language/js/tests/rule_kind_override/main.spec.ts @@ -0,0 +1,3 @@ +import { A } from './main'; + +console.log(A); diff --git a/language/js/tests/rule_kind_override/main.ts b/language/js/tests/rule_kind_override/main.ts new file mode 100644 index 000000000..dc9f4a98d --- /dev/null +++ b/language/js/tests/rule_kind_override/main.ts @@ -0,0 +1 @@ +export const A = 'hello'; diff --git a/language/js/tests/rule_kind_override/sub/BUILD.in b/language/js/tests/rule_kind_override/sub/BUILD.in new file mode 100644 index 000000000..e69de29bb diff --git a/language/js/tests/rule_kind_override/sub/BUILD.out b/language/js/tests/rule_kind_override/sub/BUILD.out new file mode 100644 index 000000000..aa758bccd --- /dev/null +++ b/language/js/tests/rule_kind_override/sub/BUILD.out @@ -0,0 +1,13 @@ +load("@aspect_rules_js//js:defs.bzl", "js_test") +load("@aspect_rules_ts//ts:defs.bzl", "ts_project") + +ts_project( + name = "sub", + srcs = ["lib.ts"], +) + +js_test( + name = "sub_tests", + srcs = ["lib.spec.ts"], + deps = [":sub"], +) diff --git a/language/js/tests/rule_kind_override/sub/lib.spec.ts b/language/js/tests/rule_kind_override/sub/lib.spec.ts new file mode 100644 index 000000000..175a7dc73 --- /dev/null +++ b/language/js/tests/rule_kind_override/sub/lib.spec.ts @@ -0,0 +1,3 @@ +import { B } from './lib'; + +console.log(B); diff --git a/language/js/tests/rule_kind_override/sub/lib.ts b/language/js/tests/rule_kind_override/sub/lib.ts new file mode 100644 index 000000000..bfe029246 --- /dev/null +++ b/language/js/tests/rule_kind_override/sub/lib.ts @@ -0,0 +1 @@ +export const B = 'world';