@sap/cds-compiler 5.1.0 → 5.2.0

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 (51) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/cdsc.js +2 -2
  3. package/bin/cdshi.js +24 -17
  4. package/bin/cdsse.js +17 -18
  5. package/lib/api/main.js +19 -2
  6. package/lib/api/options.js +4 -1
  7. package/lib/base/builtins.js +1 -0
  8. package/lib/base/message-registry.js +16 -3
  9. package/lib/base/model.js +0 -10
  10. package/lib/checks/actionsFunctions.js +0 -12
  11. package/lib/checks/structuredAnnoExpressions.js +10 -14
  12. package/lib/compiler/assert-consistency.js +19 -11
  13. package/lib/compiler/builtins.js +1 -1
  14. package/lib/compiler/define.js +6 -4
  15. package/lib/compiler/extend.js +5 -5
  16. package/lib/compiler/populate.js +9 -9
  17. package/lib/compiler/propagator.js +1 -0
  18. package/lib/compiler/resolve.js +29 -34
  19. package/lib/compiler/shared.js +7 -8
  20. package/lib/compiler/tweak-assocs.js +155 -64
  21. package/lib/compiler/utils.js +1 -1
  22. package/lib/compiler/xpr-rewrite.js +4 -3
  23. package/lib/edm/annotations/genericTranslation.js +13 -9
  24. package/lib/edm/csn2edm.js +26 -2
  25. package/lib/edm/edm.js +23 -8
  26. package/lib/edm/edmInboundChecks.js +5 -7
  27. package/lib/edm/edmPreprocessor.js +43 -30
  28. package/lib/gen/BaseParser.js +720 -0
  29. package/lib/gen/CdlParser.js +4421 -0
  30. package/lib/gen/language.checksum +1 -1
  31. package/lib/gen/language.interp +1 -1
  32. package/lib/gen/languageParser.js +4006 -4001
  33. package/lib/language/antlrParser.js +62 -0
  34. package/lib/language/genericAntlrParser.js +28 -0
  35. package/lib/model/csnUtils.js +2 -0
  36. package/lib/model/revealInternalProperties.js +2 -0
  37. package/lib/modelCompare/utils/filter.js +70 -42
  38. package/lib/optionProcessor.js +9 -3
  39. package/lib/parsers/AstBuildingParser.js +1172 -0
  40. package/lib/parsers/CdlGrammar.g4 +1940 -0
  41. package/lib/parsers/Lexer.js +239 -0
  42. package/lib/render/toCdl.js +23 -27
  43. package/lib/render/toSql.js +5 -5
  44. package/lib/transform/db/applyTransformations.js +54 -16
  45. package/lib/transform/draft/odata.js +10 -11
  46. package/lib/transform/effective/flattening.js +10 -14
  47. package/lib/transform/odata/flattening.js +42 -31
  48. package/lib/transform/odata/toFinalBaseType.js +7 -6
  49. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  50. package/package.json +2 -2
  51. package/share/messages/redirected-to-ambiguous.md +5 -4
@@ -17,6 +17,10 @@ const { XsnSource } = require('../compiler/xsn-model');
17
17
  const Parser = require('../gen/languageParser').default;
18
18
  const Lexer = require('../gen/languageLexer').default;
19
19
 
20
+ const CdlLexer = require( '../parsers/Lexer' );
21
+ const CdlParser = require( '../gen/CdlParser' );
22
+ const { createMessageFunctions } = require( '../base/messages' );
23
+
20
24
  // Error listener used for ANTLR4-generated parser
