-
Notifications
You must be signed in to change notification settings - Fork 17
/
config.go
495 lines (414 loc) · 16.2 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
package gazelle
import (
"fmt"
"log"
"os"
"path"
"strings"
gazelle "aspect.build/cli/gazelle/common"
"github.com/bazelbuild/bazel-gazelle/label"
"github.com/bmatcuk/doublestar/v4"
"github.com/emirpasic/gods/maps/linkedhashmap"
"github.com/emirpasic/gods/sets/treeset"
)
// Directives. Keep in sync with documentation in /docs/cli/help/directives.md
const (
// Directive_TypeScriptExtension represents the directive that controls whether
// this TypeScript generation is enabled or not. Sub-packages inherit this value.
// Can be either "enabled" or "disabled". Defaults to "enabled".
Directive_TypeScriptExtension = "js"
// En/disable ts_proto_library generation
Directive_TypeScriptProtoExtension = "js_proto"
// En/disable ts_config generation
Directive_TypeScriptConfigExtension = "js_tsconfig"
// Directive_GenerationMode represents the directive that controls the BUILD generation
// mode. See below for the GenerationModeType constants.
Directive_GenerationMode = "js_generation_mode"
// The pnpm-lock.yaml file.
Directive_Lockfile = "js_pnpm_lockfile"
// Directive_IgnoreImports represents the directive that controls the
// ignored dependencies from the generated targets.
// Sub-packages extend this value.
// Ignored imports may be file path globs.
Directive_IgnoreImports = "js_ignore_imports"
// Directive_Resolve represents a gazelle:resolve state which supports globs.
Directive_Resolve = "js_resolve"
// Directive_ValidateImportStatements represents the directive that controls
// whether the TypeScript import statements should be validated.
Directive_ValidateImportStatements = "js_validate_import_statements"
// Directive_LibraryNamingConvention represents the directive that controls the
// ts_project naming convention. It interpolates {dirname} with the
// Bazel package name. E.g. if the Bazel package name is `foo`, setting this
// to `{dirname}_my_lib` would render to `foo_my_lib`.
Directive_LibraryNamingConvention = "js_project_naming_convention"
// The target name for npm_package() rules. See npm_translate_lock(npm_package_target_name)
Directive_NpmPackageNameConvention = "js_npm_package_target_name"
// Directive_TestsNamingConvention represents the directive that controls the ts_project test
// naming convention. See js_project_naming_convention for more info on
// the package name interpolation.
Directive_TestsNamingConvention = "js_tests_naming_convention"
// The glob for the main library files, excludes files matching Directive_TestFiles.
Directive_LibraryFiles = "js_files"
// The glob for test files.
Directive_TestFiles = "js_test_files"
// Add a glob to a custom library type
Directive_CustomTargetFiles = "js_custom_files"
// Add a glob to a custom library type
Directive_CustomTargetTestFiles = "js_custom_test_files"
)
// GenerationModeType represents one of the generation modes.
type GenerationModeType string
// Generation modes
const (
GenerationModeNone GenerationModeType = "none"
// GenerationModeDirectory defines the mode in which a coarse-grained target will
// be generated for each sub-directory.
GenerationModeDirectory GenerationModeType = "directory"
)
const (
DefaultNpmLinkAllTargetName = "node_modules"
TargetNameDirectoryVar = "{dirname}"
DefaultLibraryName = TargetNameDirectoryVar
DefaultTestsName = TargetNameDirectoryVar + "_tests"
NpmPackageContentName = TargetNameDirectoryVar + "_lib"
// The default name for ts_proto_library rules, where {proto_library} is the name of the proto target
ProtoNameVar = "{proto_library}"
DefaultProtoLibraryName = ProtoNameVar + "_ts"
// The suffix added to the end of a target being wrapped in a package.
PackageSrcSuffix = "_lib"
// The default should align with the rules_js default npm_translate_lock(npm_package_target_name)
DefaultNpmPackageTargetName = TargetNameDirectoryVar
)
type TargetGroup struct {
// The target name template of the target group.
// Supports {dirname} variable.
name string
// Custom glob patterns for sources.
customSources []string
// Default glob patterns for sources. Only set for pre-configured targets.
defaultSources []string
// If the targets are always testonly
testonly bool
}
var DefaultSourceGlobs = []*TargetGroup{
&TargetGroup{
name: DefaultLibraryName,
customSources: []string{},
defaultSources: []string{fmt.Sprintf("**/*.{%s}", strings.Join(typescriptFileExtensionsArray, ","))},
testonly: false,
},
&TargetGroup{
name: DefaultTestsName,
customSources: []string{},
defaultSources: []string{fmt.Sprintf("**/*.{spec,test}.{%s}", strings.Join(typescriptFileExtensionsArray, ","))},
testonly: true,
},
}
var (
// Set of supported source file extensions.
typescriptFileExtensions = treeset.NewWithStringComparator("ts", "tsx", "mts", "cts")
// Array of typescriptFileExtensions.
typescriptFileExtensionsArray = []string{"ts", "tsx", "mts", "cts"}
// Importable declaration files that are not compiled
declarationFileExtensionsArray = []string{"d.ts", "d.mts", "d.cts"}
// Supported javascript file extensions that can be sources along with dts files.
javascriptFileExtensions = treeset.NewWithStringComparator("js")
// Supported data file extensions that typescript can reference.
dataFileExtensions = treeset.NewWithStringComparator("json")
// The default TypeScript config file name
defaultTsConfig = "tsconfig.json"
)
// ValidationMode represents what should happen when validation errors are found.
type ValidationMode int
const (
// ValidationError has gazelle produce an error when validation errors are found.
ValidationError ValidationMode = iota
// ValidationWarn has gazelle print warnings when validation errors are found.
ValidationWarn
// ValidationOff has gazelle swallow validation errors silently.
ValidationOff
)
// JsGazelleConfig represents a config extension for a specific Bazel package.
type JsGazelleConfig struct {
rel string
parent *JsGazelleConfig
generationEnabled bool
generationMode GenerationModeType
protoGenerationEnabled bool
tsconfigGenerationEnabled bool
pnpmLockPath string
excludes []string
ignoreDependencies []string
resolves *linkedhashmap.Map
validateImportStatements ValidationMode
targets []*TargetGroup
// Generated rule names
npmLinkAllTargetName string
targetNamingOverrides map[string]string
npmPackageNamingConvention string
tsProtoLibraryName string
// Name/location of tsconfig files relative to BUILDs
tsconfigName string
}
// New creates a new JsGazelleConfig.
func newRootConfig() *JsGazelleConfig {
return &JsGazelleConfig{
rel: "",
generationEnabled: true,
protoGenerationEnabled: true,
tsconfigGenerationEnabled: false,
generationMode: GenerationModeDirectory,
pnpmLockPath: "pnpm-lock.yaml",
excludes: make([]string, 0),
ignoreDependencies: make([]string, 0),
resolves: linkedhashmap.New(),
validateImportStatements: ValidationError,
npmLinkAllTargetName: DefaultNpmLinkAllTargetName,
npmPackageNamingConvention: DefaultNpmPackageTargetName,
targetNamingOverrides: make(map[string]string),
tsProtoLibraryName: DefaultProtoLibraryName,
targets: DefaultSourceGlobs[:],
tsconfigName: defaultTsConfig,
}
}
func (g *TargetGroup) newChild() *TargetGroup {
sources := g.customSources
if len(sources) == 0 {
sources = g.defaultSources
}
return &TargetGroup{
name: g.name,
customSources: make([]string, 0),
defaultSources: sources,
testonly: g.testonly,
}
}
// NewChild creates a new child JsGazelleConfig. It inherits desired values from the
// current JsGazelleConfig and sets itself as the parent to the child.
func (c *JsGazelleConfig) NewChild(childPath string) *JsGazelleConfig {
cCopy := *c
cCopy.rel = childPath
cCopy.parent = c
cCopy.excludes = make([]string, 0)
cCopy.ignoreDependencies = make([]string, 0)
cCopy.resolves = linkedhashmap.New()
// Copy the targets, any modifications will be local.
cCopy.targets = make([]*TargetGroup, 0, len(c.targets))
for _, target := range c.targets {
cCopy.targets = append(cCopy.targets, target.newChild())
}
// Copy the overrides, any modifications will be local.
cCopy.targetNamingOverrides = make(map[string]string)
for k, v := range c.targetNamingOverrides {
cCopy.targetNamingOverrides[k] = v
}
return &cCopy
}
// Render a target name by applying target name tmeplate vars
func (c *JsGazelleConfig) renderTargetName(baseName, packageName string) string {
return strings.ReplaceAll(baseName, TargetNameDirectoryVar, packageName)
}
// AddExcludedPattern adds a glob pattern parsed from the standard gazelle:exclude directive.
func (c *JsGazelleConfig) AddExcludedPattern(pattern string) {
c.excludes = append(c.excludes, pattern)
}
// Determine if the file path is ignored based on the current configuration.
func (c *JsGazelleConfig) IsFileExcluded(fileRelPath string) bool {
return gazelle.IsFileExcluded(c.rel, fileRelPath, c.excludes)
}
// SetGenerationEnabled sets whether the extension is enabled or not.
func (c *JsGazelleConfig) SetGenerationEnabled(enabled bool) {
c.generationEnabled = enabled
}
// GenerationEnabled returns whether the extension is enabled or not.
func (c *JsGazelleConfig) GenerationEnabled() bool {
return c.generationEnabled
}
func (c *JsGazelleConfig) SetTsConfigGenerationEnabled(enabled bool) {
c.tsconfigGenerationEnabled = enabled
}
// If ts_config extension is enabled.
func (c *JsGazelleConfig) GetTsConfigGenerationEnabled() bool {
return c.tsconfigGenerationEnabled
}
func (c *JsGazelleConfig) SetProtoGenerationEnabled(enabled bool) {
c.protoGenerationEnabled = enabled
}
// If ts_proto_library extension is enabled.
func (c *JsGazelleConfig) ProtoGenerationEnabled() bool {
return c.generationEnabled && c.protoGenerationEnabled
}
// Set the pnpm-workspace.yaml file path.
func (c *JsGazelleConfig) SetPnpmLockfile(pnpmLockPath string) {
c.pnpmLockPath = pnpmLockPath
}
func (c *JsGazelleConfig) PnpmLockfile() string {
return c.pnpmLockPath
}
// Adds a dependency to the list of ignored dependencies for
// a given package. Adding an ignored dependency to a package also makes it
// ignored on a subpackage.
func (c *JsGazelleConfig) AddIgnoredImport(impGlob string) {
c.ignoreDependencies = append(c.ignoreDependencies, impGlob)
}
// Checks if a import is ignored in the given package or
// in one of the parent packages up to the workspace root.
func (c *JsGazelleConfig) IsImportIgnored(impt string) bool {
config := c
for config != nil {
for _, glob := range config.ignoreDependencies {
m, e := doublestar.Match(glob, impt)
if e != nil {
fmt.Println("Ignore import glob error: ", e)
return false
}
if m {
return true
}
}
config = config.parent
}
return false
}
func (c *JsGazelleConfig) AddResolve(imprt string, label *label.Label) {
c.resolves.Put(imprt, label)
}
func (c *JsGazelleConfig) GetResolution(imprt string) *label.Label {
config := c
for config != nil {
for _, glob := range config.resolves.Keys() {
m, e := doublestar.Match(glob.(string), imprt)
if e != nil {
fmt.Println("Resolve import glob error: ", e)
return nil
}
if m {
resolveLabel, _ := config.resolves.Get(glob)
return resolveLabel.(*label.Label)
}
}
config = config.parent
}
return nil
}
// SetValidateImportStatements sets the ValidationMode for TypeScript import
// statements. It throws an error if this is set multiple times, i.e. if the
// directive is specified multiple times in the Bazel workspace.
func (c *JsGazelleConfig) SetValidateImportStatements(mode ValidationMode) {
c.validateImportStatements = mode
}
// ValidateImportStatements returns whether the TypeScript import statements should
// be validated or not. If this option was not explicitly specified by the user,
// it defaults to true.
func (c *JsGazelleConfig) ValidateImportStatements() ValidationMode {
return c.validateImportStatements
}
// SetGenerationMode sets whether coarse-grained targets should be
// generated or not.
func (c *JsGazelleConfig) SetGenerationMode(generationMode GenerationModeType) {
c.generationMode = generationMode
}
// GenerationMode returns whether coarse-grained targets should be
// generated or not.
func (c *JsGazelleConfig) GenerationMode() GenerationModeType {
return c.generationMode
}
// SetLibraryNamingConvention sets the ts_project target naming convention.
func (c *JsGazelleConfig) SetLibraryNamingConvention(libraryNamingConvention string) {
c.targetNamingOverrides[DefaultLibraryName] = libraryNamingConvention
}
// SetTestsNamingLibraryConvention sets the ts_project test target naming convention.
func (c *JsGazelleConfig) SetTestsNamingLibraryConvention(testsNamingConvention string) {
c.targetNamingOverrides[DefaultTestsName] = testsNamingConvention
}
func (c *JsGazelleConfig) MapTargetName(name string) string {
if c.targetNamingOverrides[name] != "" {
return c.targetNamingOverrides[name]
}
return name
}
func (c *JsGazelleConfig) SetNpmPackageNamingConvention(testsNamingConvention string) {
c.npmPackageNamingConvention = testsNamingConvention
}
func (c *JsGazelleConfig) RenderNpmPackageTargetName(packageName string) string {
return c.renderTargetName(c.npmPackageNamingConvention, packageName)
}
// The library name when wrapped within an npm package.
func (c *JsGazelleConfig) RenderNpmSourceLibraryName(npmPackageName string) string {
return npmPackageName + PackageSrcSuffix
}
func (c *JsGazelleConfig) SetTsProtoLibraryNamingConvention(tsProtoLibraryName string) {
c.tsProtoLibraryName = tsProtoLibraryName
}
func (c *JsGazelleConfig) RenderTsProtoLibraryName(protoLibraryName string) string {
return strings.ReplaceAll(c.tsProtoLibraryName, ProtoNameVar, protoLibraryName)
}
func (c *JsGazelleConfig) RenderTsConfigName(tsconfigName string) string {
return strings.ReplaceAll(strings.TrimRight(path.Base(tsconfigName), ".json"), ".", "_")
}
// Returns the ts_project target name by performing all substitutions.
func (c *JsGazelleConfig) RenderSourceTargetName(groupName, packageName string, isNpmPackage bool) string {
ruleName := c.renderTargetName(c.MapTargetName(groupName), packageName)
// The default library name changes when alongside an npm_package rule
if isNpmPackage && groupName == DefaultLibraryName {
ruleName = c.RenderNpmSourceLibraryName(ruleName)
}
return ruleName
}
// AddTargetGlob sets the glob used to find source files for the specified target
func (c *JsGazelleConfig) AddTargetGlob(target, fileGlob string, isTestOnly bool) {
c.addTargetGlob(target, fileGlob, isTestOnly)
}
// Determine if and which target the passed file belongs in.
func (c *JsGazelleConfig) GetSourceTarget(filePath string) *TargetGroup {
// Rules are evaluated in reverse order, so we want to
for i := len(c.targets) - 1; i >= 0; i-- {
target := c.targets[i]
sources := target.customSources
// Fallback to default sources if no sources are specified
if len(sources) == 0 {
sources = target.defaultSources
}
for _, glob := range sources {
m, e := doublestar.Match(glob, filePath)
if e != nil {
log.Fatalf("Target (%s) glob error: %v", target.name, e)
os.Exit(1)
}
if m {
return target
}
}
}
return nil
}
// Return a list of all source groups for this config, including primary library + tests.
// The list is the source of truth for the order of groups
func (c *JsGazelleConfig) GetSourceTargets() []*TargetGroup {
return c.targets
}
func (c *JsGazelleConfig) addTargetGlob(targetName, glob string, isTestOnly bool) {
// Update existing target with the same name
for _, target := range c.targets {
if target.name == targetName {
if target.testonly != isTestOnly {
targetWord := "non-test"
overrideWord := "test"
if target.testonly {
targetWord = "test"
overrideWord = "non-test"
}
log.Fatalf("Custom %s target %s:%s can not override %s target", targetWord, c.rel, targetName, overrideWord)
os.Exit(1)
}
target.customSources = append(target.customSources, glob)
return
}
}
// ... otherwise create a new target
c.targets = append(c.targets, &TargetGroup{
name: targetName,
customSources: []string{glob},
testonly: isTestOnly,
})
}