@clear-capabilities/agentic-security-scanner 0.195.13 → 0.195.15

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.
@@ -18,6 +18,12 @@ const KW = new Set(['case', 'class', 'data', 'default', 'deriving', 'do', 'else'
18
18
  const NUMERIC_TYPE = /^\(?\s*(?:(?:Maybe|IO|ActionM|Handler|Either\s+String)\s+)?\(?\s*(?:Int|Integer|Int(?:8|16|32|64)|Word|Word(?:8|16|32|64)|Double|Float|Natural|Bool|Scientific)\s*\)?\s*\)?$/;
19
19
  // The module enables Template Haskell (a LANGUAGE pragma; a cabal `default-extensions` entry is not seen here, a stated limit).
20
20
  const TH_PRAGMA = /\{-#\s*LANGUAGE\b[^#]*\bTemplateHaskell\b/;
21
+ // OverloadedRecordDot: `r.field` (no spaces, lowercase field) is a field selection, `(.field)` a selector section. OverloadedLabels: `#field`
22
+ // in prefix position is a label value (IHP's `get #name r`, `set #name v r`). Both read from the LANGUAGE pragma only, as above.
23
+ const RECORD_DOT_PRAGMA = /\{-#\s*LANGUAGE\b[^#]*\bOverloadedRecordDot\b/;
24
+ const LABELS_PRAGMA = /\{-#\s*LANGUAGE\b[^#]*\bOverloadedLabels\b/;
25
+ const RECDO_PRAGMA = /\{-#\s*LANGUAGE\b[^#]*\bRecursiveDo\b/;
26
+ const DIGEST_TYPE = /^\(?\s*Digest\s+([A-Za-z][A-Za-z0-9_]*)\s*\)?$/;
21
27
  const RESERVED_OPS = new Set(['=', '|', '->', '<-', '::', '=>', '\\', '@', '~', '..']);
22
28
  const SYM = new Set('!#$%&*+./<=>?@\\^|-~:'.split(''));
23
29
  const OPEN = { '(': ')', '[': ']', '{': '}' };
@@ -36,6 +42,9 @@ export function lexHs(src, opts = {}) {
36
42
  const T = [];
37
43
  const n = src.length;
38
44
  const thSplices = !!opts.th;
45
+ const recordDot = !!opts.recordDot;
46
+ const labels = !!opts.labels;
47
+ const recursiveDo = !!opts.recursiveDo;
39
48
  let i = 0;
40
49
  let line = 1;
41
50
  let col = 0;
@@ -138,7 +147,9 @@ export function lexHs(src, opts = {}) {
138
147
  let k = i;
139
148
  while (isIdChar(src[k])) k++;
140
149
  const v = src.slice(i, k);
141
- push(KW.has(v) ? 'kw' : 'var', v, {}, k - i);
150
+ // `mdo` (RecursiveDo) is a `do` whose statements may refer to later ones; for the lowering that is a `do`. (`rec` blocks are not read.)
151
+ if (v === 'mdo' && recursiveDo) push('kw', 'do', {}, k - i);
152
+ else push(KW.has(v) ? 'kw' : 'var', v, {}, k - i);
142
153
  continue;
143
154
  }
144
155
  if (c === '(' || c === ')' || c === '[' || c === ']' || c === ',' || c === ';' || c === '`' || c === '{' || c === '}') {
@@ -153,6 +164,22 @@ export function lexHs(src, opts = {}) {
153
164
  const prefix = !(isIdChar(prev) || isSym(prev) || prev === ')' || prev === ']' || prev === '}' || prev === '"' || prev === "'");
154
165
  if (prefix && (src[k] === '(' || (isIdStart(src[k]) && !isUpper(src[k])))) { push('splice', src.slice(i, k), {}, k - i); continue; }
155
166
  }
167
+ // `r.field` / `(e).field` / `r.a.b` with OverloadedRecordDot: a `.` touching an identifier, `)` or `}` on its left and a lowercase name on
168
+ // its right is a field selection (`sel` token), never composition (GHC reads it the same way under the extension). `(.field)` is the
169
+ // selector section: a `.` touching `(` on its left.
170
+ if (c === '.' && recordDot && isIdStart(src[i + 1]) && !isUpper(src[i + 1]) && i > 0 && (isIdChar(src[i - 1]) || src[i - 1] === ')' || src[i - 1] === '}' || src[i - 1] === '(')) {
171
+ let m = i + 1;
172
+ while (isIdChar(src[m])) m++;
173
+ push('sel', src.slice(i + 1, m), { section: src[i - 1] === '(' }, m - i);
174
+ continue;
175
+ }
176
+ // `#field` with OverloadedLabels, in prefix position: a label value.
177
+ if (c === '#' && labels && isIdStart(src[i + 1]) && !isUpper(src[i + 1]) && (i === 0 || !(isIdChar(src[i - 1]) || isSym(src[i - 1]) || src[i - 1] === ')' || src[i - 1] === ']'))) {
178
+ let m = i + 1;
179
+ while (isIdChar(src[m])) m++;
180
+ push('label', src.slice(i + 1, m), {}, m - i);
181
+ continue;
182
+ }
156
183
  if (isSym(c)) {
157
184
  let j = i;
158
185
  while (isSym(src[j])) j++;
@@ -196,7 +223,7 @@ const BT_FIX = { elem: [4, 'l'], notElem: [4, 'l'], div: [7, 'l'], mod: [7, 'l']
196
223
  * @returns {{tokens:object[], module:object|null, imports:object[], decls:object[], sigs:object[], data:object[], classes:object[], instances:object[], errors:object[], boundaries:object[]}}
197
224
  */
198
225
  export function parseSyntax(src, opts = {}) {
199
- const T = lexHs(src, { th: TH_PRAGMA.test(src) });
226
+ const T = lexHs(src, { th: TH_PRAGMA.test(src), recordDot: RECORD_DOT_PRAGMA.test(src), labels: LABELS_PRAGMA.test(src), recursiveDo: RECDO_PRAGMA.test(src) });
200
227
  const mate = matchBrackets(T);
201
228
  const N = T.length;
202
229
  const errors = [];
@@ -433,6 +460,8 @@ export function parseSyntax(src, opts = {}) {
433
460
  // lowers to a constant (no caller text survives such a type). Every other annotation is dropped as before.
434
461
  const typeText = T.slice(from, pos).map((x) => x.v).join(' ');
435
462
  if (NUMERIC_TYPE.test(typeText)) e = { t: 'typed', e, type: typeText, line: e && e.line };
463
+ // `hash x :: Digest MD5`: the annotation chooses the digest algorithm (type-directed selection); kept for the weak-hash rule.
464
+ else { const m = DIGEST_TYPE.exec(typeText); if (m) e = { t: 'typed', e, type: typeText, digest: m[1], line: e && e.line }; }
436
465
  }
437
466
  return e;
438
467
  } finally { depth--; }
@@ -469,6 +498,19 @@ export function parseSyntax(src, opts = {}) {
469
498
  pos = blk.b;
470
499
  return { t: 'lamcase', alts: withIndent(blk.indent, () => blk.items.map(([a, b]) => parseAlt(a, b))), line: t.line };
471
500
  }
501
+ // `\cases` (GHC 9.4): alternatives with SEVERAL patterns each, `0 y -> ..; x y -> ..`.
502
+ if (peek() && peek().t === 'var' && peek().v === 'cases') {
503
+ const blk = blockAfter(pos, lim, layoutIndent);
504
+ pos = blk.b;
505
+ const parseAltMulti = (a, b) => {
506
+ const k = findTop(a, b, (x) => isRop(x, '->') || isRop(x, '|'));
507
+ if (k < 0) { err('expected-arrow', T[a], 'case alternative has no `->`'); return { pats: [{ t: 'pwild' }], rhs: { guards: null, body: { t: 'unknown' }, where: [] }, line: T[a].line, bad: true }; }
508
+ const pats = [];
509
+ for (let q = a; q < k;) { const r = apatAt(q, k); if (!r) break; pats.push(r[0]); q = r[1]; }
510
+ return { pats, rhs: parseRhs(k, b, '->'), line: T[a].line };
511
+ };
512
+ return { t: 'lamcases', alts: withIndent(blk.indent, () => blk.items.map(([a, b]) => parseAltMulti(a, b))), line: t.line };
513
+ }
472
514
  const params = [];
473
515
  while (pos < lim && !isRop(T[pos], '->')) {
474
516
  const r = apatAt(pos, lim);
@@ -532,7 +574,7 @@ export function parseSyntax(src, opts = {}) {
532
574
  function startsAexp() {
533
575
  const t = peek();
534
576
  if (!t) return false;
535
- if (t.t === 'var' || t.t === 'con' || t.t === 'int' || t.t === 'float' || t.t === 'str' || t.t === 'chr' || t.t === 'splice') return true;
577
+ if (t.t === 'var' || t.t === 'con' || t.t === 'int' || t.t === 'float' || t.t === 'str' || t.t === 'chr' || t.t === 'splice' || t.t === 'label') return true;
536
578
  return isSp(t, '(') || isSp(t, '[');
537
579
  }
538
580
  function fexp() {
@@ -565,6 +607,7 @@ export function parseSyntax(src, opts = {}) {
565
607
  else if (peek() && peek().t === 'var') pos++;
566
608
  return { t: 'splice', line: t.line };
567
609
  }
610
+ else if (t.t === 'label') { pos++; e = { t: 'label', name: t.v, line: t.line }; }
568
611
  else { err('unexpected-token', t, `unexpected token ${t.v}`); pos++; return { t: 'unknown', line: t.line }; }
569
612
  while (isSp(peek(), '{') && mate[pos] > pos && mate[pos] < lim && (e.t === 'con' || e.t === 'var' || e.t === 'rec' || e.t === 'paren')) {
570
613
  const close = mate[pos];
@@ -580,6 +623,8 @@ export function parseSyntax(src, opts = {}) {
580
623
  e = e.t === 'con' ? { t: 'rec', con: e, fields, line: t.line } : { t: 'recupd', base: e, fields, line: t.line };
581
624
  pos = close + 1;
582
625
  }
626
+ // `e.field` (OverloadedRecordDot): the field selector applied to `e`, which the IR lowers to a member read of a known record field.
627
+ while (peek() && peek().t === 'sel' && !peek().section) { const s = peek(); pos++; e = { t: 'app', f: { t: 'var', name: s.v, qual: null, line: s.line }, args: [e], line: s.line, recordDot: true }; }
583
628
  return e;
584
629
  }
585
630
  const isOpTok = (x) => x && x.t === 'op';
@@ -594,6 +639,8 @@ export function parseSyntax(src, opts = {}) {
594
639
  }
595
640
  const [s, e] = parts[0];
596
641
  const first = T[s];
642
+ // `(.field)` (OverloadedRecordDot): the selector function itself.
643
+ if (first && first.t === 'sel' && first.section && e - s === 1) return { t: 'var', name: first.v, qual: null, line };
597
644
  if (isOpTok(first) && e - s === 1) return { t: 'var', name: first.v, qual: first.q || null, op: true, line };
598
645
  if (isOpTok(first) && first.v !== '-') return { t: 'rsec', op: first.v, qual: first.q || null, e: sub(s + 1, e, expr0), line };
599
646
  if (isSp(first, '`') && T[s + 2] && isSp(T[s + 2], '`') && e - s > 3) return { t: 'rsec', op: T[s + 1].v, qual: T[s + 1].q || null, e: sub(s + 3, e, expr0), line, backtick: true };
@@ -700,6 +747,9 @@ export function parseSyntax(src, opts = {}) {
700
747
  return out;
701
748
  }
702
749
  function parseDecl(a, b) {
750
+ // `pattern P :: T` / `pattern P x = ..` (PatternSynonyms): a synonym, recorded by name, not a function called `pattern`. Its right-hand
751
+ // side is not modelled (a match on `P` is a constructor pattern to the IR, which is sound for taint: it binds what it binds).
752
+ if (T[a].t === 'var' && T[a].v === 'pattern' && T[a + 1] && T[a + 1].t === 'con' && a + 1 < b) return { t: 'patsyn', name: T[a + 1].v, line: T[a].line };
703
753
  const mark = findTop(a, b, (x) => x.t === 'rop' && (x.v === '=' || x.v === '|' || x.v === '::'));
704
754
  if (mark < 0) { err('expected-declaration', T[a], 'item is neither a binding nor a signature'); return null; }
705
755
  if (T[mark].v === '::') {
@@ -754,7 +804,39 @@ export function parseSyntax(src, opts = {}) {
754
804
  let nameTok = null;
755
805
  for (let q = a + 1; q < (eq < 0 ? b : eq); q++) if (T[q].t === 'con') { nameTok = T[q]; break; }
756
806
  const decl = { t: 'data', newtype: isNewtype, name: nameTok ? nameTok.v : null, constructors: [], line: T[a].line };
757
- if (eq < 0) return decl;
807
+ if (eq < 0) {
808
+ // GADT syntax: `data T where { A :: Int -> T; B :: { fld :: String } -> T }`. Each item names one or more constructors, their record
809
+ // fields when written with braces, else an arity of one per top-level arrow; the result type is not read (no type checking).
810
+ const w = findTop(a, b, (x) => isKw(x, 'where'));
811
+ if (w < 0) return decl;
812
+ const blk = blockAfter(w, b, layoutIndent);
813
+ for (const [s, e] of blk.items) {
814
+ const names = [];
815
+ let i = s;
816
+ while (i < e && T[i].t === 'con') { names.push(T[i].v); i++; if (isSp(T[i], ',')) i++; else break; }
817
+ if (!names.length || !isRop(T[i], '::')) continue;
818
+ i++;
819
+ while (i < e && ((T[i].t === 'var' && T[i].v === 'forall') || T[i].t === 'op' || isRop(T[i], '=>'))) i = findTop(i, e, (x) => isRop(x, '=>')) >= 0 ? findTop(i, e, (x) => isRop(x, '=>')) + 1 : e;
820
+ let fields = null;
821
+ let arity = 0;
822
+ if (isSp(T[i], '{') && mate[i] > i) {
823
+ fields = [];
824
+ const close = mate[i];
825
+ let j = i + 1;
826
+ while (j < close) {
827
+ const ns = [];
828
+ while (j < close && T[j].t === 'var') { ns.push(T[j].v); j++; if (isSp(T[j], ',')) j++; else break; }
829
+ if (isRop(T[j], '::')) { j++; while (j < close && !isSp(T[j], ',')) j = Math.min(close, groupEnd(j)); j++; } else break;
830
+ fields.push(...ns);
831
+ }
832
+ arity = fields.length;
833
+ } else {
834
+ for (let j = i; j < e;) { if (isRop(T[j], '->')) arity++; j = Math.min(e, groupEnd(j)); }
835
+ }
836
+ for (const n of names) decl.constructors.push({ name: n, fields, arity });
837
+ }
838
+ return decl;
839
+ }
758
840
  let end = findTop(eq + 1, b, (x) => isKw(x, 'deriving'));
759
841
  if (end < 0) end = b;
760
842
  for (const [s0, e] of splitTop(eq + 1, end, (x) => isRop(x, '|'))) {