@sap/cds-compiler 4.0.0 → 4.1.2

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 (85) hide show
  1. package/CHANGELOG.md +115 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +60 -12
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +30 -2
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +12 -5
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/model/sortViews.js +4 -2
  54. package/lib/modelCompare/compare.js +112 -39
  55. package/lib/modelCompare/utils/filter.js +54 -24
  56. package/lib/optionProcessor.js +6 -6
  57. package/lib/render/manageConstraints.js +20 -17
  58. package/lib/render/toCdl.js +34 -20
  59. package/lib/render/toHdbcds.js +2 -2
  60. package/lib/render/toRename.js +4 -9
  61. package/lib/render/toSql.js +77 -26
  62. package/lib/render/utils/common.js +3 -3
  63. package/lib/render/utils/unique.js +52 -0
  64. package/lib/transform/db/applyTransformations.js +61 -20
  65. package/lib/transform/db/assertUnique.js +7 -8
  66. package/lib/transform/db/associations.js +2 -2
  67. package/lib/transform/db/cdsPersistence.js +8 -8
  68. package/lib/transform/db/expansion.js +17 -21
  69. package/lib/transform/db/flattening.js +23 -23
  70. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  71. package/lib/transform/db/temporal.js +1 -1
  72. package/lib/transform/db/transformExists.js +8 -7
  73. package/lib/transform/db/views.js +73 -33
  74. package/lib/transform/draft/db.js +11 -9
  75. package/lib/transform/draft/odata.js +1 -1
  76. package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
  77. package/lib/transform/forRelationalDB.js +69 -75
  78. package/lib/transform/localized.js +6 -5
  79. package/lib/transform/odata/toFinalBaseType.js +3 -3
  80. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  81. package/lib/transform/translateAssocsToJoins.js +14 -28
  82. package/package.json +1 -1
  83. package/share/messages/check-proper-type-of.md +1 -1
  84. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  85. package/share/messages/message-explanations.json +1 -1
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const {
4
- getUtils, cloneCsnNonDict, applyTransformationsOnNonDictionary,
4
+ getUtils, cloneCsnNonDict, applyTransformationsOnNonDictionary, forEachDefinition,
5
5
  } = require('../../model/csnUtils');
6
- const { implicitAs } = require('../../model/csnRefs');
6
+ const { implicitAs, columnAlias } = require('../../model/csnRefs');
7
7
  const { ModelError } = require('../../base/error');
8
+ const { setProp } = require('../../base/model');
8
9
 
9
10
  /**
10
11
  * If a mixin association is published, return the mixin association.
@@ -66,10 +67,11 @@ function usesMixinAssociation( query, association, associationName ) {
66
67
  * @returns {(query: CSN.Query, artifact: CSN.Artifact, artName: string, path: CSN.Path) => void} Transformer function for views
67
68
  */
68
69
  function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
70
+ const csnUtils = getUtils(csn);
69
71
  const {
70
72
  get$combined, isAssocOrComposition,
71
73
  inspectRef, queryOrMain, // csnRefs
72
- } = getUtils(csn);
74
+ } = csnUtils;
73
75
  const pathDelimiter = options.forHana && (options.sqlMapping === 'hdbcds') ? '.' : '_';
74
76
  const { error, info } = messageFunctions;
75
77
  const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
@@ -213,7 +215,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
213
215
  else
214
216
  info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': 'std' });
215
217
 
216
- elem._ignore = true;
218
+ elem.$ignore = true;
217
219
  }
218
220
  else {
219
221
  error(null, queryPath, { name: elemName }, 'Association $(NAME) can\'t be published in a SAP HANA CDS UNION');
@@ -270,7 +272,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
270
272
  }, {}, elementsPath.concat(elemName));
271
273
  }
272
274
 
273
- if (!mixinElem._ignore)
275
+ if (!mixinElem.$ignore)
274
276
  columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
275
277
 
