• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openconfig / ygot / 16390939476

19 Jul 2025 05:14PM UTC coverage: 88.744% (-0.07%) from 88.813%
16390939476

push

github

web-flow
Add skip_deprecated and skip_obsolete flags to ygot (#1037)

* Add SkipDeprecated and SkipObsolete flags to ygot/ygen

* Extend to individual deprecated/obsolete leaf fields within containers

* Add helper functions isDeprecated(yang.Node) and isObsolete(yang.Node)

35 of 51 new or added lines in 2 files covered. (68.63%)

2 existing lines in 1 file now uncovered.

13986 of 15760 relevant lines covered (88.74%)

485.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.43
/ygen/codegen.go
1
// Copyright 2017 Google Inc.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
// Package ygen contains a library and base configuration options that can be
16
// extended to generate language-specific structs from a YANG model.
17
// The Goyang parsing library is used to parse YANG. The output can consider
18
// OpenConfig-specific conventions such that the schema is compressed.
19
// The output of this library is an intermediate representation (IR) designed
20
// to reduce the need for working with the Goyang parsing library's AST.
21
package ygen
22

23
import (
24
        "errors"
25
        "fmt"
26
        "strings"
27

28
        log "github.com/golang/glog"
29
        "github.com/openconfig/goyang/pkg/yang"
30
        "github.com/openconfig/ygot/genutil"
31
        "github.com/openconfig/ygot/util"
32
        "github.com/openconfig/ygot/yangschema"
33

34
        "github.com/openconfig/ygot/internal/igenutil"
35

36
        gpb "github.com/openconfig/gnmi/proto/gnmi"
37
)
38

39
// isDeprecated checks if a YANG node has status "deprecated".
40
func isDeprecated(node yang.Node) bool {
8✔
41
        if node == nil {
8✔
NEW
42
                return false
×
NEW
43
        }
×
44
        var status *yang.Value
8✔
45
        switch n := node.(type) {
8✔
46
        case *yang.Container:
2✔
47
                status = n.Status
2✔
48
        case *yang.Leaf:
6✔
49
                status = n.Status
6✔
NEW
50
        case *yang.LeafList:
×
NEW
51
                status = n.Status
×
NEW
52
        case *yang.List:
×
NEW
53
                status = n.Status
×
NEW
54
        case *yang.Typedef:
×
NEW
55
                status = n.Status
×
56
        }
57
        return status != nil && status.Name == "deprecated"
8✔
58
}
59

60
// isObsolete checks if a YANG node has status "obsolete".
61
func isObsolete(node yang.Node) bool {
9✔
62
        if node == nil {
9✔
NEW
63
                return false
×
NEW
64
        }
×
65
        var status *yang.Value
9✔
66
        switch n := node.(type) {
9✔
67
        case *yang.Container:
4✔
68
                status = n.Status
4✔
69
        case *yang.Leaf:
5✔
70
                status = n.Status
5✔
NEW
71
        case *yang.LeafList:
×
NEW
72
                status = n.Status
×
NEW
73
        case *yang.List:
×
NEW
74
                status = n.Status
×
NEW
75
        case *yang.Typedef:
×
NEW
76
                status = n.Status
×
77
        }
78
        return status != nil && status.Name == "obsolete"
9✔
79
}
80

81
// ParseOpts contains parsing configuration for a given schema.
82
type ParseOpts struct {
83
        // IgnoreUnsupportedStatements ignores unsupported YANG statements when
84
        // parsing, such that they do not show up errors during IR generation.
85
        IgnoreUnsupportedStatements bool
86
        // ExcludeModules specifies any modules that are included within the set of
87
        // modules that should have code generated for them that should be ignored during
88
        // code generation. This is due to the fact that some schemas (e.g., OpenConfig
89
        // interfaces) currently result in overlapping entities (e.g., /interfaces).
90
        ExcludeModules []string
91
        // YANGParseOptions provides the options that should be handed to the
92
        // github.com/openconfig/goyang/pkg/yang library. These specify how the
93
        // input YANG files should be parsed.
94
        YANGParseOptions yang.Options
95
}
96

97
// TransformationOpts specifies transformations to the generated code with
98
// respect to the input schema.
99
type TransformationOpts struct {
100
        // CompressBehaviour specifies how the set of direct children of any
101
        // entry should determined. Specifically, whether compression is
102
        // enabled, and whether state fields in the schema should be excluded.
103
        CompressBehaviour genutil.CompressBehaviour
104
        // GenerateFakeRoot specifies whether an entity that represents the
105
        // root of the YANG schema tree should be generated in the generated
106
        // code.
107
        GenerateFakeRoot bool
108
        // FakeRootName specifies the name of the struct that should be generated
109
        // representing the root.
110
        FakeRootName string
111
        // ExcludeState specifies whether config false values should be
112
        // included in the generated code output. When set, all values that are
113
        // not writeable (i.e., config false) within the YANG schema and their
114
        // children are excluded from the generated code.
115
        ExcludeState bool
116
        // SkipEnumDeduplication specifies whether leaves of type 'enumeration' that
117
        // are used in multiple places in the schema should share a common type within
118
        // the generated code that is output by ygen. By default (false), a common type
119
        // is used.
120
        //
121
        // This behaviour is useful in scenarios where there is no difference between
122
        // two types, and the leaf is mirrored in a logical way (e.g., the OpenConfig
123
        // config/state split). For example:
124
        //
125
        // grouping foo-config {
126
        //        leaf enabled {
127
        //                type enumeration {
128
        //                        enum A;
129
        //                        enum B;
130
        //                        enum C;
131
        //                }
132
        //         }
133
        // }
134
        //
135
        //  container config { uses foo-config; }
136
        //  container state { uses foo-config; }
137
        //
138
        // will result in a single enumeration type (ModuleName_Config_Enabled) being
139
        // output when de-duplication is enabled.
140
        //
141
        // When it is disabled, two different enumerations (ModuleName_(State|Config)_Enabled)
142
        // will be output in the generated code.
143
        SkipEnumDeduplication bool
144
        // ShortenEnumLeafNames removes the module name from the name of
145
        // enumeration leaves.
146
        ShortenEnumLeafNames bool
147
        // EnumOrgPrefixesToTrim trims the organization name from the module
148
        // part of the name of enumeration leaves if there is a match.
149
        EnumOrgPrefixesToTrim []string
150
        // UseDefiningModuleForTypedefEnumNames uses the defining module name
151
        // to prefix typedef enumerated types instead of the module where the
152
        // typedef enumerated value is used.
153
        UseDefiningModuleForTypedefEnumNames bool
154
        // EnumerationsUseUnderscores specifies whether enumeration names
155
        // should use underscores between path segments.
156
        EnumerationsUseUnderscores bool
157
        // SkipDeprecated specifies whether YANG fields with status "deprecated"
158
        // should be excluded from the generated code output.
159
        SkipDeprecated bool
160
        // SkipObsolete specifies whether YANG fields with status "obsolete"
161
        // should be excluded from the generated code output.
162
        SkipObsolete bool
163
}
164

165
// yangEnum represents an enumerated type in YANG that is to be output in the
166
// Go code. The enumerated type may be a YANG 'identity' or enumeration.
167
type yangEnum struct {
168
        // name is the name of the enumeration or identity.
169
        name string
170
        // entry is the yang.Entry corresponding to the enumerated value.
171
        entry *yang.Entry
172
        // kind indicates the type of the enumeration.
173
        kind EnumeratedValueType
174
        // id is a unique synthesized key for the enumerated type.
175
        id string
176
}
177

178
// processModules takes a list of the filenames of YANG modules (yangFiles),
179
// and a list of paths in which included modules or submodules may be found,
180
// and returns a processed set of yang.Entry pointers which correspond to the
181
// generated code for the modules. If errors are returned during the Goyang
182
// processing of the modules, these errors are returned.
183
func processModules(yangFiles, includePaths []string, options yang.Options) ([]*yang.Entry, util.Errors) {
839✔
184
        // Initialise the set of YANG modules within the Goyang parsing package.
839✔
185
        moduleSet := yang.NewModules()
839✔
186
        // Propagate the options for the YANG library through to the parsing
839✔
187
        // code - this allows the calling binary to specify characteristics
839✔
188
        // of the YANG in a manner that we are transparent to.
839✔
189
        moduleSet.ParseOptions = options
839✔
190
        // Append the includePaths to the Goyang path variable, this ensures
839✔
191
        // that where a YANG module uses an 'include' statement to reference
839✔
192
        // another module, then Goyang can find this module to process.
839✔
193
        for _, path := range includePaths {
950✔
194
                moduleSet.AddPath(path)
111✔
195
        }
111✔
196

197
        var errs util.Errors
839✔
198
        for _, name := range yangFiles {
1,728✔
199
                errs = util.AppendErr(errs, moduleSet.Read(name))
889✔
200
        }
889✔
201

202
        if errs != nil {
839✔
203
                return nil, errs
×
204
        }
×
205

206
        if errs := moduleSet.Process(); errs != nil {
839✔
207
                return nil, errs
×
208
        }
×
209

210
        // Deduplicate the modules that are to be processed.
211
        var modNames []string
839✔
212
        mods := make(map[string]*yang.Module)
839✔
213
        for _, m := range moduleSet.Modules {
2,060✔
214
                if mods[m.Name] == nil {
2,398✔
215
                        mods[m.Name] = m
1,177✔
216
                        modNames = append(modNames, m.Name)
1,177✔
217
                }
1,177✔
218
        }
219

220
        // Process the ASTs that have been generated for the modules using the Goyang ToEntry
221
        // routines.
222
        entries := []*yang.Entry{}
839✔
223
        for _, modName := range modNames {
2,014✔
224
                entry := yang.ToEntry(mods[modName])
1,175✔
225
                if errs := entry.GetErrors(); len(errs) > 0 {
1,176✔
226
                        return nil, util.Errors(errs)
1✔
227
                }
1✔
228
                entries = append(entries, entry)
1,174✔
229
        }
230
        return entries, nil
838✔
231
}
232

233
// mappedYANGDefinitions stores the entities extracted from a YANG schema that are to be mapped to
234
// entities within the generated code, or can be used to look up entities within the YANG schema.
235
type mappedYANGDefinitions struct {
236
        // directoryEntries is the set of entities that are to be mapped to directories (e.g.,
237
        // Go structs, proto messages) in the generated code. The map is keyed by the string
238
        // path to the directory in the YANG schema.
239
        directoryEntries map[string]*yang.Entry
240
        // enumEntries is the set of entities that contain an enumerated type within the input
241
        // YANG and are to be mapped to enumerated types in the output code. This consists of
242
        // leaves that are of type enumeration, identityref, or unions that contain either of
243
        // these types. The map is keyed by the string path to the entry in the YANG schema.
244
        enumEntries map[string]*yang.Entry
245
        // schematree is a copy of the YANG schema tree, containing only leaf
246
        // entries, such that schema paths can be referenced.
247
        schematree *yangschema.Tree
248
        // modules is the set of parsed YANG modules that are being processed as part of the
249
        // code generatio, expressed as a slice of yang.Entry pointers.
250
        modules []*yang.Entry
251
        // modelData stores the details of the set of modules that were parsed to produce
252
        // the code. It is optionally returned in the generated code.
253
        modelData []*gpb.ModelData
254
}
255

256
// mappedDefinitions finds the set of directory and enumeration entities
257
// that are mapped to objects within output code in a language agnostic manner.
258
// It takes:
259
//   - yangFiles: an input set of YANG schema files and the paths that
260
//   - includePaths: the set of paths that are to be searched for included or
261
//     imported YANG modules.
262
//   - opts: the current generator's configuration.
263
//
264
// It returns a mappedYANGDefinitions struct populated with the directory, enum
265
// entries in the input schemas as well as the calculated schema tree.
266
func mappedDefinitions(yangFiles, includePaths []string, opts IROptions) (*mappedYANGDefinitions, util.Errors) {
839✔
267
        modules, errs := processModules(yangFiles, includePaths, opts.ParseOptions.YANGParseOptions)
839✔
268
        if errs != nil {
840✔
269
                return nil, errs
1✔
270
        }
1✔
271

272
        // Build a map of excluded modules to simplify lookup.
273
        excluded := map[string]bool{}
838✔
274
        for _, e := range opts.ParseOptions.ExcludeModules {
861✔
275
                excluded[e] = true
23✔
276
        }
23✔
277

278
        // Extract the entities that are eligible to have code generated for
279
        // them from the modules that are provided as an argument.
280
        dirs := map[string]*yang.Entry{}
838✔
281
        enums := map[string]*yang.Entry{}
838✔
282
        var rootElems, treeElems []*yang.Entry
838✔
283
        for _, module := range modules {
2,012✔
284
                // Need to transform the AST based on compression behaviour.
1,174✔
285
                genutil.TransformEntry(module, opts.TransformationOptions.CompressBehaviour)
1,174✔
286

1,174✔
287
                errs = append(errs, findMappableEntities(module, dirs, enums, opts.ParseOptions.ExcludeModules, opts.TransformationOptions.CompressBehaviour.CompressEnabled(), opts.ParseOptions.IgnoreUnsupportedStatements, modules, opts.TransformationOptions)...)
1,174✔
288
                if module == nil {
1,174✔
289
                        errs = append(errs, errors.New("found a nil module in the returned module set"))
×
290
                        continue
×
291
                }
292

293
                for _, e := range module.Dir {
2,638✔
294
                        if !excluded[module.Name] {
2,905✔
295
                                rootElems = append(rootElems, e)
1,441✔
296
                        }
1,441✔
297
                        treeElems = append(treeElems, e)
1,464✔
298
                }
299
        }
300
        if errs != nil {
839✔
301
                return nil, errs
1✔
302
        }
1✔
303

304
        // Build the schematree for the modules provided - we build for all of the
305
        // root elements, since we might need to reference a part of the schema that
306
        // we are not outputting for leafref lookups.
307
        st, err := yangschema.BuildTree(treeElems)
837✔
308
        if err != nil {
837✔
309
                return nil, []error{err}
×
310
        }
×
311

312
        // If we were asked to generate a fake root entity, then go and find the top-level entities that
313
        // we were asked for.
314
        if opts.TransformationOptions.GenerateFakeRoot {
1,175✔
315
                if err := createFakeRoot(dirs, rootElems, opts.TransformationOptions.FakeRootName, opts.TransformationOptions.CompressBehaviour.CompressEnabled()); err != nil {
338✔
316
                        return nil, []error{err}
×
317
                }
×
318
        }
319

320
        // For all non-excluded modules, we store these to be
321
        // used as the schema tree.
322
        ms := []*yang.Entry{}
837✔
323
        for _, m := range modules {
2,009✔
324
                if _, ok := excluded[m.Name]; !ok {
2,321✔
325
                        ms = append(ms, m)
1,149✔
326
                }
1,149✔
327
        }
328

329
        modelData, err := util.FindModelData(modules)
837✔
330
        if err != nil {
837✔
331
                return nil, util.NewErrs(fmt.Errorf("cannot extract model data, %v", err))
×
332
        }
×
333

334
        return &mappedYANGDefinitions{
837✔
335
                directoryEntries: dirs,
837✔
336
                enumEntries:      enums,
837✔
337
                schematree:       st,
837✔
338
                modules:          ms,
837✔
339
                modelData:        modelData,
837✔
340
        }, nil
837✔
341
}
342

343
// findMappableEntities finds the descendants of a yang.Entry (e) that should be mapped in
344
// the generated code. The descendants that represent directories are appended to the dirs
345
// map (keyed by the schema path). Those that represent enumerated types (identityref, enumeration,
346
// unions containing these types, or typedefs containing these types) are appended to the
347
// enums map, which is again keyed by schema path. If any child of the entry is in a module
348
// defined in excludeModules, it is skipped. If compressPaths is set to true, then names are
349
// mapped with path compression enabled. The set of modules that the current code generation
350
// is processing is specified by the modules slice. This function returns slice of errors
351
// encountered during processing.
352
func findMappableEntities(e *yang.Entry, dirs map[string]*yang.Entry, enums map[string]*yang.Entry, excludeModules []string, compressPaths, ignoreUnsupportedStatements bool, modules []*yang.Entry, transformOpts TransformationOpts) util.Errors {
6,566✔
353
        // Skip entities who are defined within a module that we have been instructed
6,566✔
354
        // not to generate code for.
6,566✔
355
        for _, s := range excludeModules {
6,658✔
356
                for _, m := range modules {
262✔
357
                        if m.Name == s && m.Namespace().Name == e.Namespace().Name {
195✔
358
                                return nil
25✔
359
                        }
25✔
360
                }
361
        }
362

363
        var errs util.Errors
6,541✔
364
        for _, ch := range util.Children(e) {
18,613✔
365
                // Skip children with deprecated status if SkipDeprecated is enabled
12,072✔
366
                if transformOpts.SkipDeprecated && isDeprecated(ch.Node) {
12,074✔
367
                        continue // skip this entity
2✔
368
                }
369

370
                // Skip children with obsolete status if SkipObsolete is enabled
371
                if transformOpts.SkipObsolete && isObsolete(ch.Node) {
12,072✔
372
                        continue // skip this entity
2✔
373
                }
374

375
                switch {
12,068✔
376
                case ch.IsLeaf(), ch.IsLeafList():
6,597✔
377
                        // Leaves are not mapped as directories so do not map them unless we find
6,597✔
378
                        // something that will be an enumeration - so that we can deal with this
6,597✔
379
                        // as a top-level code entity.
6,597✔
380
                        if e := igenutil.MappableLeaf(ch); e != nil {
9,389✔
381
                                enums[ch.Path()] = e
2,792✔
382
                        }
2,792✔
383
                case util.IsConfigState(ch) && compressPaths:
1,613✔
384
                        // If this is a config or state container and we are compressing paths
1,613✔
385
                        // then we do not want to map this container - but we do want to map its
1,613✔
386
                        // children.
1,613✔
387
                        errs = util.AppendErrs(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, ignoreUnsupportedStatements, modules, transformOpts))
1,613✔
388
                case util.HasOnlyChild(ch) && util.Children(ch)[0].IsList() && compressPaths:
508✔
389
                        // This is a surrounding container for a list, and we are compressing
508✔
390
                        // paths, so we don't want to map it but again we do want to map its
508✔
391
                        // children.
508✔
392
                        errs = util.AppendErrs(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, ignoreUnsupportedStatements, modules, transformOpts))
508✔
393
                case util.IsChoiceOrCase(ch):
37✔
394
                        // Don't map for a choice or case node itself, and rather skip over it.
37✔
395
                        // However, we must walk each branch to find the first container that
37✔
396
                        // exists there (if one does) to provide a mapping.
37✔
397
                        nonchoice := util.FindFirstNonChoiceOrCase(ch)
37✔
398
                        for _, gch := range nonchoice {
100✔
399
                                // The first entry that is not a choice or case could be a leaf
63✔
400
                                // so we need to check whether it is an enumerated leaf that
63✔
401
                                // should have code generated for it.
63✔
402
                                if gch.IsLeaf() || gch.IsLeafList() {
122✔
403
                                        if e := igenutil.MappableLeaf(gch); e != nil {
63✔
404
                                                enums[e.Path()] = e
4✔
405
                                        }
4✔
406
                                        continue
59✔
407
                                }
408

409
                                if gch.IsContainer() || gch.IsList() {
8✔
410
                                        dirs[fmt.Sprintf("%s/%s", ch.Parent.Path(), gch.Name)] = gch
4✔
411
                                }
4✔
412
                                errs = util.AppendErrs(errs, findMappableEntities(gch, dirs, enums, excludeModules, compressPaths, ignoreUnsupportedStatements, modules, transformOpts))
4✔
413
                        }
414
                case ch.IsContainer(), ch.IsList():
3,237✔
415
                        dirs[ch.Path()] = ch
3,237✔
416
                        // Recurse down the tree.
3,237✔
417
                        errs = util.AppendErrs(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, ignoreUnsupportedStatements, modules, transformOpts))
3,237✔
418
                case ch.Kind == yang.AnyDataEntry:
36✔
419
                        continue
36✔
420
                default:
40✔
421
                        if ignoreUnsupportedStatements {
75✔
422
                                log.Infof("Unsupported statement type (%v) ignored: %s", ch.Kind, ch.Path())
35✔
423
                                continue
35✔
424
                        }
425
                        errs = util.AppendErr(errs, fmt.Errorf("unsupported statement type (%v) in findMappableEntities for %s", ch.Kind, ch.Path()))
5✔
426
                }
