@sap/cds-compiler
Advanced tools
Comparing version 3.4.2 to 3.4.4
@@ -508,3 +508,3 @@ #!/usr/bin/env node | ||
hasAtLeastOneExplanation = hasAtLeastOneExplanation || main.hasMessageExplanation(msg.messageId); | ||
const name = msg.location && msg.location.file; | ||
const name = msg.$location && msg.$location.file; | ||
const fullFilePath = name ? path.resolve('', name) : undefined; | ||
@@ -518,5 +518,4 @@ log(main.messageStringMultiline(msg, { | ||
})); | ||
if (fullFilePath && msg.location.col) { | ||
// A message context only makes sense, if we have at least a medium precision, | ||
// i.e. line and column for start position. | ||
if (fullFilePath && msg.$location.line) { | ||
// A message context only makes sense, if we have at least start line | ||
const context = sourceLines(fullFilePath); | ||
@@ -523,0 +522,0 @@ log(main.messageContext(context, msg, { color: options.color })); |
@@ -40,9 +40,22 @@ #!/usr/bin/env node | ||
for (const tok of ts.tokens) { | ||
const cat = tok.isIdentifier; | ||
if (cat && tok.start >= 0) { | ||
if (cat !== 'ref' || chars[tok.start] !== '$') | ||
chars[tok.start] = categoryChars[cat] || cat.charAt(0); | ||
if (tok.stop > tok.start) // stop in ANTLR at last char, not behind | ||
chars[tok.start + 1] = '_'; | ||
if (tok.start < 0) | ||
continue; | ||
if (tok.$isSkipped) { | ||
if (tok.stop > tok.start) { | ||
chars[tok.start] = (tok.$isSkipped === true ? '\x0f' : '\x16'); | ||
chars[tok.stop] = '\x17'; | ||
} | ||
else { | ||
chars[tok.start] = (tok.$isSkipped === true ? '\x0e' : '\x15'); | ||
} | ||
} | ||
else { | ||
const cat = tok.isIdentifier; | ||
if (cat) { | ||
if (cat !== 'ref' || chars[tok.start] !== '$') | ||
chars[tok.start] = categoryChars[cat] || cat.charAt(0); | ||
if (tok.stop > tok.start) // stop in ANTLR at last char, not behind | ||
chars[tok.start + 1] = '_'; | ||
} | ||
} | ||
} | ||
@@ -49,0 +62,0 @@ for (const c of chars) |
@@ -10,2 +10,10 @@ # ChangeLog for cds compiler and backends | ||
## Version 3.4.4 - 2022-11-25 | ||
### Fixed | ||
- compiler: CSN flavor `gensrc` (known as `xtended` in `@sap/cds`) lost annotations | ||
on enum values and projection columns. | ||
## Version 3.4.2 - 2022-11-11 | ||
@@ -12,0 +20,0 @@ |
@@ -15,3 +15,3 @@ /** @module API */ | ||
const modelCompare = lazyload('../modelCompare/compare'); | ||
const diffFilter = lazyload('../modelCompare/filter'); | ||
const diffFilter = lazyload('../modelCompare/utils/filter'); | ||
const sortViews = lazyload('../model/sortViews'); | ||
@@ -21,2 +21,3 @@ const csnUtils = lazyload('../model/csnUtils'); | ||
const forRelationalDB = lazyload('../transform/forRelationalDB'); | ||
const sqlUtils = lazyload('../render/utils/sql'); | ||
@@ -403,6 +404,8 @@ /** | ||
const identifierUtils = sqlUtils.getIdentifierUtils(internalOptions); | ||
const drops = { | ||
creates: {}, | ||
final: Object.entries(diff.deletions).reduce((previous, [ name, artifact ]) => { | ||
previous[name] = `DROP ${ (artifact.query || artifact.projection) ? 'VIEW' : 'TABLE' } ${ artifact['@cds.persistence.name'] };`; | ||
previous[name] = `DROP ${ (artifact.query || artifact.projection) ? 'VIEW' : 'TABLE' } ${ identifierUtils.quoteSqlId(artifact['@cds.persistence.name']) };`; | ||
return previous; | ||
@@ -423,3 +426,3 @@ }, {}), | ||
diffArtifact[modelCompare.isChanged] === undefined)) { // if it was removed in the after, then we don't have the flag | ||
drops.creates[artifactName] = `DROP VIEW ${ diffArtifact['@cds.persistence.name'] };`; | ||
drops.creates[artifactName] = `DROP VIEW ${ identifierUtils.quoteSqlId(diffArtifact['@cds.persistence.name']) };`; | ||
} // TODO: What happens with a changed kind -> entity becomes a view? | ||
@@ -426,0 +429,0 @@ else if (diffArtifact && |
@@ -119,2 +119,3 @@ // Central registry for messages. | ||
'ref-autoexposed': { severity: 'Error', configurableFor: 'deprecated' }, | ||
// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues | ||
'ref-undefined-art': { severity: 'Error' }, | ||
@@ -141,16 +142,18 @@ 'ref-undefined-def': { severity: 'Error' }, | ||
// Published! Used in @sap/cds-lsp; if renamed, add to oldMessageIds and contact colleagues | ||
// Also used by other projects that rely on double-quotes for delimited identifiers. | ||
'syntax-deprecated-ident': { severity: 'Error', configurableFor: true }, | ||
// 'syntax-duplicate-annotate' came late with v3 - make it configurable as | ||
// fallback, but then parse.cdl is not supposed to work correctly (it can | ||
// then either issue an error or produce a CSN missing some annotations): | ||
'syntax-duplicate-annotate': { severity: 'Error', configurableFor: true }, | ||
'syntax-unexpected-property': { severity: 'Error' }, | ||
'syntax-deprecated-ident': { severity: 'Error', configurableFor: true }, | ||
'syntax-fragile-alias': { severity: 'Error', configurableFor: true }, | ||
'syntax-fragile-ident': { severity: 'Error', configurableFor: true }, | ||
'syntax-duplicate-annotate': { severity: 'Error', configurableFor: 'v3' }, | ||
'syntax-invalid-name': { severity: 'Error', configurableFor: 'v3' }, | ||
'syntax-missing-as': { severity: 'Error', configurableFor: true }, | ||
'syntax-unexpected-null': { severity: 'Error', configurableFor: true }, | ||
'syntax-unexpected-reserved-word': { severity: 'Error', configurableFor: true }, | ||
'syntax-unknown-escape': { severity: 'Error', configurableFor: true }, | ||
'syntax-unsupported-masked': { severity: 'Error', configurableFor: 'v3' }, | ||
'syntax-unexpected-null': { severity: 'Error', configurableFor: true }, | ||
'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config | ||
'type-unsupported-precision-change': { severity: 'Error'}, | ||
@@ -236,3 +239,3 @@ 'def-missing-element': { severity: 'Error' }, | ||
}, | ||
// Syntax messages, both CDL and CSN parser: ---------------------------------- | ||
@@ -245,10 +248,21 @@ 'syntax-dollar-ident': { // Warning, TODO: make it name-invalid-alias | ||
}, | ||
// 'syntax-duplicate-excluding' (TODO: also CDL) | ||
'syntax-deprecated-abstract': 'Abstract entity definitions are deprecated; use aspect definitions instead', // Warning, TODO: also use for CSN | ||
'syntax-duplicate-excluding': { | ||
std: 'Duplicate $(NAME) in the $(KEYWORD) clause', | ||
csn: 'Duplicate $(NAME) in property $(PROP)', | ||
}, | ||
'syntax-expecting-integer': { // TODO: currently 'syntax-expecting-natnum' in CSN | ||
std: 'A safe integer is expected here', | ||
normal: 'An integer number is expected here', | ||
unsafe: 'The provided integer is too large', | ||
}, | ||
'syntax-ignoring-anno': { | ||
std: 'Annotations can\'t be used at prefix references', | ||
doc: 'Doc comments can\'t be used at prefix references', | ||
std: 'Annotations can\'t be used in a column with $(CODE)', | ||
doc: 'Doc comments can\'t be used in a column with $(CODE)', | ||
}, | ||
'syntax-invalid-name': { | ||
std: 'Identifier for name must not be empty', // TODO: use | ||
std: 'Identifier must consist of at least one character', | ||
csn: 'Property name in dictionary $(PARENTPROP) must not be empty', | ||
as: 'String in property $(PROP) must not be empty', // TODO: use | ||
}, | ||
@@ -275,4 +289,9 @@ 'syntax-invalid-literal': { // TODO: write texts less CDL specific | ||
// Syntax messages, CDL parser - default: Error ------------------------------ | ||
// Syntax messages, CDL parser ----------------------------------------------- | ||
// 'syntax-deprecated-auto-as', 'syntax-deprecated-ident' | ||
'syntax-duplicate-annotate': 'You can\'t refer to $(NAME) repeatedly with property $(PROP) in the same annotate statement', | ||
'syntax-duplicate-argument': { | ||
std: 'Duplicate value for parameter $(NAME)', | ||
type: 'Duplicate value for type parameter $(NAME)', | ||
}, | ||
'syntax-duplicate-extend': { | ||
@@ -283,9 +302,28 @@ std: 'You can\'t define and refer to $(NAME) repeatedly in the same extend statement', | ||
}, | ||
'syntax-invalid-extend': 'Can\'t extend an element with $(KIND)', | ||
// 'syntax-duplicate-anno', 'syntax-duplicate-doc-comment', 'syntax-duplicate-cardinality', | ||
// 'syntax-duplicate-property' | ||
'syntax-unexpected-reserved-word': '$(CODE) is a reserved word - write $(DELIMITED) instead if you want to use it as name', | ||
'syntax-invalid-text-block': 'Missing newline in text block', | ||
// 'syntax-missing-newline' (Warning), 'syntax-missing-as', | ||
// 'syntax-missing-token' | ||
'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before', | ||
'syntax-unexpected-token': { | ||
std: 'Mismatched $(OFFENDING), expecting $(EXPECTING)', | ||
unwanted: 'Extraneous $(OFFENDING), expecting $(EXPECTING)' | ||
}, | ||
'syntax-unexpected-vocabulary': { | ||
std: 'Annotations can\'t be defined inside contexts or services', // not used | ||
service: 'Annotations can\'t be defined inside services', | ||
context: 'Annotations can\'t be defined inside contexts', | ||
}, | ||
// 'syntax-unexpected-space', 'syntax-unexpected-doc-comment' (TODO is: -ignoring-, Info) | ||
// 'syntax-unexpected-alias' (is 'syntax-unexpected-property' in CSN), | ||
// 'syntax-unexpected-right-paren' | ||
'syntax-unsupported-calc-field': 'Calculated fields are not supported', | ||
'syntax-unsupported-param': { | ||
std: 'Parameter not supported', // unused | ||
dynamic: 'Dynamic parameter $(NAME) is not supported', | ||
positional: 'Positional parameter $(NAME) is not supported', | ||
dynamic: 'Dynamic parameter $(CODE) is not supported', | ||
positional: 'Positional parameter $(CODE) is not supported', | ||
}, | ||
// 'syntax-unsupported-method', 'syntax-unsupported-new' | ||
@@ -323,6 +361,6 @@ // Syntax messages, CSN parser - default: Error ------------------------------ | ||
}, | ||
// 'syntax-invalid-ref' (Warning?), 'syntax-invalid-kind', 'syntax-invalid-literal' (Warning) | ||
'syntax-invalid-string': { | ||
std: 'Invalid string value in property $(PROP)', | ||
}, | ||
// 'syntax-invalid-ref' (Warning?), 'syntax-invalid-kind', 'syntax-invalid-literal' (Warning) | ||
'syntax-missing-property': { // location at sibling or '}' otherwise | ||
@@ -345,2 +383,3 @@ std: 'Object in $(PARENTPROP) must have the property $(PROP)', | ||
// multi-line strings: -------------------------------------------------------- | ||
'syntax-unknown-escape': 'Unknown escape sequence $(CODE)', | ||
@@ -361,20 +400,2 @@ 'syntax-invalid-escape': { | ||
}, | ||
'syntax-expecting-integer': { | ||
std: 'A safe integer is expected here', | ||
normal: 'An integer number is expected here', | ||
unsafe: 'The provided integer is too large', | ||
}, | ||
'syntax-duplicate-argument': { // TODO: also CDL | ||
std: 'Unexpected argument $(CODE)', | ||
unknown: 'Unknown argument $(CODE)', // huh? | ||
duplicate: 'Duplicate argument $(CODE)', | ||
}, | ||
'syntax-unexpected-null': 'Keyword $(KEYWORD) must appear after the enum definition and not before', | ||
'syntax-unexpected-vocabulary': { | ||
std: 'Annotations can\'t be defined inside contexts or services', | ||
service: 'Annotations can\'t be defined inside services', | ||
context: 'Annotations can\'t be defined inside contexts', | ||
}, | ||
'syntax-fragile-ident': '$(ID) is a reserved name here - write $(DELIMITED) instead if you want to use it', | ||
'syntax-unsupported-field': 'Calculated fields are not supported, yet', | ||
@@ -555,3 +576,3 @@ // Syntax messages for errorneous references ---------------------------------- | ||
std: 'Unexpected array type for element $(NAME)', | ||
scalar: 'Unexpected array type' | ||
scalar: 'Unexpected array type', | ||
}, | ||
@@ -572,2 +593,4 @@ 'odata-spec-violation-key-null': { | ||
facet: 'Unexpected EDM Type facet $(NAME) of type $(TYPE) for OData $(VERSION)', | ||
missing: 'Expected referenced type $(TYPE) to be included in service $(NAME)', | ||
external: 'Referenced type $(TYPE) marked as $(ANNO) can\'t be rendered as $(CODE) in service $(NAME) for OData $(VERSION)', | ||
}, | ||
@@ -597,3 +620,3 @@ 'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)', | ||
* @property {MessageSeverity} severity Default severity for the message. | ||
* @property {string[]|'deprecated'|true} [configurableFor] | ||
* @property {string[]|'deprecated'|'v3'|true} [configurableFor] | ||
* Whether the error can be reclassified to a warning or lower. | ||
@@ -600,0 +623,0 @@ * If not `true` then an array is expected with specified modules in which the error is downgradable. |
@@ -658,3 +658,3 @@ // Functions and classes for syntax messages | ||
annos: anno => anno.map(paramsTransform.anno).join(', '), | ||
delimited: n => '![' + n + ']', // TODO: use quote.single around? | ||
delimited: n => quote.single( `![${ n }]` ), // represent as delimited id (TODO: double-']') | ||
file: quote.single, | ||
@@ -675,2 +675,3 @@ prop: quote.single, | ||
value: n => (typeof n !== 'number' ? quote.single( n ) : quote.number( n )), | ||
values: value => value.map(paramsTransform.value).join(', '), | ||
othervalue: n => (typeof n !== 'number' ? quote.single( n ) : quote.number( n )), | ||
@@ -712,3 +713,5 @@ art: transformArg, | ||
function quoted( name ) { | ||
return (name) ? quote.double( name ) : quote.angle( '?' ); // TODO: failure in --test-mode, then remove | ||
if (typeof name === 'string') | ||
return quote.double( name ); | ||
throw new CompilerAssertion( `Expecting a string, not ${ name }` ); | ||
} | ||
@@ -805,3 +808,4 @@ | ||
let t = transform && transform[p] || paramsTransform[p]; | ||
args[p] = (t) ? t( params[p], args, params, texts ) : params[p]; | ||
if (params[p] !== undefined) | ||
args[p] = (t) ? t( params[p], args, params, texts ) : params[p]; | ||
} | ||
@@ -808,0 +812,0 @@ let variant = args['#']; |
@@ -17,2 +17,4 @@ { | ||
"jsdoc/require-param-description": 0, | ||
// there seem to be false positives | ||
"jsdoc/require-returns-check": 0, | ||
// =airbnb, >eslint: | ||
@@ -19,0 +21,0 @@ "max-len": [ "error", { |
{ | ||
"root": true, | ||
"extends": "../../.eslintrc-ydkjsi.json" | ||
"extends": "../../.eslintrc-ydkjsi.json", | ||
"rules": { | ||
"cds-compiler/space-in-func-decl": "error" | ||
} | ||
} |
@@ -407,3 +407,2 @@ // Consistency checker on model (XSN = augmented CSN) | ||
requires: [ 'literal', 'location' ], | ||
// TODO: rename symbol to sym | ||
// TODO: struct and variant only for annotation assignments | ||
@@ -445,3 +444,3 @@ optional: [ | ||
optional: [ | ||
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate', 'upTo', | ||
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo', | ||
], | ||
@@ -467,3 +466,6 @@ // TODO: restrict path to #simplePath | ||
// preliminary ----------------------------------------------------------- | ||
doc: { kind: true, test: locationVal( isStringOrNull ) }, // doc comment | ||
doc: { | ||
kind: true, | ||
test: locationVal( isStringOrNull ), | ||
}, // doc comment | ||
'@': { | ||
@@ -894,3 +896,3 @@ kind: true, | ||
const requires = [ 'val', 'location' ]; | ||
const optional = [ 'literal', '$inferred', '_pathHead' ]; | ||
const optional = [ 'literal', '$inferred', '$priority', '_pathHead' ]; | ||
standard( node, parent, prop, { schema: valSchema, requires, optional }, name ); | ||
@@ -919,3 +921,3 @@ }; | ||
function isOneOf(values) { | ||
function isOneOf( values ) { | ||
return function isOneOfInner( node, parent, prop ) { | ||
@@ -967,3 +969,3 @@ if (!values.includes(node)) | ||
function isScope(node, parent, prop) { | ||
function isScope( node, parent, prop ) { | ||
// artifact refs in CDL have scope:0 in XSN | ||
@@ -970,0 +972,0 @@ if (Number.isInteger(node)) |
@@ -262,3 +262,3 @@ // The builtin artifacts of CDS | ||
*/ | ||
function checkDate(year, month, day) { | ||
function checkDate( year, month, day ) { | ||
// Negative years are allowed | ||
@@ -279,3 +279,3 @@ year = Math.abs(Number.parseInt(year, 10)); | ||
*/ | ||
function checkTime(hour, minutes, seconds) { | ||
function checkTime( hour, minutes, seconds ) { | ||
hour = Number.parseInt(hour, 10); | ||
@@ -316,31 +316,31 @@ minutes = Number.parseInt(minutes, 10); | ||
/** @param {string} typeName */ | ||
function isIntegerTypeName(typeName) { | ||
function isIntegerTypeName( typeName ) { | ||
return typeCategories.integer.includes(typeName); | ||
} | ||
/** @param {string} typeName */ | ||
function isDecimalTypeName(typeName) { | ||
function isDecimalTypeName( typeName ) { | ||
return typeCategories.decimal.includes(typeName); | ||
} | ||
/** @param {string} typeName */ | ||
function isNumericTypeName(typeName) { | ||
function isNumericTypeName( typeName ) { | ||
return isIntegerTypeName(typeName) || isDecimalTypeName(typeName); | ||
} | ||
/** @param {string} typeName */ | ||
function isStringTypeName(typeName) { | ||
function isStringTypeName( typeName ) { | ||
return typeCategories.string.includes(typeName); | ||
} | ||
/** @param {string} typeName */ | ||
function isDateOrTimeTypeName(typeName) { | ||
function isDateOrTimeTypeName( typeName ) { | ||
return typeCategories.dateTime.includes(typeName); | ||
} | ||
/** @param {string} typeName */ | ||
function isBooleanTypeName(typeName) { | ||
function isBooleanTypeName( typeName ) { | ||
return typeCategories.boolean.includes(typeName); | ||
} | ||
/** @param {string} typeName */ | ||
function isBinaryTypeName(typeName) { | ||
function isBinaryTypeName( typeName ) { | ||
return typeCategories.binary.includes(typeName); | ||
} | ||
/** @param {string} typeName */ | ||
function isGeoTypeName(typeName) { | ||
function isGeoTypeName( typeName ) { | ||
return typeCategories.geo.includes(typeName); | ||
@@ -353,3 +353,3 @@ } | ||
*/ | ||
function isRelationTypeName(typeName) { | ||
function isRelationTypeName( typeName ) { | ||
return typeCategories.relation.includes(typeName); | ||
@@ -364,3 +364,3 @@ } | ||
*/ | ||
function isInReservedNamespace(absolute) { | ||
function isInReservedNamespace( absolute ) { | ||
return absolute.startsWith( 'cds.') && | ||
@@ -383,3 +383,3 @@ !absolute.match(/^cds\.foundation(\.|$)/) && | ||
*/ | ||
function isBuiltinType(type) { | ||
function isBuiltinType( type ) { | ||
return typeof type === 'string' && isInReservedNamespace(type); | ||
@@ -386,0 +386,0 @@ } |
@@ -26,5 +26,6 @@ // Checks on XSN performed during compile() | ||
const { | ||
error, warning, message, | ||
error, warning, message, info, | ||
} = model.$messageFunctions; | ||
forEachDefinition( model, checkArtifact ); | ||
forEachGeneric( model, 'vocabularies', checkAnnotationDefinition ); | ||
checkSapCommonLocale( model, model.$messageFunctions ); | ||
@@ -40,2 +41,7 @@ return; | ||
function checkAnnotationDefinition( art ) { | ||
checkEnumType( art ); | ||
// TODO: Should we check elements similar to definition-elements as well? | ||
} | ||
function checkGenericConstruct( art ) { | ||
@@ -85,3 +91,3 @@ checkName( art ); | ||
function checkLocalizedElement(elem) { | ||
function checkLocalizedElement( elem ) { | ||
// if it is directly a localized element | ||
@@ -92,4 +98,5 @@ if (elem.localized && elem.localized.val) { | ||
if (!type || !type.builtin || type.category !== 'string') { | ||
warning(null, [ elem.type?.location, elem ], { keyword: 'localized' }, | ||
'Keyword $(KEYWORD) should only be used in combination with string types'); | ||
info('ref-expecting-localized-string', [ elem.type?.location, elem ], | ||
{ keyword: 'localized' }, | ||
'Expecting a string type in combination with keyword $(KEYWORD)'); | ||
} | ||
@@ -158,3 +165,3 @@ } | ||
*/ | ||
function checkEnum(enumNode) { | ||
function checkEnum( enumNode ) { | ||
if (!enumNode.value) | ||
@@ -175,3 +182,3 @@ return; | ||
function checkEnumType(enumNode) { | ||
function checkEnumType( enumNode ) { | ||
// Either the type is an enum or an arrayed enum. We are only interested in | ||
@@ -231,3 +238,3 @@ // the enum and don't care whether the enum is arrayed. | ||
*/ | ||
function checkEnumValue(enumNode) { | ||
function checkEnumValue( enumNode ) { | ||
const type = enumNode.type && enumNode.type._artifact && | ||
@@ -303,3 +310,3 @@ enumNode.type._artifact._effectiveType; | ||
*/ | ||
function checkLocalizedSubElement(element) { | ||
function checkLocalizedSubElement( element ) { | ||
if (element._parent.kind !== 'element') | ||
@@ -321,3 +328,3 @@ return; | ||
// TODO: Recursive check | ||
function isTypeLocalized(type) { | ||
function isTypeLocalized( type ) { | ||
return (type && type.localized && type.localized.val); | ||
@@ -333,3 +340,3 @@ } | ||
*/ | ||
function checkForUnmanagedAssociations(element, keyObj) { | ||
function checkForUnmanagedAssociations( element, keyObj ) { | ||
if (element.targetAspect) { | ||
@@ -355,3 +362,3 @@ // TODO: bad location / message | ||
// TODO: move to define.js or parsers | ||
function checkCardinality(elem) { | ||
function checkCardinality( elem ) { | ||
if (!elem.cardinality) | ||
@@ -458,3 +465,3 @@ return; | ||
// found within paths (e.g. filters, parameters, ...) | ||
function checkExpressionsInPaths(node) { | ||
function checkExpressionsInPaths( node ) { | ||
foreachPath(node, (path) => { | ||
@@ -473,3 +480,3 @@ for (const pathStep of path) { | ||
function checkAssociation(elem) { | ||
function checkAssociation( elem ) { | ||
// TODO: yes, a check similar to this could make it into the compiler) | ||
@@ -490,3 +497,3 @@ // when virtual element is part of association | ||
function checkAssociationCondition(elem, onCond) { | ||
function checkAssociationCondition( elem, onCond ) { | ||
if (Array.isArray(onCond)) // condition in brackets results an array | ||
@@ -498,3 +505,3 @@ onCond.forEach(Cond => checkAssociationCondition(elem, Cond)); | ||
function checkAssociationConditionArgs(elem, args, op) { | ||
function checkAssociationConditionArgs( elem, args, op ) { | ||
if (args) | ||
@@ -504,3 +511,3 @@ args.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op)); | ||
function checkAssociationOnCondArg(elem, arg, op) { | ||
function checkAssociationOnCondArg( elem, arg, op ) { | ||
if (Array.isArray(arg)) { | ||
@@ -522,3 +529,3 @@ arg.forEach(Arg => checkAssociationOnCondArg(elem, Arg, op)); | ||
// Additionally, `$self.assoc` references are also not found. | ||
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op) { | ||
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc( elem, arg, op ) { | ||
if (!arg.path) | ||
@@ -565,3 +572,3 @@ return; | ||
*/ | ||
function checkTypeStructure(artifact) { | ||
function checkTypeStructure( artifact ) { | ||
// Just a basic check. We do not check that the inner structure of `items` | ||
@@ -596,3 +603,3 @@ // is the same as the type but only that all are arrayed or structured. | ||
*/ | ||
function checkExpression(xpr, allowAssocTail = false) { | ||
function checkExpression( xpr, allowAssocTail = false ) { | ||
// Since the checks for tree-like and token-stream expressions differ, | ||
@@ -612,3 +619,3 @@ // check here what kind of expression we are looking at | ||
*/ | ||
function isVirtualElement(arg) { | ||
function isVirtualElement( arg ) { | ||
return arg.path && | ||
@@ -625,3 +632,3 @@ arg._artifact && arg._artifact.virtual && arg._artifact.virtual.val === true && | ||
*/ | ||
function checkTokenStreamExpression(xpr, allowAssocTail) { | ||
function checkTokenStreamExpression( xpr, allowAssocTail ) { | ||
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {}); | ||
@@ -645,3 +652,3 @@ // Check for illegal argument usage within the expression | ||
*/ | ||
function checkTreeLikeExpression(xpr, allowAssocTail) { | ||
function checkTreeLikeExpression( xpr, allowAssocTail ) { | ||
// No further checks regarding associations and $self required if this is a | ||
@@ -680,3 +687,3 @@ // backlink-like expression (a comparison of $self with an assoc) | ||
// Return true if 'arg' is an expression argument of type association or composition | ||
function isAssociationOperand(arg) { | ||
function isAssociationOperand( arg ) { | ||
if (!arg.path) { | ||
@@ -692,3 +699,3 @@ // Not a path, hence not an association (literal, expression, function, whatever ...) | ||
// Return true if 'arg' is an expression argument denoting "$self" || "$projection" | ||
function isDollarSelfOrProjectionOperand(arg) { | ||
function isDollarSelfOrProjectionOperand( arg ) { | ||
return arg.path && arg.path.length === 1 && | ||
@@ -704,3 +711,3 @@ (arg.path[0].id === '$self' || arg.path[0].id === '$projection'); | ||
*/ | ||
function isBinaryDollarSelfComparisonWithAssoc(xpr) { | ||
function isBinaryDollarSelfComparisonWithAssoc( xpr ) { | ||
// Must be an expression with arguments | ||
@@ -763,3 +770,3 @@ if (!xpr.op || !xpr.args) | ||
// or undefined). Report errors on 'options.messages. | ||
function checkAnnotationAssignment(anno, annoDecl, elementDecl, art) { | ||
function checkAnnotationAssignment( anno, annoDecl, elementDecl, art ) { | ||
// Nothing to check if no actual annotation declaration was found | ||
@@ -808,3 +815,3 @@ if (!annoDecl || annoDecl.artifacts && !elementDecl) | ||
// if not | ||
function checkValueAssignableTo(value, elementDecl, art) { | ||
function checkValueAssignableTo( value, elementDecl, art ) { | ||
// FIXME: We currently do not have any element declaration that could match | ||
@@ -870,3 +877,3 @@ // a 'path' value, so we simply leave those alone | ||
} | ||
else { | ||
else if (!elementDecl._effectiveType.enum) { | ||
throw new CompilerAssertion(`Unknown primitive type name: ${ type }`); | ||
@@ -892,3 +899,5 @@ } | ||
// Enum symbol not provided but expected | ||
if (!Object.keys(expectedEnum).some(symbol => expectedEnum[symbol].value.val === value.val)) { | ||
const hasValidValue = Object.keys(expectedEnum) | ||
.some(symbol => getEnumValue(expectedEnum[symbol]) === value.val); | ||
if (!hasValidValue) { | ||
// ... and none of the valid enum symbols matches the value | ||
@@ -900,2 +909,10 @@ warning(null, loc, {}, 'An enum value is required here'); | ||
function getEnumValue( enumSymbol ) { | ||
if (enumSymbol.value) | ||
return enumSymbol.value?.val; | ||
if (enumSymbol._effectiveType) | ||
return enumSymbol._effectiveType?.value?.val; | ||
return null; | ||
} | ||
// TODO: remove the following | ||
@@ -910,3 +927,3 @@ | ||
// functions, parameters etc.) | ||
function resolvePathFrom(path, from, result = {}) { | ||
function resolvePathFrom( path, from, result = {} ) { | ||
// Keep last encountered artifacts | ||
@@ -930,7 +947,7 @@ if (from && !from._main) | ||
// anonymous types | ||
function getFinalTypeNameOf(node) { | ||
function getFinalTypeNameOf( node ) { | ||
let type = node._effectiveType; | ||
if (type.type) | ||
type = type.type._artifact; | ||
return type && type.name && type.name.absolute; | ||
return type?.name?.absolute; | ||
} | ||
@@ -963,3 +980,3 @@ } | ||
// TODO: remove - this is not a good way to traverse expressions | ||
function foreachPath(node, callback) { | ||
function foreachPath( node, callback ) { | ||
if (node === null || typeof node !== 'object') { | ||
@@ -966,0 +983,0 @@ // Primitive node |
@@ -675,10 +675,13 @@ // Compiler phase 1 = "define": transform dictionary of AST-like XSNs into XSN | ||
if (col.inline) { // `@anno elem.{ * }` does not work | ||
if (col.doc) | ||
warning( 'syntax-ignoring-anno', [ col.doc.location, col ], { '#': 'doc' } ); | ||
if (col.doc) { | ||
warning( 'syntax-ignoring-anno', [ col.doc.location, col ], | ||
{ '#': 'doc', code: '.{ ‹inline› }' } ); | ||
} | ||
// col.$annotations no available for CSN input, have to search. | ||
// Warning about first annotation should be enough to avoid spam. | ||
const firstAnno = Object.keys(col).find(key => key.startsWith('@')); | ||
if (firstAnno) | ||
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ] ); | ||
if (firstAnno) { | ||
warning( 'syntax-ignoring-anno', [ col[firstAnno].name.location, col ], | ||
{ code: '.{ ‹inline› }' } ); | ||
} | ||
} | ||
@@ -742,3 +745,3 @@ // TODO: allow sub queries? at least in top-level expand without parallel ref | ||
*/ | ||
function approveExistsInChildren(exprOrPathElement) { | ||
function approveExistsInChildren( exprOrPathElement ) { | ||
if (!exprOrPathElement) // may be null in case of parse error | ||
@@ -745,0 +748,0 @@ return; |
@@ -56,6 +56,4 @@ // Extend, include, localized data and managed compositions | ||
const commonLanguagesEntity = options.addTextsLanguageAssoc && | ||
model.definitions['sap.common.Languages']; | ||
const addTextsLanguageAssoc = !!(commonLanguagesEntity && commonLanguagesEntity.elements && | ||
commonLanguagesEntity.elements.code); | ||
const addTextsLanguageAssoc = checkTextsLanguageAssocOption(model, options); | ||
Object.keys( model.definitions ).forEach( processArtifact ); | ||
@@ -111,3 +109,3 @@ | ||
function processCompositionPersistence(def) { | ||
function processCompositionPersistence( def ) { | ||
if (def.$inferred === 'composition-entity' && !processed.has(def)) { | ||
@@ -212,3 +210,3 @@ if (def._parent) | ||
*/ | ||
function extendArtifact( extensions, art, noIncludes = false) { | ||
function extendArtifact( extensions, art, noIncludes = false ) { | ||
if (!noIncludes && !(canApplyIncludes( art, art ) && | ||
@@ -338,3 +336,3 @@ extensions.every( ext => canApplyIncludes(ext, art) ))) | ||
*/ | ||
function applyTypeExtensions(art) { | ||
function applyTypeExtensions( art ) { | ||
/** | ||
@@ -749,3 +747,3 @@ * Contains the previous extension for each property that was applied | ||
const errpos = elem.localized || elem.type || elem.name; | ||
warning( 'localized-key', [ errpos.location, elem ], { keyword: 'localized' }, | ||
warning( 'def-ignoring-localized-key', [ errpos.location, elem ], { keyword: 'localized' }, | ||
'Keyword $(KEYWORD) is ignored for primary keys' ); | ||
@@ -758,3 +756,3 @@ } | ||
if (!keys) { | ||
warning( null, [ art.name.location, art ], {}, | ||
warning( 'def-expecting-key', [ art.name.location, art ], {}, | ||
'No texts entity can be created when no key element exists' ); | ||
@@ -809,2 +807,44 @@ return false; | ||
function createTextsEntity( base, absolute, textElems, fioriEnabled ) { | ||
const art = createTextsEntityWithDefaultElements( base, absolute, textElems, fioriEnabled ); | ||
const { location } = base.name; | ||
const { locale } = art.elements; | ||
// assertUnique array value, first entry is 'locale' | ||
const assertUniqueValue = [ { | ||
path: [ { id: locale.name.id, location: locale.location } ], | ||
location: locale.location, | ||
} ]; | ||
for (const orig of textElems) { | ||
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' ); | ||
if (orig.key && orig.key.val) { | ||
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location }; | ||
// TODO: the previous would be better, but currently not supported in toCDL | ||
if (!fioriEnabled) { | ||
elem.key = { val: true, $inferred: 'localized', location }; | ||
// If the propagated elements remain key (that is not fiori.draft.enabled) | ||
// they should be omitted from OData containment EDM | ||
annotateWith( elem, '@odata.containment.ignore', location ); | ||
} | ||
else { | ||
// add the former key paths to the unique constraint | ||
assertUniqueValue.push({ | ||
path: [ { id: orig.name.id, location: orig.location } ], | ||
location: orig.location, | ||
}); | ||
} | ||
} | ||
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword | ||
const localized = orig.localized || orig.type || orig.name; | ||
elem.localized = { val: null, $inferred: 'localized', location: localized.location }; | ||
} | ||
} | ||
if (fioriEnabled) | ||
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' ); | ||
return art; | ||
} | ||
function createTextsEntityWithDefaultElements( base, absolute, textElems, fioriEnabled ) { | ||
const elements = Object.create(null); | ||
@@ -821,4 +861,3 @@ const { location } = base.name; | ||
// If not, use the default `cds.String` with a length of 14. | ||
const hasLocaleType = model.definitions['sap.common.Locale'] && | ||
model.definitions['sap.common.Locale'].kind === 'type'; | ||
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type'; | ||
const locale = { | ||
@@ -874,35 +913,2 @@ name: { location, id: 'locale' }, | ||
// assertUnique array value, first entry is 'locale' | ||
const assertUniqueValue = [ { | ||
path: [ { id: locale.name.id, location: locale.location } ], | ||
location: locale.location, | ||
} ]; | ||
for (const orig of textElems) { | ||
const elem = linkToOrigin( orig, orig.name.id, art, 'elements' ); | ||
if (orig.key && orig.key.val) { | ||
// elem.key = { val: fioriEnabled ? null : true, $inferred: 'localized', location }; | ||
// TODO: the previous would be better, but currently not supported in toCDL | ||
if (!fioriEnabled) { | ||
elem.key = { val: true, $inferred: 'localized', location }; | ||
// If the propagated elements remain key (that is not fiori.draft.enabled) | ||
// they should be omitted from OData containment EDM | ||
annotateWith( elem, '@odata.containment.ignore', location ); | ||
} | ||
else { | ||
// add the former key paths to the unique constraint | ||
assertUniqueValue.push({ | ||
path: [ { id: orig.name.id, location: orig.location } ], | ||
location: orig.location, | ||
}); | ||
} | ||
} | ||
if (hasTruthyProp( orig, 'localized' )) { // use location of LOCALIZED keyword | ||
const localized = orig.localized || orig.type || orig.name; | ||
elem.localized = { val: null, $inferred: 'localized', location: localized.location }; | ||
} | ||
} | ||
if (fioriEnabled) | ||
annotateWith( art, '@assert.unique.locale', art.location, assertUniqueValue, 'array' ); | ||
return art; | ||
@@ -1280,3 +1286,3 @@ } | ||
*/ | ||
function copyPersistenceAnnotations(target, source, options) { | ||
function copyPersistenceAnnotations( target, source, options ) { | ||
if (!source) | ||
@@ -1338,2 +1344,22 @@ return; | ||
function checkTextsLanguageAssocOption( model, options ) { | ||
const languages = model.definitions['sap.common.Languages']; | ||
const commonLanguagesEntity = options.addTextsLanguageAssoc && languages?.elements?.code; | ||
if (options.addTextsLanguageAssoc && !commonLanguagesEntity) { | ||
const variant = !languages ? 'std' : 'code'; | ||
const loc = model.definitions['sap.common.Languages']?.name?.location || null; | ||
model.$messageFunctions.info('api-ignoring-language-assoc', loc, { | ||
'#': variant, option: 'addTextsLanguageAssoc', art: 'sap.common.Languages', name: 'code', | ||
}, { | ||
std: 'Ignoring option $(OPTION) because entity $(ART) is missing', | ||
code: 'Ignoring option $(OPTION) because entity $(ART) is missing element $(NAME)', | ||
}); | ||
} | ||
return !!commonLanguagesEntity; | ||
} | ||
module.exports = extend; |
@@ -70,3 +70,3 @@ // Things which needs to done for parse.cdl after define() | ||
*/ | ||
function resolveTypesForParseCdl(artifact, main) { | ||
function resolveTypesForParseCdl( artifact, main ) { | ||
if (!artifact || typeof artifact !== 'object') | ||
@@ -164,3 +164,3 @@ return; | ||
*/ | ||
function resolveTypeUnchecked(artWithType, user) { | ||
function resolveTypeUnchecked( artWithType, user ) { | ||
const root = artWithType.type.path && artWithType.type.path[0]; | ||
@@ -207,3 +207,3 @@ if (!root) // parse error | ||
function chooseAndReportDuplicateAnnotation(artifact, annoName) { | ||
function chooseAndReportDuplicateAnnotation( artifact, annoName ) { | ||
for (const anno of artifact[annoName]) | ||
@@ -210,0 +210,0 @@ message( 'anno-duplicate', [ anno.name.location, artifact ], { anno: annoName } ); |
@@ -48,2 +48,3 @@ // Populate views with elements, elements with association targets, ... | ||
setExpandStatus, | ||
setExpandStatusAnnotate, | ||
} = require('./utils'); | ||
@@ -167,3 +168,2 @@ | ||
(art.type || art._origin || art.value?.path || art.value?.type) && | ||
// TODO: really stop at art.enum? See #8942 | ||
!art.target && !art.enum && !art.elements && !art.items) { | ||
@@ -197,3 +197,2 @@ chain.push( art ); | ||
art._effectiveType && (() => getCardinality( art._effectiveType )); | ||
let prev = art; | ||
for (const a of chain) { | ||
@@ -207,4 +206,6 @@ if (a.cardinality) | ||
art = a; | ||
else if (art.enum && expandEnum( a, prev )) | ||
prev = a; // do not set art - effective type is base | ||
else if (art.enum && expandEnum( a, art )) | ||
art = a; | ||
setLink( a, '_effectiveType', art ); | ||
@@ -476,7 +477,17 @@ } | ||
else { | ||
let wasAnnotated = false; | ||
for (const prop in selem) { | ||
// just annotation assignments and doc comments for the moment | ||
if (prop.charAt(0) === '@' || prop === 'doc') | ||
if (prop.charAt(0) === '@' || prop === 'doc') { | ||
ielem[prop] = selem[prop]; | ||
// required for gensrc mode of to-csn.js, otherwise the annotation | ||
// may be lost during recompilation. | ||
ielem[prop].$priority = 'annotate'; | ||
wasAnnotated = true; | ||
} | ||
} | ||
if (wasAnnotated) | ||
setExpandStatusAnnotate(art, 'annotate'); | ||
selem.$replacement = true; | ||
@@ -483,0 +494,0 @@ if (selem.elements) { |
@@ -66,2 +66,3 @@ // Compiler phase "resolve": resolve all references | ||
dependsOnSilent, | ||
setExpandStatusAnnotate, | ||
testExpr, | ||
@@ -622,16 +623,2 @@ targetMaxNotOne, | ||
function setExpandStatusAnnotate( elem, status ) { | ||
for (;;) { | ||
if (elem.$expand === status) | ||
return; // already set | ||
elem.$expand = status; // meaning: expanded, containing annos | ||
for (let line = elem.items; line; line = line.items) | ||
line.$expand = status; // to-csn just uses the innermost $expand | ||
if (!elem._main) | ||
return; | ||
elem = elem._parent; | ||
} | ||
} | ||
function expandParameters( action ) { | ||
@@ -1296,3 +1283,3 @@ // see also expandElements() | ||
function resolveExpr( expr, expected, user, extDict, expandOrInline) { | ||
function resolveExpr( expr, expected, user, extDict, expandOrInline ) { | ||
// TODO: when we have rewritten the resolvePath functions, | ||
@@ -1299,0 +1286,0 @@ // define a traverseExpr() in ./utils.js |
@@ -531,3 +531,3 @@ // Compiler functions and utilities shared across all phases | ||
*/ | ||
function resolveTypeArgumentsUnchecked(artifact, typeArtifact, user) { | ||
function resolveTypeArgumentsUnchecked( artifact, typeArtifact, user ) { | ||
let args = artifact.$typeArgs || []; | ||
@@ -877,3 +877,3 @@ const parameters = typeArtifact.parameters || []; | ||
*/ | ||
function signalNotFound(msgId, location, valid, textParams ) { | ||
function signalNotFound( msgId, location, valid, textParams ) { | ||
if (location.$notFound) | ||
@@ -896,3 +896,3 @@ return; | ||
*/ | ||
function signalMissingElementAccess(art, location) { | ||
function signalMissingElementAccess( art, location ) { | ||
const err = message( 'ref-expected-element', location, | ||
@@ -915,3 +915,3 @@ { '#': 'magicVar', id: art.name.id } ); | ||
*/ | ||
function attachAndEmitValidNames(msg, ...validDicts) { | ||
function attachAndEmitValidNames( msg, ...validDicts ) { | ||
if (!options.testMode && !options.attachValidNames) | ||
@@ -918,0 +918,0 @@ return; |
@@ -403,2 +403,15 @@ // Simple compiler utility functions | ||
function setExpandStatusAnnotate( elem, status ) { | ||
for (;;) { | ||
if (elem.$expand === status) | ||
return; // already set | ||
elem.$expand = status; // meaning: expanded, containing annos | ||
for (let line = elem.items; line; line = line.items) | ||
line.$expand = status; // to-csn just uses the innermost $expand | ||
if (!elem._main) | ||
return; | ||
elem = elem._parent; | ||
} | ||
} | ||
module.exports = { | ||
@@ -428,2 +441,3 @@ pushLink, | ||
setExpandStatus, | ||
setExpandStatusAnnotate, | ||
}; |
@@ -33,3 +33,3 @@ 'use strict'; | ||
const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx'); | ||
const { info, warning, error, message, throwWithAnyError } = messageFunctions; | ||
const { info, warning, error, message, throwWithError } = messageFunctions; | ||
checkCSNVersion(csn, _options); | ||
@@ -58,3 +58,3 @@ | ||
if(Object.keys(allServices).length === 0) { | ||
info(null, null, `No Services in model`); | ||
info(null, null, 'No Services in model'); | ||
return rc; | ||
@@ -81,3 +81,3 @@ } | ||
} | ||
throwWithAnyError(); | ||
throwWithError(); | ||
return rc; | ||
@@ -97,2 +97,4 @@ | ||
function markRendered(def) { setProp(def, '$isRendered', true); } | ||
const service = new Edm.DataServices(v); | ||
@@ -138,6 +140,17 @@ /** @type {object} */ | ||
const fqSchemaXRef = [serviceCsn.name]; | ||
const whatsMySchemaName = function(n) { | ||
return fqSchemaXRef.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? sn : rc, undefined); | ||
const whatsMySchemaName = function(n, arr=fqSchemaXRef) { | ||
return arr.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? sn : rc, undefined); | ||
} | ||
let xServiceRefs = []; | ||
const UsedTypes = {}; | ||
function collectUsedType(csn, typeName = (csn.items?.type || csn.type)) { | ||
if(typeName) { | ||
if(UsedTypes[typeName]) | ||
UsedTypes[typeName].push(csn) | ||
else | ||
UsedTypes[typeName] = [ csn ]; | ||
} | ||
} | ||
// create schema containers | ||
@@ -183,16 +196,3 @@ const subSchemaDictionary = { | ||
populateSchemas(subSchemaDictionary); | ||
const xServiceRefs = populateXserviceRefs(); | ||
/* TODO: | ||
const references = Object.entries(allSchemas).reduce((references, [fqName, art]) => { | ||
// add references | ||
if(fqName.startsWith(serviceCsn.name + '.') && art.kind === 'reference') { | ||
fqSchemaXRef.push(fqName); | ||
references.push(art); | ||
} | ||
return references; | ||
}, []); | ||
*/ | ||
// Add xrefs to full schema cross ref list for further usage | ||
fqSchemaXRef.push(...xServiceRefs); | ||
fqSchemaXRef.sort((a,b) => b.length-a.length); | ||
xServiceRefs = populateXserviceRefs(); | ||
@@ -232,4 +232,7 @@ // Bring the schemas in alphabetical order, service first, root last | ||
if(arr.length > 1) { | ||
error(null, null, { name: c._edmAttributes.Namespace, id: setName }, | ||
`Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for ${arr.map(a =>a.getDuplicateMessage()).join(', ')} `); | ||
error(null, null, { | ||
name: c._edmAttributes.Namespace, | ||
id: setName, | ||
names: arr.map(a => a.getDuplicateMessage()) | ||
}, 'Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for $(NAMES)'); | ||
} | ||
@@ -240,2 +243,38 @@ })); | ||
addAnnotations(); | ||
// type cross check | ||
const xServiceRefNames = xServiceRefs.map(r => r.name).sort((a,b)=>b.length-a.length); | ||
for(let typeName in UsedTypes) { | ||
if(!isBuiltinType(typeName)) { | ||
let iTypeName = typeName; | ||
/* | ||
Report type ref, if the type is | ||
- not a builtin, | ||
- not included in required definitions (for this service) | ||
- not a type clash (reported in type exposure), | ||
- a @cds.external service member but can't be rendered | ||
- and not a cross referenced type | ||
*/ | ||
if(!typeName.startsWith(serviceCsn.name + '.')) | ||
iTypeName = serviceCsn.name + '.' + typeName; | ||
const def = reqDefs.definitions[iTypeName]; | ||
const usages = UsedTypes[typeName].filter(u => !u.$NameClashReported); | ||
if(usages.length > 0) { | ||
if(def && !def.$isRendered && def['@cds.external']) | ||
message('odata-spec-violation-type', usages[0].$location, | ||
{ | ||
type: typeName, | ||
anno: '@cds.external', | ||
name: serviceCsn.name, | ||
code: def.elements ? 'Edm.ComplexType' : 'Edm.TypeDefinition', | ||
version: options.isV4() ? '4.0' : '2.0', | ||
'#': 'external' } ); | ||
else if(!def && !whatsMySchemaName(typeName, xServiceRefNames)) | ||
message('odata-spec-violation-type', usages[0].$location, | ||
{ type: typeName, name: serviceCsn.name, '#': 'missing' } ); | ||
} | ||
} | ||
} | ||
return edm | ||
@@ -301,3 +340,2 @@ | ||
if(art.kind === 'reference' && whatsMySchemaName(fqName) && serviceCsn.name === whatsMyServiceRootName(fqName, false)) { | ||
fqSchemaXRef.push(fqName); | ||
references.push(art); | ||
@@ -343,8 +381,8 @@ } | ||
a => a.kind === 'entity' && !a.abstract && a.name.startsWith(schemaNamePrefix), | ||
createEntityTypeAndSet | ||
[createEntityTypeAndSet, markRendered] | ||
); | ||
// create unbound actions/functions | ||
edmUtils.foreach(schemaCsn.definitions, | ||
edmUtils.foreach(schemaCsn.definitions, | ||
a => (a.kind === 'action' || a.kind === 'function') && a.name.startsWith(schemaNamePrefix), | ||
(options.isV4()) ? createActionV4 : createActionV2); | ||
[(options.isV4()) ? createActionV4 : createActionV2, markRendered]); | ||
@@ -354,3 +392,3 @@ // create the complex types | ||
a => edmUtils.isStructuredType(a) && a.name.startsWith(schemaNamePrefix), | ||
createComplexType); | ||
[createComplexType, markRendered]); | ||
@@ -363,6 +401,6 @@ if(options.isV4()) | ||
artifact.name.startsWith(schemaNamePrefix), | ||
createTypeDefinition); | ||
[createTypeDefinitionV4, markRendered]); | ||
} | ||
// fetch all exising children names in a map | ||
// fetch all existing child names in a map | ||
const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => { | ||
@@ -385,11 +423,8 @@ const name = cur._edmAttributes.Name; | ||
else { | ||
addAssociation(np); | ||
addAssociationV2(np); | ||
} | ||
}); | ||
// remove EntityContainer if empty | ||
if(Schema._ec && Schema._ec._children.length === 0) { | ||
Schema._children.splice(Schema._children.indexOf(Schema._ec), 1); | ||
} | ||
if(Schema._children.length === 0) { | ||
if(Schema._children.length === 0 || | ||
(Schema._children.length === 1 && Schema._children[0].kind === 'EntityContainer')) { | ||
// FIXME: Location for sub schemas? | ||
@@ -484,2 +519,130 @@ warning(null, ['definitions', Schema._edmAttributes.Namespace], { name: Schema._edmAttributes.Namespace }, 'Schema $(NAME) is empty'); | ||
function createComplexType(structuredTypeCsn) | ||
{ | ||
// V4 attributes: Name, BaseType, Abstract, OpenType | ||
const attributes = { Name: structuredTypeCsn.name.replace(schemaNamePrefix, '') }; | ||
const complexType = new Edm.ComplexType(v, attributes, structuredTypeCsn); | ||
const elementsCsn = structuredTypeCsn.items || structuredTypeCsn; | ||
const properties = createProperties(elementsCsn, structuredTypeCsn)[0]; | ||
const loc = ['definitions', structuredTypeCsn.name]; | ||
if(properties.length === 0) { | ||
warning(null, loc, { name: structuredTypeCsn.name }, | ||
'EDM ComplexType $(NAME) has no properties'); | ||
} | ||
if(!edmUtils.isODataSimpleIdentifier(attributes.Name)) | ||
message('odata-spec-violation-id', loc, { id: attributes.Name }); | ||
properties.forEach(p => { | ||
const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ]; | ||
edmTypeCompatibilityCheck(p, pLoc); | ||
if(p._edmAttributes.Name === complexType._edmAttributes.Name) | ||
message('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind }); | ||
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name)) | ||
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name }); | ||
if(options.isV2()) { | ||
if(p._isCollection && !p._csn.target) | ||
message('odata-spec-violation-array', pLoc, { version: '2.0' }); | ||
if(p._csn.target) | ||
message('odata-spec-violation-assoc', pLoc, { version: '2.0' }); | ||
} | ||
}); | ||
complexType.append(...(properties)); | ||
Schema.append(complexType); | ||
} | ||
/** | ||
* @param {object} elementsCsn | ||
* @param {object} edmParentCsn | ||
* @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]: | ||
* array of Edm Properties | ||
* hasStream : value of @Core.MediaType assignment | ||
*/ | ||
function createProperties(elementsCsn, edmParentCsn=elementsCsn) | ||
{ | ||
const props = []; | ||
let hasStream = false; | ||
const streamProps = []; | ||
elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) => | ||
{ | ||
if(elementCsn._edmParentCsn == undefined) | ||
setProp(elementCsn, '_edmParentCsn', edmParentCsn); | ||
if(elementCsn.target) { | ||
// Foreign keys are part of the generic elementCsn.elements property creation | ||
// This is the V4 edmx:NavigationProperty | ||
// gets rewritten for V2 in addAssociations() | ||
// suppress navprop creation only if @odata.navigable:false is not annotated. | ||
// (undefined !== false) still evaluates to true | ||
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false) | ||
{ | ||
const navProp = new Edm.NavigationProperty(v, { | ||
Name: elementName, | ||
Type: elementCsn._target.name | ||
}, elementCsn); | ||
collectUsedType(elementCsn, elementCsn._target.name); | ||
props.push(navProp); | ||
// save the navProp in the global array for late constraint building | ||
navigationProperties.push(navProp); | ||
} | ||
} | ||
// render ordinary property if element is NOT ... | ||
// 1) ... annotated @cds.api.ignore | ||
// 2) ... annotated @odata.foreignKey4 and odataFormat: structured | ||
else if(isEdmPropertyRendered(elementCsn, options)) | ||
{ | ||
// CDXCORE-CDXCORE-173 | ||
// V2: filter @Core.MediaType | ||
if ( options.isV2() && elementCsn['@Core.MediaType']) { | ||
hasStream = elementCsn['@Core.MediaType']; | ||
delete elementCsn['@Core.MediaType']; | ||
// CDXCORE-CDXCORE-177: | ||
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType | ||
// V4: render property type 'Edm.Stream' | ||
streamProps.push(elementName); | ||
} else { | ||
collectUsedType(elementCsn); | ||
props.push(new Edm.Property(v, { Name: elementName }, elementCsn)); | ||
} | ||
} | ||
}); | ||
if(options.isV2()) { | ||
if(streamProps.length > 1) { // TODO: why not mention 2.0 in text? | ||
error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' }, | ||
'Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)'); | ||
} | ||
else if(streamProps.length === 1) { | ||
info(null, ['definitions', elementsCsn.name], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' }, | ||
'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)'); | ||
} | ||
} | ||
return [ props, hasStream ]; | ||
} | ||
// V4 <TypeDefintion> | ||
function createTypeDefinitionV4(typeCsn) | ||
{ | ||
// derived types are already resolved to base types | ||
const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') }; | ||
if(!edmUtils.isODataSimpleIdentifier(attributes.Name)) | ||
message('odata-spec-violation-id', ['definitions', typeCsn.name], { id: attributes.Name }); | ||
const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn ); | ||
edmTypeCompatibilityCheck(typeDef, [ 'definitions', typeCsn.name ]); | ||
Schema.append(typeDef); | ||
} | ||
// add bound/unbound actions/functions for V4 | ||
@@ -545,3 +708,3 @@ function createActionV4(actionCsn, _name, entityCsn=undefined) | ||
message('odata-spec-violation-id', pLoc, { id: parameterName }); | ||
collectUsedType(parameterCsn); | ||
edmTypeCompatibilityCheck(p, pLoc); | ||
@@ -554,2 +717,3 @@ actionNode.append(p); | ||
actionNode._returnType = new Edm.ReturnType(v, actionCsn.returns); | ||
collectUsedType(actionCsn.returns); | ||
edmTypeCompatibilityCheck(actionNode._returnType, [ ...loc, 'returns' ]); | ||
@@ -636,2 +800,3 @@ // if binding type matches return type add attribute EntitySetPath | ||
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' ); | ||
collectUsedType(parameterCsn); | ||
edmTypeCompatibilityCheck(param, pLoc); | ||
@@ -664,2 +829,3 @@ if(!edmUtils.isODataSimpleIdentifier(parameterName)) | ||
if (type) { | ||
collectUsedType(action.returns); | ||
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') { | ||
@@ -692,143 +858,17 @@ message('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' }); | ||
/** | ||
* @param {object} elementsCsn | ||
* @param {object} edmParentCsn | ||
* @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]: | ||
* array of Edm Properties | ||
* hasStream : value of @Core.MediaType assignment | ||
*/ | ||
function createProperties(elementsCsn, edmParentCsn=elementsCsn) | ||
{ | ||
const props = []; | ||
let hasStream = false; | ||
const streamProps = []; | ||
/* | ||
addAssociation() constructs a V2 association. | ||
In V4 all this has been simplified very much, the only thing actually left over is | ||
<ReferentialConstriant> that is then a sub element to <NavigationProperty>. | ||
However, referential constraints are substantially different to its V2 counterpart, | ||
so it is better to reimplement proper V4 construction of<NavigationProperty> in a separate | ||
function. | ||
elementsCsn.elements && Object.entries(elementsCsn.elements).forEach(([elementName, elementCsn]) => | ||
{ | ||
if(elementCsn._edmParentCsn == undefined) | ||
setProp(elementCsn, '_edmParentCsn', edmParentCsn); | ||
if(elementCsn.target) { | ||
// Foreign keys are part of the generic elementCsn.elements property creation | ||
// This is the V4 edmx:NavigationProperty | ||
// gets rewritten for V2 in addAssociations() | ||
// suppress navprop creation only if @odata.navigable:false is not annotated. | ||
// (undefined !== false) still evaluates to true | ||
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false) | ||
{ | ||
const navProp = new Edm.NavigationProperty(v, { | ||
Name: elementName, | ||
Type: elementCsn._target.name | ||
}, elementCsn); | ||
props.push(navProp); | ||
// save the navProp in the global array for late constraint building | ||
navigationProperties.push(navProp); | ||
} | ||
} | ||
// render ordinary property if element is NOT ... | ||
// 1) ... annotated @cds.api.ignore | ||
// 2) ... annotated @odata.foreignKey4 and odataFormat: structured | ||
else if(isEdmPropertyRendered(elementCsn, options)) | ||
{ | ||
// CDXCORE-CDXCORE-173 | ||
// V2: filter @Core.MediaType | ||
if ( options.isV2() && elementCsn['@Core.MediaType']) { | ||
hasStream = elementCsn['@Core.MediaType']; | ||
delete elementCsn['@Core.MediaType']; | ||
// CDXCORE-CDXCORE-177: | ||
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType | ||
// V4: render property type 'Edm.Stream' | ||
streamProps.push(elementName); | ||
} else { | ||
props.push(new Edm.Property(v, { Name: elementName }, elementCsn)); | ||
} | ||
} | ||
}); | ||
if(options.isV2()) { | ||
if(streamProps.length > 1) { // TODO: why not mention 2.0 in text? | ||
error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' }, | ||
`Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)`); | ||
} | ||
else if(streamProps.length === 1) { | ||
info(null, ['definitions', elementsCsn.name], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' }, | ||
'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)'); | ||
} | ||
} | ||
return [ props, hasStream ]; | ||
} | ||
function createComplexType(structuredTypeCsn) | ||
{ | ||
// V4 attributes: Name, BaseType, Abstract, OpenType | ||
const attributes = { Name: structuredTypeCsn.name.replace(schemaNamePrefix, '') }; | ||
const complexType = new Edm.ComplexType(v, attributes, structuredTypeCsn); | ||
const elementsCsn = structuredTypeCsn.items || structuredTypeCsn; | ||
const properties = createProperties(elementsCsn, structuredTypeCsn)[0]; | ||
const loc = ['definitions', structuredTypeCsn.name]; | ||
if(properties.length === 0) { | ||
warning(null, loc, { name: structuredTypeCsn.name }, | ||
'EDM ComplexType $(NAME) has no properties'); | ||
} | ||
if(!edmUtils.isODataSimpleIdentifier(attributes.Name)) | ||
message('odata-spec-violation-id', loc, { id: attributes.Name }); | ||
properties.forEach(p => { | ||
const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ]; | ||
edmTypeCompatibilityCheck(p, pLoc); | ||
if(p._edmAttributes.Name === complexType._edmAttributes.Name) | ||
message('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind }); | ||
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name)) | ||
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name }); | ||
if(options.isV2()) { | ||
if(p._isCollection && !p._csn.target) | ||
message('odata-spec-violation-array', pLoc, { version: '2.0' }); | ||
if(p._csn.target) | ||
message('odata-spec-violation-assoc', pLoc, { version: '2.0' }); | ||
} | ||
}); | ||
complexType.append(...(properties)); | ||
Schema.append(complexType); | ||
} | ||
// V4 <TypeDefintion> | ||
function createTypeDefinition(typeCsn) | ||
{ | ||
// derived types are already resolved to base types | ||
const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') }; | ||
if(!edmUtils.isODataSimpleIdentifier(attributes.Name)) | ||
message('odata-spec-violation-id', ['definitions', typeCsn.name], { id: attributes.Name }); | ||
const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn ); | ||
edmTypeCompatibilityCheck(typeDef, [ 'definitions', typeCsn.name ]); | ||
Schema.append(typeDef); | ||
} | ||
/* | ||
* addAssociation() constructs a V2 association. | ||
* In V4 all this has been simplified very much, the only thing actually left over is | ||
* <ReferentialConstriant> that is then a sub element to <NavigationProperty>. | ||
* However, referential constraints are substantially different to its V2 counterpart, | ||
* so it is better to reimplement proper V4 construction of<NavigationProperty> in a separate | ||
* function. | ||
* | ||
* This method does: | ||
* rewrite <NavigationProperty> attributes to be V2 compliant | ||
* add <Association> elements to the schema | ||
* add <End>, <ReferentialConstraint>, <Dependent> and <Principal> sub elements to <Association> | ||
* add <AssociationSet> to the EntityContainer for each <Association> | ||
This method does: | ||
rewrite <NavigationProperty> attributes to be V2 compliant | ||
add <Association> elements to the schema | ||
add <End>, <ReferentialConstraint>, <Dependent> and <Principal> sub elements to <Association> | ||
add <AssociationSet> to the EntityContainer for each <Association> | ||
*/ | ||
function addAssociation(navigationProperty) | ||
function addAssociationV2(navigationProperty) | ||
{ | ||
@@ -835,0 +875,0 @@ let constraints = navigationProperty._csn._constraints; |
@@ -112,3 +112,3 @@ 'use strict'; | ||
{ anno: '@Common.attribute', code: JSON.stringify(CommonAttributes) }, | ||
`Expect array value for $(ANNOTATION): $(CODE)`); | ||
'Expect array value for $(ANNOTATION): $(CODE)'); | ||
return; | ||
@@ -129,3 +129,3 @@ } | ||
{ anno: '@Aggregation.ContextDefiningProperties', code: JSON.stringify(ContextDefiningProperties) }, | ||
`Expect array value for $(ANNOTATION): $(CODE)`); | ||
'Expect array value for $(ANNOTATION): $(CODE)'); | ||
return; | ||
@@ -203,3 +203,3 @@ } | ||
code: JSON.stringify(filterRestrictions) }, | ||
`Expect array value for $(ANNOTATION): $(CODE)`); | ||
'Expect array value for $(ANNOTATION): $(CODE)'); | ||
return; | ||
@@ -221,3 +221,3 @@ } | ||
code: JSON.stringify(requiredProperties) }, | ||
`Expect array value for $(ANNOTATION): $(CODE)`); | ||
'Expect array value for $(ANNOTATION): $(CODE)'); | ||
return; | ||
@@ -353,2 +353,2 @@ } | ||
setSAPSpecificV2AnnotationsToAssociation | ||
}; | ||
}; |
@@ -27,7 +27,7 @@ 'use strict'; | ||
const csnUtils = getUtils(csn); | ||
const { message, throwWithAnyError } = messageFunctions; | ||
const { message, throwWithError } = messageFunctions; | ||
forEachDefinition(csn, [ attach$path, checkChainedArray ]); | ||
checkNestedContextsAndServices(); | ||
throwWithAnyError(); | ||
throwWithError(); | ||
@@ -34,0 +34,0 @@ // attach $path to all |
@@ -208,3 +208,3 @@ 'use strict'; | ||
if(arg.xpr) | ||
arg.xpr.map(fillConstraints); | ||
getExpressionArguments(arg.xpr); | ||
else if(pos > 0 && pos < expr.length) | ||
@@ -489,3 +489,3 @@ { | ||
if(cdsType === undefined) { | ||
error(null, location, `no type found`); | ||
error(null, location, 'no type found'); | ||
return '<NOTYPE>'; | ||
@@ -549,3 +549,3 @@ } | ||
if (edmType == undefined) { | ||
error(null, location, { type: cdsType }, `No EDM type available for $(TYPE)`); | ||
error(null, location, { type: cdsType }, 'No EDM type available for $(TYPE)'); | ||
} | ||
@@ -552,0 +552,0 @@ if(isV2) |
@@ -964,3 +964,3 @@ // CSN frontend - transform CSN into XSN | ||
if (!name) { | ||
warning( 'syntax-invalid-name', location(true), // TODO: Error | ||
message( 'syntax-invalid-name', location(true), | ||
{ '#': 'csn', parentprop: spec.prop } ); | ||
@@ -1152,4 +1152,7 @@ } | ||
return { val, literal: 'number', location: location() }; | ||
error( spec.msgId || 'syntax-expecting-natnum', location(true), | ||
{ prop: spec.msgProp } ); | ||
const loc = location(true); | ||
if (spec.msgId) | ||
error( spec.msgId, loc, { prop: spec.msgProp } ); | ||
else | ||
error( 'syntax-expecting-natnum', loc, { prop: spec.msgProp } ); | ||
return ignore( val ); | ||
@@ -1461,4 +1464,3 @@ } | ||
function duplicateExcluding( name, loc ) { | ||
error( 'syntax-duplicate-excluding', loc, { name, keyword: 'excluding' }, // TODO: also CDL | ||
'Duplicate $(NAME) in the $(KEYWORD) clause' ); | ||
error( 'syntax-duplicate-excluding', loc, { '#': 'csn', name, prop: 'excluding[]' } ); | ||
} | ||
@@ -1547,10 +1549,10 @@ | ||
: (parentSpec.msgProp ? 'prop' : 'top'); | ||
message( 'syntax-unexpected-property', location(true), | ||
{ | ||
'#': variant, | ||
prop, | ||
parentprop: | ||
parentSpec.msgProp, | ||
kind, | ||
} ); | ||
error( 'syntax-unexpected-property', location(true), | ||
{ | ||
'#': variant, | ||
prop, | ||
parentprop: | ||
parentSpec.msgProp, | ||
kind, | ||
} ); | ||
} | ||
@@ -1629,3 +1631,3 @@ else if (checkAndSetXorGroup( s.xorGroup, s.xorException, prop, xor )) { | ||
return true; | ||
message( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } ); | ||
error( 'syntax-unexpected-property', location(true), { '#': 'sibling', prop, siblingprop } ); | ||
return false; | ||
@@ -1660,4 +1662,7 @@ } | ||
return obj; | ||
error( spec.msgId || 'syntax-expecting-object', location(true), | ||
{ prop: spec.msgProp }); | ||
const loc = location(true); | ||
if (spec.msgId) | ||
error( spec.msgId, loc, { prop: spec.msgProp }); | ||
else | ||
error( 'syntax-expecting-object', loc, { prop: spec.msgProp }); | ||
return ignore( obj ); | ||
@@ -1664,0 +1669,0 @@ } |
@@ -26,2 +26,3 @@ // Transform XSN (augmented CSN) into CSN | ||
function: 'action', | ||
enum: 'enum', | ||
}; | ||
@@ -433,17 +434,6 @@ | ||
// From definitions (without redefinitions) with potential inferred elements: | ||
const annotate = { annotate: name }; | ||
if (art.$inferred) | ||
Object.assign( annotate, annotationsAndDocComment( art, true ) ); | ||
if (art.$expand === 'annotate') { | ||
if (art.actions) | ||
attachAnnotations( annotate, 'actions', art.actions, art.$inferred ); | ||
else if (art.params) | ||
attachAnnotations( annotate, 'params', art.params, art.$inferred ); | ||
const obj = art.returns || art; | ||
const elems = (obj.items || obj).elements; // no targetAspect here | ||
if (elems) | ||
attachAnnotations( annotate, 'elements', elems, art.$inferred, art.returns ); | ||
} | ||
if (Object.keys( annotate ).length > 1) | ||
exts.push( annotate ); | ||
const result = { annotate: Object.create(null) }; | ||
attachAnnotations(result, 'annotate', { [name]: art }, art.$inferred ); | ||
if (result.annotate[name]) | ||
exts.push({ annotate: name, ...result.annotate[name] } ); | ||
} | ||
@@ -475,3 +465,3 @@ } | ||
continue; | ||
const render = annotationsAndDocComment( par, true ); | ||
const render = annotationsAndDocComment( par ); | ||
const subElems = par.$expand !== 'origin' && (par.items || par).elements; | ||
@@ -493,3 +483,3 @@ if (subElems) { | ||
return; | ||
const render = annotationsAndDocComment( par, true ); | ||
const render = annotationsAndDocComment( par ); | ||
const subElems = par.$expand !== 'origin' && (par.items || par).elements; | ||
@@ -515,3 +505,3 @@ if (subElems) { | ||
// happen through extensions. | ||
const annos = annotationsAndDocComment( art, true ); | ||
const annos = annotationsAndDocComment( art ); | ||
const annotate = Object.assign( { annotate: name }, annos ); | ||
@@ -575,12 +565,17 @@ if (Object.keys( annotate ).length > 1) { | ||
for (const name in dict) { | ||
const elem = dict[name]; | ||
const inf = inferred || elem.$inferred; // is probably always inferred if parent was | ||
const sub = (inf) ? annotationsAndDocComment( elem, true ) : {}; | ||
if (elem.$expand === 'annotate') { | ||
if (elem.params) | ||
attachAnnotations( sub, 'params', elem.params, inf ); | ||
const obj = elem.returns || elem; | ||
const elems = (obj.items || obj.targetAspect || obj).elements; | ||
const entry = dict[name]; | ||
const inf = inferred || entry.$inferred; // is probably always inferred if parent was | ||
const sub = (inf) ? annotationsAndDocComment( entry ) : {}; | ||
if (entry.$expand === 'annotate') { | ||
if (entry.actions) | ||
attachAnnotations( sub, 'actions', entry.actions, inf ); | ||
else if (entry.params) | ||
attachAnnotations( sub, 'params', entry.params, inf ); | ||
const obj = entry.returns || entry; | ||
const many = obj.items || obj; | ||
const elems = (many.targetAspect || many).elements; | ||
if (elems) | ||
attachAnnotations( sub, 'elements', elems, inf, elem.returns ); | ||
attachAnnotations( sub, 'elements', elems, inf, entry.returns ); | ||
if (many.enum) | ||
attachAnnotations( sub, 'enum', many.enum, inf ); | ||
} | ||
@@ -732,6 +727,10 @@ if (Object.keys( sub ).length) | ||
// for gensrcFlavor and namespace/builtin annotation extraction: | ||
// return annotations from definition (annotated==false) | ||
// or annotations (annotated==true) | ||
function annotationsAndDocComment( node, annotated ) { | ||
/** | ||
* For gensrcFlavor and namespace/builtin annotation extraction: | ||
* return annotations from definition and annotations. | ||
* The call side should check that node.$inferred is truthy. | ||
* | ||
* @param {object} node | ||
*/ | ||
function annotationsAndDocComment( node ) { | ||
const csn = {}; | ||
@@ -744,11 +743,8 @@ const transformer = transformers['@']; | ||
// and @odata.containment.ignore | ||
// TODO: use $inferred instead special $priority value | ||
if (val.$priority !== undefined && (!!val.$priority) === annotated) { | ||
// transformer (= value) takes care to exclude $inferred annotation assignments | ||
const sub = transformer( val ); | ||
// As value() just has one value, so we do not provide ( val, csn, node, prop ) | ||
// which would be more robust, but makes some JS checks unhappy | ||
if (sub !== undefined) | ||
csn[prop] = sub; | ||
} | ||
// transformer (= value) takes care to exclude $inferred annotation assignments | ||
const sub = transformer( val ); | ||
// As value() just has one value, so we do not provide ( val, csn, node, prop ) | ||
// which would be more robust, but makes some JS checks unhappy | ||
if (sub !== undefined) | ||
csn[prop] = sub; | ||
} | ||
@@ -1545,3 +1541,3 @@ if (node.doc) | ||
// only list annotations here which are provided directly with definition | ||
const col = (gensrcFlavor) ? annotationsAndDocComment( elem, false ) : {}; | ||
const col = (gensrcFlavor) ? annotationsAndDocComment( elem ) : {}; | ||
// with `client` flavor, assignments are available at the element | ||
@@ -1548,0 +1544,0 @@ const gensrcSaved = gensrcFlavor; |
{ | ||
"root": true, | ||
"extends": "../../.eslintrc-ydkjsi.json" | ||
"extends": "../../.eslintrc-ydkjsi.json", | ||
"rules": { | ||
"cds-compiler/space-in-func-decl": "error" | ||
} | ||
} |
@@ -167,4 +167,7 @@ // @ts-nocheck : Issues with Tokens on `this`, e.g. `this.DOT`. | ||
if (token.type === parser.constructor.DocComment && !token.isUsed) { | ||
messageFunctions.info('syntax-ignoring-doc-comment', parser.tokenLocation(token), {}, | ||
'Ignoring doc comment as it does not belong to any artifact'); | ||
// TODO: think of 'syntax-unexpected-doc-comment' | ||
messageFunctions.info( 'syntax-ignoring-doc-comment', parser.tokenLocation(token), {}, | ||
'Ignoring doc comment as it is not written at a defined position' ); | ||
// this is also for position inside some artifact definition, i.e. previous text | ||
// "does not belong to any artifact" might be confusing | ||
} | ||
@@ -171,0 +174,0 @@ } |
@@ -19,3 +19,3 @@ 'use strict'; | ||
*/ | ||
function parseDocComment(comment) { | ||
function parseDocComment( comment ) { | ||
// Also return "null" for empty doc comments so that doc comment propagation | ||
@@ -87,3 +87,3 @@ // can be stopped. | ||
*/ | ||
function stripCommentIndentation(lines, ignoreFirstLine) { | ||
function stripCommentIndentation( lines, ignoreFirstLine ) { | ||
const n = lines.length; | ||
@@ -119,3 +119,3 @@ | ||
*/ | ||
function removeFence(line) { | ||
function removeFence( line ) { | ||
return line.replace(/^\s*[*]\s?/, ''); | ||
@@ -131,3 +131,3 @@ } | ||
*/ | ||
function removeHeaderFence(line) { | ||
function removeHeaderFence( line ) { | ||
return line.replace(/^\/[*]{2,}\s?/, ''); | ||
@@ -145,3 +145,3 @@ } | ||
*/ | ||
function removeFooterFence(line) { | ||
function removeFooterFence( line ) { | ||
return line.replace(/\s*[*]+\/$/, ''); | ||
@@ -156,3 +156,3 @@ } | ||
*/ | ||
function isFencedComment(lines) { | ||
function isFencedComment( lines ) { | ||
const index = lines.findIndex((line, i) => { | ||
@@ -159,0 +159,0 @@ const exclude = (i === 0 || i === lines.length - 1); |
@@ -58,4 +58,7 @@ // Error strategy with special handling for (non-reserved) keywords | ||
return antlr4.Parser.prototype.match.call( this, ttype ); | ||
this.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } ); | ||
// This is very likely to be dead code: we do not use a simple Identifier | ||
// without alternatives in the grammar. With alternatives, recoverInline() is | ||
// the place to go. (But this code should work with a changed grammar…) | ||
this.message( 'syntax-unexpected-reserved-word', token, | ||
{ code: token.text, delimited: token.text } ); | ||
this._errHandler.reportMatch(this); | ||
@@ -76,3 +79,2 @@ this.consume(); | ||
this._super = { | ||
consumeUntil: super.consumeUntil, | ||
recoverInline: super.recoverInline, | ||
@@ -94,2 +96,3 @@ getExpectedTokens: super.getExpectedTokens, | ||
consumeUntil, | ||
consumeAndMarkUntil, | ||
recoverInline, | ||
@@ -143,6 +146,7 @@ getMissingSymbol, | ||
// Expected token is identifier, current is (reserved) KEYWORD: | ||
// TODO: do not use this if "close enough" (1 char diff) to a keyword in nextTokens | ||
// TODO: do not use this if "close enough" (1 char diff or prefix) | ||
// to a keyword in nextTokens | ||
// | ||
// NOTE: it is important to do this only if EPSILON is not in `nextTokens`, | ||
// which means that we cannot bring the better special syntax-fragile-ident | ||
// which means that we cannot bring the better special syntax-unexpected-reserved | ||
// in all cases. Reason: high performance impact of the alternative, | ||
@@ -153,3 +157,5 @@ // i.e. calling method Parser#isExpectedToken() = invoking the ATN | ||
if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) { | ||
recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } ); | ||
recognizer.message( 'syntax-unexpected-reserved-word', token, | ||
{ code: token.text, delimited: token.text } ); | ||
// TODO: attach tokens like for 'syntax-unexpected-token' | ||
token.type = identType; // make next ANTLR decision assume identifier | ||
@@ -218,7 +224,7 @@ return; | ||
const offending = this.getTokenDisplay( e.offendingToken, recognizer ); | ||
e.offendingToken.$isSkipped = 'offending'; | ||
let err; | ||
if (expecting && expecting.length) { | ||
err = recognizer.error( 'syntax-mismatched-token', e.offendingToken, | ||
{ offending, expecting }, | ||
'Mismatched $(OFFENDING), expecting $(EXPECTING)' ); | ||
err = recognizer.error( 'syntax-unexpected-token', e.offendingToken, | ||
{ offending, expecting } ); | ||
err.expectedTokens = expecting; | ||
@@ -241,7 +247,8 @@ } | ||
const token = recognizer.getCurrentToken(); | ||
token.$isSkipped = 'offending'; | ||
const expecting = this.getExpectedTokensForMessage( recognizer, token ); | ||
const offending = this.getTokenDisplay( token, recognizer ); | ||
const err = recognizer.error( 'syntax-extraneous-token', token, | ||
{ offending, expecting }, | ||
'Extraneous $(OFFENDING), expecting $(EXPECTING)' ); | ||
// Just text variant, no other message id! Would depend on ANTLR-internals | ||
const err = recognizer.error( 'syntax-unexpected-token', token, | ||
{ '#': 'unwanted', offending, expecting } ); | ||
err.expectedTokens = expecting; // TODO: remove next token? | ||
@@ -259,5 +266,7 @@ if (!recognizer.avoidErrorListeners) // with --trace-parser or --trace-parser-ambig | ||
const token = recognizer.getCurrentToken(); | ||
token.$isSkipped = 'offending'; | ||
const expecting = this.getExpectedTokensForMessage( recognizer, token ); | ||
const offending = this.getTokenDisplay( token, recognizer ); | ||
// TODO: if non-reserved keyword will not been parsed as keyword, use Identifier for offending | ||
// Hopefully not too ANTLR-specific, so extra message id is ok: | ||
const err = recognizer.error( 'syntax-missing-token', token, | ||
@@ -275,6 +284,6 @@ { offending, expecting }, | ||
const expecting = this.getExpectedTokensForMessage( recognizer, t ); | ||
const m = recognizer.warning( 'syntax-ignored-with', t, | ||
{ offending: "';'", expecting }, | ||
const m = recognizer.warning( 'syntax-unexpected-semicolon', t, | ||
{ offending: "';'", expecting, keyword: 'with' }, | ||
// eslint-disable-next-line max-len | ||
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous WITH' ); | ||
'Unexpected $(OFFENDING), expecting $(EXPECTING) - ignored previous $(KEYWORD)' ); | ||
m.expectedTokens = expecting; | ||
@@ -292,6 +301,6 @@ } | ||
if (SEMI < 1 || RBRACE < 1) { | ||
this._super.consumeUntil.call( this, recognizer, set ); | ||
this.consumeAndMarkUntil( recognizer, set ); | ||
} | ||
else if (set.contains(SEMI)) { // do not check for RBRACE here! | ||
this._super.consumeUntil.call( this, recognizer, set ); | ||
this.consumeAndMarkUntil( recognizer, set ); | ||
// console.log('CONSUMED-ORIG:',s,this.getTokenDisplay( recognizer.getCurrentToken(), | ||
@@ -308,5 +317,5 @@ // recognizer ),recognizer.getCurrentToken().line,intervalSetToArray( recognizer, set )); | ||
stop.addOne( RBRACE ); | ||
this._super.consumeUntil.call( this, recognizer, stop ); | ||
if (recognizer.getTokenStream().LA(1) === SEMI || | ||
recognizer.getTokenStream().LA(1) === RBRACE && !set.contains(RBRACE)) { | ||
this.consumeAndMarkUntil( recognizer, stop ); | ||
const ttype = recognizer.getTokenStream().LA(1); | ||
if (ttype === SEMI || ttype === RBRACE && !set.contains(RBRACE)) { | ||
recognizer.consume(); | ||
@@ -326,2 +335,11 @@ this.reportMatch(recognizer); // we know current token is correct | ||
function consumeAndMarkUntil( recognizer, set ) { | ||
let t = recognizer.getTokenStream().LT(1); | ||
while (t.type !== antlr4.Token.EOF && !set.contains( t.type )) { | ||
if (!t.$isSkipped) | ||
t.$isSkipped = true; | ||
recognizer.consume(); | ||
t = recognizer.getTokenStream().LT(1); | ||
} | ||
} | ||
@@ -343,3 +361,5 @@ // As the `match` function of the parser `recognizer` does not allow to check | ||
recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text } ); | ||
// TODO: attach `Identifier` as valid name to message? | ||
recognizer.message( 'syntax-unexpected-reserved-word', token, | ||
{ code: token.text, delimited: token.text } ); | ||
this.reportMatch(recognizer); // we know current token is correct | ||
@@ -512,3 +532,3 @@ recognizer.consume(); | ||
function addSet(other) { | ||
function addSet( other ) { | ||
if (!other.contains( hideAltsType )) | ||
@@ -520,3 +540,3 @@ origAddSet.call( this, other ); | ||
// type `Identifier`, do not add non-reserved keywords in `v`. | ||
function addInterval(v) { | ||
function addInterval( v ) { | ||
if (v.stop <= identType) { | ||
@@ -523,0 +543,0 @@ origAddInterval.call(this, v); |
@@ -92,2 +92,3 @@ // Generic ANTLR parser class with AST-building functions | ||
classifyImplicitName, | ||
warnIfColonFollows, | ||
fragileAlias, | ||
@@ -113,3 +114,2 @@ identAst, | ||
createPrefixOp, | ||
setOnce, | ||
setMaxCardinality, | ||
@@ -122,3 +122,2 @@ pushIdent, | ||
csnParseOnly, | ||
disallowElementExtension, | ||
noAssignmentInSameLine, | ||
@@ -223,25 +222,8 @@ noSemicolonHere, | ||
/** | ||
* For element extensions (`extend E:elem` syntax). | ||
* If `elemName.path` is set, remove the last extension from `$outer` and | ||
* emit an error that the extension is invalid. | ||
* | ||
* @param {object} elemName | ||
* @param {object} outer | ||
* @param {string} extensionVariant | ||
*/ | ||
function disallowElementExtension(elemName, outer, extensionVariant) { | ||
if (elemName.path) { | ||
const loc = this.tokenLocation(this.getCurrentToken()); | ||
this.error( 'syntax-invalid-extend', loc, { kind: extensionVariant } ); | ||
// remove last, i.e. new extension | ||
outer.extensions.pop(); | ||
} | ||
} | ||
function noAssignmentInSameLine() { | ||
const t = this.getCurrentToken(); | ||
if (t.text === '@' && t.line <= this._input.LT(-1).line) { | ||
this.warning( 'syntax-anno-same-line', t, {}, | ||
'Annotation assignment belongs to next statement' ); | ||
this.warning( 'syntax-missing-newline', t, { anno: '‹anno›' }, // TODO: single quotes, @() | ||
// eslint-disable-next-line max-len | ||
'Add a newline before $(ANNO) to indicate that it belongs to the next statement' ); | ||
} | ||
@@ -274,3 +256,3 @@ } | ||
function prepareGenericKeywords( pathItem, expected = null) { | ||
function prepareGenericKeywords( pathItem, expected = null ) { | ||
const length = pathItem?.args?.length || 0; | ||
@@ -500,3 +482,3 @@ const argPos = (expected ? length - 1 : length); | ||
function isMultiLineToken(token) { | ||
function isMultiLineToken( token ) { | ||
return ( | ||
@@ -593,2 +575,13 @@ token.type === this.constructor.DocComment || | ||
// ANTLR on some OS might corrupt non-ASCII chars for messages | ||
function warnIfColonFollows( anno ) { | ||
const t = this.getCurrentToken(); | ||
if (t.text === ':') { | ||
this.warning( 'syntax-missing-parens', anno.name.location, | ||
{ code: '@‹anno›', op: ':', newcode: '@(‹anno›…)' }, | ||
// eslint-disable-next-line max-len | ||
'When $(CODE) is followed by $(OP), use $(NEWCODE) for annotation assignments at this position' ); | ||
} | ||
} | ||
// If the token before the current one is a doc comment (ignoring other tokens | ||
@@ -621,5 +614,5 @@ // on the hidden channel), put its "cleaned-up" text as value of property `doc` | ||
// to be used in the empty alternative to AS <explicitName>. | ||
function classifyImplicitName( category, ref ) { | ||
function classifyImplicitName( category, ref, tokpos = 1 ) { | ||
if (!ref || ref.path && this.getCurrentToken().text !== '.') { | ||
const implicit = this._input.LT(-1); | ||
const implicit = this._input.LT( tokpos - 1 || -1 ); | ||
if (implicit.isIdentifier) | ||
@@ -634,7 +627,7 @@ implicit.isIdentifier = category; | ||
if (safe || ast.$delimited || !/^[a-zA-Z][a-zA-Z_]+$/.test( ast.id )) { | ||
this.warning( 'syntax-sloppy-alias', ast.location, { keyword: 'as' }, | ||
this.warning( 'syntax-deprecated-auto-as', ast.location, { keyword: 'as' }, | ||
'Please add the keyword $(KEYWORD) in front of the alias name' ); | ||
} | ||
else { // configurable error | ||
this.message( 'syntax-fragile-alias', ast.location, { keyword: 'as' }, | ||
this.message( 'syntax-missing-as', ast.location, { keyword: 'as' }, | ||
'Please add the keyword $(KEYWORD) in front of the alias name' ); | ||
@@ -654,6 +647,5 @@ } | ||
id = id.slice( 2, -1 ).replace( /]]/g, ']' ); | ||
if (!id) { | ||
this.error( 'syntax-empty-ident', token, {}, | ||
'Delimited identifier must contain at least one character' ); | ||
} | ||
if (!id) | ||
this.message( 'syntax-invalid-name', token, {} ); | ||
// $delimited is used to complain about ![$self] and other magic vars usage; | ||
@@ -668,4 +660,3 @@ // we might complain about that already here via @arg{category} | ||
if (!id) { | ||
this.error( 'syntax-empty-ident', token, {}, | ||
'Delimited identifier must contain at least one character' ); | ||
this.message( 'syntax-invalid-name', token, {} ); | ||
} | ||
@@ -719,3 +710,3 @@ else { | ||
return ref; | ||
this.error( 'syntax-not-supported', item.location, {}, | ||
this.error( 'syntax-unsupported-method', item.location, {}, | ||
'Methods in expressions are not supported yet' ); | ||
@@ -834,3 +825,3 @@ path.broken = true; | ||
function atChar(i) { | ||
function atChar( i ) { | ||
// Is only used with single-line strings. | ||
@@ -859,3 +850,3 @@ return location.col + pos + i; | ||
}; | ||
this.error( 'syntax-anno-space', wsLocation, {}, // TODO: really Error? | ||
this.error( 'syntax-unexpected-space', wsLocation, {}, // TODO: really Error? | ||
'Expected identifier after \'@\' but found whitespace' ); | ||
@@ -917,11 +908,11 @@ } | ||
if (kind === 0) { | ||
this.error( 'duplicate-argument', loc, { name: duplicateName }, | ||
this.error( 'syntax-duplicate-argument', loc, { name: duplicateName }, | ||
'Duplicate value for parameter $(NAME)' ); | ||
} | ||
else if (kind === '') { | ||
this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' }, | ||
'Duplicate $(NAME) in the $(KEYWORD) clause' ); | ||
this.error( 'syntax-duplicate-excluding', loc, | ||
{ name: duplicateName, keyword: 'excluding' } ); | ||
} | ||
else { | ||
this.error( 'duplicate-prop', loc, { name: duplicateName }, | ||
this.error( 'syntax-duplicate-property', loc, { name: duplicateName }, | ||
'Duplicate value for structure property $(NAME)' ); | ||
@@ -1044,21 +1035,2 @@ } | ||
// Set property `prop` of `target` to value `value`. Issue error if that | ||
// property has been set before, while mentioning the keywords previously | ||
// provided (as arguments `tokens`). | ||
function setOnce( target, prop, value, ...tokens ) { | ||
const loc = this.tokenLocation( tokens[0], tokens[tokens.length - 1] ); | ||
const prev = target[prop]; | ||
if (prev) { | ||
this.error( 'syntax-repeated-option', loc, { option: prev.option }, | ||
'Option $(OPTION) has already been specified' ); | ||
} | ||
if (typeof value === 'boolean') { | ||
if (!value) | ||
loc.value = false; | ||
value = loc; | ||
} | ||
value.option = tokens.map( t => t.text.toUpperCase() ).join(' '); | ||
target[prop] = value; | ||
} | ||
function setMaxCardinality( art, token, max ) { | ||
@@ -1070,4 +1042,4 @@ const location = this.tokenLocation( token ); | ||
else { | ||
this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text }, | ||
'The target cardinality has already been specified - ignored $(KEYWORD)' ); | ||
this.warning( 'syntax-duplicate-cardinality', location, { keyword: token.text }, | ||
'The target cardinality has already been specified - ignoring $(KEYWORD)' ); | ||
} | ||
@@ -1089,29 +1061,16 @@ } | ||
function associationInSelectItem( art ) { | ||
if (!art.value) // e.g. `expand` without value (for new structures) | ||
return; | ||
const isPath = art.value.path && art.value.path.length; | ||
const isIdentifier = isPath && art.value.path.length === 1; | ||
if (isIdentifier) { | ||
if (!art.name) { | ||
art.name = art.value.path[0]; | ||
} | ||
else { | ||
// Use alias if provided, i.e. ignore art.value.path. | ||
this.error( 'query-unexpected-alias', art.name.location, {}, | ||
'Unexpected alias for association' ); | ||
} | ||
delete art.value; | ||
} | ||
else { | ||
const loc = isPath ? art.value.path[1].location : art.value.location; | ||
// If neither path nor alias are present, `query-req-name` is emitted in `populate.js`. | ||
if (isPath || art.name) { | ||
this.error( 'query-expected-identifier', loc, { '#': 'assoc' } ); | ||
if (isPath) | ||
art.name = art.value.path[art.value.path.length - 1]; | ||
const { value } = art; | ||
const path = value?.path; | ||
// we cannot compare "just one token before `:`" because there might be annos | ||
if (path && path.length === 1 && !art.name && !art.expand && !art.inline) { | ||
const name = value.path[0]; | ||
if (path.length === 1 && !name.args && !name.cardinality && !name.where) { | ||
art.name = name; | ||
delete art.value; | ||
return art; | ||
} | ||
} | ||
this.error( 'syntax-unexpected-assoc', this.getCurrentToken(), {}, | ||
'Unexpected association definition in select item' ); | ||
return {}; // result of the association rules are written into /dev/null | ||
} | ||
@@ -1129,4 +1088,4 @@ | ||
this.error( 'syntax-unexpected-nested-proj', token, | ||
{ prop: isInline ? 'inline' : 'expand' }, | ||
{ std: 'Unexpected nested $(PROP), can only be used after a reference' } ); | ||
{ code: isInline ? '.{ ‹inline› }' : '{ ‹expand› }' }, | ||
'Unexpected $(CODE); nested projections can only be used after a reference' ); | ||
// continuation semantics: | ||
@@ -1139,4 +1098,4 @@ // - add elements anyway (could lead to duplicate errors as usual) | ||
const location = this.tokenLocation( isInline, this._input.LT(-1) ); | ||
this.error( 'syntax-unexpected-alias', location, { prop: 'inline' }, | ||
'Unexpected alias name before nested $(PROP)' ); | ||
this.error( 'syntax-unexpected-alias', location, { code: '.{ ‹inline› }' }, | ||
'Unexpected alias name before $(CODE)' ); | ||
// continuation semantics: ignore AS | ||
@@ -1147,14 +1106,14 @@ } | ||
function checkTypeFacet( art, argIdent ) { | ||
// TODO: use dictAddArray or dictAdd? | ||
const { id } = argIdent; | ||
if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') { | ||
if (art[id] !== undefined) { | ||
this.error( 'syntax-duplicate-argument', argIdent.location, | ||
{ '#': 'duplicate', code: id } ); | ||
this.error( 'syntax-duplicate-argument', art[id].location, | ||
{ '#': 'duplicate', code: id } ); | ||
{ '#': 'type', name: id } ); | ||
// continuation semantics: use last | ||
} | ||
return true; | ||
} | ||
this.error( 'syntax-duplicate-argument', argIdent.location, | ||
{ '#': 'unknown', code: id } ); | ||
this.error( 'syntax-undefined-param', argIdent.location, { name: id }, | ||
'There is no type parameter called $(NAME)'); | ||
return false; | ||
@@ -1161,0 +1120,0 @@ } |
@@ -27,3 +27,3 @@ 'use strict'; | ||
*/ | ||
function stripIndentation(str) { | ||
function stripIndentation( str ) { | ||
if (str === '') | ||
@@ -514,3 +514,3 @@ return [ '', 0 ]; | ||
*/ | ||
function parseMultiLineStringLiteral(token) { | ||
function parseMultiLineStringLiteral( token ) { | ||
const p = new MultiLineStringParser(this, token); | ||
@@ -517,0 +517,0 @@ return p.parse(); |
@@ -14,3 +14,3 @@ 'use strict'; | ||
*/ | ||
function isWhitespaceOrNewLineOnly(str) { | ||
function isWhitespaceOrNewLineOnly( str ) { | ||
return /^\s*$/.test(str); | ||
@@ -37,3 +37,3 @@ } | ||
*/ | ||
function isWhitespaceCharacterNoNewline(char) { | ||
function isWhitespaceCharacterNoNewline( char ) { | ||
return whitespaceRegEx.test(char); | ||
@@ -40,0 +40,0 @@ } |
@@ -207,2 +207,3 @@ // CSN functionality for resolving references | ||
const referenceSemantics = { | ||
$init: { $initOnly: true }, | ||
type: { lexical: false, dynamic: 'global' }, // TODO: assoc: 'static', see Issue #8458 | ||
@@ -536,2 +537,4 @@ includes: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway | ||
const semantics = referenceSemantics[refCtx] || {}; | ||
if (semantics.$initOnly) | ||
return undefined; | ||
if (semantics.dynamic === 'global' || ref.global) | ||
@@ -984,2 +987,4 @@ return resolvePath( path, csn.definitions[head], null, 'global', semantics.assoc ); | ||
// $self refers to the anonymous aspect | ||
if (resolve) | ||
resolve( '', '$init', main ); | ||
main = obj; | ||
@@ -986,0 +991,0 @@ art = obj; |
@@ -52,3 +52,3 @@ 'use strict'; | ||
const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare'); | ||
error(null, null, { version: beforeVersion }, 'Invalid CSN version: $(VERSION)'); | ||
error(null, null, { version: beforeVersion || 'undefined' }, 'Invalid CSN version: $(VERSION)'); | ||
throwWithAnyError(); | ||
@@ -58,3 +58,3 @@ } | ||
const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare'); | ||
error(null, null, { version: afterVersion }, 'Invalid CSN version: $(VERSION)'); | ||
error(null, null, { version: afterVersion || 'undefined' }, 'Invalid CSN version: $(VERSION)'); | ||
throwWithAnyError(); | ||
@@ -61,0 +61,0 @@ } |
@@ -13,2 +13,3 @@ { | ||
"template-curly-spacing":["error", "never"], | ||
"class-methods-use-this": "off", | ||
// Who cares - just very whiny and in the way | ||
@@ -15,0 +16,0 @@ "complexity": "off", |
@@ -75,3 +75,2 @@ // Common render functions for toCdl.js, toHdbcds.js and toSql.js | ||
/** | ||
@@ -90,3 +89,2 @@ * Get the part that is really the name of this artifact and not just prefix caused by a context/service | ||
const namespace = getNamespace(csn, artifactName); | ||
@@ -134,3 +132,2 @@ const startIndex = namespace ? namespace.split('.').length : 0; | ||
} | ||
return null; | ||
@@ -410,4 +407,3 @@ } | ||
* @param {object|array} expression | ||
* @param {CdlRenderEnvironment} env | ||
* @this {{inline: Boolean, nestedExpr: Boolean}} | ||
* @this {ExpressionRenderer} | ||
* @returns {string} | ||
@@ -422,3 +418,3 @@ */ | ||
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning | ||
* @property {renderPart} explicitTypeCast | ||
* @property {renderPart} typeCast | ||
* @property {renderPart} val | ||
@@ -433,90 +429,144 @@ * @property {renderPart} enum | ||
* @property {renderPart} SET | ||
* @property {boolean} [inline] | ||
* @property {boolean} [nestedExpr] | ||
* @property {Function} [visitExpr] | ||
* @property {Function} [renderExpr] | ||
* @property {Function} [renderSubExpr] | ||
* @property {boolean} [isNestedXpr] | ||
* @property {CdlRenderEnvironment} [env] | ||
*/ | ||
/** | ||
* @typedef {object} ExpressionRenderer | ||
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning | ||
* @property {renderPart} typeCast | ||
* @property {renderPart} val | ||
* @property {renderPart} enum | ||
* @property {renderPart} ref | ||
* @property {renderPart} aliasOnly | ||
* @property {renderPart} windowFunction | ||
* @property {renderPart} func | ||
* @property {renderPart} xpr | ||
* @property {renderPart} SELECT | ||
* @property {renderPart} SET | ||
* @property {Function} visitExpr | ||
* @property {Function} renderExpr | ||
* @property {Function} renderSubExpr | ||
* @property {boolean} isNestedXpr | ||
* @property {CdlRenderEnvironment} env | ||
*/ | ||
/** | ||
* If `xpr` has a `cast` property, return a copy without it, otherwise return `xpr`. | ||
* Useful for removing e.g. top-level CDL-style casts that should not be rendered as CAST(). | ||
* | ||
* @param xpr | ||
*/ | ||
function withoutCast(xpr) { | ||
return !xpr.cast ? xpr : { ...xpr, cast: undefined }; | ||
} | ||
/** | ||
* Render an expression (including paths and values) or condition 'x'. | ||
* (no trailing LF, don't indent if inline) | ||
* | ||
* @param {ExpressionConfiguration} renderer | ||
* @returns {Function} Rendered expression | ||
* @param {ExpressionConfiguration} rendererBase | ||
* @returns {ExpressionRenderer} Expression rendering utility | ||
*/ | ||
function getExpressionRenderer(renderer) { | ||
function createExpressionRenderer(rendererBase) { | ||
const renderer = Object.create(rendererBase); | ||
renderer.visitExpr = visitExpr; | ||
/** | ||
* Render an expression (including paths and values) or condition 'x'. | ||
* (no trailing LF, don't indent if inline) | ||
* | ||
* @todo Reuse this with toCdl | ||
* @param {Array|object|string} expr Expression to render | ||
* @param {object} env Render environment | ||
* @param {boolean} [inline=true] Whether to render the expression inline | ||
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested | ||
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`. | ||
* Note: This is a hack for casts() inside groupBy. | ||
* @returns {string} Rendered expression | ||
* @param {any} x | ||
* @param {CdlRenderEnvironment} env | ||
*/ | ||
return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) { | ||
// Compound expression | ||
if (Array.isArray(expr)) { | ||
const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr)); | ||
return beautifyExprArray(tokens); | ||
} | ||
else if (typeof expr === 'object' && expr !== null) { | ||
if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target) | ||
return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env); | ||
return renderExprObject(expr); | ||
} | ||
// Not a literal value but part of an operator, function etc - just leave as it is | ||
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql | ||
return renderer.finalize.call({ inline, nestedExpr }, expr, env); | ||
renderer.renderExpr = function renderExpr(x, env) { | ||
/** @type {ExpressionRenderer} */ | ||
const renderObj = Object.create(renderer); | ||
renderObj.env = env || this?.env; | ||
// The outermost expression is not nested. All `.xpr` inside `expr` | ||
// are nested. This information is used for adding parentheses around | ||
// expressions (see `this.xpr()`). | ||
renderObj.isNestedXpr = false; | ||
return renderObj.visitExpr(x); | ||
}; | ||
/** | ||
* @param {any} x | ||
* @param {CdlRenderEnvironment} env | ||
*/ | ||
renderer.renderSubExpr = function renderSubExpr(x, env) { | ||
/** @type {ExpressionRenderer} */ | ||
const renderObj = Object.create(renderer); | ||
renderObj.env = env || this?.env; | ||
renderObj.isNestedXpr = true; | ||
return renderObj.visitExpr(x); | ||
}; | ||
return renderer; | ||
} | ||
/** | ||
* Various special cases represented as objects | ||
* | ||
* @param {object} x Expression | ||
* @returns {string} String representation of the expression | ||
*/ | ||
function renderExprObject(x) { | ||
if (x.list) { // TODO: Does this still exist? | ||
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`; | ||
} | ||
else if (x.val !== undefined) { | ||
return renderer.val.call({ inline, nestedExpr }, x, env); | ||
} | ||
// Enum symbol | ||
else if (x['#']) { | ||
return renderer.enum.call({ inline, nestedExpr }, x, env); | ||
} | ||
// Reference: Array of path steps, possibly preceded by ':' | ||
else if (x.ref) { | ||
return renderer.ref.call({ inline, nestedExpr }, x, env); | ||
} | ||
// Function call, possibly with args (use '=>' for named args) | ||
else if (x.func) { | ||
if (x.xpr) | ||
return renderer.windowFunction.call({ inline, nestedExpr }, x, env); | ||
return renderer.func.call({ inline, nestedExpr }, x, env); | ||
} | ||
// Nested expression | ||
else if (x.xpr) { | ||
return renderer.xpr.call({ inline, nestedExpr }, x, env); | ||
} | ||
// Sub-select | ||
else if (x.SELECT) { | ||
return renderer.SELECT.call({ inline, nestedExpr }, x, env); | ||
} | ||
else if (x.SET) { | ||
return renderer.SET.call({ inline, nestedExpr }, x, env); | ||
} | ||
else if (x.as && x.cast && x.cast.type && x.cast.target) { | ||
return renderer.aliasOnly.call({ inline, nestedExpr }, x, env); | ||
} | ||
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`); | ||
} | ||
}; | ||
/** | ||
* Render an expression (including paths and values) or condition 'x'. | ||
* (no trailing LF, don't indent if inline) | ||
* | ||
* `this` must refer to an object of type `ExpressionRenderer`, see | ||
* `createExpressionRenderer()` | ||
* | ||
* @param {any} x (Sub-)Expression to render | ||
* | ||
* @this ExpressionRenderer | ||
* @returns {string} Rendered expression | ||
*/ | ||
function visitExpr(x) { | ||
if (Array.isArray(x)) { | ||
// Compound expression, e.g. for on- or where-conditions. | ||
// If xpr is part of an array, it's always a nested xpr, | ||
// e.g. CSN for `(1=1 or 2=2) and 3=3`. | ||
const tokens = x.map(item => this.renderSubExpr(item)); | ||
return beautifyExprArray(tokens); | ||
} | ||
else if (typeof x !== 'object' || x === null) { | ||
// Not a literal value but part of an operator, function etc - just leave as it is | ||
return this.finalize(x); | ||
} | ||
else if (x.cast?.type && !x.cast.target) { | ||
return this.typeCast(x); | ||
} | ||
else if (x.list) { | ||
// Render as non-nested expr. | ||
return `(${x.list.map(item => this.renderExpr(item)).join(', ')})`; | ||
} | ||
else if (x.val !== undefined) { | ||
return this.val(x); | ||
} | ||
else if (x['#']) { | ||
// Enum symbol | ||
return this.enum(x); | ||
} | ||
else if (x.ref) { | ||
// Reference: Array of path steps, possibly preceded by ':' | ||
return this.ref(x); | ||
} | ||
else if (x.func) { | ||
// Function call, possibly with args (use '=>' for named args) | ||
if (x.xpr) | ||
return this.windowFunction(x); | ||
return this.func(x); | ||
} | ||
else if (x.xpr) { | ||
return this.xpr(x); | ||
} | ||
else if (x.SELECT) { | ||
return this.SELECT(x); | ||
} | ||
else if (x.SET) { | ||
return this.SET(x); | ||
} | ||
else if (x.as) { | ||
return this.aliasOnly(x); | ||
} | ||
throw new ModelError(`renderExpr(): Unknown expression: ${JSON.stringify(x)}`); | ||
} | ||
/** | ||
@@ -540,4 +590,3 @@ * @typedef CdlRenderEnvironment Rendering environment used throughout the render process. | ||
renderFunc, | ||
getExpressionRenderer, | ||
beautifyExprArray, | ||
createExpressionRenderer, | ||
getNamespace, | ||
@@ -554,2 +603,3 @@ getRealName, | ||
getSqlSnippets, | ||
withoutCast, | ||
}; |
@@ -13,2 +13,4 @@ { | ||
"max-len": "off", | ||
// there seem to be false positives | ||
"jsdoc/require-returns-check": "off", | ||
// Don't enforce stupid descriptions | ||
@@ -15,0 +17,0 @@ "jsdoc/require-param-description": "off", |
{ | ||
"root": true, | ||
"plugins": ["sonarjs", "jsdoc"], | ||
"extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"], | ||
"rules": { | ||
"prefer-const": "error", | ||
"quotes": ["error", "single", "avoid-escape"], | ||
"prefer-template": "error", | ||
"no-trailing-spaces": "error", | ||
"template-curly-spacing":["error", "never"], | ||
"complexity": ["warn", 30], | ||
"max-len": "off", | ||
// Don't enforce stupid descriptions | ||
"jsdoc/require-param-description": "off", | ||
"jsdoc/require-returns-description": "off", | ||
// Sometimes if-else's are more specific | ||
"sonarjs/prefer-single-boolean-return": "off", | ||
// Very whiny and nitpicky | ||
"sonarjs/cognitive-complexity": "off", | ||
// Does not recognize TS types | ||
"jsdoc/no-undefined-types": "off", | ||
// Whiny and annoying | ||
"sonarjs/no-duplicate-string": "off" | ||
}, | ||
"parserOptions": { | ||
"ecmaVersion": 2020, | ||
"sourceType": "script" | ||
}, | ||
"env": { | ||
"es2020": true, | ||
"node": true | ||
}, | ||
"settings": { | ||
"jsdoc": { | ||
"mode": "typescript" | ||
} | ||
} | ||
"extends": ["../db/.eslintrc.json"] | ||
} |
@@ -216,3 +216,3 @@ 'use strict'; | ||
// Resolve annotation shorthands for entities, types, annotations, ... | ||
renameShorthandAnnotations(def); | ||
renameShorthandAnnotations(def, ['definitions', defName ]); | ||
@@ -224,3 +224,3 @@ // Annotate artifacts with their DB names if requested. | ||
forEachMemberRecursively(def, (member, memberName, propertyName) => { | ||
forEachMemberRecursively(def, (member, memberName, propertyName, path) => { | ||
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested | ||
@@ -238,3 +238,3 @@ // Only these are actually required and don't annotate virtual elements in entities or types | ||
// Resolve annotation shorthands for elements, actions, action parameters | ||
renameShorthandAnnotations(member); | ||
renameShorthandAnnotations(member, path); | ||
@@ -304,3 +304,3 @@ // - If the association target is annotated with @cds.odata.valuelist, annotate the | ||
// list. | ||
function renameShorthandAnnotations(node) { | ||
function renameShorthandAnnotations(node, path) { | ||
// FIXME: Verify this list - are they all still required? Do we need any more? | ||
@@ -375,18 +375,25 @@ const mappings = { | ||
if(typeDef.enum) { | ||
let enumValue = Object.keys(typeDef.enum).map(enumSymbol => { | ||
const enumValue = []; | ||
for(const enumSymbol in typeDef.enum) { | ||
const enumSymbolDef = typeDef.enum[enumSymbol]; | ||
let result = { '@Core.SymbolicName': enumSymbol }; | ||
if (enumSymbolDef.val !== undefined) | ||
result.Value = enumSymbolDef.val; | ||
else if (node.type && node.type === 'cds.String') | ||
// the symbol is used as value only for type 'cds.String' | ||
result.Value = enumSymbol; | ||
// Can't rely that @description has already been renamed to @Core.Description | ||
// Eval description according to precedence (doc comment must be considered already in Odata transformer | ||
// as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues) | ||
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc; | ||
if (desc) | ||
result['@Core.Description'] = desc; | ||
return result; | ||
}); | ||
if(enumSymbolDef.val === null) | ||
info('odata-enum-value-type', path, | ||
{name: enumSymbol, value: null, anno: '@Valiation.AllowedValues' }, | ||
'Value $(VALUE) for enum element $(NAME) not added to $(ANNO)'); | ||
else { | ||
const result = { '@Core.SymbolicName': enumSymbol }; | ||
if (enumSymbolDef.val !== undefined) | ||
result.Value = enumSymbolDef.val; | ||
else if (node.type && node.type === 'cds.String') | ||
// the symbol is used as value only for type 'cds.String' | ||
result.Value = enumSymbol; | ||
// Can't rely that @description has already been renamed to @Core.Description | ||
// Eval description according to precedence (doc comment must be considered already in Odata transformer | ||
// as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues) | ||
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc; | ||
if (desc) | ||
result['@Core.Description'] = desc; | ||
enumValue.push(result); | ||
} | ||
} | ||
setAnnotation(node, '@Validation.AllowedValues', enumValue); | ||
@@ -393,0 +400,0 @@ } |
@@ -5,3 +5,2 @@ 'use strict'; | ||
const { setProp, isDeprecatedEnabled} = require('../base/model'); | ||
const { hasErrors } = require('../base/messages'); | ||
const { forEachKey } = require('../utils/objectUtils'); | ||
@@ -48,4 +47,7 @@ const { cleanSymbols } = require('../base/cleanSymbols.js'); | ||
/** | ||
* Create transitive localized convenience views | ||
* Create transitive localized convenience views. | ||
* | ||
* A convenience view is created if the entity/view has a localized element | ||
* or if it exposes an association leading to a localized-tagged target. | ||
* | ||
* INTERNALS: | ||
@@ -77,9 +79,7 @@ * We have three kinds of localized convenience views: | ||
function _addLocalizationViews(csn, options, useJoins, config) { | ||
// Don't try to create convenience views with errors. | ||
if (hasErrors(options.messages)) // TODO: this is actually wrong, consider --test-mode | ||
return csn; | ||
const messageFunctions = makeMessageFunction(csn, options); | ||
if (hasExistingLocalizationViews(csn, options, messageFunctions)) | ||
if (checkExistingLocalizationViews(csn, options, messageFunctions)) { | ||
messageFunctions.throwWithError(); | ||
return csn; | ||
} | ||
@@ -95,2 +95,3 @@ const { acceptLocalizedView, ignoreUnknownExtensions } = config; | ||
sortCsnDefinitionsForTests(csn, options); | ||
messageFunctions.throwWithError(); | ||
return csn; | ||
@@ -718,3 +719,3 @@ | ||
*/ | ||
function hasExistingLocalizationViews(csn, options, messageFunctions) { | ||
function checkExistingLocalizationViews(csn, options, messageFunctions) { | ||
if (!csn || !csn.definitions) | ||
@@ -721,0 +722,0 @@ return false; |
@@ -111,5 +111,6 @@ 'use strict'; | ||
if (!exposedTypes[fullQualifiedNewTypeName]) { | ||
setProp(node, '$NameClashReported', true); | ||
error(null, path, { type: fullQualifiedNewTypeName, name: memberName }, | ||
'Can\'t create artificial type $(TYPE) for $(NAME) because the name is already used'); | ||
return; | ||
return { isExposable, typeDef, typeName, isAnonymous }; | ||
} | ||
@@ -136,4 +137,24 @@ } | ||
defName = typeDef.kind === 'type' ? typeName : defName; | ||
exposeTypeOf(newElem, isKey, elemName, defName, serviceName, | ||
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName); | ||
{ | ||
const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName, | ||
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName); | ||
// if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't | ||
// been catched by expandToFinalBaseType() (forODataNew must not modify external imported services) | ||
if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) { | ||
if(typeDef.items) { | ||
newElem.items = typeDef.items; | ||
delete newElem.type; | ||
} | ||
else if(newElem.items) { | ||
newElem.items.type = typeName; | ||
if(typeDef.enum) | ||
newElem.items.enum = typeDef.enum; | ||
} | ||
else { | ||
newElem.type = typeName; | ||
if(typeDef.enum) | ||
newElem.enum = typeDef.enum; | ||
} | ||
} | ||
} | ||
}); | ||
@@ -160,2 +181,3 @@ copyAnnotations(typeDef, newType); | ||
} | ||
return { isExposable, typeDef, typeName, isAnonymous }; | ||
@@ -203,3 +225,3 @@ /** | ||
} | ||
return { isExposable: false }; | ||
return { isExposable: false, typeDef, typeName, isAnonymous: false }; | ||
} | ||
@@ -206,0 +228,0 @@ |
@@ -1223,13 +1223,20 @@ 'use strict'; | ||
const bop = (op === 'is' && not) || op === '!=' || op === '<>' ? 'or' : 'and'; | ||
rc.push('('); | ||
const xpr = { xpr: [] }; | ||
xrefvalues.filter(x => x.lhs && x.rhs).forEach((x,i) => { | ||
if(i>0) | ||
rc.push(bop); | ||
rc.push(x.lhs); | ||
rc.push(op); | ||
xpr.i = i; | ||
if(i>0) { | ||
xpr.xpr.push(bop); | ||
} | ||
xpr.xpr.push(x.lhs); | ||
xpr.xpr.push(op); | ||
if(not) | ||
rc.push('not') | ||
rc.push(x.rhs); | ||
xpr.xpr.push('not') | ||
xpr.xpr.push(x.rhs); | ||
}); | ||
rc.push(')'); | ||
if(xpr.i > 0) { | ||
delete xpr.i; | ||
rc.push(xpr); | ||
} | ||
else | ||
rc.push(...xpr.xpr); | ||
i += not ? 3 : 2; | ||
@@ -1236,0 +1243,0 @@ } |
{ | ||
"name": "@sap/cds-compiler", | ||
"version": "3.4.2", | ||
"version": "3.4.4", | ||
"description": "CDS (Core Data Services) compiler and backends", | ||
@@ -33,3 +33,3 @@ "homepage": "https://cap.cloud.sap/", | ||
"coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/ && nyc report --reporter=lcov", | ||
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .", | ||
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .", | ||
"tslint": "tsc --pretty -p .", | ||
@@ -45,3 +45,2 @@ "updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs", | ||
"generateChecksRefs": "cross-env MAKEREFS='true' mocha test/testChecks.js", | ||
"generateScenarioRefs": "cross-env MAKEREFS='true' mocha test/testScenarios.js", | ||
"generateDraftRefs": "cross-env MAKEREFS='true' mocha test/testDraft.js", | ||
@@ -48,0 +47,0 @@ "generateAllRefs": "node scripts/verifyGrammarChecksum.js && cross-env MAKEREFS='true' mocha --reporter-option maxDiffSize=0 test/ test3/" |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
4203010
184
83901