276
278
  if (query.SELECT) {
@@ -311,7 +313,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
311
313
 
312
314
 
313
315
  /**
314
- * Loop over the columns and call all of the given functions with the column and the path
316
+ * Loop over the columns and call all the given functions with the column and the path
315
317
  *
316
318
  * @param {Function[]} functions
317
319
  * @param {CSN.Column[]} columns
@@ -332,6 +334,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
332
334
  */
333
335
  // eslint-disable-next-line complexity
334
336
  function transformViewOrEntity( query, artifact, artName, path ) {
337
+ csnUtils.initDefinition(artifact);
335
338
  const { elements } = queryOrMain(query, artifact);
336
339
  // We use the elements from the leading query/main artifact - adapt the path
337
340
  const elementsPath = elements === artifact.elements ? path.slice(0, 2).concat('elements') : path.concat('elements');
@@ -340,7 +343,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
340
343
  let hasNonAssocElements = false;
341
344
  const isSelect = query && query.SELECT;
342
345
  const isProjection = !!artifact.projection || query && query.SELECT && !query.SELECT.columns;
343
- const columnMap = getColumnMap(query);
346
+ const columnMap = getColumnMap(query, csnUtils);
344
347
  const isSelectStar = query && query.SELECT && query.SELECT.columns && query.SELECT.columns.indexOf('*') !== -1;
345
348
 
346
349
  // check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
@@ -365,7 +368,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
365
368
  addForeignKeysToColumns(columnMap, elem, elemName);
366
369
  }
367
370
  // Views must have at least one element that is not an unmanaged assoc
368
- if (!elem.on && !elem._ignore)
371
+ if (!elem.on && !elem.$ignore)
369
372
  hasNonAssocElements = true;
370
373
 
371
374
  // (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
@@ -383,7 +386,7 @@ function getViewTransformer( csn, options, messageFunctions, transformCommon ) {
383
386
 
384
387
  if (isSelect) {
385
388
  // Build new columns from the column map - bring elements and columns back in sync basically
386
- query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
389
+ query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem].$ignore).map(key => stripLeadingSelf(columnMap[key]));
387
390
  // If following an association, explicitly set the implicit alias
388
391
  // due to an issue with HANA - this seems to only have an effect on ref files with hdbcds-hdbcds, so only run then
389
392
  const columnProcessors = [];
@@ -476,45 +479,82 @@ function getLastRefStepString( ref ) {
476
479
  }
477
480
 
478
481
  /**
479
- * Build a map of the resulting names (i.e. the element name of the column) and references to the respective columns
482
+ * This function is similar to csnRefs()' `columnName()`, but does not split the
483
+ * last `col.ref` segment on `.`.
480
484
  *
485
+ * TODO: The HDBCDS backend relies on this. Also the HDI backend relies
486
+ * on this for virtual elements somehow. That can probably be fixed
487
+ * by using csnRefs()'s `getElement()`.
488
+ * TODO: Remove this function; update HDBCDS/HDI
489
+ *
490
+ * @param {CSN.Column} col
491
+ * @returns {string}
492
+ */
493
+ function columnNameForMap( col ) {
494
+ return col.as || (!col.args && col.func) || (col.ref && getLastRefStepString( col.ref ));
495
+ }
496
+
497
+ /**
498
+ * Build a map of the resulting names (i.e. the element name of the column) and references
499
+ * to the respective columns.
481
500
  * This can later be used to match from elements to columns.
482
501
  *
483
502
  * @param {CSN.Query} query
503
+ * @param {object} csnUtils
484
504
  * @returns {object}
485
505
  */
486
- function getColumnMap( query ) {
506
+ function getColumnMap( query, csnUtils ) {
487
507
  const map = Object.create(null);
488
- if (query && query.SELECT && query.SELECT.columns) {
508
+ if (query?.SELECT?.columns) {
489
509
  query.SELECT.columns.forEach((col) => {
490
- if (col === '*') {
491
- // do nothing
492
- }
493
- else if (col.as) {
494
- if (!map[col.as])
495
- map[col.as] = col;
496
- }
497
- else if (col.ref) {
498
- // .id on last path step can happen with hdbcds.hdbcds and malicious CSN input - maybe also with params?
499
- // We made things right in the end with the second add of missing stuff, but why not do it
500
- // right from the getgo
501
- const last = getLastRefStepString(col.ref);
502
- if (!map[last])
503
- map[last] = col;
504
- }
505
- else if (col.func) {
506
- map[col.func] = col;
507
- }
508
- else if (!map[col]) {
509
- map[col] = col;
510
+ if (col !== '*') {
511
+ // Fallback to csnUtils for columns without any alias (internal one is created)
512
+ const as = columnNameForMap(col) || csnUtils.getColumnName( col );
513
+ if (as && !map[as])
514
+ map[as] = col;
510
515
  }
511
516
  });
512
517
  }
513
-
514
518
  return map;
515
519
  }
516
520
 
521
+
522
+ /**
523
+ * Ensure that each column in the CSN has a name. A column does not have
524
+ * a name if the column is an expression and there is no explicit alias.
525
+ * In that case an internal alias (from csnRefs()) is used and made explicit
526
+ * via non-enumerable `as`.
527
+ *
528
+ * For HDBCDS, the alias is made explicit as an enumerable property, because
529
+ * HDBCDS does not support expressions as columns without aliases.
530
+ *
531
+ * Notes:
532
+ * - The alias is removed after A2J: we rely on the compiler ignoring non-enumerable CSN properties.
533
+ * - We can't use e.g. `$as`, as csnRefs() does not use that property, and it must not
534
+ * invent another name for the column (could happen after flattening).
535
+ *
536
+ * @param {CSN.Model} csn
537
+ * @param {CSN.Options} options
538
+ * @param {object} csnUtils
539
+ */
540
+ function ensureColumnNames( csn, options, csnUtils ) {
541
+ forEachDefinition(csn, (def) => {
542
+ csnUtils.initDefinition(def);
543
+ for (const query of csnUtils.$getQueries(def) || []) {
544
+ for (const col of query._select.columns || []) {
545
+ if (col !== '*' && !columnAlias(col)) {
546
+ if (options.transformation === 'hdbcds')
547
+ col.as = csnUtils.getColumnName(col);
548
+ else
549
+ setProp(col, 'as', csnUtils.getColumnName(col));
550
+ }
551
+ }
552
+ }
553
+ });
554
+ }
555
+
517
556
  module.exports = {
518
557
  getViewTransformer,
519
558
  getColumnMap,
559
+ ensureColumnNames,
520
560
  };
@@ -5,7 +5,7 @@ const {
5
5
  getResultingName, forEachMemberRecursively,
6
6
  } = require('../../model/csnUtils');
7
7
  const { setProp, isDeprecatedEnabled } = require('../../base/model');
8
- const { getTransformers } = require('../transformUtilsNew');
8
+ const { getTransformers } = require('../transformUtils');
9
9
  const { ModelError } = require('../../base/error');
10
10
  const draftAnnotation = '@odata.draft.enabled';
11
11
  const booleanBuiltin = 'cds.Boolean';
@@ -124,16 +124,18 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
124
124
  error(null, path, 'Expecting element to have a type when used in a draft-enabled artifact');
125
125
  if (elt.key && elt.key === true && !elt.virtual)
126
126
  keys.push(elt);
127
- }, [ 'definitions', artifactName ], true, { elementsOnly: true });
127
+ }, [ 'definitions', artifactName ], false, { elementsOnly: true });
128
128
 
