@malloydata/malloy 0.0.395 → 0.0.397

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 (61) hide show
  1. package/dist/annotation.d.ts +85 -1
  2. package/dist/annotation.js +133 -47
  3. package/dist/api/core.js +2 -7
  4. package/dist/api/foundation/compile.d.ts +7 -6
  5. package/dist/api/foundation/compile.js +22 -6
  6. package/dist/api/foundation/core.d.ts +10 -0
  7. package/dist/api/foundation/core.js +32 -9
  8. package/dist/api/foundation/runtime.d.ts +85 -5
  9. package/dist/api/foundation/runtime.js +184 -14
  10. package/dist/api/foundation/types.d.ts +2 -0
  11. package/dist/lang/ast/expressions/expr-func.js +30 -11
  12. package/dist/lang/ast/expressions/expr-given.js +1 -0
  13. package/dist/lang/ast/field-space/reference-field.js +1 -1
  14. package/dist/lang/ast/source-elements/sql-source.js +4 -0
  15. package/dist/lang/ast/source-elements/table-source.js +4 -0
  16. package/dist/lang/ast/statements/define-given.d.ts +1 -0
  17. package/dist/lang/ast/statements/define-given.js +7 -0
  18. package/dist/lang/ast/statements/import-statement.js +4 -0
  19. package/dist/lang/ast/types/annotation-elements.d.ts +1 -0
  20. package/dist/lang/ast/types/annotation-elements.js +10 -3
  21. package/dist/lang/ast/types/malloy-element.d.ts +1 -0
  22. package/dist/lang/ast/types/malloy-element.js +4 -0
  23. package/dist/lang/composite-source-utils.js +1 -1
  24. package/dist/lang/malloy-to-ast.d.ts +9 -1
  25. package/dist/lang/malloy-to-ast.js +37 -11
  26. package/dist/lang/malloy-to-stable-query.js +3 -3
  27. package/dist/lang/parse-log.d.ts +7 -1
  28. package/dist/lang/parse-log.js +12 -0
  29. package/dist/lang/parse-malloy.d.ts +4 -1
  30. package/dist/lang/parse-malloy.js +26 -4
  31. package/dist/lang/parse-tree-walkers/model-annotation-walker.js +3 -11
  32. package/dist/lang/parse-utils.d.ts +10 -2
  33. package/dist/lang/parse-utils.js +89 -29
  34. package/dist/lang/test/test-translator.d.ts +19 -5
  35. package/dist/lang/test/test-translator.js +15 -12
  36. package/dist/lang/zone.d.ts +2 -0
  37. package/dist/lang/zone.js +10 -0
  38. package/dist/model/constant_expression_compiler.js +14 -5
  39. package/dist/model/expression_compiler.js +19 -17
  40. package/dist/model/field_instance.js +7 -3
  41. package/dist/model/given_binding.js +26 -21
  42. package/dist/model/index.d.ts +1 -0
  43. package/dist/model/index.js +3 -1
  44. package/dist/model/malloy_compile_error.d.ts +13 -0
  45. package/dist/model/malloy_compile_error.js +23 -0
  46. package/dist/model/malloy_types.d.ts +9 -0
  47. package/dist/model/persist_utils.js +1 -1
  48. package/dist/model/query_model_impl.js +2 -1
  49. package/dist/model/query_node.d.ts +5 -5
  50. package/dist/model/query_node.js +22 -17
  51. package/dist/model/query_query.js +23 -11
  52. package/dist/model/sql_compiled.js +6 -3
  53. package/dist/prefix.d.ts +51 -0
  54. package/dist/prefix.js +99 -0
  55. package/dist/taggable.d.ts +17 -1
  56. package/dist/test/resultMatchers.js +2 -1
  57. package/dist/to_stable.d.ts +7 -1
  58. package/dist/to_stable.js +13 -16
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/package.json +4 -4
@@ -62,6 +62,7 @@ const AbstractParseTreeVisitor_1 = require("antlr4ts/tree/AbstractParseTreeVisit
62
62
  const ast = __importStar(require("./ast"));
63
63
  const parse_log_1 = require("./parse-log");
64
64
  const Interval_1 = require("antlr4ts/misc/Interval");
65
+ const prefix_1 = require("../prefix");
65
66
  const ast_1 = require("./ast");
66
67
  const parse_utils_1 = require("./parse-utils");
67
68
  const malloy_types_1 = require("../model/malloy_types");
@@ -92,10 +93,11 @@ const LEGAL_FILTER_TYPES = 'string, number, boolean, date, timestamp, timestampt
92
93
  * AST from an ANTLR parse tree.
93
94
  */
94
95
  class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
95
- constructor(parseInfo, msgLog, compilerFlagSrc) {
96
+ constructor(parseInfo, msgLog, compilerFlagSrc, restrictedMode = false) {
96
97
  super();
97
98
  this.parseInfo = parseInfo;
98
99
  this.msgLog = msgLog;
100
+ this.restrictedMode = restrictedMode;
99
101
  this.timer = new timing_1.Timer('generate_ast');
100
102
  const parseCompilerFlagsTimer = new timing_1.Timer('parse_compiler_flags');
101
103
  this.compilerFlagSrc = [...DEFAULT_COMPILER_FLAGS, ...compilerFlagSrc];
@@ -270,12 +272,26 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
270
272
  return this.astAt(def, pcx);
271
273
  }
272
274
  getAnnotation(cx) {
273
- const text = (0, parse_utils_1.getAnnotationText)(cx, (wcx, msg) => {
274
- this.contextError(wcx, 'block-annotation-warning', msg, {
275
- severity: 'warn',
276
- });
277
- });
278
- return { text: text, at: this.getLocation(cx) };
275
+ const note = (0, parse_utils_1.noteFromAnnotation)(cx, this.parseInfo);
276
+ this.warnIfMalformedPrefix(note.text, cx);
277
+ return note;
278
+ }
279
+ /**
280
+ * Warn if the annotation prefix is not a well-formed route. The note is still
281
+ * stored either way — the malformation only drives the diagnostic, never the
282
+ * IR. Warnings fire at note construction; inherited annotations carry no
283
+ * malformation marker through the IR and are not re-warned by importers.
284
+ */
285
+ warnIfMalformedPrefix(text, cx) {
286
+ const parsed = (0, prefix_1.parsePrefix)(text);
287
+ if (parsed.malformation === undefined)
288
+ return;
289
+ // The slice up to contentIndex is "prefix + separator"; trim trailing
290
+ // whitespace to land on the prefix the user wrote. (A no-content single-
291
+ // line note like `#malformed\n` exposes this: contentIndex === text.length
292
+ // but the slice still ends at the `\n`.)
293
+ const prefix = text.slice(0, parsed.contentIndex).replace(/\s+$/, '');
294
+ this.contextError(cx, parsed.malformation, { prefix });
279
295
  }
280
296
  getNotes(cx) {
281
297
  return cx.annotation().map(a => this.getAnnotation(a));
@@ -1498,6 +1514,15 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
1498
1514
  }
1499
1515
  updateCompilerFlags(tags) {
1500
1516
  const parseCompilerFlagsTimer = new timing_1.Timer('parse_compiler_flags');
1517
+ if (this.restrictedMode) {
1518
+ // `##!` lines in restricted text never become active flags. The
1519
+ // user-facing rejection is logged at execute time (see
1520
+ // ModelAnnotation.execute) so it doesn't short-circuit ASTStep,
1521
+ // which lets every other restricted-mode violation in the same
1522
+ // compile log its own diagnostic.
1523
+ this.timer.contribute([parseCompilerFlagsTimer.stop()]);
1524
+ return;
1525
+ }
1501
1526
  const newLines = tags.getCompilerFlagLines();
1502
1527
  if (newLines.length > 0) {
1503
1528
  const oldLength = this.compilerFlagSrc.length;
@@ -1523,10 +1548,11 @@ class MalloyToAST extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor {
1523
1548
  this.contextError(pcx, 'unclosed-block-annotation', 'Block annotation is not closed, add correctly indented "|##"');
1524
1549
  }
1525
1550
  }
1526
- const allNotes = pcx.docAnnotation().map(a => ({
1527
- text: (0, parse_utils_1.getAnnotationText)(a),
1528
- at: this.getLocation(pcx),
1529
- }));
1551
+ const allNotes = pcx.docAnnotation().map(a => {
1552
+ const note = (0, parse_utils_1.noteFromAnnotation)(a, this.parseInfo);
1553
+ this.warnIfMalformedPrefix(note.text, a);
1554
+ return note;
1555
+ });
1530
1556
  const tags = new ast.ModelAnnotation(allNotes);
1531
1557
  this.updateCompilerFlags(tags);
1532
1558
  return tags;
@@ -101,9 +101,9 @@ class MalloyToQuery extends AbstractParseTreeVisitor_1.AbstractParseTreeVisitor
101
101
  * @returns Array of texts for the annotations
102
102
  */
103
103
  getAnnotations(cx) {
104
- const annotations = cx.annotation().map(a => {
105
- return { value: (0, parse_utils_1.getAnnotationText)(a) };
106
- });
104
+ const annotations = cx.annotation().map(a => ({
105
+ value: (0, parse_utils_1.getAnnotationText)(a),
106
+ }));
107
107
  return annotations.length > 0 ? annotations : undefined;
108
108
  }
109
109
  getIsAnnotations(cx) {
@@ -358,7 +358,12 @@ type MessageParameterTypes = {
358
358
  'failed-to-parse-function-name': string;
359
359
  'orphaned-object-annotation': string;
360
360
  'unclosed-block-annotation': string;
361
- 'block-annotation-warning': string;
361
+ 'malformed-route': {
362
+ prefix: string;
363
+ };
364
+ 'reserved-route': {
365
+ prefix: string;
366
+ };
362
367
  'misplaced-model-annotation': string;
363
368
  'unexpected-non-source-query-expression-node': string;
364
369
  'sql-not-like': string;
@@ -430,6 +435,7 @@ type MessageParameterTypes = {
430
435
  'missing-required-group-by': string;
431
436
  'invalid-partition-composite': string;
432
437
  'integer-literal-out-of-range': string;
438
+ 'restricted-construct-forbidden': string;
433
439
  };
434
440
  export declare const MESSAGE_FORMATTERS: PartialErrorCodeMessageMap;
435
441
  export type MessageCode = keyof MessageParameterTypes;
@@ -101,6 +101,18 @@ exports.MESSAGE_FORMATTERS = {
101
101
  'inline-no-default': e => `inline given \`${e.name}\` must have a value — there is nothing to inline without one`,
102
102
  'inline-bad-operator': e => `inline given \`${e.name}\` uses operator(s) not allowed in inline expressions: ${e.operators}`,
103
103
  'invalid-given-modifier': e => `Unknown modifier \`${e.modifier}\` on \`given:\` declaration; the only modifier allowed here is \`inline\``,
104
+ 'restricted-construct-forbidden': e => ({
105
+ message: e,
106
+ tag: 'restricted-mode',
107
+ }),
108
+ 'malformed-route': e => ({
109
+ message: `Annotation prefix \`${e.prefix}\` is not a well-formed route — write \`# ...\` for a tag (note the space) or \`#(name)\` for an app route`,
110
+ severity: 'warn',
111
+ }),
112
+ 'reserved-route': e => ({
113
+ message: `Annotation prefix \`${e.prefix}\` uses an unclaimed sigil; punct-only prefixes are reserved for Malloy's own use`,
114
+ severity: 'warn',
115
+ }),
104
116
  };
105
117
  function makeLogMessage(code, parameters, options) {
106
118
  var _a, _b, _c, _d, _e;
@@ -193,6 +193,7 @@ export declare class MalloyChildTranslator extends MalloyTranslation {
193
193
  */
194
194
  export declare class MalloyTranslator extends MalloyTranslation {
195
195
  private readonly eventStream;
196
+ readonly restrictedMode: boolean;
196
197
  schemaZone: Zone<SourceDef>;
197
198
  importZone: Zone<string>;
198
199
  pretranslatedModels: Map<string, ModelDef>;
@@ -200,8 +201,10 @@ export declare class MalloyTranslator extends MalloyTranslation {
200
201
  connectionDialectZone: Zone<string>;
201
202
  logger: BaseMessageLogger;
202
203
  readonly root: MalloyTranslator;
203
- constructor(rootURL: string, importURL?: string | null, preload?: ParseUpdate | null, eventStream?: EventStream | null);
204
+ constructor(rootURL: string, importURL?: string | null, preload?: ParseUpdate | null, eventStream?: EventStream | null, restrictedMode?: boolean);
204
205
  update(dd: ParseUpdate): void;
206
+ private lockZonesIfRestricted;
207
+ translate(extendingModel?: ModelDef): TranslateResponse;
205
208
  logError<T extends MessageCode>(code: T, parameters: MessageParameterType<T>, options?: Omit<LogMessageOptions, 'severity'>): T;
206
209
  }
207
210
  interface ErrorData {
@@ -139,6 +139,12 @@ class ImportsAndTablesStep {
139
139
  if (parseReq.parse === undefined) {
140
140
  return parseReq;
141
141
  }
142
+ // Every reference this step would register — connection dialects,
143
+ // imports, table schemas — is for a construct that's forbidden in
144
+ // restricted code.
145
+ if (that.root.restrictedMode) {
146
+ return { timingInfo: parseReq.timingInfo };
147
+ }
142
148
  if (!this.parseReferences) {
143
149
  this.parseReferences = (0, find_external_references_1.findReferences)(that, parseReq.parse.tokenStream, parseReq.parse.root);
144
150
  // Register connection dialects and imports immediately. Table
@@ -278,7 +284,7 @@ class ASTStep {
278
284
  throw new Error('TRANSLATOR INTERNAL ERROR: Translator parse response had no errors, but also no parser');
279
285
  }
280
286
  stepTimer.incorporate(parseResponse.timingInfo);
281
- const secondPass = new malloy_to_ast_1.MalloyToAST(parse, that.root.logger, that.compilerFlagSrc);
287
+ const secondPass = new malloy_to_ast_1.MalloyToAST(parse, that.root.logger, that.compilerFlagSrc, that.root.restrictedMode);
282
288
  const { ast: newAST, compilerFlagSrc, timingInfo } = secondPass.run();
283
289
  stepTimer.contribute([timingInfo]);
284
290
  that.compilerFlagSrc = compilerFlagSrc;
@@ -466,10 +472,13 @@ class TranslateStep {
466
472
  modelWasModified: false,
467
473
  };
468
474
  }
469
- // begin with the compiler flags of the model we are extending
475
+ // Layer the extending model's compiler flags on top of whatever was
476
+ // there already. In production the array starts empty so push vs.
477
+ // overwrite produce the same result; the push lets constructor-time
478
+ // seeding (e.g. TestTranslator's compilerFlags option) survive.
470
479
  if (extendingModel && !this.importedAnnotations) {
471
480
  const parseCompilerFlagsTimer = new timing_1.Timer('parse_compiler_flags');
472
- that.compilerFlagSrc = (0, annotation_1.annotationToTaglines)(extendingModel.annotation, /^##! /);
481
+ that.compilerFlagSrc.push(...new annotation_1.Annotations(extendingModel.annotation).texts('!'));
473
482
  stepTimer.contribute([parseCompilerFlagsTimer.stop()]);
474
483
  this.importedAnnotations = true;
475
484
  }
@@ -821,9 +830,10 @@ exports.MalloyChildTranslator = MalloyChildTranslator;
821
830
  * no need to call again, the translation is finished or error'd.
822
831
  */
823
832
  class MalloyTranslator extends MalloyTranslation {
824
- constructor(rootURL, importURL = null, preload = null, eventStream = null) {
833
+ constructor(rootURL, importURL = null, preload = null, eventStream = null, restrictedMode = false) {
825
834
  super(rootURL, importURL);
826
835
  this.eventStream = eventStream;
836
+ this.restrictedMode = restrictedMode;
827
837
  this.schemaZone = new zone_1.Zone();
828
838
  this.importZone = new zone_1.Zone();
829
839
  this.pretranslatedModels = new Map();
@@ -845,6 +855,18 @@ class MalloyTranslator extends MalloyTranslation {
845
855
  this.pretranslatedModels.set(url, dd.translations[url]);
846
856
  }
847
857
  }
858
+ lockZonesIfRestricted() {
859
+ if (!this.restrictedMode)
860
+ return;
861
+ this.schemaZone.lock();
862
+ this.importZone.lock();
863
+ this.sqlQueryZone.lock();
864
+ this.connectionDialectZone.lock();
865
+ }
866
+ translate(extendingModel) {
867
+ this.lockZonesIfRestricted();
868
+ return super.translate(extendingModel);
869
+ }
848
870
  logError(code, parameters, options) {
849
871
  this.logger.log((0, parse_log_1.makeLogMessage)(code, parameters, { severity: 'error', ...options }));
850
872
  return code;
@@ -32,18 +32,10 @@ class ModelAnnotationWalker {
32
32
  this.parseInfo = parseInfo;
33
33
  this.notes = [];
34
34
  }
35
- getLocation(cx) {
36
- return {
37
- url: this.parseInfo.sourceURL,
38
- range: this.translator.rangeFromContext(cx),
39
- };
40
- }
41
35
  enterDocAnnotations(pcx) {
42
- const allNotes = pcx.docAnnotation().map(a => ({
43
- text: (0, parse_utils_1.getAnnotationText)(a),
44
- at: this.getLocation(pcx),
45
- }));
46
- this.notes.push(...allNotes);
36
+ for (const a of pcx.docAnnotation()) {
37
+ this.notes.push((0, parse_utils_1.noteFromAnnotation)(a, this.parseInfo));
38
+ }
47
39
  }
48
40
  get annotation() {
49
41
  return { notes: this.notes };
@@ -1,6 +1,8 @@
1
1
  import type { ParserRuleContext } from 'antlr4ts';
2
2
  import type { DocAnnotationContext } from './lib/Malloy/MalloyParser';
3
3
  import { type StringContext, type ShortStringContext, type SqlStringContext, type IdContext, AnnotationContext } from './lib/Malloy/MalloyParser';
4
+ import type { Note } from '../model/malloy_types';
5
+ import type { MalloyParseInfo } from './malloy-parse-info';
4
6
  /**
5
7
  * Take the text of a matched string, including the matching quote
6
8
  * characters, and return the actual contents of the string after
@@ -35,6 +37,12 @@ export declare function unIndent(parts: (string | unknown)[]): void;
35
37
  * @returns string part and an error list.
36
38
  */
37
39
  export declare function getPlainString(cx: HasString, strictCheck?: boolean): [string | undefined, ParserRuleContext[]];
38
- type AnnotationWarn = (cx: ParserRuleContext, msg: string) => void;
39
- export declare function getAnnotationText(cx: AnnotationContext | DocAnnotationContext, warn?: AnnotationWarn): string;
40
+ /**
41
+ * Build the IR `Note` for an annotation: reads the text, dedents the body if
42
+ * it's a block, and computes the source `at` from the parse context. The
43
+ * single entry point for going from a parse-tree annotation to an IR note.
44
+ */
45
+ export declare function noteFromAnnotation(cx: AnnotationContext | DocAnnotationContext, parseInfo: MalloyParseInfo): Note;
46
+ /** Text-only reader, for callers that don't need an IR `Note`. */
47
+ export declare function getAnnotationText(cx: AnnotationContext | DocAnnotationContext): string;
40
48
  export {};
@@ -30,9 +30,11 @@ exports.idToStr = idToStr;
30
30
  exports.getOptionalId = getOptionalId;
31
31
  exports.unIndent = unIndent;
32
32
  exports.getPlainString = getPlainString;
33
+ exports.noteFromAnnotation = noteFromAnnotation;
33
34
  exports.getAnnotationText = getAnnotationText;
34
35
  const MalloyParser_1 = require("./lib/Malloy/MalloyParser");
35
36
  const malloy_tag_1 = require("@malloydata/malloy-tag");
37
+ const utils_1 = require("./utils");
36
38
  /**
37
39
  * Take the text of a matched string, including the matching quote
38
40
  * characters, and return the actual contents of the string after
@@ -182,51 +184,109 @@ function getPlainString(cx, strictCheck = false) {
182
184
  // string: shortString | sqlString; So this will never happen
183
185
  return ['', errorList];
184
186
  }
185
- function stripBlockIndent(lines, column, cx, warn) {
186
- if (column === 0) {
187
- return lines.join('');
188
- }
189
- const prefix = ' '.repeat(column);
190
- let warnedLeft = false;
191
- let warnedTab = false;
192
- return lines
193
- .map(line => {
194
- if (warn && !warnedTab && line.slice(0, column).includes('\t')) {
195
- warn(cx, 'Block annotation indentation contains tabs, use spaces');
196
- warnedTab = true;
197
- }
198
- if (line.startsWith(prefix)) {
199
- return line.slice(column);
187
+ /**
188
+ * Python `textwrap.dedent`-style: find the longest leading-whitespace prefix
189
+ * common to every non-blank body line and strip it from each line that starts
190
+ * with it. Blank (whitespace-only) lines don't constrain the prefix. Returns
191
+ * the stripped text and the number of characters removed per line — the
192
+ * latter is stored on the `Note` so payload-parser error columns can be
193
+ * mapped back to source (`source_col = indentStripped + parser_col`).
194
+ *
195
+ * Replaces an older "strip exactly opener_column spaces" rule that fired
196
+ * warnings for less-indented lines and had no clean column mapping when
197
+ * stripping was inconsistent. Common prefix is uniform per block, so column
198
+ * mapping is one number per block.
199
+ */
200
+ function dedentBlockLines(lines) {
201
+ let common;
202
+ for (const line of lines) {
203
+ const content = line.replace(/\r?\n$/, '');
204
+ if (!/\S/.test(content))
205
+ continue;
206
+ const indent = content.match(/^[ \t]*/)[0];
207
+ if (common === undefined) {
208
+ common = indent;
209
+ continue;
200
210
  }
201
- if (warn && !warnedLeft && !warnedTab && line.match(/\S/)) {
202
- warn(cx, 'Block annotation content is left of the opening #|');
203
- warnedLeft = true;
211
+ let n = 0;
212
+ while (n < common.length && n < indent.length && common[n] === indent[n]) {
213
+ n++;
204
214
  }
205
- return line;
206
- })
207
- .join('');
215
+ common = common.slice(0, n);
216
+ if (common === '')
217
+ break;
218
+ }
219
+ const prefix = common !== null && common !== void 0 ? common : '';
220
+ if (prefix === '')
221
+ return { text: lines.join(''), indentStripped: 0 };
222
+ return {
223
+ text: lines
224
+ .map(line => (line.startsWith(prefix) ? line.slice(prefix.length) : line))
225
+ .join(''),
226
+ indentStripped: prefix.length,
227
+ };
208
228
  }
209
229
  function stripTrailingNewline(s) {
210
- return s.endsWith('\n') ? s.slice(0, -1) : s;
230
+ // A trailing line ending may be CRLF or LF — strip either.
231
+ return s.replace(/\r?\n$/, '');
211
232
  }
212
- function getAnnotationText(cx, warn) {
233
+ /**
234
+ * Annotation note text is normalized to LF line endings, so a block's stored
235
+ * text and content are identical regardless of the source's CRLF/LF style.
236
+ * The lexer keeps the source `\r` in token text (it sits at line ends, after a
237
+ * line's content); this is where it is dropped.
238
+ */
239
+ function normalizeEol(s) {
240
+ return s.replace(/\r\n/g, '\n');
241
+ }
242
+ /**
243
+ * Read the text and dedent amount of an annotation from its parse tree.
244
+ * Internal — public callers want `noteFromAnnotation` or `getAnnotationText`.
245
+ */
246
+ function readAnnotation(cx) {
213
247
  if (cx instanceof MalloyParser_1.AnnotationContext) {
214
248
  const annot = cx.ANNOTATION();
215
249
  if (annot)
216
- return annot.text;
250
+ return { text: normalizeEol(annot.text), indentStripped: 0 };
217
251
  const block = cx.blockAnnotation();
218
252
  const beginToken = block.BLOCK_ANNOTATION_BEGIN();
219
253
  const textLines = block.BLOCK_ANNOTATION_TEXT().map(t => t.text);
220
- return stripTrailingNewline(beginToken.text +
221
- stripBlockIndent(textLines, beginToken.symbol.charPositionInLine, cx, warn));
254
+ const dedented = dedentBlockLines(textLines);
255
+ return {
256
+ text: normalizeEol(stripTrailingNewline(beginToken.text + dedented.text)),
257
+ indentStripped: dedented.indentStripped,
258
+ };
222
259
  }
223
260
  const doc = cx.DOC_ANNOTATION();
224
261
  if (doc)
225
- return doc.text;
262
+ return { text: normalizeEol(doc.text), indentStripped: 0 };
226
263
  const block = cx.docBlockAnnotation();
227
264
  const beginToken = block.DOC_BLOCK_ANNOTATION_BEGIN();
228
265
  const textLines = block.BLOCK_ANNOTATION_TEXT().map(t => t.text);
229
- return stripTrailingNewline(beginToken.text +
230
- stripBlockIndent(textLines, beginToken.symbol.charPositionInLine, cx, warn));
266
+ const dedented = dedentBlockLines(textLines);
267
+ return {
268
+ text: normalizeEol(stripTrailingNewline(beginToken.text + dedented.text)),
269
+ indentStripped: dedented.indentStripped,
270
+ };
271
+ }
272
+ /**
273
+ * Build the IR `Note` for an annotation: reads the text, dedents the body if
274
+ * it's a block, and computes the source `at` from the parse context. The
275
+ * single entry point for going from a parse-tree annotation to an IR note.
276
+ */
277
+ function noteFromAnnotation(cx, parseInfo) {
278
+ const { text, indentStripped } = readAnnotation(cx);
279
+ const at = {
280
+ url: parseInfo.sourceURL,
281
+ range: (0, utils_1.rangeFromContext)(parseInfo.sourceInfo, cx),
282
+ };
283
+ const note = { text, at };
284
+ if (indentStripped > 0)
285
+ note.indentStripped = indentStripped;
286
+ return note;
287
+ }
288
+ /** Text-only reader, for callers that don't need an IR `Note`. */
289
+ function getAnnotationText(cx) {
290
+ return readAnnotation(cx).text;
231
291
  }
232
292
  //# sourceMappingURL=parse-utils.js.map
@@ -42,12 +42,27 @@ export declare class TestChildTranslator extends MalloyChildTranslator {
42
42
  translate(): TranslateResponse;
43
43
  addChild(url: string): void;
44
44
  }
45
+ export interface TestTranslatorOptions {
46
+ rootRule?: string;
47
+ importBaseURL?: string | null;
48
+ eventStream?: EventStream | null;
49
+ internalModel?: ModelDef;
50
+ restrictedMode?: boolean;
51
+ /**
52
+ * Each entry is a compiler-flag tag fragment — the content that
53
+ * would follow `##! ` in a Malloy source annotation. Rendered to
54
+ * `##! <flag>\n` and pushed onto compilerFlagSrc before any
55
+ * TranslateStep seeding, so the flags are active during AST-build
56
+ * inExperiment() checks.
57
+ */
58
+ compilerFlags?: string[];
59
+ }
45
60
  export declare class TestTranslator extends MalloyTranslator {
46
61
  readonly testSrc: string;
47
62
  allDialectsEnabled: boolean;
48
63
  testRoot?: TestRoot;
49
64
  internalModel: ModelDef;
50
- constructor(testSrc: string, importBaseURL?: string | null, eventStream?: EventStream | null, rootRule?: string, internalModel?: ModelDef);
65
+ constructor(testSrc: string, options?: TestTranslatorOptions);
51
66
  translate(): TranslateResponse;
52
67
  addChild(url: string): void;
53
68
  ast(): MalloyElement | undefined;
@@ -64,7 +79,7 @@ export declare class TestTranslator extends MalloyTranslator {
64
79
  export declare class BetaExpression extends TestTranslator {
65
80
  readonly sourceName: string;
66
81
  private compiled?;
67
- constructor(src: string, model?: ModelDef, sourceName?: string);
82
+ constructor(src: string, model?: ModelDef, sourceName?: string, options?: Omit<TestTranslatorOptions, 'rootRule' | 'internalModel'>);
68
83
  private testFS;
69
84
  compile(): void;
70
85
  generated(): ExprValue;
@@ -85,12 +100,11 @@ interface HasTranslator<TT extends TestTranslator> extends MarkedSource {
85
100
  }
86
101
  export declare function expr(unmarked: TemplateStringsArray, ...marked: string[]): HasTranslator<BetaExpression>;
87
102
  export declare function model(unmarked: TemplateStringsArray, ...marked: string[]): HasTranslator<TestTranslator>;
88
- export declare function makeModelFunc(options: {
89
- model?: ModelDef;
103
+ export declare function makeModelFunc(options: TestTranslatorOptions & {
90
104
  prefix?: string;
91
105
  wrap?: (code: string) => string;
92
106
  }): (unmarked: TemplateStringsArray, ...marked: string[]) => HasTranslator<TestTranslator>;
93
- export declare function makeExprFunc(model: ModelDef, sourceName: string): (unmarked: TemplateStringsArray, ...marked: string[]) => HasTranslator<TestTranslator>;
107
+ export declare function makeExprFunc(model?: ModelDef, sourceName?: string, options?: Omit<TestTranslatorOptions, 'rootRule' | 'internalModel'>): (unmarked: TemplateStringsArray, ...marked: string[]) => HasTranslator<TestTranslator>;
94
108
  export declare function markSource(unmarked: TemplateStringsArray, ...marked: string[]): MarkedSource;
95
109
  export declare function getSelectOneStruct(sqlBlock: SQLSourceRequest): {
96
110
  [key: string]: SQLSourceDef;
@@ -330,8 +330,9 @@ class TestChildTranslator extends parse_malloy_1.MalloyChildTranslator {
330
330
  exports.TestChildTranslator = TestChildTranslator;
331
331
  const testURI = 'internal://test/langtests/root.malloy';
332
332
  class TestTranslator extends parse_malloy_1.MalloyTranslator {
333
- constructor(testSrc, importBaseURL = null, eventStream = null, rootRule = 'malloyDocument', internalModel) {
334
- super(testURI, importBaseURL, null, eventStream);
333
+ constructor(testSrc, options = {}) {
334
+ var _a, _b, _c, _d, _e;
335
+ super(testURI, (_a = options.importBaseURL) !== null && _a !== void 0 ? _a : null, null, (_b = options.eventStream) !== null && _b !== void 0 ? _b : null, (_c = options.restrictedMode) !== null && _c !== void 0 ? _c : false);
335
336
  this.testSrc = testSrc;
336
337
  this.allDialectsEnabled = true;
337
338
  /*
@@ -415,16 +416,19 @@ class TestTranslator extends parse_malloy_1.MalloyTranslator {
415
416
  },
416
417
  },
417
418
  };
418
- this.grammarRule = rootRule;
419
+ this.grammarRule = (_d = options.rootRule) !== null && _d !== void 0 ? _d : 'malloyDocument';
419
420
  this.importZone.define(testURI, testSrc);
420
- if (internalModel !== undefined) {
421
- this.internalModel = internalModel;
421
+ if (options.internalModel !== undefined) {
422
+ this.internalModel = options.internalModel;
422
423
  }
423
424
  for (const actualSchema of exports.mockSchema) {
424
425
  this.schemaZone.define(`${actualSchema.connection}:${actualSchema.tablePath}`, actualSchema);
425
426
  }
426
427
  this.connectionDialectZone.define('_db_', exports.TEST_DIALECT);
427
428
  this.connectionDialectZone.define('_bq_', 'standardsql');
429
+ for (const flag of (_e = options.compilerFlags) !== null && _e !== void 0 ? _e : []) {
430
+ this.compilerFlagSrc.push(`##! ${flag}\n`);
431
+ }
428
432
  }
429
433
  translate() {
430
434
  return super.translate(this.internalModel);
@@ -532,8 +536,8 @@ class TestTranslator extends parse_malloy_1.MalloyTranslator {
532
536
  exports.TestTranslator = TestTranslator;
533
537
  TestTranslator.inspectCompile = false;
534
538
  class BetaExpression extends TestTranslator {
535
- constructor(src, model, sourceName = 'ab') {
536
- super(src, null, null, 'debugExpr', model);
539
+ constructor(src, model, sourceName = 'ab', options = {}) {
540
+ super(src, { ...options, rootRule: 'debugExpr', internalModel: model });
537
541
  this.sourceName = sourceName;
538
542
  }
539
543
  testFS() {
@@ -627,21 +631,20 @@ function model(unmarked, ...marked) {
627
631
  }
628
632
  function makeModelFunc(options) {
629
633
  return function model(unmarked, ...marked) {
630
- var _a;
631
634
  const ms = markSource(unmarked, ...marked);
635
+ const { prefix, wrap, ...ttOptions } = options;
632
636
  return {
633
637
  ...ms,
634
- translator: new TestTranslator(((_a = options.prefix) !== null && _a !== void 0 ? _a : '') +
635
- (options.wrap ? options.wrap(ms.code) : ms.code), null, null, undefined, options === null || options === void 0 ? void 0 : options.model),
638
+ translator: new TestTranslator((prefix !== null && prefix !== void 0 ? prefix : '') + (wrap ? wrap(ms.code) : ms.code), ttOptions),
636
639
  };
637
640
  };
638
641
  }
639
- function makeExprFunc(model, sourceName) {
642
+ function makeExprFunc(model, sourceName = 'ab', options = {}) {
640
643
  return function expr(unmarked, ...marked) {
641
644
  const ms = markSource(unmarked, ...marked);
642
645
  return {
643
646
  ...ms,
644
- translator: new BetaExpression(ms.code, model, sourceName),
647
+ translator: new BetaExpression(ms.code, model, sourceName, options),
645
648
  };
646
649
  };
647
650
  }
@@ -28,7 +28,9 @@ export type ZoneEntry<T> = EntryPresent<T> | ReferenceEntry | EntryErrored;
28
28
  export declare class Zone<TValue> {
29
29
  zone: Map<string, ZoneEntry<TValue>>;
30
30
  location: Record<string, DocumentLocation>;
31
+ private locked;
31
32
  constructor();
33
+ lock(): void;
32
34
  get(str: string): TValue | undefined;
33
35
  getEntry(str: string): ZoneEntry<TValue>;
34
36
  /**
package/dist/lang/zone.js CHANGED
@@ -33,8 +33,12 @@ exports.Zone = void 0;
33
33
  class Zone {
34
34
  constructor() {
35
35
  this.location = {};
36
+ this.locked = false;
36
37
  this.zone = new Map();
37
38
  }
39
+ lock() {
40
+ this.locked = true;
41
+ }
38
42
  get(str) {
39
43
  const zst = this.zone.get(str);
40
44
  if ((zst === null || zst === void 0 ? void 0 : zst.status) === 'present') {
@@ -57,6 +61,8 @@ class Zone {
57
61
  * @param val
58
62
  */
59
63
  define(str, val) {
64
+ if (this.locked)
65
+ return;
60
66
  this.zone.set(str, { status: 'present', value: val });
61
67
  }
62
68
  /**
@@ -65,6 +71,8 @@ class Zone {
65
71
  * @param loc The location of the reference
66
72
  */
67
73
  reference(str, loc) {
74
+ if (this.locked)
75
+ return;
68
76
  const zst = this.zone.get(str);
69
77
  if ((zst === null || zst === void 0 ? void 0 : zst.status) === undefined) {
70
78
  this.zone.set(str, { status: 'reference', firstReference: loc });
@@ -89,6 +97,8 @@ class Zone {
89
97
  * @param errorData Pass on errors encountered during fetch
90
98
  */
91
99
  updateFrom(updateData, errorData) {
100
+ if (this.locked)
101
+ return;
92
102
  if (updateData) {
93
103
  for (const [updateKey, updateVal] of Object.entries(updateData)) {
94
104
  if (updateVal !== undefined) {
@@ -14,11 +14,20 @@ const field_instance_1 = require("./field_instance");
14
14
  * Used to distinguish expected errors from unexpected ones.
15
15
  */
16
16
  class ConstantExpressionError extends Error {
17
- constructor(message) {
17
+ constructor(message, at) {
18
18
  super(message);
19
+ this.at = at;
19
20
  this.name = 'ConstantExpressionError';
20
21
  }
21
22
  }
23
+ function locSuffix(at) {
24
+ if (!at)
25
+ return '';
26
+ // Display 1-based line/column; range positions are 0-based.
27
+ const line = at.range.start.line + 1;
28
+ const col = at.range.start.character + 1;
29
+ return ` at ${at.url}:${line}:${col}`;
30
+ }
22
31
  /**
23
32
  * Minimal FieldInstanceResultRoot for constant expressions.
24
33
  * This serves as both the result set and its own root, providing
@@ -106,11 +115,11 @@ class ConstantQueryStruct extends query_node_1.QueryStruct {
106
115
  /**
107
116
  * These methods should not be called for constant expressions
108
117
  */
109
- getFieldByName(path) {
110
- throw new ConstantExpressionError(`Illegal reference to '${path.join('.')}' in constant expressions`);
118
+ getFieldByName(path, at) {
119
+ throw new ConstantExpressionError(`Illegal reference to '${path.join('.')}' in constant expressions${locSuffix(at)}`, at);
111
120
  }
112
- getStructByName(path) {
113
- throw new ConstantExpressionError(`Illegal reference to '${path.join('.')}' in constant expressions`);
121
+ getStructByName(path, at) {
122
+ throw new ConstantExpressionError(`Illegal reference to '${path.join('.')}' in constant expressions${locSuffix(at)}`, at);
114
123
  }
115
124
  getSQLIdentifier() {
116
125
  throw new ConstantExpressionError('Constant expressions do not need SQL identifiers');