@malloydata/malloy 0.0.43-dev230621212545 → 0.0.43-dev230622180905

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/lang/ast/ast-utils.js +1 -1
  2. package/dist/lang/ast/expressions/expr-coalesce.js +4 -0
  3. package/dist/lang/ast/expressions/expr-func.js +9 -34
  4. package/dist/lang/ast/expressions/expr-granular-time.js +14 -2
  5. package/dist/lang/ast/expressions/expr-minus.js +2 -2
  6. package/dist/lang/ast/expressions/expr-time-extract.js +22 -4
  7. package/dist/lang/ast/expressions/for-range.d.ts +1 -1
  8. package/dist/lang/ast/expressions/for-range.js +10 -2
  9. package/dist/lang/ast/expressions/pick-when.js +5 -1
  10. package/dist/lang/ast/field-space/query-spaces.js +3 -6
  11. package/dist/lang/ast/field-space/reference-field.js +1 -1
  12. package/dist/lang/ast/fragtype-utils.d.ts +1 -1
  13. package/dist/lang/ast/fragtype-utils.js +3 -2
  14. package/dist/lang/ast/parameters/constant-parameter.js +1 -1
  15. package/dist/lang/ast/query-items/field-declaration.js +5 -8
  16. package/dist/lang/ast/types/expression-def.js +29 -3
  17. package/dist/lang/ast/types/malloy-element.d.ts +2 -1
  18. package/dist/lang/ast/types/malloy-element.js +2 -10
  19. package/dist/lang/ast/types/space-param.js +1 -1
  20. package/dist/lang/parse-error-handler.js +1 -0
  21. package/dist/lang/parse-log.d.ts +1 -1
  22. package/dist/lang/parse-malloy.d.ts +13 -7
  23. package/dist/lang/parse-malloy.js +28 -17
  24. package/dist/lang/test/parse-expects.d.ts +21 -2
  25. package/dist/lang/test/parse-expects.js +97 -73
  26. package/dist/lang/test/parse.spec.js +269 -20
  27. package/dist/lang/test/sql-block.spec.js +1 -1
  28. package/dist/lang/test/test-translator.js +3 -0
  29. package/dist/lang/translate-response.d.ts +8 -8
  30. package/dist/malloy.d.ts +6 -4
  31. package/dist/malloy.js +14 -7
  32. package/dist/model/malloy_types.d.ts +6 -3
  33. package/package.json +1 -1
@@ -88,11 +88,12 @@ class ParseStep {
88
88
  that.urlIsFullPath = false;
89
89
  that.root.logger.log({
90
90
  message: `Could not compute full path URL: ${msg}`,
91
+ severity: 'error',
91
92
  });
92
93
  }
93
94
  }
94
95
  if (!that.urlIsFullPath) {
95
- return that.fatalErrors();
96
+ return that.fatalResponse();
96
97
  }
97
98
  const srcEnt = that.root.importZone.getEntry(that.sourceURL);