129
129
  // In contrast to EDM, the DB entity may have more than one technical keys but should have ideally exactly one key of type cds.UUID
130
- if (keys.length !== 1)
131
- warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have exactly one key element');
132
-
133
- const uuidCount = keys.reduce((acc, k) => ((k.type === 'cds.String' && k.$renamed === 'cds.UUID' && k.length === 36) ? acc + 1 : acc), 0);
134
- if (uuidCount === 0)
135
- warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
136
-
130
+ if (keys.length !== 1) {
131
+ warning(null, [ 'definitions', artifactName ], { count: keys.length },
132
+ 'Entity annotated with “@odata.draft.enabled” should have exactly one key element, but found $(COUNT)');
133
+ }
134
+ else {
135
+ const uuidCount = keys.reduce((acc, k) => ((k.type === 'cds.String' && k.$renamed === 'cds.UUID' && k.length === 36) ? acc + 1 : acc), 0);
136
+ if (uuidCount === 0)
137
+ warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
138
+ }
137
139
 
138
140
  // Ignore boolean return value. We know that we're inside a service or else we wouldn't have reached this code.
139
141
  const matchingService = getMatchingService(artifactName) || '';
@@ -3,7 +3,7 @@
3
3
  const { forEachDefinition, getServiceNames } = require('../../model/csnUtils');
