@suss/contract-terraform 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,6 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ filterCalls: () => filterCalls,
34
+ filterTerms: () => filterTerms,
35
+ filterValuesFor: () => filterValuesFor,
36
+ parseFilterQuery: () => parseFilterQuery,
33
37
  terraformFileToSummaries: () => terraformFileToSummaries,
34
38
  terraformToSummaries: () => terraformToSummaries
35
39
  });
@@ -39,38 +43,393 @@ var import_node_path = __toESM(require("path"), 1);
39
43
  var import_hcl2_parser = __toESM(require("hcl2-parser"), 1);
40
44
  var import_semver = __toESM(require("semver"), 1);
41
45
  var import_behavioral_ir = require("@suss/behavioral-ir");
42
- function terraformToSummaries(source, sourceFile, options) {
43
- let parsed;
44
- try {
45
- const [read] = import_hcl2_parser.default.parseToObject(source);
46
- parsed = read;
47
- } catch {
46
+
47
+ // src/filterQuery.ts
48
+ var OPERATORS = ["!=", ">=", "<=", "=", ">", "<", ":"];
49
+ var KEYWORDS = /* @__PURE__ */ new Set(["AND", "OR", "NOT"]);
50
+ function filterValuesFor(query, key) {
51
+ return filterTerms(query).filter((term) => term.key === key && term.operator === "=").map((term) => term.value);
52
+ }
53
+ function filterTerms(query) {
54
+ if (query.type === "term") {
55
+ return [query];
56
+ }
57
+ if (query.type === "call") {
48
58
  return [];
49
59
  }
50
- const document = asRecord(parsed);
51
- if (document === null) {
60
+ if (query.type === "negation") {
61
+ return filterTerms(query.operand);
62
+ }
63
+ return query.operands.flatMap(filterTerms);
64
+ }
65
+ function filterCalls(query) {
66
+ if (query.type === "call") {
67
+ return [query];
68
+ }
69
+ if (query.type === "term") {
52
70
  return [];
53
71
  }
54
- const constraints = providerConstraints(document);
55
- const summaries = [];
56
- for (const [resourceType, label, body] of resourcesIn(document)) {
57
- for (const pack of options.packs) {
58
- const pattern = patternFor(pack, resourceType, constraints);
59
- if (pattern === void 0) {
60
- continue;
72
+ if (query.type === "negation") {
73
+ return filterCalls(query.operand);
74
+ }
75
+ return query.operands.flatMap(filterCalls);
76
+ }
77
+ function parseFilterQuery(source) {
78
+ const tokens = tokenize(source);
79
+ if (!tokens.ok) {
80
+ return { ok: false, reason: tokens.reason };
81
+ }
82
+ const reader = new TokenReader(tokens.tokens);
83
+ const query = reader.readExpression();
84
+ if (query === null) {
85
+ return { ok: false, reason: reader.reason };
86
+ }
87
+ if (!reader.atEnd()) {
88
+ return {
89
+ ok: false,
90
+ reason: `nothing joins the term at "${reader.rest()}"`
91
+ };
92
+ }
93
+ return { ok: true, query };
94
+ }
95
+ function tokenize(source) {
96
+ const tokens = [];
97
+ let at = 0;
98
+ while (at < source.length) {
99
+ const char = source[at];
100
+ if (/\s/.test(char) || char === ",") {
101
+ at += 1;
102
+ continue;
103
+ }
104
+ if (char === "(" || char === ")") {
105
+ tokens.push({ type: char === "(" ? "open" : "close", text: char });
106
+ at += 1;
107
+ continue;
108
+ }
109
+ if (char === '"' || char === "'") {
110
+ const quoted = readQuoted(source, at, char);
111
+ if (quoted === null) {
112
+ return { ok: false, reason: `a quote is never closed at ${at}` };
113
+ }
114
+ const previous = tokens[tokens.length - 1];
115
+ if (previous !== void 0 && isPartialKey(previous)) {
116
+ previous.text += quoted.value;
117
+ } else {
118
+ tokens.push({ type: "string", text: quoted.value });
61
119
  }
62
- summaries.push(
63
- ...summariesFor({ pattern, label, body, sourceFile, resourceType })
64
- );
120
+ at = quoted.next;
121
+ continue;
122
+ }
123
+ const operator = OPERATORS.find(
124
+ (candidate) => source.startsWith(candidate, at)
125
+ );
126
+ if (operator !== void 0) {
127
+ tokens.push({ type: "operator", text: operator });
128
+ at += operator.length;
129
+ continue;
130
+ }
131
+ const word = readWord(source, at);
132
+ if (word === null) {
133
+ return { ok: false, reason: `nothing readable at "${char}"` };
65
134
  }
135
+ tokens.push({ type: "word", text: word.value });
136
+ at = word.next;
66
137
  }
67
- return summaries;
138
+ return { ok: true, tokens };
139
+ }
140
+ function readQuoted(source, start, quote) {
141
+ let value = "";
142
+ let at = start + 1;
143
+ while (at < source.length) {
144
+ const char = source[at];
145
+ if (char === "\\" && at + 1 < source.length) {
146
+ value += source[at + 1];
147
+ at += 2;
148
+ continue;
149
+ }
150
+ if (char === quote) {
151
+ return { value, next: at + 1 };
152
+ }
153
+ value += char;
154
+ at += 1;
155
+ }
156
+ return null;
157
+ }
158
+ function isPartialKey(token) {
159
+ return token.type === "word" && token.text.endsWith(".");
160
+ }
161
+ function readWord(source, start) {
162
+ const match = /^[^\s(),="'!<>:]+/.exec(source.slice(start));
163
+ if (match === null) {
164
+ return null;
165
+ }
166
+ return { value: match[0], next: start + match[0].length };
167
+ }
168
+ var TokenReader = class {
169
+ constructor(tokens) {
170
+ this.tokens = tokens;
171
+ }
172
+ tokens;
173
+ at = 0;
174
+ /** What stopped the last read, for the caller to report. */
175
+ reason = "the filter states no comparison";
176
+ atEnd() {
177
+ return this.at >= this.tokens.length;
178
+ }
179
+ rest() {
180
+ return this.tokens.slice(this.at).map((token) => token.text).join(" ");
181
+ }
182
+ readExpression() {
183
+ return this.readJunction(
184
+ "or",
185
+ "OR",
186
+ () => this.readJunction("and", "AND", () => this.readUnary())
187
+ );
188
+ }
189
+ readJunction(operator, keyword, readOperand) {
190
+ const first = readOperand();
191
+ if (first === null) {
192
+ return null;
193
+ }
194
+ const operands = [first];
195
+ while (!this.atEnd()) {
196
+ const joined = this.keywordAhead(keyword);
197
+ if (!joined && !(operator === "and" && this.termAhead())) {
198
+ break;
199
+ }
200
+ if (joined) {
201
+ this.at += 1;
202
+ }
203
+ const next = readOperand();
204
+ if (next === null) {
205
+ return null;
206
+ }
207
+ operands.push(next);
208
+ }
209
+ return operands.length === 1 ? operands[0] : { type: "junction", operator, operands };
210
+ }
211
+ readUnary() {
212
+ if (this.keywordAhead("NOT")) {
213
+ this.at += 1;
214
+ const operand = this.readUnary();
215
+ return operand === null ? null : { type: "negation", operand };
216
+ }
217
+ return this.readPrimary();
218
+ }
219
+ readPrimary() {
220
+ const token = this.tokens[this.at];
221
+ if (token === void 0) {
222
+ this.reason = "the filter ends where a comparison should be";
223
+ return null;
224
+ }
225
+ if (token.type === "open") {
226
+ this.at += 1;
227
+ const inner = this.readExpression();
228
+ if (inner === null) {
229
+ return null;
230
+ }
231
+ if (this.tokens[this.at]?.type !== "close") {
232
+ this.reason = "a parenthesis is never closed";
233
+ return null;
234
+ }
235
+ this.at += 1;
236
+ return inner;
237
+ }
238
+ if (token.type === "word" && this.tokens[this.at + 1]?.type === "open") {
239
+ return this.readCall();
240
+ }
241
+ return this.readTerm();
242
+ }
243
+ /**
244
+ * A call where a comparison would be. Cloud Monitoring writes an SLO
245
+ * burn-rate condition that way, and the call says what the condition
246
+ * is about instead of any comparison.
247
+ */
248
+ readCall() {
249
+ const name = this.tokens[this.at];
250
+ this.at += 2;
251
+ const args = [];
252
+ while (!this.atEnd() && this.tokens[this.at]?.type !== "close") {
253
+ const argument = this.tokens[this.at];
254
+ if (argument.type === "word" || argument.type === "string") {
255
+ args.push(argument.text);
256
+ }
257
+ this.at += 1;
258
+ }
259
+ if (this.atEnd()) {
260
+ this.reason = `the call to "${name.text}" is never closed`;
261
+ return null;
262
+ }
263
+ this.at += 1;
264
+ return { type: "call", name: name.text, arguments: args };
265
+ }
266
+ readTerm() {
267
+ const key = this.tokens[this.at];
268
+ const operator = this.tokens[this.at + 1];
269
+ const value = this.tokens[this.at + 2];
270
+ if (key === void 0 || key.type !== "word") {
271
+ this.reason = `"${key?.text ?? ""}" is not a key`;
272
+ return null;
273
+ }
274
+ if (operator === void 0 || operator.type !== "operator") {
275
+ this.reason = `"${key.text}" is compared to nothing`;
276
+ return null;
277
+ }
278
+ if (value === void 0 || value.type !== "word" && value.type !== "string") {
279
+ this.reason = `"${key.text}" is compared to no value`;
280
+ return null;
281
+ }
282
+ this.at += 3;
283
+ return {
284
+ type: "term",
285
+ key: key.text,
286
+ operator: operator.text,
287
+ value: value.text
288
+ };
289
+ }
290
+ /** Whether the next token is this keyword rather than a key. */
291
+ keywordAhead(keyword) {
292
+ const token = this.tokens[this.at];
293
+ return token !== void 0 && token.type === "word" && token.text === keyword;
294
+ }
295
+ /** Whether a new comparison starts here, with no keyword joining it. */
296
+ termAhead() {
297
+ const token = this.tokens[this.at];
298
+ if (token === void 0) {
299
+ return false;
300
+ }
301
+ if (token.type === "open") {
302
+ return true;
303
+ }
304
+ return token.type === "word" && !KEYWORDS.has(token.text);
305
+ }
306
+ };
307
+
308
+ // src/references.ts
309
+ var SUB_TOKEN = /\$\{([^}]*)\}/g;
310
+ var RESOURCE_ATTRIBUTE = /^([A-Za-z][\w-]*)\.([A-Za-z_][\w-]*)\.([A-Za-z_][\w-]*)$/;
311
+ var LOCAL_VALUE = /^local\.([A-Za-z_][\w-]*)$/;
312
+ var LOCALS = "local";
313
+ var CHAIN_LIMIT = 4;
314
+ function referenceScope(resources, locals = []) {
315
+ const scope = /* @__PURE__ */ new Map();
316
+ for (const [resourceType, label, body] of resources) {
317
+ const address = `${resourceType}.${label}`;
318
+ if (!scope.has(address)) {
319
+ scope.set(address, body);
320
+ }
321
+ }
322
+ const stated = {};
323
+ for (const block of locals) {
324
+ for (const [name, value] of Object.entries(block)) {
325
+ if (!(name in stated)) {
326
+ stated[name] = value;
327
+ }
328
+ }
329
+ }
330
+ scope.set(LOCALS, stated);
331
+ return scope;
332
+ }
333
+ function resolveReferences(value, scope) {
334
+ return expand(value, scope, []) ?? value;
335
+ }
336
+ function expand(value, scope, resolving) {
337
+ let cycled = false;
338
+ const expanded = value.replace(SUB_TOKEN, (written, inner) => {
339
+ const reference = inner.trim();
340
+ if (resolving.includes(reference)) {
341
+ cycled = true;
342
+ return written;
343
+ }
344
+ const stated = statedValue(reference, scope);
345
+ if (stated === null || resolving.length >= CHAIN_LIMIT) {
346
+ return written;
347
+ }
348
+ const nested = expand(stated, scope, [...resolving, reference]);
349
+ if (nested === null) {
350
+ cycled = true;
351
+ return written;
352
+ }
353
+ return nested;
354
+ });
355
+ return cycled ? null : expanded;
356
+ }
357
+ function statedValue(reference, scope) {
358
+ const local = LOCAL_VALUE.exec(reference);
359
+ if (local !== null) {
360
+ const stated2 = scope.get(LOCALS)?.[local[1]];
361
+ return typeof stated2 === "string" ? stated2 : null;
362
+ }
363
+ const parsed = RESOURCE_ATTRIBUTE.exec(reference);
364
+ if (parsed === null) {
365
+ return null;
366
+ }
367
+ const [, resourceType, label, attribute] = parsed;
368
+ const stated = scope.get(`${resourceType}.${label}`)?.[attribute];
369
+ return typeof stated === "string" ? stated : null;
370
+ }
371
+
372
+ // src/index.ts
373
+ function terraformToSummaries(source, sourceFile, options) {
374
+ return summariesForFiles([{ source, sourceFile }], options);
68
375
  }
69
376
  function terraformFileToSummaries(target, options) {
70
377
  const files = import_node_fs.default.statSync(target).isDirectory() ? import_node_fs.default.readdirSync(target).filter((name) => name.endsWith(".tf")).map((name) => import_node_path.default.join(target, name)) : [target];
71
- return files.flatMap(
72
- (file) => terraformToSummaries(import_node_fs.default.readFileSync(file, "utf8"), file, options)
378
+ return summariesForFiles(
379
+ files.map((file) => ({
380
+ source: import_node_fs.default.readFileSync(file, "utf8"),
381
+ sourceFile: file
382
+ })),
383
+ options
384
+ );
385
+ }
386
+ function summariesForFiles(files, options) {
387
+ const parsed = files.map((file) => parseSource(file)).filter((file) => file !== null);
388
+ const scope = referenceScope(
389
+ parsed.flatMap((file) => file.resources),
390
+ parsed.flatMap((file) => file.locals)
73
391
  );
392
+ const summaries = [];
393
+ for (const file of parsed) {
394
+ for (const [resourceType, label, body] of file.resources) {
395
+ for (const pack of options.packs) {
396
+ const pattern = patternFor(pack, resourceType, file.constraints);
397
+ if (pattern === void 0 || !entryApplies(pattern, body)) {
398
+ continue;
399
+ }
400
+ summaries.push(
401
+ ...summariesFor({
402
+ pattern,
403
+ label,
404
+ body,
405
+ sourceFile: file.sourceFile,
406
+ resourceType,
407
+ scope
408
+ })
409
+ );
410
+ }
411
+ }
412
+ }
413
+ return summaries;
414
+ }
415
+ function parseSource(file) {
416
+ let read;
417
+ try {
418
+ const [parsed] = import_hcl2_parser.default.parseToObject(file.source);
419
+ read = parsed;
420
+ } catch {
421
+ return null;
422
+ }
423
+ const document = asRecord(read);
424
+ if (document === null) {
425
+ return null;
426
+ }
427
+ return {
428
+ sourceFile: file.sourceFile,
429
+ constraints: providerConstraints(document),
430
+ resources: resourcesIn(document),
431
+ locals: localsIn(document)
432
+ };
74
433
  }
75
434
  function providerConstraints(document) {
76
435
  const constraints = /* @__PURE__ */ new Map();
@@ -110,22 +469,39 @@ function patternFor(pack, resourceType, constraints) {
110
469
  }
111
470
  });
112
471
  }
472
+ function entryApplies(pattern, body) {
473
+ const gate = pattern.appliesWhen;
474
+ if (gate === void 0) {
475
+ return true;
476
+ }
477
+ const value = body[gate.attribute];
478
+ if (value === void 0) {
479
+ return gate.whenUnset === "read";
480
+ }
481
+ const text = stringOf(value);
482
+ return text !== null && gate.equals.includes(text);
483
+ }
484
+ var READERS = {
485
+ storage: (site, boundary) => {
486
+ const types = fieldTypes(site.body, boundary);
487
+ return keyedShapes(site.body, boundary).map(
488
+ (shape) => storageSummary({ ...site, boundary, shape, types })
489
+ );
490
+ },
491
+ "message-bus": (site, boundary) => [busSummary({ ...site, boundary })],
492
+ metric: (site, boundary) => [metricSummary({ ...site, boundary })],
493
+ "metric-reading": (site, boundary) => readingSummaries({ ...site, boundary })
494
+ };
113
495
  function summariesFor(opts) {
114
- const { pattern } = opts;
115
- if (pattern.boundary.kind === "message-bus") {
116
- return [busSummary({ ...opts, boundary: pattern.boundary })];
117
- }
118
- const boundary = pattern.boundary;
119
- const types = fieldTypes(opts.body, boundary);
120
- return keyedShapes(opts.body, boundary).map(
121
- (shape) => storageSummary({ ...opts, boundary, shape, types })
122
- );
496
+ const { pattern, ...site } = opts;
497
+ const read = READERS[pattern.boundary.kind];
498
+ return read(site, pattern.boundary);
123
499
  }
124
500
  function storageSummary(opts) {
125
501
  const { boundary, label, shape, types } = opts;
126
502
  const address = `${opts.resourceType}.${label}`;
127
- const declaredName = boundary.nameAttribute === void 0 ? null : stringOf(opts.body[boundary.nameAttribute]);
128
- const physicalTable = declaredName === null ? null : (0, import_behavioral_ir.namePatternFromSub)(declaredName);
503
+ const declaresContainer = boundary.declares !== "store";
504
+ const physicalTable = !declaresContainer || boundary.nameAttribute === void 0 ? null : namePattern(opts.body[boundary.nameAttribute], opts.scope);
129
505
  return {
130
506
  kind: "library",
131
507
  location: {
@@ -141,7 +517,7 @@ function storageSummary(opts) {
141
517
  storageSystem: boundary.storageSystem,
142
518
  ...boundary.transport !== void 0 ? { transport: boundary.transport } : {},
143
519
  scope: "default",
144
- container: label,
520
+ container: declaresContainer ? label : null,
145
521
  accessPath: shape.accessPath
146
522
  })
147
523
  },
@@ -169,8 +545,7 @@ function storageSummary(opts) {
169
545
  }
170
546
  function busSummary(opts) {
171
547
  const { boundary, label } = opts;
172
- const declaredName = boundary.nameAttribute === void 0 ? null : stringOf(opts.body[boundary.nameAttribute]);
173
- const physicalName = declaredName === null ? null : (0, import_behavioral_ir.namePatternFromSub)(declaredName);
548
+ const physicalName = boundary.nameAttribute === void 0 ? null : namePattern(opts.body[boundary.nameAttribute], opts.scope);
174
549
  return {
175
550
  kind: "library",
176
551
  location: {
@@ -200,6 +575,132 @@ function busSummary(opts) {
200
575
  }
201
576
  };
202
577
  }
578
+ var NAME_HOLE = "{name}";
579
+ function metricSummary(opts) {
580
+ const { boundary } = opts;
581
+ const declaredName = namePattern(
582
+ opts.body[boundary.nameAttribute],
583
+ opts.scope
584
+ );
585
+ const metricType = declaredName === null ? null : boundary.metricTypeTemplate.replace(NAME_HOLE, declaredName);
586
+ return {
587
+ kind: "library",
588
+ location: {
589
+ file: opts.sourceFile,
590
+ range: { start: 1, end: 1 },
591
+ exportName: null
592
+ },
593
+ identity: {
594
+ name: `${opts.resourceType}.${opts.label}`,
595
+ exportPath: null,
596
+ boundaryBinding: (0, import_behavioral_ir.metricBinding)({
597
+ recognition: "terraform",
598
+ metricSystem: boundary.metricSystem,
599
+ metricType
600
+ })
601
+ },
602
+ inputs: [],
603
+ transitions: [],
604
+ gaps: [],
605
+ confidence: { source: "declared", level: "high" },
606
+ metadata: { metricContract: metricContract(opts.body, boundary) }
607
+ };
608
+ }
609
+ function metricContract(body, boundary) {
610
+ const values = meaningOf(body, boundary.values);
611
+ const accumulates = meaningOf(body, boundary.accumulates);
612
+ return {
613
+ ...values !== void 0 ? { values } : {},
614
+ ...accumulates !== void 0 ? { accumulates } : {}
615
+ };
616
+ }
617
+ function readingSummaries(opts) {
618
+ const { boundary } = opts;
619
+ const summaries = [];
620
+ for (const reading of blocksAt(opts.body, boundary.readingBlocks)) {
621
+ const query = stringOf(valueAt(reading, boundary.queryAttribute));
622
+ for (const metricType of metricTypesIn(
623
+ query,
624
+ boundary.queryIdentityKey,
625
+ opts.scope
626
+ )) {
627
+ summaries.push({
628
+ kind: "consumer",
629
+ location: {
630
+ file: opts.sourceFile,
631
+ range: { start: 1, end: 1 },
632
+ exportName: null
633
+ },
634
+ identity: {
635
+ name: `${opts.resourceType}.${opts.label}#${summaries.length}`,
636
+ exportPath: null,
637
+ boundaryBinding: (0, import_behavioral_ir.metricBinding)({
638
+ recognition: "terraform",
639
+ metricSystem: boundary.metricSystem,
640
+ metricType
641
+ })
642
+ },
643
+ inputs: [],
644
+ transitions: [],
645
+ gaps: [],
646
+ confidence: { source: "declared", level: "high" },
647
+ metadata: { metricReading: metricReading(reading, boundary) }
648
+ });
649
+ }
650
+ }
651
+ return summaries;
652
+ }
653
+ function metricTypesIn(query, identityKey, scope) {
654
+ if (query === null) {
655
+ return [null];
656
+ }
657
+ const parsed = parseFilterQuery(query);
658
+ if (!parsed.ok) {
659
+ return [null];
660
+ }
661
+ const found = filterValuesFor(parsed.query, identityKey).map((value) => namePattern(value, scope)).filter((value) => value !== null);
662
+ return found.length === 0 ? [null] : found;
663
+ }
664
+ function metricReading(reading, boundary) {
665
+ const compares = boundary.comparesTo;
666
+ const comparesTo = compares !== void 0 && valueAt(reading, compares.attribute) !== void 0 ? compares.whenSet : void 0;
667
+ const reduces = boundary.reducesTo;
668
+ const reducesTo = meaningOf(reading, reduces);
669
+ return {
670
+ ...comparesTo !== void 0 ? { comparesTo } : {},
671
+ ...reducesTo !== void 0 ? { reducesTo } : {},
672
+ ...reduces === void 0 ? {} : { reduction: { setting: reduces.attribute, leaves: reduces.means } }
673
+ };
674
+ }
675
+ function meaningOf(body, spec) {
676
+ if (spec === void 0) {
677
+ return void 0;
678
+ }
679
+ const stated = stringOf(valueAt(body, spec.attribute));
680
+ return stated === null ? void 0 : spec.means[stated];
681
+ }
682
+ function valueAt(body, path2) {
683
+ const steps = path2.split(".");
684
+ const last = steps.pop();
685
+ let current = body;
686
+ for (const step of steps) {
687
+ const nested = arrayOf(current[step])[0];
688
+ current = asRecord(nested);
689
+ if (current === null) {
690
+ return void 0;
691
+ }
692
+ }
693
+ return current[last];
694
+ }
695
+ function blocksAt(body, blocks) {
696
+ let found = [body];
697
+ for (const block of blocks) {
698
+ found = found.flatMap(
699
+ (record) => arrayOf(record[block]).map(asRecord).filter((nested) => nested !== null)
700
+ );
701
+ }
702
+ return found;
703
+ }
203
704
  function keyedShapes(body, boundary) {
204
705
  if (boundary.identifies === void 0) {
205
706
  return [{ accessPath: null, keyFields: [], serves: null }];
@@ -264,6 +765,16 @@ function fieldTypes(body, boundary) {
264
765
  }
265
766
  return types;
266
767
  }
768
+ function localsIn(document) {
769
+ const found = [];
770
+ for (const block of arrayOf(document.locals)) {
771
+ const stated = asRecord(block);
772
+ if (stated !== null) {
773
+ found.push(stated);
774
+ }
775
+ }
776
+ return found;
777
+ }
267
778
  function resourcesIn(document) {
268
779
  const found = [];
269
780
  for (const resource of arrayOf(document.resource)) {
@@ -290,6 +801,11 @@ function resourcesIn(document) {
290
801
  }
291
802
  return found;
292
803
  }
804
+ function namePattern(value, scope) {
805
+ return (0, import_behavioral_ir.namePatternFromSub)(
806
+ typeof value === "string" ? resolveReferences(value, scope) : value
807
+ );
808
+ }
293
809
  function arrayOf(value) {
294
810
  if (value === void 0 || value === null) {
295
811
  return [];
@@ -304,6 +820,10 @@ function stringOf(value) {
304
820
  }
305
821
  // Annotate the CommonJS export names for ESM import in node:
306
822
  0 && (module.exports = {
823
+ filterCalls,
824
+ filterTerms,
825
+ filterValuesFor,
826
+ parseFilterQuery,
307
827
  terraformFileToSummaries,
308
828
  terraformToSummaries
309
829
  });