98
99
  if (srcEnt.status !== 'present') {
@@ -101,8 +102,8 @@ class ParseStep {
101
102
  ? `import error: ${srcEnt.message}`
102
103
  : `import '${that.sourceURL}' error: ${srcEnt.message}`;
103
104
  const at = srcEnt.firstReference || that.defaultLocation();
104
- that.root.logger.log({ message, at });
105
- this.response = that.fatalErrors();
105
+ that.root.logger.log({ message, at, severity: 'error' });
106
+ this.response = that.fatalResponse();
106
107
  return this.response;
107
108
  }
108
109
  return { urls: [that.sourceURL] };
@@ -116,13 +117,14 @@ class ParseStep {
116
117
  catch (parseException) {
117
118
  that.root.logger.log({
118
119
  message: `Malloy internal parser exception [${parseException.message}]`,
120
+ severity: 'error',
119
121
  });
120
122
  parse = undefined;
121
123
  }
122
124
  if (that.root.logger.hasErrors()) {
123
125
  this.response = {
124
126
  parse,
125
- ...that.fatalErrors(),
127
+ ...that.fatalResponse(),
126
128
  };
127
129
  }
128
130
  else {
@@ -228,6 +230,7 @@ class ImportsAndTablesStep {
228
230
  that.root.logger.log({
229
231
  message: `Malformed URL '${relativeRef}'"`,
230
232
  at: { url: that.sourceURL, range: firstRef },
233
+ severity: 'error',
231
234
  });
232
235
  }
233
236
  }
@@ -275,7 +278,7 @@ class ASTStep {
275
278
  const parseResponse = that.parseStep.response;
276
279
  // Errors in self or children will show up here ..
277
280
  if (that.root.logger.hasErrors()) {
278
- this.response = that.fatalErrors();
281
+ this.response = that.fatalResponse();
279
282
  return this.response;
280
283
  }
281
284
  const parse = parseResponse === null || parseResponse === void 0 ? void 0 : parseResponse.parse;
@@ -285,7 +288,7 @@ class ASTStep {
285
288
  const secondPass = new malloy_to_ast_1.MalloyToAST(parse, that.root.logger);
286
289
  const newAst = secondPass.visit(parse.root);
287
290
  if (that.root.logger.hasErrors()) {
288
- this.response = that.fatalErrors();
291
+ this.response = that.fatalResponse();
289
292
  return this.response;
290
293
  }
291
294
  if (newAst.elementType === 'unimplemented') {
@@ -316,7 +319,7 @@ class ASTStep {
316
319
  }
317
320
  // If there is a partial ast ...
318
321
  if (that.root.logger.hasErrors()) {
319
- this.response = that.fatalErrors();
322
+ this.response = that.fatalResponse();
320
323
  return this.response;
321
324
  }
322
325
  /*
@@ -336,7 +339,7 @@ class ASTStep {
336
339
  }
337
340
  newAst.setTranslator(that);
338
341
  this.response = {
339
- ...that.errors(),
342
+ ...that.problemResponse(),
340
343
  ast: newAst,
341
344
  final: true,
342
345
  };
@@ -474,11 +477,12 @@ class TranslateStep {
474
477
  that.root.logger.log({
475
478
  message: `'${that.sourceURL}' did not parse to malloy document`,
476
479
  at: that.defaultLocation(),
480
+ severity: 'error',
477
481
  });
478
482
  }
479
483
  }
480
484
  if (that.root.logger.hasErrors()) {
481
- this.response = that.fatalErrors();
485
+ this.response = that.fatalResponse();
482
486
  }
483
487
  else {
484
488
  this.response = {
@@ -487,7 +491,7 @@ class TranslateStep {
487
491
  queryList: that.queryList,
488
492
  sqlBlocks: that.sqlBlocks,
489
493
  },
490
- ...that.errors(),
494
+ ...that.problemResponse(),
491
495
  final: true,
492
496
  };
493
497
  }
@@ -533,19 +537,26 @@ class MalloyTranslation {
533
537
  referenceAt(position) {
534
538
  return this.references.find(position);
535
539
  }
536
- fatalErrors() {
540
+ /**
541
+ * This returns a *final* response containing all problems, for when there are
542
+ * errors and the translation needs to stop and report errors. When doing so,
543
+ * it also reports warnings.
544
+ */
545
+ fatalResponse() {
537
546
  return {
538
547
  final: true,
539
- errors: [...this.root.logger.getLog()],
548
+ ...this.problemResponse(),
540
549
  };
541
550
  }
542
551
  /**
543
- * The error log can grow as progressively deeper questions are asked.
544
- * When returning "errors so far", make a snapshot.
552
+ * The problem log can grow as progressively deeper questions are asked.
553
+ * When returning "problems so far", make a snapshot.
545
554
  */
546
- errors() {
547
- const errors = this.root.logger.getLog();
548
- return { errors: [...errors] };
555
+ problemResponse() {
556
+ return { problems: this.problems() };
557
+ }
558
+ problems() {
559
+ return [...this.root.logger.getLog()];
549
560
  }
550
561
  getLineMap(url) {
551
562
  var _a, _b;
@@ -1,5 +1,11 @@
1
1
  import { DocumentLocation } from '../../model';
2
- declare type ErrorSpec = string | RegExp;
2
+ import { LogSeverity } from '../parse-log';
3
+ declare type SimpleProblemSpec = string | RegExp;
4
+ declare type ComplexProblemSpec = {
5
+ severity: LogSeverity;
6
+ message: SimpleProblemSpec;
7
+ };
8
+ declare type ProblemSpec = SimpleProblemSpec | ComplexProblemSpec;
3
9
  declare global {
4
10
  namespace jest {
5
11
  interface Matchers<R> {
@@ -24,6 +30,19 @@ declare global {
24
30
  * the markings.
25
31
  */
26
32
  toTranslate(): R;
33
+ /**
34
+ * expect(X).toTranslateWithWarnings(expectedWarnings)
35
+ *
36
+ * Passes if the source compiles to code which could be used to
37
+ * generate SQL, and the specified warnings appear. If X is a marked
38
+ * source, the warnings which are found must match the locations of
39
+ * the markings.
40
+ *
41
+ * X can be a MarkedSource, a string, or a model. If it is a marked
42
+ * source, the errors which are found must match the locations of
43
+ * the markings.
44
+ */
45
+ toTranslateWithWarnings(...expectedWarnings: SimpleProblemSpec[]): R;
27
46
  toReturnType(tp: string): R;
28
47
  /**
29
48
  * expect(X).translateToFailWith(expectedErrors)
@@ -35,7 +54,7 @@ declare global {
35
54
  * @param expectedErrors varargs list of strings which must match
36
55
  * exactly, or regular expressions.
37
56
  */
38
- translationToFailWith(...expectedErrors: ErrorSpec[]): R;
57
+ translationToFailWith(...expectedErrors: ProblemSpec[]): R;
39
58
  isLocationIn(at: DocumentLocation, txt: string): R;
40
59
  }
41
60
  }
@@ -25,13 +25,13 @@
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  const test_translator_1 = require("./test-translator");
27
27
  const util_1 = require("util");
28
- function checkForErrors(trans) {
28
+ function ensureNoProblems(trans) {
29
29
  if (trans.logger === undefined) {
30
30
  throw new Error('JESTERY BROKEN, CANT FIND ERORR LOG');
31
31
  }
32
- if (trans.logger.hasErrors()) {
32
+ if (!trans.logger.empty()) {
33
33
  return {
34
- message: () => `Translation Errors:\n${trans.prettyErrors()}`,
34
+ message: () => `Translation problems:\n${trans.prettyErrors()}`,
35
35
  pass: false,
36
36
  };
37
37
  }
@@ -92,6 +92,19 @@ function highlightError(dl, txt) {
92
92
  }
93
93
  return output.join('\n');
94
94
  }
95
+ function normalizeProblemSpec(defaultSeverity) {
96
+ return function (spec) {
97
+ if (typeof spec === 'string') {
98
+ return { severity: defaultSeverity, message: spec };
99
+ }
100
+ else if (spec instanceof RegExp) {
101
+ return { severity: defaultSeverity, message: spec };
102
+ }
103
+ else {
104
+ return spec;
105
+ }
106
+ };
107
+ }
95
108
  function isMarkedSource(ts) {
96
109
  return typeof ts !== 'string' && !(ts instanceof test_translator_1.TestTranslator);
97
110
  }
@@ -105,7 +118,7 @@ function xlator(ts) {
105
118
  return ts.translator || new test_translator_1.TestTranslator(ts.code);
106
119
  }
107
120
  function xlated(tt) {
108
- const errorCheck = checkForErrors(tt);
121
+ const errorCheck = ensureNoProblems(tt);
109
122
  if (!errorCheck.pass) {
110
123
  return errorCheck;
111
124
  }
@@ -116,7 +129,7 @@ expect.extend({
116
129
  toParse: function (tx) {
117
130
  const x = xlator(tx);
118
131
  x.compile();
119
- return checkForErrors(x);
132
+ return ensureNoProblems(x);
120
133
  },
121
134
  toTranslate: function (tx) {
122
135
  const x = xlator(tx);
@@ -136,74 +149,10 @@ expect.extend({
136
149
  return { pass, message: () => msg };
137
150
  },
138
151
  translationToFailWith: function (s, ...msgs) {
139
- var _a;
140
- let emsg = 'Compile Error expectation not met\nExpected message';
141
- const mSrc = isMarkedSource(s) ? s : undefined;
142
- const qmsgs = msgs.map(s => `error '${s}'`);
143
- if (msgs.length === 1) {
144
- emsg += ` ${qmsgs[0]}`;
145
- }
146
- else {
147
- emsg += `s [\n${qmsgs.join('\n')}\n]`;
148
- }
149
- const m = xlator(s);
150
- const src = m.testSrc;
151
- emsg += `\nSource:\n${src}`;
152
- m.compile();
153
- const t = m.translate();
154
- if (t.translated) {
155
- return { pass: false, message: () => emsg };
156
- }
157
- else if (t.errors === undefined) {
158
- return {
159
- pass: false,
160
- message: () => 'TEST ERROR, not all objects resolved in source\n' +
161
- (0, test_translator_1.pretty)(t) +
162
- '\n' +
163
- emsg,
164
- };
165
- }
166
- else {
167
- const explain = [];
168
- const errList = m.errors().errors;
169
- let i;
170
- for (i = 0; i < msgs.length && errList[i]; i += 1) {
171
- const msg = msgs[i];
172
- const err = errList[i];
173
- const matched = typeof msg === 'string'
174
- ? msg === err.message
175
- : err.message.match(msg);
176
- if (!matched) {
177
- explain.push(`Expected: ${msg}\nGot: ${err.message}`);
178
- }
179
- else {
180
- if (mSrc === null || mSrc === void 0 ? void 0 : mSrc.locations[i]) {
181
- const have = (_a = err.at) === null || _a === void 0 ? void 0 : _a.range;
182
- const want = mSrc.locations[i].range;
183
- if (!this.equals(have, want)) {
184
- explain.push(`Expected '${msg}' at location: ${(0, util_1.inspect)(want)}\n` +
185
- `Actual location: ${(0, util_1.inspect)(have)}`);
186
- }
187
- }
188
- }
189
- }
190
- if (i !== msgs.length) {
191
- explain.push(...msgs.slice(i).map(m => `Missing: ${m}`));
192
- }
193
- if (i !== errList.length) {
194
- explain.push(...errList.slice(i).map(m => `Unexpected Error: ${m.message}`));
195
- }
196
- if (explain.length === 0) {
197
- return {
198
- pass: true,
199
- message: () => `All expected errors found: ${(0, test_translator_1.pretty)(msgs)}`,
200
- };
201
- }
202
- return {
203
- pass: false,
204
- message: () => `Compiler did not generated expected errors\n${explain.join('\n')}`,
205
- };
206
- }
152
+ return checkForProblems(this, false, s, 'error', ...msgs);
153
+ },
154
+ toTranslateWithWarnings: function (s, ...msgs) {
155
+ return checkForProblems(this, true, s, 'warn', ...msgs);
207
156
  },
208
157
  isLocationIn: function (checkAt, at, text) {
209
158
  if (this.equals(at, checkAt)) {
@@ -221,4 +170,79 @@ expect.extend({
221
170
  };
222
171
  },
223
172
  });
173
+ function checkForProblems(context, expectCompiles, s, defaultSeverity, ...msgs) {
174
+ var _a;
175
+ let emsg = `Expected ${expectCompiles ? 'to' : 'to not'} compile with: `;
176
+ const mSrc = isMarkedSource(s) ? s : undefined;
177
+ const normalize = normalizeProblemSpec(defaultSeverity);
178
+ const normMsgs = msgs.map(normalize);
179
+ const qmsgs = normMsgs.map(s => `${s.severity} '${s.message}'`);
180
+ if (msgs.length === 1) {
181
+ emsg += ` ${qmsgs[0]}`;
182
+ }
183
+ else {
184
+ emsg += `s [\n${qmsgs.join('\n')}\n]`;
185
+ }
186
+ const m = xlator(s);
187
+ const src = m.testSrc;
188
+ emsg += `\nSource:\n${src}`;
189
+ m.compile();
190
+ const t = m.translate();
191
+ if (t.translated && !expectCompiles) {
192
+ return { pass: false, message: () => emsg };
193
+ }
194
+ else if (t.problems === undefined) {
195
+ return {
196
+ pass: false,
197
+ message: () => 'TEST ERROR, not all objects resolved in source\n' +
198
+ (0, test_translator_1.pretty)(t) +
199
+ '\n' +
200
+ emsg,
201
+ };
202
+ }
203
+ else {
204
+ const explain = [];
205
+ const errList = m.problemResponse().problems;
206
+ let i;
207
+ for (i = 0; i < normMsgs.length && errList[i]; i += 1) {
208
+ const msg = normMsgs[i];
209
+ const err = errList[i];
210
+ const matched = typeof msg.message === 'string'
211
+ ? msg.message === err.message
212
+ : err.message.match(msg.message);
213
+ if (err.severity !== msg.severity) {
214
+ explain.push(`Expected ${msg.severity}, got ${err.severity} ${err.message}`);
215
+ }
216
+ if (!matched) {
217
+ explain.push(`Expected: ${msg.message}\nGot: ${err.message}`);
218
+ }
219
+ else {
220
+ if (mSrc === null || mSrc === void 0 ? void 0 : mSrc.locations[i]) {
221
+ const have = (_a = err.at) === null || _a === void 0 ? void 0 : _a.range;
222
+ const want = mSrc.locations[i].range;
223
+ if (!context.equals(have, want)) {
224
+ explain.push(`Expected '${msg.message}' at location: ${(0, util_1.inspect)(want)}\n` +
225
+ `Actual location: ${(0, util_1.inspect)(have)}`);
226
+ }
227
+ }
228
+ }
229
+ }
230
+ if (i !== msgs.length) {
231
+ explain.push(...msgs.slice(i).map(m => `Missing: ${m}`));
232
+ }
233
+ if (i !== errList.length) {
234
+ explain.push(...errList.slice(i).map(m => `Unexpected Error: ${m.message}`));
235
+ }
236
+ if (explain.length === 0) {
237
+ return {
238
+ pass: true,
239
+ message: () => `All expected errors found: ${(0, test_translator_1.pretty)(msgs)}`,
240
+ };
241
+ }
242
+ return {
243
+ pass: false,
244
+ message: () => `Compiler did not generated expected errors\n${explain.join('\n')}`,
245
+ };
246
+ }
247
+ }
224
248
  //# sourceMappingURL=parse-expects.js.map