427
        }
428
        return errs
6,541✔
429
}
430

431
// findRootEntries finds the entities that are at the root of the YANG schema tree,
432
// and returns them.
433
func findRootEntries(structs map[string]*yang.Entry, compressPaths bool) map[string]*yang.Entry {
344✔
434
        rootEntries := map[string]*yang.Entry{}
344✔
435
        for n, s := range structs {
1,721✔
436
                pp := strings.Split(s.Path(), "/")
1,377✔
437
                switch len(pp) {
1,377✔
438
                case 3:
417✔
439
                        // Find all containers and lists that have a path of
417✔
440
                        // the form /module/entity-name regardless of whether
417✔
441
                        // when compression is enabled. In the case that we
417✔
442
                        // are compressing, then all invalid elements have
417✔
443
                        // already been compressed out of the schema by this
417✔
444
                        // stage.
417✔
445
                        if s.IsContainer() || s.IsList() {
834✔
446
                                rootEntries[n] = s
417✔
447
                        }
417✔
448
                case 4:
417✔
449
                        // If schema path compression is enabled then we need
417✔
450
                        // to find entities that might be one level deeper in the
417✔
451
                        // tree, for example, the /interfaces/interface list.
417✔
452
                        // Since we never expect a top-level 'state' or 'config'
417✔
453
                        // container, then it is only such lists that must be
417✔
454
                        // identified.
417✔
455
                        if compressPaths && s.IsList() {
461✔
456
                                rootEntries[n] = s
44✔
457
                        }
44✔
458
                }
459
        }
460
        return rootEntries
344✔
461
}
462

