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

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,11 @@ 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/;
21
26
  const RESERVED_OPS = new Set(['=', '|', '->', '<-', '::', '=>', '\\', '@', '~', '..']);
22
27
  const SYM = new Set('!#$%&*+./<=>?@\\^|-~:'.split(''));
23
28
  const OPEN = { '(': ')', '[': ']', '{': '}' };
@@ -36,6 +41,9 @@ export function lexHs(src, opts = {}) {
36
41
  const T = [];
37
42
  const n = src.length;
38
43
  const thSplices = !!opts.th;
44
+ const recordDot = !!opts.recordDot;
45
+ const labels = !!opts.labels;
46
+ const recursiveDo = !!opts.recursiveDo;
39
47
  let i = 0;
40
48
  let line = 1;
41
49
  let col = 0;
@@ -138,7 +146,9 @@ export function lexHs(src, opts = {}) {
138
146
  let k = i;
139
147
  while (isIdChar(src[k])) k++;
140
148
  const v = src.slice(i, k);
141
- push(KW.has(v) ? 'kw' : 'var', v, {}, k - i);
149
+ // `mdo` (RecursiveDo) is a `do` whose statements may refer to later ones; for the lowering that is a `do`. (`rec` blocks are not read.)
150
+ if (v === 'mdo' && recursiveDo) push('kw', 'do', {}, k - i);
151
+ else push(KW.has(v) ? 'kw' : 'var', v, {}, k - i);
142
152
  continue;
143
153
  }
144
154
  if (c === '(' || c === ')' || c === '[' || c === ']' || c === ',' || c === ';' || c === '`' || c === '{' || c === '}') {
@@ -153,6 +163,22 @@ export function lexHs(src, opts = {}) {
153
163
  const prefix = !(isIdChar(prev) || isSym(prev) || prev === ')' || prev === ']' || prev === '}' || prev === '"' || prev === "'");
154
164
  if (prefix && (src[k] === '(' || (isIdStart(src[k]) && !isUpper(src[k])))) { push('splice', src.slice(i, k), {}, k - i); continue; }
155
165
  }
166
+ // `r.field` / `(e).field` / `r.a.b` with OverloadedRecordDot: a `.` touching an identifier, `)` or `}` on its left and a lowercase name on
167
+ // its right is a field selection (`sel` token), never composition (GHC reads it the same way under the extension). `(.field)` is the
168
+ // selector section: a `.` touching `(` on its left.
169
+ 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] === '(')) {
170
+ let m = i + 1;
171
+ while (isIdChar(src[m])) m++;
172
+ push('sel', src.slice(i + 1, m), { section: src[i - 1] === '(' }, m - i);
173
+ continue;
174
+ }
175
+ // `#field` with OverloadedLabels, in prefix position: a label value.
176
+ 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] === ']'))) {
177
+ let m = i + 1;
178
+ while (isIdChar(src[m])) m++;
179
+ push('label', src.slice(i + 1, m), {}, m - i);
180
+ continue;
181
+ }
156
182
  if (isSym(c)) {
157
183
  let j = i;
158
184
  while (isSym(src[j])) j++;
@@ -196,7 +222,7 @@ const BT_FIX = { elem: [4, 'l'], notElem: [4, 'l'], div: [7, 'l'], mod: [7, 'l']
196
222
  * @returns {{tokens:object[], module:object|null, imports:object[], decls:object[], sigs:object[], data:object[], classes:object[], instances:object[], errors:object[], boundaries:object[]}}
197
223
  */
198
224
  export function parseSyntax(src, opts = {}) {
199
- const T = lexHs(src, { th: TH_PRAGMA.test(src) });
225
+ 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
226
  const mate = matchBrackets(T);
201
227
  const N = T.length;
202
228
  const errors = [];
@@ -469,6 +495,19 @@ export function parseSyntax(src, opts = {}) {
469
495
  pos = blk.b;
470
496
  return { t: 'lamcase', alts: withIndent(blk.indent, () => blk.items.map(([a, b]) => parseAlt(a, b))), line: t.line };
471
497
  }
498
+ // `\cases` (GHC 9.4): alternatives with SEVERAL patterns each, `0 y -> ..; x y -> ..`.
499
+ if (peek() && peek().t === 'var' && peek().v === 'cases') {
500
+ const blk = blockAfter(pos, lim, layoutIndent);
501
+ pos = blk.b;
502
+ const parseAltMulti = (a, b) => {
503
+ const k = findTop(a, b, (x) => isRop(x, '->') || isRop(x, '|'));
504
+ 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 }; }
505
+ const pats = [];
506
+ for (let q = a; q < k;) { const r = apatAt(q, k); if (!r) break; pats.push(r[0]); q = r[1]; }
507
+ return { pats, rhs: parseRhs(k, b, '->'), line: T[a].line };
508
+ };
509
+ return { t: 'lamcases', alts: withIndent(blk.indent, () => blk.items.map(([a, b]) => parseAltMulti(a, b))), line: t.line };
510
+ }
472
511
  const params = [];
473
512
  while (pos < lim && !isRop(T[pos], '->')) {
474
513
  const r = apatAt(pos, lim);
@@ -532,7 +571,7 @@ export function parseSyntax(src, opts = {}) {
532
571
  function startsAexp() {
533
572
  const t = peek();
534
573
  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;
574
+ 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
575
  return isSp(t, '(') || isSp(t, '[');
537
576
  }
538
577
  function fexp() {
@@ -565,6 +604,7 @@ export function parseSyntax(src, opts = {}) {
565
604
  else if (peek() && peek().t === 'var') pos++;
566
605
  return { t: 'splice', line: t.line };
567
606
  }
607
+ else if (t.t === 'label') { pos++; e = { t: 'label', name: t.v, line: t.line }; }
568
608
  else { err('unexpected-token', t, `unexpected token ${t.v}`); pos++; return { t: 'unknown', line: t.line }; }
569
609
  while (isSp(peek(), '{') && mate[pos] > pos && mate[pos] < lim && (e.t === 'con' || e.t === 'var' || e.t === 'rec' || e.t === 'paren')) {
570
610
  const close = mate[pos];
@@ -580,6 +620,8 @@ export function parseSyntax(src, opts = {}) {
580
620
  e = e.t === 'con' ? { t: 'rec', con: e, fields, line: t.line } : { t: 'recupd', base: e, fields, line: t.line };
581
621
  pos = close + 1;
582
622
  }
623
+ // `e.field` (OverloadedRecordDot): the field selector applied to `e`, which the IR lowers to a member read of a known record field.
624
+ 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
625
  return e;
584
626
  }
585
627
  const isOpTok = (x) => x && x.t === 'op';
@@ -594,6 +636,8 @@ export function parseSyntax(src, opts = {}) {
594
636
  }
595
637
  const [s, e] = parts[0];
596
638
  const first = T[s];
639
+ // `(.field)` (OverloadedRecordDot): the selector function itself.
640
+ if (first && first.t === 'sel' && first.section && e - s === 1) return { t: 'var', name: first.v, qual: null, line };
597
641
  if (isOpTok(first) && e - s === 1) return { t: 'var', name: first.v, qual: first.q || null, op: true, line };
598
642
  if (isOpTok(first) && first.v !== '-') return { t: 'rsec', op: first.v, qual: first.q || null, e: sub(s + 1, e, expr0), line };
599
643
  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 +744,9 @@ export function parseSyntax(src, opts = {}) {
700
744
  return out;
701
745
  }
702
746
  function parseDecl(a, b) {
747
+ // `pattern P :: T` / `pattern P x = ..` (PatternSynonyms): a synonym, recorded by name, not a function called `pattern`. Its right-hand
748
+ // 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).
749
+ 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
750
  const mark = findTop(a, b, (x) => x.t === 'rop' && (x.v === '=' || x.v === '|' || x.v === '::'));
704
751
  if (mark < 0) { err('expected-declaration', T[a], 'item is neither a binding nor a signature'); return null; }
705
752
  if (T[mark].v === '::') {
@@ -754,7 +801,39 @@ export function parseSyntax(src, opts = {}) {
754
801
  let nameTok = null;
755
802
  for (let q = a + 1; q < (eq < 0 ? b : eq); q++) if (T[q].t === 'con') { nameTok = T[q]; break; }
756
803
  const decl = { t: 'data', newtype: isNewtype, name: nameTok ? nameTok.v : null, constructors: [], line: T[a].line };
757
- if (eq < 0) return decl;
804
+ if (eq < 0) {
805
+ // GADT syntax: `data T where { A :: Int -> T; B :: { fld :: String } -> T }`. Each item names one or more constructors, their record
806
+ // fields when written with braces, else an arity of one per top-level arrow; the result type is not read (no type checking).
807
+ const w = findTop(a, b, (x) => isKw(x, 'where'));
808
+ if (w < 0) return decl;
809
+ const blk = blockAfter(w, b, layoutIndent);
810
+ for (const [s, e] of blk.items) {
811
+ const names = [];
812
+ let i = s;
813
+ while (i < e && T[i].t === 'con') { names.push(T[i].v); i++; if (isSp(T[i], ',')) i++; else break; }
814
+ if (!names.length || !isRop(T[i], '::')) continue;
815
+ i++;
816
+ 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;
817
+ let fields = null;
818
+ let arity = 0;
819
+ if (isSp(T[i], '{') && mate[i] > i) {
820
+ fields = [];
821
+ const close = mate[i];
822
+ let j = i + 1;
823
+ while (j < close) {
824
+ const ns = [];
825
+ while (j < close && T[j].t === 'var') { ns.push(T[j].v); j++; if (isSp(T[j], ',')) j++; else break; }
826
+ if (isRop(T[j], '::')) { j++; while (j < close && !isSp(T[j], ',')) j = Math.min(close, groupEnd(j)); j++; } else break;
827
+ fields.push(...ns);
828
+ }
829
+ arity = fields.length;
830
+ } else {
831
+ for (let j = i; j < e;) { if (isRop(T[j], '->')) arity++; j = Math.min(e, groupEnd(j)); }
832
+ }
833
+ for (const n of names) decl.constructors.push({ name: n, fields, arity });
834
+ }
835
+ return decl;
836
+ }
758
837
  let end = findTop(eq + 1, b, (x) => isKw(x, 'deriving'));
759
838
  if (end < 0) end = b;
760
839
  for (const [s0, e] of splitTop(eq + 1, end, (x) => isRop(x, '|'))) {