@sap/cds-compiler 2.7.0 → 2.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +167 -0
- package/bin/cdsc.js +42 -25
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +10 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +17 -33
- package/lib/api/options.js +25 -13
- package/lib/api/validate.js +33 -9
- package/lib/backends.js +9 -8
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +26 -2
- package/lib/base/messages.js +25 -9
- package/lib/base/model.js +5 -3
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/onConditions.js +5 -0
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/validator.js +7 -2
- package/lib/compiler/assert-consistency.js +18 -5
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +5 -2
- package/lib/compiler/definer.js +145 -120
- package/lib/compiler/index.js +16 -4
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +207 -47
- package/lib/compiler/shared.js +47 -200
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +94 -98
- package/lib/edm/edm.js +16 -20
- package/lib/edm/edmPreprocessor.js +302 -115
- package/lib/edm/edmUtils.js +31 -12
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5308 -4308
- package/lib/json/from-csn.js +59 -30
- package/lib/json/to-csn.js +354 -105
- package/lib/language/antlrParser.js +11 -0
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +81 -14
- package/lib/language/language.g4 +163 -31
- package/lib/main.d.ts +136 -17
- package/lib/main.js +7 -1
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +115 -32
- package/lib/model/csnUtils.js +71 -33
- package/lib/model/enrichCsn.js +36 -9
- package/lib/model/revealInternalProperties.js +20 -4
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -16
- package/lib/render/.eslintrc.json +3 -1
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/toCdl.js +60 -17
- package/lib/render/toHdbcds.js +122 -74
- package/lib/render/toSql.js +57 -32
- package/lib/render/utils/common.js +6 -10
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/constraints.js +273 -119
- package/lib/transform/db/draft.js +9 -6
- package/lib/transform/db/expansion.js +19 -7
- package/lib/transform/db/flattening.js +31 -7
- package/lib/transform/db/transformExists.js +344 -66
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +65 -436
- package/lib/transform/forOdataNew.js +21 -10
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +44 -38
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +13 -10
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +55 -9
- package/lib/transform/translateAssocsToJoins.js +11 -17
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -3
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
|
@@ -61,6 +61,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
61
61
|
recurseElements,
|
|
62
62
|
renameAnnotation,
|
|
63
63
|
setAnnotation,
|
|
64
|
+
resetAnnotation,
|
|
64
65
|
expandStructsInExpression,
|
|
65
66
|
};
|
|
66
67
|
|
|
@@ -79,6 +80,14 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
79
80
|
else if(defStrLen5k)
|
|
80
81
|
element.length = 5000;
|
|
81
82
|
}
|
|
83
|
+
if (element.type === 'cds.Binary' && element.length === undefined) {
|
|
84
|
+
if(options.defaultBinaryLength) {
|
|
85
|
+
element.length = options.defaultBinaryLength;
|
|
86
|
+
setProp(element, '$default', true);
|
|
87
|
+
}
|
|
88
|
+
else if(defStrLen5k)
|
|
89
|
+
element.length = 5000;
|
|
90
|
+
}
|
|
82
91
|
/*
|
|
83
92
|
if (element.type === 'cds.Decimal' && element.precision === undefined && options.precision) {
|
|
84
93
|
element.precision = options.precision;
|
|
@@ -342,6 +351,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
342
351
|
|
|
343
352
|
/**
|
|
344
353
|
* Copy properties of the referenced type, but don't resolve to the final base type.
|
|
354
|
+
*
|
|
355
|
+
* Do not copy the length if it was just set via the default-value.
|
|
345
356
|
*
|
|
346
357
|
* @param {any} node Node to copy to
|
|
347
358
|
* @returns {void}
|
|
@@ -378,9 +389,10 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
378
389
|
*
|
|
379
390
|
* @param {CSN.Artifact} node
|
|
380
391
|
* @param {WeakMap} [resolved] WeakMap containing already resolved refs
|
|
392
|
+
* @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
|
|
381
393
|
* @returns {void}
|
|
382
394
|
*/
|
|
383
|
-
function toFinalBaseType(node, resolved) {
|
|
395
|
+
function toFinalBaseType(node, resolved, keepLocalized=false) {
|
|
384
396
|
// Nothing to do if no type (or if array/struct type)
|
|
385
397
|
if (!node || !node.type) return;
|
|
386
398
|
// In case of a ref -> Follow the ref
|
|
@@ -440,6 +452,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
440
452
|
Object.assign(node, { scale: typeDef.scale });
|
|
441
453
|
if (node.srid === undefined && typeDef.srid !== undefined)
|
|
442
454
|
Object.assign(node, { srid: typeDef.srid });
|
|
455
|
+
if (keepLocalized && node.localized === undefined && typeDef.localized !== undefined)
|
|
456
|
+
Object.assign(node, { localized: typeDef.localized });
|
|
443
457
|
node.type = typeDef.type;
|
|
444
458
|
toFinalBaseType(node);
|
|
445
459
|
}
|
|
@@ -920,6 +934,34 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
920
934
|
node[name] = value;
|
|
921
935
|
}
|
|
922
936
|
|
|
937
|
+
/**
|
|
938
|
+
* Assigns unconditionally annotation to a node, which means it overwrites already existing annotation assignment.
|
|
939
|
+
* Overwritting is when the assignment differs from undefined and null, also when differs from the already set value.
|
|
940
|
+
* Setting new assignment results false as return value and overwriting - true.
|
|
941
|
+
*
|
|
942
|
+
* @param {object} node Assignee
|
|
943
|
+
* @param {string} name Annotation name
|
|
944
|
+
* @param {any} value Annotation value
|
|
945
|
+
* @param {function} info function that reports info messages
|
|
946
|
+
* @param {CSN.Path} path location of the warning
|
|
947
|
+
* @returns {boolean} wasOverwritten true when the annotation was overwritten
|
|
948
|
+
*/
|
|
949
|
+
function resetAnnotation(node, name, value, info, path) {
|
|
950
|
+
if (!name.startsWith('@')) {
|
|
951
|
+
throw Error('Annotation name should start with "@": ' + name);
|
|
952
|
+
}
|
|
953
|
+
if (value === undefined) {
|
|
954
|
+
throw Error('Annotation value must not be undefined');
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const wasOverwritten = node[name] !== undefined && node[name] !== null && node[name] !== value;
|
|
958
|
+
const oldValue = node[name];
|
|
959
|
+
node[name] = value;
|
|
960
|
+
if(wasOverwritten)
|
|
961
|
+
info(null, path, { anno: name, prop: value, otherprop: oldValue },
|
|
962
|
+
`Value $(OTHERPROP) of annotation $(ANNO) is overwritten with new value $(PROP)`);
|
|
963
|
+
return wasOverwritten;
|
|
964
|
+
}
|
|
923
965
|
|
|
924
966
|
/*
|
|
925
967
|
Resolve the type of an artifact
|
|
@@ -1052,16 +1094,16 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1052
1094
|
function expandStructsInExpression(csn, options = {}) {
|
|
1053
1095
|
applyTransformations(csn, {
|
|
1054
1096
|
'on': (parent, name, on, path) => {
|
|
1055
|
-
parent.on = expand(parent.on, path);
|
|
1097
|
+
parent.on = expand(parent.on, path.concat(name));
|
|
1056
1098
|
},
|
|
1057
1099
|
'having': (parent, name, having, path) => {
|
|
1058
|
-
parent.having = expand(parent.having, path);
|
|
1100
|
+
parent.having = expand(parent.having, path.concat(name));
|
|
1059
1101
|
},
|
|
1060
1102
|
'where': (parent, name, where, path) => {
|
|
1061
|
-
parent.where = expand(parent.where, path);
|
|
1103
|
+
parent.where = expand(parent.where, path.concat(name));
|
|
1062
1104
|
},
|
|
1063
1105
|
'xpr': (parent, name, xpr, path) => {
|
|
1064
|
-
parent.xpr = expand(parent.xpr, path);
|
|
1106
|
+
parent.xpr = expand(parent.xpr, path.concat(name));
|
|
1065
1107
|
}
|
|
1066
1108
|
}, undefined, undefined, options);
|
|
1067
1109
|
|
|
@@ -1079,17 +1121,21 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1079
1121
|
if(i < expr.length-2)
|
|
1080
1122
|
{
|
|
1081
1123
|
const [lhs, op, rhs] = expr.slice(i);
|
|
1124
|
+
|
|
1125
|
+
// we might have to ad-hoc resolve a ref, since handleExists is run before hand and generates new refs.
|
|
1126
|
+
const lhsArt = lhs._art || lhs.ref && !lhs.$scope && inspectRef(location.concat(i)).art;
|
|
1127
|
+
const rhsArt = rhs._art || rhs.ref && !rhs.$scope && inspectRef(location.concat(i+2)).art;
|
|
1082
1128
|
// lhs & rhs must be expandable types (structures or managed associations)
|
|
1083
|
-
if(
|
|
1129
|
+
if(lhsArt && rhsArt &&
|
|
1084
1130
|
lhs.ref && rhs.ref &&
|
|
1085
|
-
isExpandable(
|
|
1131
|
+
isExpandable(lhsArt) && isExpandable(rhsArt) &&
|
|
1086
1132
|
['=', '<', '>', '>=', '<=', '!=', '<>'].includes(op) &&
|
|
1087
1133
|
!(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs))) {
|
|
1088
1134
|
|
|
1089
1135
|
// if path is scalar and no assoc or has no type (@Core.Computed) use original expression
|
|
1090
1136
|
// only do the expansion on (managed) assocs and (items.)elements, array of check in ON cond is done elsewhere
|
|
1091
|
-
const lhspaths = /*isScalarOrNoType(lhs._art) ? [ lhs ] : */ flattenPath({ _art:
|
|
1092
|
-
const rhspaths = /*isScalarOrNoType(rhs._art) ? [ rhs ] : */ flattenPath({ _art:
|
|
1137
|
+
const lhspaths = /*isScalarOrNoType(lhs._art) ? [ lhs ] : */ flattenPath({ _art: lhsArt, ref: lhs.ref }, false, true );
|
|
1138
|
+
const rhspaths = /*isScalarOrNoType(rhs._art) ? [ rhs ] : */ flattenPath({ _art: rhsArt, ref: rhs.ref }, false, true );
|
|
1093
1139
|
|
|
1094
1140
|
// mapping dict for lhs/rhs for mismatch check
|
|
1095
1141
|
// strip lhs/rhs prefix from flattened paths to check remaining common trailing path
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { setProp, forEachGeneric, forEachDefinition, isBetaEnabled } = require('../base/model');
|
|
4
|
-
var {
|
|
4
|
+
var { makeMessageFunction } = require('../base/messages');
|
|
5
5
|
const { recompileX } = require('../compiler/index');
|
|
6
|
-
var { linkToOrigin } = require('../compiler/
|
|
6
|
+
var { linkToOrigin } = require('../compiler/utils');
|
|
7
7
|
const {compactModel, compactExpr} = require('../json/to-csn');
|
|
8
8
|
const { deduplicateMessages } = require('../base/messages');
|
|
9
|
-
const timetrace = require('../utils/timetrace');
|
|
9
|
+
const { timetrace } = require('../utils/timetrace');
|
|
10
10
|
// Paths that start with an artifact of protected kind are special
|
|
11
11
|
// either ignore them in QAT building or in path rewriting
|
|
12
12
|
const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
|
|
@@ -14,7 +14,9 @@ const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
|
|
|
14
14
|
function translateAssocsToJoinsCSN(csn, options){
|
|
15
15
|
timetrace.start('Recompiling model');
|
|
16
16
|
// Do not re-complain about localized
|
|
17
|
-
const
|
|
17
|
+
const compileOptions = { ...options, $skipNameCheck: true };
|
|
18
|
+
delete compileOptions.csnFlavor;
|
|
19
|
+
const model = recompileX(csn, compileOptions);
|
|
18
20
|
timetrace.stop();
|
|
19
21
|
timetrace.start('Translating associations to joins');
|
|
20
22
|
translateAssocsToJoins(model, options);
|
|
@@ -42,9 +44,9 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
// If A2J reports error - end! Continuing with a broken CSN makes no sense
|
|
45
|
-
|
|
47
|
+
makeMessageFunction(model, options).throwWithError();
|
|
46
48
|
// FIXME: Move this somewhere more appropriate
|
|
47
|
-
const compact = compactModel(model,
|
|
49
|
+
const compact = compactModel(model, compileOptions);
|
|
48
50
|
return compact;
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -177,7 +179,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
177
179
|
let joinTree = query.from;
|
|
178
180
|
for(let tan in query.$tableAliases)
|
|
179
181
|
{
|
|
180
|
-
if(
|
|
182
|
+
if(query.$tableAliases[tan].kind !== '$self') // don't drive into $projection/$self tableAlias (yet)
|
|
181
183
|
{
|
|
182
184
|
let ta = query.$tableAliases[tan];
|
|
183
185
|
joinTree = createJoinTree(env, joinTree, ta.$qat, 'left', '$qat', ta.$QA);
|
|
@@ -198,7 +200,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
198
200
|
function createQAForFromClauseSubQuery(query, env)
|
|
199
201
|
{
|
|
200
202
|
for (let taName in query.$tableAliases) {
|
|
201
|
-
if (
|
|
203
|
+
if (query.$tableAliases[taName].kind !== '$self') {
|
|
202
204
|
let ta = query.$tableAliases[taName];
|
|
203
205
|
if(!ta.$QA) {
|
|
204
206
|
ta.$QA = createQA(env, ta._origin, taName, undefined);
|
|
@@ -239,14 +241,6 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
239
241
|
{
|
|
240
242
|
art.$QA = createQA(env, art.target._artifact, art.name.id );
|
|
241
243
|
art.$QA.mixin = true;
|
|
242
|
-
/* Mark mixin definition to be _ignored:
|
|
243
|
-
- If the mixin is used, it is now resolved into a join => definition vaporizes
|
|
244
|
-
- If the mixin is published, forHana backend must create a __copy with rewritten
|
|
245
|
-
$projection ON conditon and publish it with alias.
|
|
246
|
-
- If the mixin is neither be used nor published it shall not be visible to the database
|
|
247
|
-
(internal mixin).
|
|
248
|
-
*/
|
|
249
|
-
art.$a2j = { _ignore: true };
|
|
250
244
|
}
|
|
251
245
|
});
|
|
252
246
|
}
|
|
@@ -1401,7 +1395,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1401
1395
|
|
|
1402
1396
|
let [head, ...tail] = path;
|
|
1403
1397
|
|
|
1404
|
-
if(['$projection', '$self'].includes(head.id) && tail.length) {
|
|
1398
|
+
if(['$projection', '$self'].includes(head.id) && tail.length && head._navigation.kind === '$self') {
|
|
1405
1399
|
// make sure not to truncate tail
|
|
1406
1400
|
if(tail.length > 1)
|
|
1407
1401
|
[head, ...tail] = tail;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { forEachDefinition } = require('../base/model');
|
|
4
|
+
const {
|
|
5
|
+
applyTransformations,
|
|
6
|
+
cloneCsn,
|
|
7
|
+
getUtils,
|
|
8
|
+
isBuiltinType,
|
|
9
|
+
} = require('../model/csnUtils');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Loop through a universal CSN and enrich it with the properties
|
|
13
|
+
* from the source definition - modifies the input model in-place
|
|
14
|
+
*
|
|
15
|
+
* @param {CSN.Model} csn
|
|
16
|
+
* @param {CSN.Options} options
|
|
17
|
+
*/
|
|
18
|
+
module.exports = function(csn, options) {
|
|
19
|
+
let { getOrigin, getFinalType, getFinalTypeDef } = getUtils(csn);
|
|
20
|
+
// User-defined structured types do not have the elements propagated any longer
|
|
21
|
+
// if there is no association among the elements. For that reason,
|
|
22
|
+
// as a first step propagate the elements of these
|
|
23
|
+
forEachDefinition(csn, (def) => {
|
|
24
|
+
if (def.kind === 'type' && def.type && !def.elements) {
|
|
25
|
+
const finalType = getFinalType(def.type);
|
|
26
|
+
if (isBuiltinType(finalType)) return;
|
|
27
|
+
const finalTypeDef = getFinalTypeDef(def.type);
|
|
28
|
+
if (finalTypeDef.elements)
|
|
29
|
+
def.elements = cloneCsn(finalTypeDef.elements, options);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// as a second step, loop through all the $origin properties in the model
|
|
34
|
+
// and propagate the properties from the origin definition
|
|
35
|
+
applyTransformations(csn, {
|
|
36
|
+
'$origin': (node, _$orign, $originValue, _path, parent, propName) => {
|
|
37
|
+
if (!node.kind) { // we do not want to replace whole definitions
|
|
38
|
+
if (Array.isArray($originValue))
|
|
39
|
+
propagatePropsFromOrigin(node, propName, parent);
|
|
40
|
+
else if ($originValue.$origin && Array.isArray($originValue.$origin)) {
|
|
41
|
+
// cover the case of query entity elements where we have own and ihnerited attributes/annotations
|
|
42
|
+
propagatePropsFromOrigin($originValue, propName, parent);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, undefined, undefined, options);
|
|
48
|
+
|
|
49
|
+
function propagatePropsFromOrigin(member, memberName, construct) {
|
|
50
|
+
// TODO: shall the $origin be kept as part of the element?
|
|
51
|
+
const origin = getOrigin(member);
|
|
52
|
+
if (origin.kind) return;
|
|
53
|
+
if (member.elements && origin.type) {
|
|
54
|
+
delete member.$origin;
|
|
55
|
+
member.type = origin.type;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let newMember = cloneCsn(origin, options);
|
|
59
|
+
// keep targets and keys of assoc, if it was redirected
|
|
60
|
+
if (origin.type === 'cds.Association') {
|
|
61
|
+
newMember.target = member.target || newMember.target;
|
|
62
|
+
newMember.keys = member.keys || newMember.keys;
|
|
63
|
+
}
|
|
64
|
+
// TODO: check if this works fine for items/returns/actions
|
|
65
|
+
construct[memberName] = newMember;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/lib/utils/file.js
CHANGED
|
@@ -30,7 +30,7 @@ function cdsFs(fileCache, enableTrace) {
|
|
|
30
30
|
const readFile = _wrapReadFileCached(fs.readFile);
|
|
31
31
|
const readFileSync = _wrapReadFileCached((filename, enc, cb) => {
|
|
32
32
|
try {
|
|
33
|
-
cb(null, fs.readFileSync( filename, enc ));
|
|
33
|
+
cb(null, fs.readFileSync( filename, { encoding: enc } ));
|
|
34
34
|
}
|
|
35
35
|
catch (err) {
|
|
36
36
|
cb(err, null);
|
|
@@ -72,7 +72,7 @@ function cdsFs(fileCache, enableTrace) {
|
|
|
72
72
|
* Wraps the given reader into a cached environment including a trace.
|
|
73
73
|
* The given @p reader must have the same signature as fs.readFile.
|
|
74
74
|
*
|
|
75
|
-
* @param {(filename: string, enc
|
|
75
|
+
* @param {(filename: string, enc, cb: (err, data) => void) => void} reader
|
|
76
76
|
*/
|
|
77
77
|
function _wrapReadFileCached( reader ) {
|
|
78
78
|
return (filename, enc, cb) => {
|
|
@@ -93,7 +93,9 @@ function cdsFs(fileCache, enableTrace) {
|
|
|
93
93
|
body.syscall = 'open';
|
|
94
94
|
body.path = filename;
|
|
95
95
|
}
|
|
96
|
-
if (body
|
|
96
|
+
if (body && body.stack && body.message) {
|
|
97
|
+
// NOTE: checks for instanceof Error are not reliable if error
|
|
98
|
+
// created in different execution env
|
|
97
99
|
traceFS( 'READFILE:cache-error:', filename, body.message );
|
|
98
100
|
cb( body ); // no need for process.nextTick( cb, body ) with moduleResolve
|
|
99
101
|
}
|
package/lib/utils/term.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// This file is used for color output to stderr and stdout.
|
|
3
3
|
// Use `term.error`, `term.warn` and `term.info` as they use color output
|
|
4
4
|
// per default if the process runs in a TTY, i.e. stdout as well as
|
|
5
|
-
// stderr are TTYs.
|
|
6
|
-
//
|
|
5
|
+
// stderr are TTYs. stderr/stdout are no TTYs if they are
|
|
6
|
+
// (for example) piped into another process or written to file:
|
|
7
7
|
//
|
|
8
8
|
// node myApp.js # stdout.isTTY: true, stderr.isTTY: true
|
|
9
9
|
// node myApp.js | cat # stdout.isTTY: undefined, stderr.isTTY: true
|
|
@@ -17,23 +17,9 @@
|
|
|
17
17
|
const stderrHasColor = process.stderr.isTTY;
|
|
18
18
|
const stdoutHasColor = process.stdout.isTTY;
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
switch (mode) {
|
|
24
|
-
case false:
|
|
25
|
-
case 'never':
|
|
26
|
-
hasColor = false;
|
|
27
|
-
break;
|
|
28
|
-
case true:
|
|
29
|
-
case 'always':
|
|
30
|
-
hasColor = true;
|
|
31
|
-
break;
|
|
32
|
-
default:
|
|
33
|
-
hasColor = stdoutHasColor && stderrHasColor;
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
20
|
+
// Note: We require both stderr and stdout to be TTYs, as we don't
|
|
21
|
+
// know (in our exported functions) where the text will end up.
|
|
22
|
+
const hasColorShell = stdoutHasColor && stderrHasColor;
|
|
37
23
|
|
|
38
24
|
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
|
|
39
25
|
const t = {
|
|
@@ -47,29 +33,66 @@ const t = {
|
|
|
47
33
|
cyan: '\x1b[36m', // Foreground Cyan
|
|
48
34
|
};
|
|
49
35
|
|
|
50
|
-
|
|
36
|
+
function term(useColor = 'auto') {
|
|
37
|
+
let hasColor = hasColorShell;
|
|
38
|
+
changeColorMode(useColor);
|
|
51
39
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
40
|
+
function changeColorMode(mode) {
|
|
41
|
+
switch (mode) {
|
|
42
|
+
case false:
|
|
43
|
+
case 'never':
|
|
44
|
+
hasColor = false;
|
|
45
|
+
break;
|
|
46
|
+
case true:
|
|
47
|
+
case 'always':
|
|
48
|
+
hasColor = true;
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
// Note: See also: https://no-color.org/
|
|
52
|
+
// > Command-line software which adds ANSI color to its output by default
|
|
53
|
+
// > should check for the presence of a `NO_COLOR` environment variable
|
|
54
|
+
// > that, when present (regardless of its value), prevents the addition
|
|
55
|
+
// > of ANSI color.
|
|
56
|
+
// Note: To be able to disable colors in tests, we check the environment
|
|
57
|
+
// variable here again.
|
|
58
|
+
hasColor = hasColorShell && (process.env.NO_COLOR === undefined);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
67
61
|
}
|
|
68
|
-
};
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
const as = (codes, o) => (hasColor ? (codes + o + t.reset) : (`${ o }`));
|
|
64
|
+
|
|
65
|
+
const asError = o => as(t.red + t.bold, o);
|
|
66
|
+
const asWarning = o => as(t.yellow, o);
|
|
67
|
+
const asInfo = o => as(t.green, o);
|
|
68
|
+
const asHelp = o => as(t.cyan, o);
|
|
69
|
+
|
|
70
|
+
const underline = o => as(t.underline, o);
|
|
71
|
+
const bold = o => as(t.bold, o);
|
|
72
|
+
|
|
73
|
+
const asSeverity = (severity, msg) => {
|
|
74
|
+
switch ((`${ severity }`).toLowerCase()) {
|
|
75
|
+
case 'error': return asError(msg);
|
|
76
|
+
case 'warning': return asWarning(msg);
|
|
77
|
+
case 'info': return asInfo(msg);
|
|
78
|
+
case 'help': return asHelp(msg);
|
|
79
|
+
// or e.g. 'none'
|
|
80
|
+
default: return msg;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
changeColorMode,
|
|
86
|
+
as,
|
|
87
|
+
underline,
|
|
88
|
+
bold,
|
|
89
|
+
|
|
90
|
+
severity: asSeverity,
|
|
91
|
+
error: asError,
|
|
92
|
+
warn: asWarning,
|
|
93
|
+
info: asInfo,
|
|
94
|
+
help: asHelp,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { term };
|
package/lib/utils/timetrace.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @class TimeTrace
|
|
7
7
|
*/
|
|
8
|
-
class
|
|
8
|
+
class StopWatch {
|
|
9
9
|
/**
|
|
10
10
|
* Creates an instance of TimeTrace.
|
|
11
11
|
* @param {string} id
|
|
@@ -13,29 +13,46 @@ class TimeTrace {
|
|
|
13
13
|
* @memberOf TimeTrace
|
|
14
14
|
*/
|
|
15
15
|
constructor(id) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* @param {number} indent
|
|
21
|
-
*/
|
|
22
|
-
this.start = function start(indent) {
|
|
23
|
-
// eslint-disable-next-line no-console
|
|
24
|
-
console.error(`${ ' '.repeat((indent) * 2) }${ id } started`);
|
|
25
|
-
startTime = process.hrtime();
|
|
26
|
-
};
|
|
16
|
+
this.id = id;
|
|
17
|
+
// eslint-disable-next-line no-multi-assign
|
|
18
|
+
this.startTime = this.lapTime = process.hrtime();
|
|
19
|
+
}
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* @param {number} indent
|
|
21
|
+
/**
|
|
22
|
+
* Start watch.
|
|
32
23
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
start() {
|
|
25
|
+
// eslint-disable-next-line no-multi-assign
|
|
26
|
+
this.startTime = this.lapTime = process.hrtime();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Stop and return delta T
|
|
31
|
+
* but do not set start time
|
|
32
|
+
*/
|
|
33
|
+
stop() {
|
|
34
|
+
return process.hrtime(this.startTime);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* return lap time
|
|
39
|
+
*/
|
|
40
|
+
lap() {
|
|
41
|
+
const dt = process.hrtime(this.lapTime);
|
|
42
|
+
this.lapTime = process.hrtime();
|
|
43
|
+
return dt;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// stop as sec.ns float
|
|
47
|
+
stopInFloatSecs() {
|
|
48
|
+
const dt = this.stop();
|
|
49
|
+
return dt[0] + dt[1] / 1000000000;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// lap as sec.ns float
|
|
53
|
+
lapInFloatSecs() {
|
|
54
|
+
const dt = this.lap();
|
|
55
|
+
return dt[0] + dt[1] / 1000000000;
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
58
|
|
|
@@ -67,9 +84,11 @@ class TimeTracer {
|
|
|
67
84
|
*/
|
|
68
85
|
start(id) {
|
|
69
86
|
try {
|
|
70
|
-
const b = new
|
|
87
|
+
const b = new StopWatch(id);
|
|
71
88
|
this.traceStack.push(b);
|
|
72
|
-
b.start(
|
|
89
|
+
b.start();
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.error(`${ ' '.repeat((this.traceStack.length - 1) * 2) }${ id } started`);
|
|
73
92
|
}
|
|
74
93
|
catch (e) {
|
|
75
94
|
// eslint-disable-next-line no-console
|
|
@@ -86,7 +105,10 @@ class TimeTracer {
|
|
|
86
105
|
stop() {
|
|
87
106
|
try {
|
|
88
107
|
const current = this.traceStack.pop();
|
|
89
|
-
current.stop(
|
|
108
|
+
const dT = current.stop();
|
|
109
|
+
const base = `${ ' '.repeat(this.traceStack.length * 2) }${ current.id } took:`;
|
|
110
|
+
// eslint-disable-next-line no-console
|
|
111
|
+
console.error( `${ base }${ ' '.repeat(60 - base.length) } %ds %dms`, dT[0], dT[1] / 1000000);
|
|
90
112
|
}
|
|
91
113
|
catch (e) {
|
|
92
114
|
// eslint-disable-next-line no-console
|
|
@@ -101,4 +123,4 @@ const ignoreTimeTrace = {
|
|
|
101
123
|
};
|
|
102
124
|
|
|
103
125
|
const doTimeTrace = process && process.env && process.env.CDSC_TIMETRACING !== undefined;
|
|
104
|
-
module.exports = doTimeTrace ? new TimeTracer() : ignoreTimeTrace;
|
|
126
|
+
module.exports = { timetrace: (doTimeTrace ? new TimeTracer() : ignoreTimeTrace), StopWatch };
|