21
25
  class ErrorListener extends antlr4.error.ErrorListener {
22
26
  // method which is called by generated parser with --trace-parser[-amg]:
@@ -129,6 +133,9 @@ const rules = {
129
133
  function parse( source, filename = '<undefined>.cds',
130
134
  options = {}, messageFunctions = null,
131
135
  rule = 'cdl' ) {
136
+ if (options.newParser)
137
+ return parseWithNewParser( source, filename, options, messageFunctions, rule );
138
+
132
139
  const lexer = new Lexer( new antlr4.InputStream(source) );
133
140
  const tokenStream = new RewriteTypeTokenStream(lexer);
134
141
  /** @type {object} */
@@ -211,4 +218,59 @@ function parse( source, filename = '<undefined>.cds',
211
218
  return ast;
212
219
  }
213
220
 
221
+ function parseWithNewParser( source, filename, options, messageFunctions, rule ) {
222
+ if (CdlParser.tracingParser) // tracing → direct console output of message
223
+ messageFunctions = createMessageFunctions( {}, 'parse', {} );
224
+ const lexer = new CdlLexer( filename, source );
225
+ const parser = new CdlParser( lexer, options, messageFunctions ).init();
226
+ parser.filename = filename; // LSP compatibility
227
+ parser.tokenStream = parser; // LSP compatibility: object with property `tokens`
228
+
229
+ // LSP feature: provide parse listener with ANTLR-like context:
230
+ const { parseListener } = options;
231
+ if (parseListener) {
232
+ // TODO LSP: we could also call different listener methods: then LSP could
233
+ // have dedicated methods for ANTLR-based and new parser
234
+ parser.rule_ = function rule_( ...args ) {
235
+ CdlParser.prototype.rule_.apply( this, args );
236
+ let state = this.s;
237
+ while (typeof this.table[--state] !== 'string')
238
+ ;
239
+ const $ctx = { // TODO LSP: more to add?
240
+ parser: this, // set in generated ANTLR parser for each rule context
241
+ ruleName: this.table[state], // instead of ruleIndex
242
+ start: this.la(), // set in Parser#enterRule
243
+ stop: null,
244
+ };
245
+ parser.stack.at( -1 ).$ctx = $ctx;
246
+ parseListener.enterEveryRule( $ctx );
247
+ };
248
+ parser.exit_ = function exit_( ...args ) {
249
+ const { $ctx } = parser.stack.at( -1 );
250
+ // TODO: what should we do in case of errors?
251
+ $ctx.stop = this.lb();
252
+ parseListener.exitEveryRule( $ctx );
253
+ CdlParser.prototype.exit_.apply( this, args );
254
+ };
255
+ }
256
+ const result = {};
257
+ const rulespec = rules[rule];
258
+ if (rulespec) {
259
+ try {
260
+ parser[rulespec.func]( result );
261
+ }
262
+ catch (e) {
263
+ if (!(e instanceof RangeError && /Maximum.*exceeded$/i.test( e.message )))
264
+ throw e;
265
+ messageFunctions.error('syntax-invalid-source', { file: filename },
266
+ { '#': 'cdl-stackoverflow' } );
267
+ result[rulespec.returns] = undefined;
268
+ }
269
+ }
270
+ const ast = result[rulespec?.returns] || (rule === 'cdl' ? new XsnSource( 'cdl' ) : {} );
271
+ if (options.attachTokens === true || options.attachTokens === filename)
272
+ ast.tokenStream = parser; // with property tokens
273
+ return ast;
274
+ }
275
+
214
276
  module.exports = parse;
@@ -101,6 +101,8 @@ Object.assign(GenericAntlrParser.prototype, {
101
101
  warnIfColonFollows,
102
102
  fragileAlias,
103
103
  identAst,
104
+ reportPathNamedManyOrOne,
105
+ reportMissingSemicolon,
104
106
  pushXprToken,
105
107
  pushOpToken,
106
108
  argsExpression,
@@ -276,6 +278,7 @@ function markAsSkippedUntilEOF() {
276
278
  function noAssignmentInSameLine() {
277
279
  const t = this.getCurrentToken();
278
280
  if (t.text === '@' && t.line <= this._input.LT(-1).line) {
281
+ // TODO: use 'syntax-missing-newline'
279
282
  this.warning( 'syntax-missing-semicolon', t, { code: ';' },
280
283
  // eslint-disable-next-line max-len
281
284
  'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
@@ -801,6 +804,31 @@ function identAst( token, category, noTokenTypeCheck = false ) {
801
804
  return ast;
802
805
  }
803
806
 
807
+ function reportPathNamedManyOrOne( { path } ) {
808
+ if (path.length === 1 && !path[0].$delimited &&
809
+ [ 'many', 'one' ].includes( path[0].id.toLowerCase() )) {
810
+ this.message( 'syntax-unexpected-many-one', path[0].location,
811
+ { code: path[0].id, delimited: path[0].id } );
812
+ }
813
+ }
814
+
815
+ function reportMissingSemicolon() {
816
+ const next = this._input.LT(1);
817
+ if (next.text !== ';' && next.text !== '' && // ';' by insertSemicolon()
818
+ next.text !== '}' && next.type !== antlr4.Token.EOF &&
819
+ this._input.LT(-1).text !== '}') {
820
+ const offending = this.literalNames[next.type] || this.symbolicNames[next.type];
821
+ const loc = this.tokenLocation( this._input.LT(-1) );
822
+ // better location after the previous token:
823
+ const location = new Location( loc.file, loc.endLine, loc.endCol );
824
+ // it would be nicer to mention the doc comment if present, but not worth the
825
+ // effort; 'syntax-missing-semicolon' already used
826
+ this.warning( 'syntax-missing-proj-semicolon', location,
827
+ { expecting: [ "';'" ], offending },
828
+ 'Missing $(EXPECTING) before $(OFFENDING)');
829
+ }
830
+ }
831
+
804
832
  function pushXprToken( args ) {
805
833
  const token = this._input.LT(-1);
806
834
  args.push( {
@@ -3,6 +3,7 @@
3
3
  const { csnRefs, implicitAs, pathId } = require('./csnRefs');
4
4
  const {
5
5
  transformExpression,
6
+ transformAnnotationExpression,
6
7
  applyTransformations,
7
8
  applyTransformationsOnNonDictionary,
8
9
  applyTransformationsOnDictionary,
@@ -1419,6 +1420,7 @@ module.exports = {
1419
1420
  getUnderscoredName,
1420
1421
  getElementDatabaseNameOf,
1421
1422
  transformExpression,
1423
+ transformAnnotationExpression,
1422
1424
  applyTransformations,
1423
1425
  applyTransformationsOnNonDictionary,
1424
1426
  applyTransformationsOnDictionary,
@@ -306,6 +306,8 @@ function revealInternalProperties( model, nameOrPath ) {
306
306
  }
307
307
 
308
308
  function array( node, fn ) {
309
+ if (!Array.isArray( node ))
310
+ return node;
309
311
  const r = node.map( n => fn( n, node ) );
310
312
  if (node[$location])
311
313
  r.push( { '[$location]': locationString( node[$location] ) } );
@@ -10,46 +10,57 @@ function isKey( element ) {
10
10
  return element.key;
11
11
  }
12
12
 
13
- module.exports = {
14
- sqlite: getFilterObject(
15
- 'sqlite',
16
- function forEachExtension(extend, name, elementOrConstraint, { error, warning }) {
17
- if (isKey(elementOrConstraint)) { // Key must not be extended
18
- error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite', '#': 'std' } );
19
- return false;
20
- }
21
- else if (elementOrConstraint.parentTable) { // constraints have a .parentTable
22
- warning('def-unsupported-constraint-add', [ 'definitions', elementOrConstraint.parentTable, 'elements', elementOrConstraint.paths ? name : name.slice(elementOrConstraint.parentTable.length + 1) ], { id: elementOrConstraint.identifier || name, name: 'sqlite' },
23
- 'Ignoring add of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
13
+ function getFilter(options) {
14
+ const filters = {
15
+ sqlite: getFilterObject(
16
+ options,
17
+ 'sqlite',
18
+ function forEachExtension(extend, name, elementOrConstraint, { error, warning }) {
19
+ if (isKey(elementOrConstraint)) { // Key must not be extended
20
+ error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite', '#': 'std' } );
21
+ return false;
22
+ }
23
+ else if (elementOrConstraint.parentTable) { // constraints have a .parentTable
24
+ warning('def-unsupported-constraint-add', [ 'definitions', elementOrConstraint.parentTable, 'elements', elementOrConstraint.paths ? name : name.slice(elementOrConstraint.parentTable.length + 1) ], { id: elementOrConstraint.identifier || name, name: 'sqlite' },
25
+ 'Ignoring add of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
26
+ return false;
27
+ }
28
+
29
+ return true;
30
+ },
31
+ function forEachMigration(migrate, name, migration, change, error) {
32
+ const newIsKey = isKey(migration.new);
33
+ const oldIsKey = isKey(migration.old);
34
+ if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) // Turned into key or key was removed
35
+ error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite', '#': 'changed' } );
36
+ else
37
+ delete change[name];
38
+ },
39
+ function forEachConstraintRemoval(constraintRemovals, name, constraint, warning) {
40
+ warning('def-unsupported-constraint-drop', [ 'definitions', constraint.parentTable, 'elements', constraint.paths ? name : name.slice(constraint.parentTable.length + 1) ], { id: constraint.identifier || name, name: 'sqlite' },
41
+ 'Ignoring drop of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
42
+ delete constraintRemovals[name];
43
+ },
44
+ function primaryKey() {
24
45
  return false;
25
46
  }
47
+ ),
48
+ postgres: getFilterObject(options, 'postgres'),
49
+ h2: getFilterObject(options, 'h2'),
50
+ hana: getFilterObject(options, 'hana'),
51
+ };
26
52
 
27
- return true;
28
- },
29
- function forEachMigration(migrate, name, migration, change, error) {
30
- const newIsKey = isKey(migration.new);
31
- const oldIsKey = isKey(migration.old);
32
- if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) // Turned into key or key was removed
33
- error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite', '#': 'changed' } );
34
- else
35
- delete change[name];
36
- },
37
- function forEachConstraintRemoval(constraintRemovals, name, constraint, warning) {
38
- warning('def-unsupported-constraint-drop', [ 'definitions', constraint.parentTable, 'elements', constraint.paths ? name : name.slice(constraint.parentTable.length + 1) ], { id: constraint.identifier || name, name: 'sqlite' },
39
- 'Ignoring drop of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
40
- delete constraintRemovals[name];
41
- },
42
- function primaryKey() {
43
- return false;
44
- }
45
- ),
46
- postgres: getFilterObject('postgres'),
47
- h2: getFilterObject('h2'),
48
- hana: getFilterObject('hana'),
53
+ return filters[options.sqlDialect];
54
+ }
55
+
56
+ module.exports = {
49
57
  csn: filterCsn,
58
+ getFilter,
50
59
  };
51
60
 
52
- function getFilterObject( dialect, extensionCallback, migrationCallback, removeConstraintsCallback, primaryKeyCallback ) {
61
+ function getFilterObject( options, dialect, extensionCallback, migrationCallback, removeConstraintsCallback, primaryKeyCallback ) {
62
+ const context = { hasLossyChanges: false };
63
+ const raiseErrorOrMarkAsLossy = getSafeguardManager(context, options);
53
64
  return {
54
65
  // will be called with a simple Array.filter, as we need to filter constraint `ADD` for SQLite
55
66
  extension: ({
@@ -70,24 +81,28 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
70
81
  },
71
82
  // will be called with a Array.forEach
72
83
  migration: (migrations, { error, warning, message }) => {
73
- forEach(migrations.remove, (name) => {
74
- error('def-unsupported-element-drop', [ 'definitions', migrations.migrate, 'elements', name ], {}, 'Dropping elements is not supported');
84
+ forEach(migrations.remove, (name, migration) => {
85
+ raiseErrorOrMarkAsLossy(migration, () => error('def-unsupported-element-drop', [ 'definitions', migrations.migrate, 'elements', name ], {}, 'Dropping elements is not supported'));
75
86
  });
87
+
76
88
  forEach(migrations.change, (name, migration) => {
77
89
  const loc = [ 'definitions', migrations.migrate, 'elements', name ];
78
90
  if (migration.new.type === migration.old.type && migration.new.length < migration.old.length)
79
- error('type-unsupported-length-change', loc, { id: name }, 'Changed element $(ID) is a length reduction and is not supported');
91
+ raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-length-change', loc, { id: name }, 'Changed element $(ID) is a length reduction and is not supported'));
80
92
  else if (migration.new.type === migration.old.type && migration.new.scale !== migration.old.scale)
81
- error('type-unsupported-scale-change', loc, { id: name }, 'Changed element $(ID) is a scale change and is not supported');
93
+ raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-scale-change', loc, { id: name }, 'Changed element $(ID) is a scale change and is not supported'));
82
94
  else if (migration.new.type === migration.old.type && migration.new.precision !== migration.old.scale)
83
- error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported');
95
+ raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported'));
84
96
  else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type))
85
- error('type-unsupported-change', loc, { id: name, name: migration.old.type, type: migration.new.type }, 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported');
97
+ raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-change', loc, { id: name, name: migration.old.type, type: migration.new.type }, 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported'));
86
98
  else if (dialect !== 'sqlite' && isKey(migration.new) && !isKey(migration.old)) // key added/changed - pg, hana and sqlite do not support it, h2 probably also - issues when data is in the table already
87
- message('type-unsupported-key-change', [ 'definitions', migrations.migrate, 'elements', name ], { id: name, '#': 'changed' } );
99
+ raiseErrorOrMarkAsLossy(migration, () => message('type-unsupported-key-change', [ 'definitions', migrations.migrate, 'elements', name ], { id: name, '#': 'changed' } ));
88
100
  else if (migrationCallback)
89
101
  migrationCallback(migrations.migrate, name, migration, migrations.change, error);
90
102
 
103
+ if (options.script && migration.lossy && migrationCallback)
104
+ migrationCallback(migrations.migrate, name, migration, migrations.change, error);
105
+
91
106
  // TODO: precision/scale growth
92
107
  });
93
108
 
@@ -102,7 +117,7 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
102
117
  },
103
118
  deletion: ([ artifactName, artifact ], error) => {
104
119
  if (isPersistedAsTable(artifact))
105
- error('def-unsupported-table-drop', [ 'definitions', artifactName ], 'Dropping tables is not supported');
120
+ raiseErrorOrMarkAsLossy(artifact, () => error('def-unsupported-table-drop', [ 'definitions', artifactName ], 'Dropping tables is not supported'));
106
121
  },
107
122
  changedPrimaryKeys: (changedPrimaryKeyArtifactName) => {
108
123
  if (primaryKeyCallback)
@@ -110,6 +125,7 @@ function getFilterObject( dialect, extensionCallback, migrationCallback, removeC
110
125
 
111
126
  return true;
112
127
  },
128
+ hasLossyChanges: () => context.hasLossyChanges,
113
129
  };
114
130
  }
115
131
 
@@ -173,3 +189,15 @@ function filterCsn( csn ) {
173
189
 
174
190
  return csn;
175
191
  }
192
+
193
+ function getSafeguardManager( context, options ) {
194
+ return function raiseErrorOrMarkAsLossy(migration, raiseError) {
195
+ if (!options.script) {
196
+ raiseError();
197
+ }
198
+ else {
199
+ migration.lossy = true;
200
+ context.hasLossyChanges = true;
201
+ }
202
+ };
203
+ }
@@ -38,6 +38,7 @@ optionProcessor
38
38
  .option(' --debug <id-list>')
39
39
  .option('-E, --enrich-csn')
40
40
  .option('-R, --raw-output <name>')
41
+ .option(' --new-parser')
41
42
  .option(' --internal-msg')
42
43
  .option(' --beta-mode')
43
44
  .option(' --beta <list>')
@@ -54,6 +55,7 @@ optionProcessor
54
55
  .option(' --default-binary-length <length>')
55
56
  .option(' --default-string-length <length>')
56
57
  .option(' --no-recompile')
58
+ .option(' --skip-name-check', { optionName: '$skipNameCheck' })
57
59
  .positionalArgument('<files...>')
58
60
  .help(`
59
61
  Usage: cdsc <command> [options] <files...>
@@ -113,6 +115,7 @@ optionProcessor
113
115
  with name = "+", write complete XSN, long!
114
116
  --tenant-discriminator Add tenant fields to entities
115
117
  --internal-msg Write raw messages with call stack to <stdout>/<stderr>
118
+ --new-parser Use the new CDL parser
116
119
  --beta-mode Enable all unsupported, incomplete (beta) features
117
120
  --beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
118
121
  Valid values are:
@@ -150,6 +153,7 @@ optionProcessor
150
153
  to "sap.common.Languages" if it exists
151
154
  --localized-without-coalesce Omit coalesce in localized convenience views
152
155
  --no-recompile Don't recompile in case of internal errors
156
+ --skip-name-check Skip certain name checks, e.g. that there must be no '.' in element names.
153
157
 
154
158
  Commands
155
159
  H, toHana [options] <files...> Generate HANA CDS source files
@@ -166,7 +170,7 @@ optionProcessor
166
170
  manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
167
171
  add / modify referential constraints.
168
172
  inspect [options] <files...> (internal) Inspect the given CDS files.
169
- toEffectiveCsn [options] <files...> (internal) Get an effective CSN; requires beta mode
173
+ forEffective [options] <files...> (internal) Get an effective CSN; requires beta mode
170
174
  forSeal [options] <files...> (internal) Get a SEAL CSN
171
175
 
172
176
  Environment variables
@@ -245,6 +249,7 @@ optionProcessor.command('O, toOdata')
245
249
  .option(' --odata-foreign-keys')
246
250
  .option(' --odata-v2-partial-constr')
247
251
  .option(' --odata-vocabularies <list>')
252
+ .option(' --odata-no-creator')
248
253
  .option('-c, --csn')
249
254
  .option('-f, --odata-format <format>', { valid: ['flat', 'structured'] })
250
255
  .option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: [ '--names' ] })
@@ -278,6 +283,7 @@ optionProcessor.command('O, toOdata')
278
283
  (Not spec compliant and V2 only)
279
284
  --odata-vocabularies <list> JSON array of adhoc vocabulary definitions
280
285
  { prefix: { alias, ns, uri }, ... }
286
+ --odata-no-creator Omit creator identification in API
281
287
  -n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is
282
288
  the corresponding database name (see "--sql-mapping" for "toHana or "toSql")
283
289
  plain : (default) Names in uppercase and flattened with underscores
@@ -564,7 +570,7 @@ optionProcessor.command('inspect')
564
570
  --propagation <art> Show propagation sources for <art>
565
571
  `);
566
572
 
567
- optionProcessor.command('toEffectiveCsn')
573
+ optionProcessor.command('forEffective')
568
574
  .option('-h, --help')
569
575
  .option('--resolve-simple-types <val>', { valid: ['true', 'false'] } )
570
576
  .option('--resolve-projections <val>', { valid: ['true', 'false'] } )
@@ -572,7 +578,7 @@ optionProcessor.command('toEffectiveCsn')
572
578
  .option('--keep-localized <val>', { valid: ['true', 'false'] } )
573
579
  .positionalArgument('<files...>')
574
580
  .help(`
575
- Usage: cdsc toEffectiveCsn [options] <files...>
581
+ Usage: cdsc forEffective [options] <files...>
576
582
 
577
583
  (internal): Get the effective CSN model compiled from the provided CDS files.
578
584
  This command may change any time, including its name.