4
4
  const { forEach } = require('../../utils/objectUtils');
5
5
  const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
6
- const { getTransformers } = require('../transformUtilsNew');
6
+ const { getTransformers } = require('../transformUtils');
7
7
  const { ModelError } = require('../../base/error');
8
8
  const { makeMessageFunction } = require('../../base/messages');
9
9
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { makeMessageFunction } = require('../base/messages');
4
4
  const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
5
- const transformUtils = require('./transformUtilsNew');
5
+ const transformUtils = require('./transformUtils');
6
6
  const { cloneCsnNonDict,
7
7
  forEachDefinition,
8
8
  forEachMemberRecursively,
@@ -312,79 +312,93 @@ function transform4odataWithCsn(inputModel, options) {
312
312
  node['@Core.Computed'] = true;
313
313
  }
314
314
 
315
- // Rename shorthand annotations within artifact or element 'node' according to a builtin
316
- // list.
315
+ // Rename shorthand annotations within artifact or element 'node' according to a builtin list
317
316
  function renameShorthandAnnotations(node) {
318
- // FIXME: Verify this list - are they all still required? Do we need any more?
319
317
  const setMappings = {
320
318
  '@label': '@Common.Label',
321
319
  '@title': '@Common.Label',
322
320
  '@description': '@Core.Description',
323
321
  };
324
322
  const renameMappings = {
325
- '@ValueList.entity': '@Common.ValueList.entity',
326
- '@ValueList.type': '@Common.ValueList.type',
327
- '@Capabilities.Deletable': '@Capabilities.DeleteRestrictions.Deletable',
328
- '@Capabilities.Insertable': '@Capabilities.InsertRestrictions.Insertable',
329
- '@Capabilities.Updatable': '@Capabilities.UpdateRestrictions.Updatable',
330
- '@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
323
+ '@ValueList.entity': { val: '@Common.ValueList', op: 'entity' },
324
+ '@ValueList.type': { val: '@Common.ValueList', op: 'type' },
325
+ '@Capabilities.Deletable': { val: '@Capabilities.DeleteRestrictions', op: 'Deletable' },
326
+ '@Capabilities.Insertable': { val: '@Capabilities.InsertRestrictions', op: 'Insertable' },
327
+ '@Capabilities.Updatable': { val: '@Capabilities.UpdateRestrictions', op: 'Updatable' },
328
+ '@Capabilities.Readable': { val: '@Capabilities.ReadRestrictions', op: 'Readable' }
331
329
  };
332
330
 
333
331
  const setShortCuts = Object.keys(setMappings);
334
332
  const renameShortCuts = Object.keys(renameMappings);
333
+
334
+ // Capabilities shortcuts have precedence over @readonly/@insertonly
335
335
  Object.keys(node).forEach( name => {
336
336
  if (!name.startsWith('@'))
337
337
  return;
338
338
  // Rename according to map above
339
- const renamePrefix = (name in renameMappings) ? name : renameShortCuts.find(p => name.startsWith(p + '.'));
339
+ const renamePrefix = (name in renameMappings)
340
+ ? name
341
+ : renameShortCuts.find(p => name.startsWith(p + '.'));
340
342
  if(renamePrefix) {
341
- renameAnnotation(node, name, name.replace(renamePrefix, renameMappings[renamePrefix]));
342
- } else {
343
+ const mapping = renameMappings[renamePrefix];
344
+ renameAnnotation(node, name, name.replace(renamePrefix, `${mapping.val}.${mapping.op}`));
345
+ }
346
+ else {
343
347
  // The two mappings have no overlap, so no need to check for second map if first matched.
344
348
  // Rename according to map above
345
- const setPrefix = (name in setMappings) ? name : setShortCuts.find(p => name.startsWith(p + '.'));
349
+ const setPrefix = (name in setMappings)
350
+ ? name
351
+ : setShortCuts.find(p => name.startsWith(p + '.') || name.startsWith(p + '#'));
346
352
  if(setPrefix) {
347
353
  setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
348
354
  }
349
355
  }
356
+ });
357
+
358
+ // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
359
+ // but '@Core.Computed' for everything else.
350
360
 
351
- // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
352
- // but '@Core.Immutable' for everything else.
353
- if (!(node['@readonly'] && node['@insertonly'])) {
354
- if (name === '@readonly' && node[name]) {
361
+ // only if not both readonly/insertonly are true do the mapping
362
+ if(!(node['@readonly'] && node['@insertonly'])) {
363
+ if(node['@readonly']) {
364
+ const setRO = (qualifier) => {
355
365
  if (node.kind === 'entity' || node.kind === 'aspect') {
356
- setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
357
- setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
358
- setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
366
+ setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
367
+ setAnnotation(node, `@Capabilities.InsertRestrictions${ qualifier ? '#' + qualifier : ''}.Insertable`, false);
368
+ setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
359
369
  } else {
360
370
  setAnnotation(node, '@Core.Computed', true);
361
371
  }
362
- }
363
- // @insertonly is effective on entities/queries only
364
- else if (name === '@insertonly' && node[name]) {
365
- if (node.kind === 'entity' || node.kind === 'aspect') {
366
- setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
367
- setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
368
- setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
369
- }
370
- }
372
+ };
373
+ setRO(undefined);
371
374
  }
372
- // Only on element level: translate @mandatory
373
- if (name === '@mandatory' && node[name] &&
374
- node.kind === undefined && node['@Common.FieldControl'] === undefined) {
375
- setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
375
+ // @insertonly is effective on entities/queries only
376
+ if (node['@insertonly'] && (node.kind === 'entity' || node.kind === 'aspect')) {
377
+ const setIO = (qualifier) => {
378
+ setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
379
+ setAnnotation(node, `@Capabilities.ReadRestrictions${ qualifier ? '#' + qualifier : ''}.Readable`, false);
380
+ setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
381
+ }
382
+ setIO(undefined);
376
383
  }
384
+ }
377
385
 
378
- if (name === '@assert.format' && node[name] !== null)
379
- setAnnotation(node, '@Validation.Pattern', node['@assert.format']);
386
+ // @Validation.Pattern is applicable to "Term" => node.kind === annotation
387
+ if (node['@assert.format'] != null)
388
+ setAnnotation(node, '@Validation.Pattern', node['@assert.format']);
380
389
 
381
- if (name === '@assert.range' && node[name] !== null) {
382
- if (Array.isArray(node['@assert.range']) && node['@assert.range'].length === 2) {
383
- setAnnotation(node, '@Validation.Minimum', node['@assert.range'][0]);
384
- setAnnotation(node, '@Validation.Maximum', node['@assert.range'][1]);
385
- }
390
+ // Only on element level
391
+ if(node.kind == null) {
392
+ if (node['@mandatory']&& node['@Common.FieldControl'] === undefined) {
393
+ setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
386
394
  }
387
- });
395
+ if (node['@assert.range'] != null &&
396
+ (Array.isArray(node['@assert.range']) &&
397
+ node['@assert.range'].length === 2)) {
398
+ setAnnotation(node, '@Validation.Minimum', node['@assert.range'][0]);
399
+ setAnnotation(node, '@Validation.Maximum', node['@assert.range'][1]);
400
+ }
401
+ }
388
402
  }
389
403
 
390
404
  // Apply default type facets to each type definition and every member