@sap/cds-compiler 2.13.6 → 2.15.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +128 -4
  2. package/bin/cdsc.js +112 -37
  3. package/lib/api/main.js +20 -22
  4. package/lib/api/options.js +2 -3
  5. package/lib/api/validate.js +6 -6
  6. package/lib/base/message-registry.js +92 -17
  7. package/lib/base/messages.js +85 -64
  8. package/lib/base/optionProcessorHelper.js +19 -0
  9. package/lib/checks/annotationsOData.js +11 -32
  10. package/lib/checks/arrayOfs.js +1 -34
  11. package/lib/checks/validator.js +2 -4
  12. package/lib/compiler/assert-consistency.js +1 -0
  13. package/lib/compiler/base.js +1 -0
  14. package/lib/compiler/builtins.js +11 -0
  15. package/lib/compiler/checks.js +22 -70
  16. package/lib/compiler/define.js +59 -11
  17. package/lib/compiler/extend.js +20 -3
  18. package/lib/compiler/finalize-parse-cdl.js +26 -20
  19. package/lib/compiler/index.js +75 -26
  20. package/lib/compiler/populate.js +6 -5
  21. package/lib/compiler/propagator.js +4 -1
  22. package/lib/compiler/resolve.js +104 -16
  23. package/lib/compiler/shared.js +61 -27
  24. package/lib/compiler/tweak-assocs.js +7 -1
  25. package/lib/edm/annotations/genericTranslation.js +93 -21
  26. package/lib/edm/csn2edm.js +216 -98
  27. package/lib/edm/edm.js +305 -226
  28. package/lib/edm/edmPreprocessor.js +499 -423
  29. package/lib/edm/edmUtils.js +22 -22
  30. package/lib/gen/Dictionary.json +98 -22
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/language.interp +3 -1
  33. package/lib/gen/languageParser.js +4636 -4368
  34. package/lib/json/csnVersion.js +10 -11
  35. package/lib/json/from-csn.js +3 -2
  36. package/lib/json/to-csn.js +0 -2
  37. package/lib/language/docCommentParser.js +2 -2
  38. package/lib/language/genericAntlrParser.js +47 -2
  39. package/lib/language/language.g4 +59 -27
  40. package/lib/main.d.ts +19 -1
  41. package/lib/main.js +6 -0
  42. package/lib/model/csnRefs.js +33 -6
  43. package/lib/model/csnUtils.js +193 -75
  44. package/lib/model/enrichCsn.js +1 -0
  45. package/lib/model/revealInternalProperties.js +2 -2
  46. package/lib/modelCompare/compare.js +6 -6
  47. package/lib/optionProcessor.js +62 -26
  48. package/lib/render/toCdl.js +844 -679
  49. package/lib/render/toHdbcds.js +189 -243
  50. package/lib/render/toSql.js +180 -198
  51. package/lib/render/utils/common.js +131 -15
  52. package/lib/transform/db/.eslintrc.json +1 -1
  53. package/lib/transform/db/associations.js +2 -2
  54. package/lib/transform/db/constraints.js +3 -1
  55. package/lib/transform/db/expansion.js +15 -10
  56. package/lib/transform/db/flattening.js +95 -68
  57. package/lib/transform/db/transformExists.js +7 -7
  58. package/lib/transform/db/views.js +6 -3
  59. package/lib/transform/forHanaNew.js +43 -26
  60. package/lib/transform/forOdataNew.js +43 -42
  61. package/lib/transform/localized.js +12 -7
  62. package/lib/transform/odata/toFinalBaseType.js +8 -6
  63. package/lib/transform/odata/typesExposure.js +145 -197
  64. package/lib/transform/transformUtilsNew.js +9 -12
  65. package/lib/transform/translateAssocsToJoins.js +5 -1
  66. package/lib/transform/universalCsn/coreComputed.js +5 -3
  67. package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
  68. package/lib/utils/moduleResolve.js +13 -6
  69. package/package.json +1 -1
  70. package/share/messages/message-explanations.json +2 -1
  71. package/share/messages/syntax-expected-integer.md +37 -0
  72. package/lib/transform/odata/attachPath.js +0 -96
  73. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  74. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  75. package/lib/transform/odata/referenceFlattener.js +0 -296
  76. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  77. package/lib/transform/odata/structuralPath.js +0 -72
  78. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -15,18 +15,17 @@
15
15
  // 0.1.99 : Like 0.1.0, but with new-style CSN
16
16
  // 0.2 : same as 0.1.99, but with new top-level properties: $version, meta
17
17
 
18
- // Use literal version constants intentionally and not number intervals to
18
+ // Use literal version constants intentionally and not number intervals to
19
19
  // record all published version strings of the core compiler.
20
- const newCSNVersions = ["0.1.99","0.2","0.2.0","1.0","2.0"];
20
+ const newCSNVersions = [ '0.1.99', '0.2', '0.2.0', '1.0', '2.0' ];
21
21
  // checks if new-csn is requested via the options of already specified in the CSN
22
22
  // default: old-style
23
23
  function isNewCSN(csn, options) {
24
- if( (options && options.newCsn === false) ||
24
+ if ( (options && options.newCsn === false) ||
25
25
  (csn.version && !newCSNVersions.includes(csn.version.csn)) ||
26
26
  (csn.$version && !newCSNVersions.includes(csn.$version)))
27
- {
28
27
  return false;
29
- }
28
+
30
29
  return true;
31
30
  }
32
31
 
@@ -34,18 +33,18 @@ function checkCSNVersion(csn, options) {
34
33
  if (!isNewCSN(csn, options)) {
35
34
  // the new transformer works only with new CSN
36
35
  const { makeMessageFunction } = require('../base/messages');
37
- const { error, throwWithError } = makeMessageFunction(csn, options);
36
+ const { error, throwWithAnyError } = makeMessageFunction(csn, options);
38
37
 
39
38
  let errStr = 'CSN Version not supported, version tag: "';
40
- errStr += (csn.version && csn.version.csn ? csn.version.csn : (csn.$version ? csn.$version : 'not available')) + '"';
41
- errStr += (options.newCsn !== undefined) ? ', options.newCsn: ' + options.newCsn : '';
39
+ errStr += `${ csn.version && csn.version.csn ? csn.version.csn : (csn.$version ? csn.$version : 'not available') }"`;
40
+ errStr += (options.newCsn !== undefined) ? `, options.newCsn: ${ options.newCsn }` : '';
42
41
 
43
42
  error(null, null, errStr);
44
- throwWithError();
43
+ throwWithAnyError();
45
44
  }
46
45
  }
47
46
 
48
47
  module.exports = {
49
48
  isNewCSN,
50
- checkCSNVersion
51
- }
49
+ checkCSNVersion,
50
+ };
@@ -152,7 +152,8 @@ const schemaClasses = {
152
152
  msgId: 'syntax-csn-expected-column',
153
153
  defaultKind: '$column',
154
154
  validKinds: [], // pseudo kind '$column'
155
- requires: [ 'ref', 'xpr', 'val', '#', 'func', 'list', 'SELECT', 'SET', 'expand' ],
155
+ // A column with only as+cast.type is a new association
156
+ requires: [ 'ref', 'as', 'xpr', 'val', '#', 'func', 'list', 'SELECT', 'SET', 'expand' ],
156
157
  schema: {
157
158
  xpr: {
158
159
  class: 'condition',
@@ -197,7 +198,7 @@ const schema = compileSchema( {
197
198
  dictionaryOf: definition,
198
199
  defaultKind: 'enum',
199
200
  validKinds: [ 'enum' ],
200
- inKind: [ 'element', 'type', 'param', 'annotation', 'annotate' ],
201
+ inKind: [ 'element', 'type', 'param', 'annotation', 'annotate', 'extend' ],
201
202
  },
202
203
  elements: {
203
204
  dictionaryOf: definition,
@@ -404,8 +404,6 @@ function usings( srcDict ) {
404
404
  * @param {object} csn
405
405
  * @param {object} model
406
406
  */
407
-
408
-
409
407
  function extensions( node, csn, model ) {
410
408
  if (model.kind && model.kind !== 'source')
411
409
  return undefined;
@@ -36,14 +36,14 @@ function parseDocComment(comment) {
36
36
  else if (lines.length === 2) {
37
37
  // Comment that is essentially just a header + footer.
38
38
  // First line, i.e. header, is always trimmed from left.
39
- lines[0] = lines[0].trimLeft();
39
+ lines[0] = lines[0].trimStart();
40
40
 
41
41
  // If the second line starts with an asterisk then remove it.
42
42
  // Otherwise trim all whitespace.
43
43
  if ((/^\s*[*]/.test(lines[1])))
44
44
  lines[1] = removeFence(lines[1]);
45
45
  else
46
- lines[1] = lines[1].trimLeft();
46
+ lines[1] = lines[1].trimStart();
47
47
  }
48
48
  else {
49
49
  const firstNonEmptyLine = lines.find((line, index) => index !== 0 && /[^\s]/.test(line)) || '';
@@ -93,7 +93,9 @@ GenericAntlrParser.prototype = Object.assign(
93
93
  setMaxCardinality,
94
94
  pushIdent,
95
95
  handleComposition,
96
+ associationInSelectItem,
96
97
  reportExpandInline,
98
+ checkTypeFacet,
97
99
  notSupportedYet,
98
100
  csnParseOnly,
99
101
  disallowElementExtension,
@@ -594,11 +596,11 @@ function numberLiteral( token, sign, text = token.text ) {
594
596
  location.endCol = endCol;
595
597
  text = sign.text + text;
596
598
  }
599
+
597
600
  const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
598
601
  if (!Number.isSafeInteger(num)) {
599
602
  if (sign == null) {
600
- this.error( 'syntax-no-integer', token, {},
601
- 'An integer number is expected here' );
603
+ this.error( 'syntax-expected-integer', token, { '#': !text.match(/^[0-9]*$/) ? 'normal' : 'unsafe'} );
602
604
  }
603
605
  else if (text !== `${num}`) {
604
606
  return { literal: 'number', val: text, location };
@@ -902,6 +904,31 @@ function handleComposition( cardinality, isComposition ) {
902
904
  this.excludeExpected( [ [ "'}'", 'COMPOSITIONofBRACE' ], brace1, ...manyOne ] );
903
905
  }
904
906
 
907
+ function associationInSelectItem( art ) {
908
+ const isPath = art.value.path && art.value.path.length
909
+ const isIdentifier = isPath && art.value.path.length === 1;
910
+ if (isIdentifier) {
911
+ if (!art.name) {
912
+ art.name = art.value.path[0];
913
+ } else {
914
+ // Use alias if provided, i.e. ignore art.value.path.
915
+ this.error( 'query-unexpected-alias', art.name.location, {},
916
+ 'Unexpected alias for association' );
917
+ }
918
+ delete art.value;
919
+ } else {
920
+ const loc = isPath ? art.value.path[1].location : art.value.location;
921
+ // If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
922
+ if (isPath || art.name) {
923
+ this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
924
+ if (isPath) {
925
+ art.name = art.value.path[art.value.path.length - 1];
926
+ }
927
+ delete art.value;
928
+ }
929
+ }
930
+ }
931
+
905
932
  function reportExpandInline( clauseName ) {
906
933
  let token = this.getCurrentToken();
907
934
  // improve error location when using "inline" `.{…}` after ref (arguments and
@@ -913,6 +940,24 @@ function reportExpandInline( clauseName ) {
913
940
  'Unexpected nested $(PROP), can only be used after a reference' );
914
941
  }
915
942
 
943
+ function checkTypeFacet( art, argIdent ) {
944
+ const id = argIdent.id;
945
+ if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
946
+ if (art[id] !== undefined) {
947
+ this.error( 'syntax-duplicate-argument', argIdent.location,
948
+ { '#': 'duplicate', code: id } );
949
+ this.error( 'syntax-duplicate-argument', art[id].location,
950
+ { '#': 'duplicate', code: id } );
951
+ }
952
+ return true;
953
+
954
+ } else {
955
+ this.error( 'syntax-duplicate-argument', argIdent.location,
956
+ { '#': 'unknown', code: id } );
957
+ return false;
958
+ }
959
+ }
960
+
916
961
  module.exports = {
917
962
  genericAntlrParser: GenericAntlrParser,
918
963
  };
@@ -1279,7 +1279,7 @@ selectItemDef[ outer ] locals[ annos = [] ]
1279
1279
  ;
1280
1280
 
1281
1281
  selectItemDefBody[ outer, annos ] returns[ art = {} ]
1282
- @after{ /* #ATN 1 */ this.attachLocation($art); }
1282
+ @after{ /* #ATN 2 */ this.attachLocation($art); }
1283
1283
  :
1284
1284
  (
1285
1285
  e=expression
@@ -1335,6 +1335,12 @@ selectItemDefBody[ outer, annos ] returns[ art = {} ]
1335
1335
  | typeRefOptArgs[ $art ] // TODO: annos here?
1336
1336
  { this.docComment( $annos ); }
1337
1337
  annotationAssignment_ll1[ $annos ]*
1338
+ |
1339
+ typeAssociationBase[ $art, false ]
1340
+ // #ATN: path could start with MANY or ONE - make sure a token follows in same rule!
1341
+ ( typeToMany[ $art ] | typeToOne[ $art ] | simplePath[ $art.target, 'artref' ] )
1342
+ typeAssociationCont[ $art ]?
1343
+ { this.associationInSelectItem( $art ); }
1338
1344
  )
1339
1345
  )?
1340
1346
  ;
@@ -1592,26 +1598,7 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1592
1598
  { $art.type = {}; }
1593
1599
  simplePath[ $art.type, 'artref' ]
1594
1600
  (
1595
- '(' // with type args, e.g. `type T : String(100) enum { ... }`
1596
- head=Number
1597
- { $art['$'+'typeArgs'] = [ this.numberLiteral( $head ) ]; }
1598
- ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1599
- (
1600
- v=VARIABLE
1601
- { $art['$'+'typeArgs'].push(
1602
- { literal: 'string', val: 'variable', location: this.tokenLocation($v) } );
1603
- }
1604
- |
1605
- f=FLOATING
1606
- { $art['$'+'typeArgs'].push(
1607
- { literal: 'string', val: 'floating', location: this.tokenLocation($f) } );
1608
- }
1609
- |
1610
- tail=Number
1611
- { $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
1612
- )
1613
- )*
1614
- ')'
1601
+ typeRefArgs[ $art ]
1615
1602
  { this.docComment( $annos ); }
1616
1603
  annotationAssignment_ll1[ $annos ]*
1617
1604
  (
@@ -1855,7 +1842,19 @@ typeRefOptArgs[ art ]
1855
1842
  :
1856
1843
  simplePath[ $art.type, 'artref' ]
1857
1844
  (
1858
- '('
1845
+ typeRefArgs[ $art ]
1846
+ |
1847
+ ':'
1848
+ { $art.type.scope = $art.type.path.length; }
1849
+ simplePath[ $art.type, 'ref']
1850
+ )?
1851
+ ;
1852
+
1853
+ typeRefArgs[ art ]
1854
+ :
1855
+ paren='('
1856
+ (
1857
+ // unnamed arguments
1859
1858
  head=Number
1860
1859
  { $art['$'+'typeArgs'] = [ this.numberLiteral( $head ) ]; }
1861
1860
  ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
@@ -1874,14 +1873,45 @@ typeRefOptArgs[ art ]
1874
1873
  { $art['$'+'typeArgs'].push( this.numberLiteral( $tail ) ); }
1875
1874
  )
1876
1875
  )*
1877
- ')'
1878
1876
  |
1879
- ':'
1880
- { $art.type.scope = $art.type.path.length; }
1881
- simplePath[ $art.type, 'ref']
1882
- )?
1877
+ // named arguments
1878
+ typeNamedArg[ $art ]
1879
+ ( ',' { if (this.isStraightBefore(')')) break; } // allow ',' before ')'
1880
+ typeNamedArg[ $art ]
1881
+ )*
1882
+ )
1883
+ ')'
1884
+ ;
1885
+
1886
+ typeNamedArg[ art ] locals[ arg = '' ]
1887
+ :
1888
+ name=ident['paramname']
1889
+ ':'
1890
+ { if (this.checkTypeFacet( $art, $name.id ))
1891
+ $arg = $name.id.id;
1892
+ }
1893
+ (
1894
+ val=Number
1895
+ { if ($arg && $art && $name.id) {
1896
+ $art[$arg] = this.numberLiteral( $val );
1897
+ }
1898
+ }
1899
+ |
1900
+ v=VARIABLE
1901
+ { if ($arg && $art && $name.id) {
1902
+ $art[$arg] = { literal: 'string', val: 'variable', location: this.tokenLocation($v) };
1903
+ }
1904
+ }
1905
+ |
1906
+ f=FLOATING
1907
+ { if ($arg && $art && $name.id) {
1908
+ $art[$arg] = { literal: 'string', val: 'floating', location: this.tokenLocation($f) };
1909
+ }
1910
+ }
1911
+ )
1883
1912
  ;
1884
1913
 
1914
+
1885
1915
  // Queries -------------------------------------------------------------------
1886
1916
 
1887
1917
  queryExpression returns[ query ] // QLSubqueryComplex, SubqueryComplex
@@ -2241,6 +2271,7 @@ conditionAnd returns [ cond ] locals [ args = [], andl = [] ]
2241
2271
  ( and=AND c2=conditionTerm { $args.push($c2.cond); $andl.push(this.valueWithTokenLocation( 'and', $and )) } )*
2242
2272
  ;
2243
2273
 
2274
+ // Note: New operators need to be added to functionExpressionOperatorsRequireParentheses[] in toCdl.js.
2244
2275
  conditionTerm returns [ cond ]
2245
2276
  @after{
2246
2277
  if ($cond) { this.attachLocation($cond); } else { $cond = $expr.expr; }
@@ -2288,6 +2319,7 @@ conditionTerm returns [ cond ]
2288
2319
  )? // optional: for conditions in parentheses
2289
2320
  ;
2290
2321
 
2322
+ // Note: New operators need to be added to functionExpressionOperatorsRequireParentheses[] in toCdl.js.
2291
2323
  predicate[ cond, negated ]
2292
2324
  // As an alternative, we could have a `negated` properties for the operations
2293
2325
  // `isNull`(!), `in`, `between` and `like` (or produce the same AST as for
package/lib/main.d.ts CHANGED
@@ -81,6 +81,11 @@ declare namespace compiler {
81
81
  * use this frontend as the fallback parser.
82
82
  */
83
83
  fallbackParser?: string | 'cdl' | 'csn'
84
+ /**
85
+ * Where to find `@sap/cds/` packages. This string, if set, is used as
86
+ * the prefix for SAP CDS packages / CDS files.
87
+ */
88
+ cdsHome?: string
84
89
  /**
85
90
  * Option for {@link compileSources}. If set, all objects inside the
86
91
  * provided sources dictionary are interpreted as XSN structures instead
@@ -895,7 +900,20 @@ declare namespace compiler {
895
900
  * The functions in `userFunctions` are usually transformer functions, which
896
901
  * change the input CSN destructively.
897
902
  */
898
- export function traverseCsn(userFunctions: Record<string, Function>, csn: object|any[]);
903
+ export function traverseCsn(userFunctions: Record<string, Function>, csn: object|any[]): void;
904
+
905
+ /**
906
+ * CSN Model related functions.
907
+ */
908
+ export namespace model {
909
+ /**
910
+ * Returns true if the given definition name is in a reserved namespace such as `cds.*`
911
+ * but not `cds.foundation.*`.
912
+ *
913
+ * @param definitionName Top-level definition name of the artifact.
914
+ */
915
+ function isInReservedNamespace(definitionName: string): boolean;
916
+ }
899
917
 
900
918
  /**
901
919
  * @private
package/lib/main.js CHANGED
@@ -25,6 +25,7 @@ const parseLanguage = require('./language/antlrParser');
25
25
  const { parseX, compileX, compileSyncX, compileSourcesX, InvocationError } = require('./compiler');
26
26
  const { fns } = require('./compiler/shared');
27
27
  const define = require('./compiler/define');
28
+ const { isInReservedNamespace } = require("./compiler/builtins");
28
29
  const finalizeParseCdl = require('./compiler/finalize-parse-cdl');
29
30
 
30
31
  // The compiler version (taken from package.json)
@@ -148,4 +149,9 @@ module.exports = {
148
149
  // it, you MUST talk with us - there can be potential incompatibilities with
149
150
  // new releases (even having the same major version):
150
151
  $lsp: { parse: parseX, compile: compileX, getArtifactName: a => a.name },
152
+
153
+ // CSN Model related functionality
154
+ model: {
155
+ isInReservedNamespace,
156
+ },
151
157
  };
@@ -442,7 +442,11 @@ function csnRefs( csn, universalReady ) {
442
442
  const queries = cached( main, '$queries', allQueries );
443
443
  for (const qcache of queries || []) {
444
444
  const { _select } = qcache;
445
- traverseType( _select, main, null, null, initNode ); // also inits elements
445
+ const { elements } = _select;
446
+ if (elements) {
447
+ for (const n of Object.keys( elements ))
448
+ traverseDef( elements[n], _select, 'element', n, initNode );
449
+ }
446
450
  if (_select.mixin) {
447
451
  for (const n of Object.keys( _select.mixin ))
448
452
  setCache( _select.mixin[n], '_parent', _select ); // relevant initNode() part
@@ -453,7 +457,13 @@ function csnRefs( csn, universalReady ) {
453
457
 
454
458
  function initNode( art, parent, kind, name ) {
455
459
  setCache( art, '_parent', parent );
456
- if (art.type || !kind || kind === 'target') // with type, top-level, query or mixin
460
+ if (kind === 'target') {
461
+ // Prevent re-initialization of anonymous aspect with initDefinition():
462
+ // (that would be with parent: null which would be wrong)
463
+ setCache( art, '$queries', null );
464
+ return;
465
+ }
466
+ if (art.type || !kind) // with type, top-level, query or mixin
457
467
  return;
458
468
  const { $origin } = art;
459
469
  if (typeof $origin === 'object') // null, […], {…}
@@ -547,8 +557,19 @@ function csnRefs( csn, universalReady ) {
547
557
  }
548
558
  if (baseEnv) // ref-target (filter condition), expand, inline
549
559
  return resolvePath( path, baseEnv.elements[head], baseEnv, semantics.dynamic );
550
- if (!query) // outside queries - TODO: items?
551
- return resolvePath( path, parent.elements[head], parent, 'parent' );
560
+ if (!query) { // outside queries - TODO: items?
561
+ let art = parent.elements[head];
562
+ // Ref to up_ in anonymous aspect
563
+ if (!art && head === 'up_') {
564
+ const up = getCache( parent, '_parent' );
565
+ const target = up && typeof up.target === 'string' && csn.definitions[up.target];
566
+ if (target && target.elements) {
567
+ initDefinition( target );
568
+ art = target.elements.up_;
569
+ }
570
+ }
571
+ return resolvePath( path, art, parent, 'parent' );
572
+ }
552
573
 
553
574
  if (semantics.dynamic === 'query')
554
575
  // TODO: for ON condition in expand, would need to use cached _element
@@ -703,6 +724,11 @@ function csnRefs( csn, universalReady ) {
703
724
  hidden = {};
704
725
  cache.set( obj, hidden );
705
726
  }
727
+ // TODO: we might keep the following with --test-mode
728
+ // if (hidden[prop] !== undefined) {
729
+ // console.log('RS:',prop,hidden[prop],val,obj)
730
+ // throw Error('RESET')
731
+ // }
706
732
  hidden[prop] = val;
707
733
  return val;
708
734
  }
@@ -818,7 +844,7 @@ function traverseDef( node, parent, kind, name, callback ) {
818
844
  }
819
845
  if (node.returns)
820
846
  traverseType( node.returns, node, 'returns', true, callback );
821
- traverseType( node, parent, kind, name, callback );
847
+ traverseType( node, true, kind, name, callback );
822
848
  if (node.actions) {
823
849
  for (const n of Object.keys( node.actions ))
824
850
  traverseDef( node.actions[n], node, 'action', n, callback )
@@ -826,7 +852,8 @@ function traverseDef( node, parent, kind, name, callback ) {
826
852
  }
827
853
 
828
854
  function traverseType( node, parent, kind, name, callback ) {
829
- callback ( node, parent, kind, name );
855
+ if (parent !== true)
856
+ callback ( node, parent, kind, name );
830
857
  const target = targetAspect( node );
831
858
  if (target && typeof target === 'object' && target.elements) {
832
859
  callback ( target, node, 'target', true );