463
// MakeFakeRoot creates and returns a fakeroot *yang.Entry with rootName as its
464
// name. It has an empty, but initialized Dir.
465
func MakeFakeRoot(rootName string) *yang.Entry {
346✔
466
        return &yang.Entry{
346✔
467
                Name: rootName,
346✔
468
                Kind: yang.DirectoryEntry,
346✔
469
                Dir:  map[string]*yang.Entry{},
346✔
470
                // Create a fake node that corresponds to the fake root, this
346✔
471
                // ensures that we can match the element elsewhere.
346✔
472
                Node: &yang.Value{
346✔
473
                        Name: igenutil.RootElementNodeName,
346✔
474
                },
346✔
475
        }
346✔
476
}
346✔
477

478
// createFakeRoot extracts the entities that are at the root of the YANG schema tree,
479
// which otherwise would have no parent in the generated structs, and appends them to
480
// a synthesised root element. Such entries are extracted from the supplied structs
481
// if they are lists or containers, or from the rootElems supplied if they are leaves
482
// or leaf-lists. In the case that the code generation is compressing the
483
// YANG schema, list entries that are two levels deep (e.g., /interfaces/interface) are
484
// also appended to the synthesised root entity (i.e., in this case the root element
485
// has a map entry named 'Interface', and the corresponding NewInterface() method.
486
// Takes the directories that are identified at the root (dirs), the elements found
487
// at the root (rootElems, such that non-directories can be mapped), and a string
488
// indicating the root name.
489
func createFakeRoot(structs map[string]*yang.Entry, rootElems []*yang.Entry, rootName string, compressPaths bool) error {
344✔
490
        if rootName == "" {
639✔
491
                rootName = igenutil.DefaultRootName
295✔
492
        }
295✔
493

494
        fakeRoot := MakeFakeRoot(rootName)
344✔
495

344✔
496
        for _, s := range findRootEntries(structs, compressPaths) {
805✔
497
                if e, ok := fakeRoot.Dir[s.Name]; ok {
462✔
498
                        return fmt.Errorf("duplicate entry %s at the root: exists: %v, new: %v", s.Name, e.Path(), s.Path())
1✔
499
                }
1✔
500
                fakeRoot.Dir[s.Name] = s
460✔
501
        }
502

503
        for _, l := range rootElems {
870✔
504
                if l.IsLeaf() || l.IsLeafList() {
597✔
505
                        fakeRoot.Dir[l.Name] = l
70✔
506
                }
70✔
507
        }
508

509
        // Append the synthesised root entry to the list of structs for which
510
        // code should be generated.
511
        structs["/"] = fakeRoot
343✔
512
        return nil
343✔
513
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc