@tslite/analysis 0.1.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 ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var core=require('@tslite/core');var I=new Set,T=(...e)=>new Set(e),m={atoms:new Set,vars:new Set},M=(...e)=>({atoms:new Set(e),vars:new Set}),C=e=>({atoms:new Set,vars:new Set([e])}),R=e=>{let t=new Set,o=new Set;for(let n of e){for(let r of n.atoms)t.add(r);for(let r of n.vars)o.add(r);}return {atoms:t,vars:o}},Y=(e,t)=>{let o=new Set(e.atoms),n=new Set;for(let r of e.vars){let i=t.get(r);if(i){for(let s of i.atoms)o.add(s);for(let s of i.vars)n.add(s);}else n.add(r);}return {atoms:o,vars:n}},U={params:[],latent:m},V=(e,t)=>{let o=new Map;return e.params.forEach((n,r)=>{let i=t[r];n&&i&&o.set(n,i);}),Y(e.latent,o)},O=e=>new Set(e.atoms),ae=e=>{let t=new Set;for(let o of e)for(let n of o)t.add(n);return t},fe=(e,t)=>{for(let o of e)if(!t.has(o))return false;return true},j=(e,t)=>[...e].filter(o=>!t.has(o));var de=e=>{let t={};for(let[o,n]of Object.entries(e))t[o]=new Set(n);return t},pe=(e,t,o=[])=>{let n=new Array(t).fill(null),r="\u03B5";return n[e]=r,{params:n,latent:{atoms:new Set(o),vars:new Set([r])}}},L={nodeEffects:{ForStatement:"loops",ForInStatement:"loops",ForOfStatement:"loops",WhileStatement:"loops",DoWhileStatement:"loops",AssignmentExpression:"mutates",UpdateExpression:"mutates"}},ye=new Set(["ArrowFunctionExpression","FunctionExpression","FunctionDeclaration"]),h=e=>ye.has(e.type),p=e=>e,ue=e=>p(e).body,me=e=>p(e).params??[],S=e=>e&&e.type==="Identifier"?p(e).name:void 0,Ee=e=>{let t=p(e).object,o=p(e).property,n=S(t);if(!n)return null;let r=p(e).computed?void 0:S(o);return {path:r?`${n}.${r}`:n,root:n}},ge=e=>typeof e=="object"&&e!==null&&"latent"in e,$=(e,t)=>ge(e)?V(e,t):{atoms:new Set(e),vars:new Set},J=(e,t)=>{let o=0;return {rules:e,env:t,schemeOf:new Map,active:new Set,fresh:()=>`\u03B5${o++}`}},Q=e=>{let t={};for(let[o,n]of Object.entries(e))t[o]={kind:"capability",decl:n};return core.rootScope(t)};function x(e,t,o){let n=o.schemeOf.get(e);if(n)return n;if(o.active.has(e))return U;o.active.add(e);let r=me(e),i=r.map(v=>v.type==="Identifier"?o.fresh():null),s={};r.forEach((v,D)=>{let K=S(v);K&&i[D]&&(s[K]={kind:"param",var:i[D]});});let a=core.childScope(t,s),f=ue(e),u=f?E(f,a,o):m,d={params:i,latent:u};return o.active.delete(e),o.schemeOf.set(e,d),d}function _(e,t,o){x(e,t,o);}function E(e,t,o){let n=o.rules.nodeEffects[e.type],r=n?M(n):m;if(h(e))return _(e,t,o),r;if(e.type==="CallExpression"||e.type==="NewExpression")return R([r,we(e,t,o)]);if(e.type==="BlockStatement"||e.type==="Program"){let s=p(e).body??[];return R([r,ve(s,t,o)])}let i=[r];return core.forEachChild(e,s=>i.push(E(s,t,o))),R(i)}function ve(e,t,o){let n=t,r={};for(let s of e)if(s.type==="FunctionDeclaration"){let a=S(p(s).id);a&&(r[a]={kind:"fn",node:s,scope:n});}Object.keys(r).length&&(n=core.childScope(n,r));for(let s in r)r[s].kind==="fn"&&(r[s].scope=n);for(let s in r){let a=r[s];a.kind==="fn"&&_(a.node,n,o);}let i=[];for(let s of e)if(s.type!=="FunctionDeclaration"){if(s.type==="VariableDeclaration"){let a=p(s).declarations??[];for(let f of a){let u=p(f).id,d=p(f).init,v=S(u);if(v&&d&&h(d)){let D=core.childScope(n,{[v]:{kind:"fn",node:d,scope:n}});_(d,D,o),n=D;}else d&&i.push(E(d,n,o)),v&&(n=core.childScope(n,{[v]:{kind:"value"}}));}continue}i.push(E(s,n,o));}return R(i)}function we(e,t,o){let n=p(e).callee,r=p(e).arguments??[],i=r.map(f=>h(f)?m:E(f,t,o)),s=r.map(f=>Re(f,t,o)),a=Se(n,s,t,o);return R([a,...i])}function Re(e,t,o){if(h(e))return x(e,t,o).latent;if(e.type==="Identifier"){let n=core.lookup(t,S(e));if(!n)return null;if(n.kind==="fn")return x(n.node,n.scope,o).latent;if(n.kind==="param")return C(n.var);if(n.kind==="capability")return $(n.decl,[])}return null}function Se(e,t,o,n){if(!e)return m;if(e.type==="Identifier"){let r=core.lookup(o,S(e));if(!r)return m;switch(r.kind){case "capability":return $(r.decl,t);case "fn":return V(x(r.node,r.scope,n),t);case "param":return C(r.var);case "value":return m}}if(h(e))return V(x(e,o,n),t);if(e.type==="MemberExpression"){let r=Ee(e),i=r&&(n.env[r.path]??n.env[r.root]);if(i)return $(i,t)}return E(e,o,n)}function W(e,t={}){let o=t.env??{},n=J(t.rules??L,o),r=Q(o);if(t.root&&t.root!==e){E(t.root,r,n);let i=n.schemeOf.get(e);if(i)return O(i.latent)}return h(e)?O(x(e,r,n).latent):O(E(e,r,n))}function z(e,t={}){let o=t.env??{},n=J(t.rules??L,o);E(e,Q(o),n);let r=new Map;for(let[i,s]of n.schemeOf)r.set(i,O(s.latent));return r}var X=e=>e.size?`{ ${[...e].sort().join(", ")} }`:"pure";function G(e,t,o){let n=W(e,o),r=j(n,t);return r.length===0?[]:[{node:e,declared:t,inferred:n,excess:r,message:`Effect contract violated: declared ${X(t)} but infers ${X(n)} \u2014 not allowed: ${r.join(", ")}.`}]}function Z(e){switch(e){case "pure":return I;case "io":return T("io");case "any":return null}}function Be(e,t,o){let n=Z(t);return n===null?[]:G(e,n,o)}var b=e=>e,B=e=>e&&e.type==="Identifier"?b(e).name:void 0,N=(e,t)=>b(e)[t],Ne=new Set(["ArrowFunctionExpression","FunctionExpression","FunctionDeclaration"]),F=e=>Ne.has(e.type);function Oe(e){if(!e)return null;if(e.type==="Identifier"){let t=B(e);return {path:t,root:t}}if(e.type==="MemberExpression"){let t=b(e).object,o=b(e).property,n=b(e).computed,r=B(t);if(!r)return null;let i=n?void 0:B(o);return {path:i?`${r}.${i}`:r,root:r}}return null}function te(e){let{categories:t,matrix:o}=e,n=o.defaultPolicy??"allow",r=s=>t[s.path]??t[s.root],i=(s,a)=>o.rules[s]?.[a]??n;return (s,a)=>{if(!a)return null;let f=t[a];if(f===void 0)return null;let u=Oe(N(s,"callee"));if(!u)return null;let d=r(u);return d===void 0||i(f,d)!=="deny"?null:{call:s,from:a,to:u.path,fromCategory:f,toCategory:d,message:`Policy violated: ${f} '${a}' must not call ${d} '${u.path}' (${f} \u2192 ${d} is deny).`}}}var oe=e=>e.type==="CallExpression"||e.type==="NewExpression";function xe(e,t){let o=te(t),n=[],r=(i,s)=>{if(oe(i)){let a=o(i,s);a&&n.push(a);}if(i.type==="VariableDeclaration"){for(let a of b(i).declarations??[]){let f=N(a,"init");if(!f)continue;let u=F(f)?B(N(a,"id"))??s:s;r(f,u);}return}if(i.type==="FunctionDeclaration"){let a=B(N(i,"id"))??s;core.forEachChild(i,f=>r(f,a));return}core.forEachChild(i,a=>r(a,s));};return r(e,null),n}function ne(e){let t=te(e),o=new Map,n=[],r=()=>n.length?n[n.length-1]:null,i=null;return {enter(s){if(s.type==="VariableDeclarator"){let a=N(s,"init"),f=B(N(s,"id"));a&&F(a)&&f&&(i={name:f,fn:a});}if(F(s)){let a=i&&i.fn===s?i.name:s.type==="FunctionDeclaration"?B(N(s,"id"))??r():r();n.push(a),i=null;}if(oe(s)){let a=t(s,r());a&&o.set(s,a);}},exit(s){F(s)&&n.pop();},result:()=>o}}var w=e=>e,be=e=>e&&e.type==="Identifier"?w(e).name:void 0,c=(e,t)=>w(e)[t],g=(e,t)=>w(e)[t]??[];function y(e,t){if(e)switch(e.type){case "Identifier":t.add(w(e).name);return;case "AssignmentPattern":y(c(e,"left"),t);return;case "RestElement":y(c(e,"argument"),t);return;case "ArrayPattern":for(let o of g(e,"elements"))y(o,t);return;case "ObjectPattern":for(let o of g(e,"properties"))o.type==="RestElement"?y(c(o,"argument"),t):y(c(o,"value"),t);return}}function ke(e){let t=new Set;for(let o of e)if(o.type==="VariableDeclaration")for(let n of g(o,"declarations"))y(c(n,"id"),t);else if(o.type==="FunctionDeclaration"){let n=be(c(o,"id"));n&&t.add(n);}return t}var l=(e,t)=>{for(let o of t)e.add(o);},k=(e,t)=>{for(let o of t)e.delete(o);},De=new Set,re=e=>new Set(e);function H(e,t){let o=new Set;switch(e.type){case "Identifier":o.add(w(e).name);break;case "MemberExpression":{l(o,t(c(e,"object"))),w(e).computed&&l(o,t(c(e,"property")));break}case "Property":{w(e).computed&&l(o,t(c(e,"key"))),l(o,t(c(e,"value")));break}case "ArrowFunctionExpression":case "FunctionExpression":case "FunctionDeclaration":{let n=g(e,"params"),r=new Set;for(let i of n)y(i,r),i.type==="AssignmentPattern"&&l(o,t(c(i,"right")));l(o,t(c(e,"body"))),k(o,r);break}case "BlockStatement":case "Program":{let n=g(e,"body");for(let r of n)l(o,t(r));k(o,ke(n));break}case "VariableDeclarator":{l(o,t(c(e,"init")));let n=c(e,"id");n&&n.type!=="Identifier"&&A(n,o,t);break}case "CatchClause":{let n=new Set;y(c(e,"param"),n),l(o,t(c(e,"body"))),k(o,n);break}case "LabeledStatement":l(o,t(c(e,"body")));break;case "BreakStatement":case "ContinueStatement":break;case "ForInStatement":case "ForOfStatement":{let n=c(e,"left");l(o,t(c(e,"right")));let r=new Set;if(n.type==="VariableDeclaration")for(let s of g(n,"declarations"))y(c(s,"id"),r);else y(n,r);let i=re(t(c(e,"body")));k(i,r),l(o,i);break}case "ForStatement":{let n=c(e,"init"),r=new Set;if(n){if(n.type==="VariableDeclaration")for(let i of g(n,"declarations"))y(c(i,"id"),r);l(o,t(n));}for(let i of ["test","update","body"]){let s=re(t(c(e,i)));k(s,r),l(o,s);}break}default:core.forEachChild(e,n=>l(o,t(n)));break}return o}function se(e,t={}){let o=new Map,n=new Set(t.globals??[]),r=i=>{if(!i)return De;let s=H(i,r);return k(s,n),o.set(i,s),s};return r(e),o}function A(e,t,o){if(e.type==="AssignmentPattern")l(t,o(c(e,"right"))),A(c(e,"left"),t,o);else if(e.type==="ArrayPattern")for(let n of g(e,"elements"))n&&A(n,t,o);else if(e.type==="ObjectPattern")for(let n of g(e,"properties")){w(n).computed&&l(t,o(c(n,"key")));let r=c(n,"value")??c(n,"argument");r&&A(r,t,o);}}function Ve(e,t){return se(e,t).get(e)??new Set}function Pe(e,t){return {root:e,diagnostics:t.diagnostics,typeOf:o=>t.overlay.get(o)?.type,scopeOf:o=>t.overlay.get(o)?.scope}}function Fe(e,t){let o=new Map,n={tree:e,read:s=>o.get(s.name)},r=t.filter(s=>!!s.visitor),i=t.filter(s=>!s.visitor&&s.run);if(r.length){let s=a=>{for(let f of r)f.visitor.enter?.(a,n);core.forEachChild(a,s);for(let f=r.length-1;f>=0;f--)r[f].visitor.exit?.(a,n);};s(e.root);for(let a of r)o.set(a.name,a.visitor.result());}for(let s of i)o.set(s.name,s.run(n));return o}var ie=new Set,Ae=(e,t)=>({name:"effects",run:o=>z(o.tree.root,{env:e,rules:t})}),Ie=e=>({name:"deps",visitor:(()=>{let t=new Set(e?.globals??[]),o=new Map;return {exit(n){let r=H(n,i=>i?o.get(i)??ie:ie);for(let i of t)r.delete(i);o.set(n,r);},result:()=>o}})()}),Te=e=>({name:"policies",visitor:ne(e)});exports.DEFAULT_RULES=L;exports.PURE=I;exports.PURE_ROW=m;exports.PURE_SCHEME=U;exports.analyze=Fe;exports.checkEffectPolicy=Be;exports.checkEffects=G;exports.checkPolicies=xe;exports.deps=se;exports.depsOverlay=Ie;exports.effect=T;exports.effectCeiling=Z;exports.effectEnv=de;exports.effectsOverlay=Ae;exports.effectsOverlayOf=z;exports.excess=j;exports.forwardEffect=pe;exports.freeVars=Ve;exports.groundRow=O;exports.inferEffects=W;exports.instantiateScheme=V;exports.join=ae;exports.joinRows=R;exports.policiesOverlay=Te;exports.rowOf=M;exports.rowOfVar=C;exports.substituteRow=Y;exports.typedTreeOf=Pe;exports.within=fe;
@@ -0,0 +1,265 @@
1
+ import { BaseNode } from '@tslite/core';
2
+ import { Effects } from '@tslite/capability';
3
+ import { Diagnostic, Schema, Scope, CheckResult } from '@tslite/checker';
4
+
5
+ /** Um átomo de efeito (vocabulário da app): `io`, `loops`, `time`, … */
6
+ type EffectAtom = string;
7
+ /** Uma variável de efeito (`ε`) — o "genérico" do eixo de efeitos. */
8
+ type EffectVar = string;
9
+ /** O efeito concreto de algo: o conjunto de átomos. `∅` = `pure`. */
10
+ type Effect = ReadonlySet<EffectAtom>;
11
+ /** O efeito vazio (bottom do lattice) — puro. */
12
+ declare const PURE: Effect;
13
+ /** Constrói um efeito concreto a partir de átomos. */
14
+ declare const effect: (...atoms: EffectAtom[]) => Effect;
15
+ /**
16
+ * Uma **linha de efeito**: átomos concretos `⊔` variáveis de efeito ainda não
17
+ * resolvidas. `pure` é `{ atoms: ∅, vars: ∅ }`.
18
+ */
19
+ interface EffectRow {
20
+ readonly atoms: ReadonlySet<EffectAtom>;
21
+ readonly vars: ReadonlySet<EffectVar>;
22
+ }
23
+ /** A linha pura (bottom). */
24
+ declare const PURE_ROW: EffectRow;
25
+ /** Uma linha só de átomos concretos. */
26
+ declare const rowOf: (...atoms: EffectAtom[]) => EffectRow;
27
+ /** Uma linha que é só uma variável de efeito (`ε`). */
28
+ declare const rowOfVar: (v: EffectVar) => EffectRow;
29
+ /** Junção (`⊔`) de linhas — união de átomos e de variáveis. */
30
+ declare const joinRows: (rows: Iterable<EffectRow>) => EffectRow;
31
+ /**
32
+ * Substitui as variáveis de `row` pelas linhas ligadas em `bindings` (unindo
33
+ * seus átomos/vars); variáveis sem ligação permanecem livres. É o `substitute`
34
+ * do eixo de efeitos.
35
+ */
36
+ declare const substituteRow: (row: EffectRow, bindings: ReadonlyMap<EffectVar, EffectRow>) => EffectRow;
37
+ /**
38
+ * O contrato de efeito de uma função: para cada parâmetro, a `ε` que ele
39
+ * introduz se for chamado dentro do corpo (`null` caso contrário), e a linha
40
+ * **latente** — o que o corpo faz quando a função é chamada (podendo mencionar
41
+ * essas `ε`). É o análogo de uma assinatura genérica `map<T,U,ε>`.
42
+ */
43
+ interface EffectScheme {
44
+ readonly params: readonly (EffectVar | null)[];
45
+ readonly latent: EffectRow;
46
+ }
47
+ /** Um scheme puro sem parâmetros-efeito. */
48
+ declare const PURE_SCHEME: EffectScheme;
49
+ /**
50
+ * Instancia um scheme no call site: liga cada `ε` de parâmetro à linha de efeito
51
+ * do argumento correspondente (quando o argumento é "chamável") e devolve a
52
+ * linha latente já substituída. É o `instantiate` do eixo de efeitos — o mesmo
53
+ * padrão do checker, sobre linhas em vez de tipos.
54
+ */
55
+ declare const instantiateScheme: (scheme: EffectScheme, argRows: readonly (EffectRow | null)[]) => EffectRow;
56
+ /**
57
+ * "Aterra" uma linha no seu efeito **concreto**: descarta as variáveis livres
58
+ * (um higher-order não-instanciado é puro *em si*). É a ponte da linha para o
59
+ * `Effect` que os contratos comparam.
60
+ */
61
+ declare const groundRow: (row: EffectRow) => Effect;
62
+ /** Junção (`⊔`) de efeitos concretos — a união. */
63
+ declare const join: (effects: Iterable<Effect>) => Effect;
64
+ /** `inferido ⊆ declarado`? (o inferido cabe no contrato). */
65
+ declare const within: (inferred: Effect, declared: Effect) => boolean;
66
+ /** Os átomos de `inferred` que `declared` **não** permite. */
67
+ declare const excess: (inferred: Effect, declared: Effect) => EffectAtom[];
68
+
69
+ /** A declaração de efeito de uma capability da DI: concreta ou higher-order. */
70
+ type EffectDecl = Effect | EffectScheme;
71
+ /** Efeitos declarados por nome — a borda DI (capabilities/funções externas). */
72
+ type EffectEnv = Readonly<Record<string, EffectDecl>>;
73
+ /** Açúcar: monta um `EffectEnv` concreto a partir de listas de átomos. */
74
+ declare const effectEnv: (decls: Readonly<Record<string, readonly string[]>>) => EffectEnv;
75
+ /**
76
+ * Açúcar para uma capability **higher-order** cujo efeito é o do seu parâmetro
77
+ * de índice `paramIndex` (ex.: `map`/`filter`/`forEach` herdam o efeito do
78
+ * callback). `extra` são átomos que a própria capability sempre faz.
79
+ */
80
+ declare const forwardEffect: (paramIndex: number, arity: number, extra?: readonly string[]) => EffectScheme;
81
+ /** Quais nós produzem qual efeito **direto** (extensível pela app). */
82
+ interface EffectRules {
83
+ readonly nodeEffects: Readonly<Record<string, string>>;
84
+ }
85
+ /** Regras padrão (capability imperativa, D30): loops → `loops`; mutação → `mutates`. */
86
+ declare const DEFAULT_RULES: EffectRules;
87
+ interface InferOptions {
88
+ /** Efeitos declarados das capabilities/funções chamadas (DI). */
89
+ readonly env?: EffectEnv;
90
+ /** Regras de efeito direto por tipo de nó. */
91
+ readonly rules?: EffectRules;
92
+ /**
93
+ * A raiz, para resolver chamadas a **funções irmãs/externas** ao `node`
94
+ * (`const g = …; const f = () => g()`). Sem ela, só nested + params + DI.
95
+ */
96
+ readonly root?: BaseNode;
97
+ }
98
+ /**
99
+ * Infere o efeito **concreto** de executar `node` (variáveis de efeito livres,
100
+ * de higher-order não instanciado, são aterradas — um `map` *em si* é puro).
101
+ */
102
+ declare function inferEffects(node: BaseNode, opts?: InferOptions): Effect;
103
+ /**
104
+ * O **overlay** de efeitos: o efeito concreto de **cada função** sob `root`, numa
105
+ * travessia (schemes memoizados). É o que a camada de overlays (`effectsOverlay`)
106
+ * consome para devolver um `Map<nó-de-função, Effect>`.
107
+ */
108
+ declare function effectsOverlayOf(root: BaseNode, opts?: {
109
+ readonly env?: EffectEnv;
110
+ readonly rules?: EffectRules;
111
+ }): Map<BaseNode, Effect>;
112
+
113
+ /** Uma violação de contrato de efeito (erro como dado). */
114
+ interface EffectViolation {
115
+ readonly node: BaseNode;
116
+ readonly declared: Effect;
117
+ readonly inferred: Effect;
118
+ readonly excess: readonly string[];
119
+ readonly message: string;
120
+ }
121
+ /**
122
+ * Checa uma função contra o efeito que ela **promete**. Se o inferido exceder o
123
+ * declarado, devolve a violação (com os átomos culpados). Lista vazia = ok.
124
+ * Sobre-declarar é seguro (`{} ⊆ { io }`); o erro é fazer **mais** do que prometeu.
125
+ */
126
+ declare function checkEffects(node: BaseNode, declared: Effect, opts?: InferOptions): EffectViolation[];
127
+
128
+ /**
129
+ * Traduz o nível `host.effects` num **teto** de efeito permitido. `null` = `"any"`
130
+ * (escape-hatch, sem restrição). `"io"` libera o átomo de side-effect padrão `io`;
131
+ * para um lattice de átomos mais fino (`net`/`fs`/`rand`…), use `checkEffects`
132
+ * direto com o conjunto declarado — `host.effects` é a régua grossa (3 níveis).
133
+ */
134
+ declare function effectCeiling(level: Effects): Effect | null;
135
+ /**
136
+ * Enforça o eixo `host.effects` de um `CapabilityProfile`: infere o efeito de `node`
137
+ * (sob o `env` de efeitos da DI — `effectEnv({ save: ["io"] })`) e devolve as
138
+ * violações que **excedem** o teto. Lista vazia = ok; `"any"` nunca viola.
139
+ *
140
+ * É a "ligação" do analysis no capability (A1): o consumidor roda
141
+ * `check(ast, env, { profile })` (gating sintático, soft) e, em paralelo,
142
+ * `checkEffectPolicy(ast, profile.host.effects, { env: effectEnv(...) })`.
143
+ */
144
+ declare function checkEffectPolicy(node: BaseNode, level: Effects, opts?: InferOptions): EffectViolation[];
145
+
146
+ /** A árvore JÁ TIPADA — a saída do kernel que todo overlay consome (read-only). */
147
+ interface TypedTree {
148
+ readonly root: BaseNode;
149
+ readonly diagnostics: readonly Diagnostic[];
150
+ /** O tipo de um nó (do `TypeOverlay` do checker). */
151
+ typeOf(node: BaseNode): Schema | undefined;
152
+ /** O escopo léxico em um nó (do `TypeOverlay` do checker). */
153
+ scopeOf(node: BaseNode): Scope | undefined;
154
+ }
155
+ /** Embrulha o `CheckResult` do kernel numa fachada read-only para os overlays. */
156
+ declare function typedTreeOf(root: BaseNode, result: CheckResult): TypedTree;
157
+ /** O que um overlay produz: um mapa por nó. */
158
+ type OverlayResult<T> = ReadonlyMap<BaseNode, T>;
159
+ /** Contexto entregue a um overlay: a árvore tipada + leitura cruzada de outros. */
160
+ interface OverlayCtx {
161
+ readonly tree: TypedTree;
162
+ /** O resultado de um overlay já calculado nesta rodada (ver `dependsOn`). */
163
+ read<U>(overlay: Overlay<U>): OverlayResult<U> | undefined;
164
+ }
165
+ /** Hooks de um overlay-visitor — o runner os dirige num walk compartilhado. */
166
+ interface OverlayVisitor<T> {
167
+ enter?(node: BaseNode, ctx: OverlayCtx): void;
168
+ exit?(node: BaseNode, ctx: OverlayCtx): void;
169
+ result(): OverlayResult<T>;
170
+ }
171
+ /** O contrato de um overlay plugin. Forneça `run` OU `visitor`. */
172
+ interface Overlay<T> {
173
+ readonly name: string;
174
+ /** Overlays cujos resultados este lê via `ctx.read` (ordena o runner). */
175
+ readonly dependsOn?: readonly Overlay<unknown>[];
176
+ /** Forma A — controle total: o overlay faz a própria travessia. */
177
+ run?(ctx: OverlayCtx): OverlayResult<T>;
178
+ /** Forma B — passe compartilhado: hooks fundidos num walk único. */
179
+ readonly visitor?: OverlayVisitor<T>;
180
+ }
181
+ /** O resultado de uma rodada de overlays: o mapa de cada um, por nome. */
182
+ type Analysis = ReadonlyMap<string, OverlayResult<unknown>>;
183
+ /**
184
+ * Roda a camada de overlays sobre a árvore tipada. Os overlays-`visitor` são
185
+ * **fundidos num único DFS** (enter na descida, exit na subida); os
186
+ * overlays-`run` rodam depois (podendo `read` os resultados dos visitors). O
187
+ * kernel já rodou fora — aqui não se tipa nada de novo.
188
+ */
189
+ declare function analyze(tree: TypedTree, overlays: readonly Overlay<unknown>[]): Analysis;
190
+
191
+ /** Uma categoria arquitetural (tag): `controller`, `service`, `repo`, … */
192
+ type Category = string;
193
+ /** A decisão de uma aresta entre categorias. */
194
+ type Decision = "allow" | "deny";
195
+ /** A matriz de permissão: `from → to → decisão`. */
196
+ interface PolicyMatrix {
197
+ /** Regras explícitas. Ausência cai no `defaultPolicy`. */
198
+ readonly rules: Readonly<Record<Category, Readonly<Record<Category, Decision>>>>;
199
+ /** Decisão para pares sem regra. Default: `allow` (estilo deny-list). */
200
+ readonly defaultPolicy?: Decision;
201
+ }
202
+ interface PolicyOptions {
203
+ /**
204
+ * A categoria de cada nome — capabilities da DI (`repo`, `repo.save`) ou
205
+ * funções locais (`handle`). Para um callee `obj.m`, tenta `obj.m` e cai em
206
+ * `obj`. Nomes sem categoria não participam de nenhuma regra.
207
+ */
208
+ readonly categories: Readonly<Record<string, Category>>;
209
+ /** A matriz de permissão entre categorias. */
210
+ readonly matrix: PolicyMatrix;
211
+ }
212
+ /** Uma violação de política (erro como dado). */
213
+ interface PolicyViolation {
214
+ readonly call: BaseNode;
215
+ /** Nome da função chamadora (`null` = chamada de topo, fora de função). */
216
+ readonly from: string | null;
217
+ /** Nome/caminho do callee (`repo.save`, `svc`). */
218
+ readonly to: string;
219
+ readonly fromCategory: Category;
220
+ readonly toCategory: Category;
221
+ readonly message: string;
222
+ }
223
+ /**
224
+ * Verifica as **políticas de arquitetura** sobre o call graph de `root`. Para
225
+ * cada chamada `A → B` cujas duas pontas têm categoria, consulta a matriz; o que
226
+ * for `deny` vira uma violação. Por aresta direta — sem propagação.
227
+ */
228
+ declare function checkPolicies(root: BaseNode, opts: PolicyOptions): PolicyViolation[];
229
+
230
+ /** Uma entrada da qual algo depende (uma variável livre). */
231
+ type Dep = string;
232
+ /** O overlay: para cada nó, o conjunto de entradas das quais ele depende. */
233
+ type DepsOverlay = Map<BaseNode, ReadonlySet<Dep>>;
234
+ interface DepsOptions {
235
+ /**
236
+ * Nomes sempre ligados (globals/DI/capabilities): nunca contam como entrada.
237
+ * Útil para focar deps em params/estado e ignorar a borda estável.
238
+ */
239
+ readonly globals?: Iterable<string>;
240
+ }
241
+ /**
242
+ * Computa o overlay de deps de `node`: para cada subárvore, suas variáveis
243
+ * livres. Travessia recursiva (bottom-up) que preenche o `Map`.
244
+ */
245
+ declare function deps(node: BaseNode, opts?: DepsOptions): DepsOverlay;
246
+ /** Conveniência: as variáveis livres (deps) da raiz `node`. */
247
+ declare function freeVars(node: BaseNode, opts?: DepsOptions): ReadonlySet<Dep>;
248
+
249
+ /**
250
+ * `effects` como overlay: o efeito concreto de **cada função** (`Map<fn, Effect>`).
251
+ * Faz a própria travessia (grafo lazy de schemes) — forma `run`.
252
+ */
253
+ declare const effectsOverlay: (env: EffectEnv, rules?: EffectRules) => Overlay<Effect>;
254
+ /**
255
+ * `deps` como overlay-**visitor**: as variáveis livres de cada subárvore. Em
256
+ * pós-ordem (`exit`), combina os filhos (já no map) — partilha o walk do runner.
257
+ */
258
+ declare const depsOverlay: (opts?: DepsOptions) => Overlay<ReadonlySet<Dep>>;
259
+ /**
260
+ * `policies` como overlay-**visitor**: a violação em cada chamada proibida
261
+ * (`Map<callNode, PolicyViolation>`). Por aresta direta, sem propagação.
262
+ */
263
+ declare const policiesOverlay: (opts: PolicyOptions) => Overlay<PolicyViolation>;
264
+
265
+ export { type Analysis, type Category, DEFAULT_RULES, type Decision, type Dep, type DepsOptions, type DepsOverlay, type Effect, type EffectAtom, type EffectDecl, type EffectEnv, type EffectRow, type EffectRules, type EffectScheme, type EffectVar, type EffectViolation, type InferOptions, type Overlay, type OverlayCtx, type OverlayResult, type OverlayVisitor, PURE, PURE_ROW, PURE_SCHEME, type PolicyMatrix, type PolicyOptions, type PolicyViolation, type TypedTree, analyze, checkEffectPolicy, checkEffects, checkPolicies, deps, depsOverlay, effect, effectCeiling, effectEnv, effectsOverlay, effectsOverlayOf, excess, forwardEffect, freeVars, groundRow, inferEffects, instantiateScheme, join, joinRows, policiesOverlay, rowOf, rowOfVar, substituteRow, typedTreeOf, within };
@@ -0,0 +1,265 @@
1
+ import { BaseNode } from '@tslite/core';
2
+ import { Effects } from '@tslite/capability';
3
+ import { Diagnostic, Schema, Scope, CheckResult } from '@tslite/checker';
4
+
5
+ /** Um átomo de efeito (vocabulário da app): `io`, `loops`, `time`, … */
6
+ type EffectAtom = string;
7
+ /** Uma variável de efeito (`ε`) — o "genérico" do eixo de efeitos. */
8
+ type EffectVar = string;
9
+ /** O efeito concreto de algo: o conjunto de átomos. `∅` = `pure`. */
10
+ type Effect = ReadonlySet<EffectAtom>;
11
+ /** O efeito vazio (bottom do lattice) — puro. */
12
+ declare const PURE: Effect;
13
+ /** Constrói um efeito concreto a partir de átomos. */
14
+ declare const effect: (...atoms: EffectAtom[]) => Effect;
15
+ /**
16
+ * Uma **linha de efeito**: átomos concretos `⊔` variáveis de efeito ainda não
17
+ * resolvidas. `pure` é `{ atoms: ∅, vars: ∅ }`.
18
+ */
19
+ interface EffectRow {
20
+ readonly atoms: ReadonlySet<EffectAtom>;
21
+ readonly vars: ReadonlySet<EffectVar>;
22
+ }
23
+ /** A linha pura (bottom). */
24
+ declare const PURE_ROW: EffectRow;
25
+ /** Uma linha só de átomos concretos. */
26
+ declare const rowOf: (...atoms: EffectAtom[]) => EffectRow;
27
+ /** Uma linha que é só uma variável de efeito (`ε`). */
28
+ declare const rowOfVar: (v: EffectVar) => EffectRow;
29
+ /** Junção (`⊔`) de linhas — união de átomos e de variáveis. */
30
+ declare const joinRows: (rows: Iterable<EffectRow>) => EffectRow;
31
+ /**
32
+ * Substitui as variáveis de `row` pelas linhas ligadas em `bindings` (unindo
33
+ * seus átomos/vars); variáveis sem ligação permanecem livres. É o `substitute`
34
+ * do eixo de efeitos.
35
+ */
36
+ declare const substituteRow: (row: EffectRow, bindings: ReadonlyMap<EffectVar, EffectRow>) => EffectRow;
37
+ /**
38
+ * O contrato de efeito de uma função: para cada parâmetro, a `ε` que ele
39
+ * introduz se for chamado dentro do corpo (`null` caso contrário), e a linha
40
+ * **latente** — o que o corpo faz quando a função é chamada (podendo mencionar
41
+ * essas `ε`). É o análogo de uma assinatura genérica `map<T,U,ε>`.
42
+ */
43
+ interface EffectScheme {
44
+ readonly params: readonly (EffectVar | null)[];
45
+ readonly latent: EffectRow;
46
+ }
47
+ /** Um scheme puro sem parâmetros-efeito. */
48
+ declare const PURE_SCHEME: EffectScheme;
49
+ /**
50
+ * Instancia um scheme no call site: liga cada `ε` de parâmetro à linha de efeito
51
+ * do argumento correspondente (quando o argumento é "chamável") e devolve a
52
+ * linha latente já substituída. É o `instantiate` do eixo de efeitos — o mesmo
53
+ * padrão do checker, sobre linhas em vez de tipos.
54
+ */
55
+ declare const instantiateScheme: (scheme: EffectScheme, argRows: readonly (EffectRow | null)[]) => EffectRow;
56
+ /**
57
+ * "Aterra" uma linha no seu efeito **concreto**: descarta as variáveis livres
58
+ * (um higher-order não-instanciado é puro *em si*). É a ponte da linha para o
59
+ * `Effect` que os contratos comparam.
60
+ */
61
+ declare const groundRow: (row: EffectRow) => Effect;
62
+ /** Junção (`⊔`) de efeitos concretos — a união. */
63
+ declare const join: (effects: Iterable<Effect>) => Effect;
64
+ /** `inferido ⊆ declarado`? (o inferido cabe no contrato). */
65
+ declare const within: (inferred: Effect, declared: Effect) => boolean;
66
+ /** Os átomos de `inferred` que `declared` **não** permite. */
67
+ declare const excess: (inferred: Effect, declared: Effect) => EffectAtom[];
68
+
69
+ /** A declaração de efeito de uma capability da DI: concreta ou higher-order. */
70
+ type EffectDecl = Effect | EffectScheme;
71
+ /** Efeitos declarados por nome — a borda DI (capabilities/funções externas). */
72
+ type EffectEnv = Readonly<Record<string, EffectDecl>>;
73
+ /** Açúcar: monta um `EffectEnv` concreto a partir de listas de átomos. */
74
+ declare const effectEnv: (decls: Readonly<Record<string, readonly string[]>>) => EffectEnv;
75
+ /**
76
+ * Açúcar para uma capability **higher-order** cujo efeito é o do seu parâmetro
77
+ * de índice `paramIndex` (ex.: `map`/`filter`/`forEach` herdam o efeito do
78
+ * callback). `extra` são átomos que a própria capability sempre faz.
79
+ */
80
+ declare const forwardEffect: (paramIndex: number, arity: number, extra?: readonly string[]) => EffectScheme;
81
+ /** Quais nós produzem qual efeito **direto** (extensível pela app). */
82
+ interface EffectRules {
83
+ readonly nodeEffects: Readonly<Record<string, string>>;
84
+ }
85
+ /** Regras padrão (capability imperativa, D30): loops → `loops`; mutação → `mutates`. */
86
+ declare const DEFAULT_RULES: EffectRules;
87
+ interface InferOptions {
88
+ /** Efeitos declarados das capabilities/funções chamadas (DI). */
89
+ readonly env?: EffectEnv;
90
+ /** Regras de efeito direto por tipo de nó. */
91
+ readonly rules?: EffectRules;
92
+ /**
93
+ * A raiz, para resolver chamadas a **funções irmãs/externas** ao `node`
94
+ * (`const g = …; const f = () => g()`). Sem ela, só nested + params + DI.
95
+ */
96
+ readonly root?: BaseNode;
97
+ }
98
+ /**
99
+ * Infere o efeito **concreto** de executar `node` (variáveis de efeito livres,
100
+ * de higher-order não instanciado, são aterradas — um `map` *em si* é puro).
101
+ */
102
+ declare function inferEffects(node: BaseNode, opts?: InferOptions): Effect;
103
+ /**
104
+ * O **overlay** de efeitos: o efeito concreto de **cada função** sob `root`, numa
105
+ * travessia (schemes memoizados). É o que a camada de overlays (`effectsOverlay`)
106
+ * consome para devolver um `Map<nó-de-função, Effect>`.
107
+ */
108
+ declare function effectsOverlayOf(root: BaseNode, opts?: {
109
+ readonly env?: EffectEnv;
110
+ readonly rules?: EffectRules;
111
+ }): Map<BaseNode, Effect>;
112
+
113
+ /** Uma violação de contrato de efeito (erro como dado). */
114
+ interface EffectViolation {
115
+ readonly node: BaseNode;
116
+ readonly declared: Effect;
117
+ readonly inferred: Effect;
118
+ readonly excess: readonly string[];
119
+ readonly message: string;
120
+ }
121
+ /**
122
+ * Checa uma função contra o efeito que ela **promete**. Se o inferido exceder o
123
+ * declarado, devolve a violação (com os átomos culpados). Lista vazia = ok.
124
+ * Sobre-declarar é seguro (`{} ⊆ { io }`); o erro é fazer **mais** do que prometeu.
125
+ */
126
+ declare function checkEffects(node: BaseNode, declared: Effect, opts?: InferOptions): EffectViolation[];
127
+
128
+ /**
129
+ * Traduz o nível `host.effects` num **teto** de efeito permitido. `null` = `"any"`
130
+ * (escape-hatch, sem restrição). `"io"` libera o átomo de side-effect padrão `io`;
131
+ * para um lattice de átomos mais fino (`net`/`fs`/`rand`…), use `checkEffects`
132
+ * direto com o conjunto declarado — `host.effects` é a régua grossa (3 níveis).
133
+ */
134
+ declare function effectCeiling(level: Effects): Effect | null;
135
+ /**
136
+ * Enforça o eixo `host.effects` de um `CapabilityProfile`: infere o efeito de `node`
137
+ * (sob o `env` de efeitos da DI — `effectEnv({ save: ["io"] })`) e devolve as
138
+ * violações que **excedem** o teto. Lista vazia = ok; `"any"` nunca viola.
139
+ *
140
+ * É a "ligação" do analysis no capability (A1): o consumidor roda
141
+ * `check(ast, env, { profile })` (gating sintático, soft) e, em paralelo,
142
+ * `checkEffectPolicy(ast, profile.host.effects, { env: effectEnv(...) })`.
143
+ */
144
+ declare function checkEffectPolicy(node: BaseNode, level: Effects, opts?: InferOptions): EffectViolation[];
145
+
146
+ /** A árvore JÁ TIPADA — a saída do kernel que todo overlay consome (read-only). */
147
+ interface TypedTree {
148
+ readonly root: BaseNode;
149
+ readonly diagnostics: readonly Diagnostic[];
150
+ /** O tipo de um nó (do `TypeOverlay` do checker). */
151
+ typeOf(node: BaseNode): Schema | undefined;
152
+ /** O escopo léxico em um nó (do `TypeOverlay` do checker). */
153
+ scopeOf(node: BaseNode): Scope | undefined;
154
+ }
155
+ /** Embrulha o `CheckResult` do kernel numa fachada read-only para os overlays. */
156
+ declare function typedTreeOf(root: BaseNode, result: CheckResult): TypedTree;
157
+ /** O que um overlay produz: um mapa por nó. */
158
+ type OverlayResult<T> = ReadonlyMap<BaseNode, T>;
159
+ /** Contexto entregue a um overlay: a árvore tipada + leitura cruzada de outros. */
160
+ interface OverlayCtx {
161
+ readonly tree: TypedTree;
162
+ /** O resultado de um overlay já calculado nesta rodada (ver `dependsOn`). */
163
+ read<U>(overlay: Overlay<U>): OverlayResult<U> | undefined;
164
+ }
165
+ /** Hooks de um overlay-visitor — o runner os dirige num walk compartilhado. */
166
+ interface OverlayVisitor<T> {
167
+ enter?(node: BaseNode, ctx: OverlayCtx): void;
168
+ exit?(node: BaseNode, ctx: OverlayCtx): void;
169
+ result(): OverlayResult<T>;
170
+ }
171
+ /** O contrato de um overlay plugin. Forneça `run` OU `visitor`. */
172
+ interface Overlay<T> {
173
+ readonly name: string;
174
+ /** Overlays cujos resultados este lê via `ctx.read` (ordena o runner). */
175
+ readonly dependsOn?: readonly Overlay<unknown>[];
176
+ /** Forma A — controle total: o overlay faz a própria travessia. */
177
+ run?(ctx: OverlayCtx): OverlayResult<T>;
178
+ /** Forma B — passe compartilhado: hooks fundidos num walk único. */
179
+ readonly visitor?: OverlayVisitor<T>;
180
+ }
181
+ /** O resultado de uma rodada de overlays: o mapa de cada um, por nome. */
182
+ type Analysis = ReadonlyMap<string, OverlayResult<unknown>>;
183
+ /**
184
+ * Roda a camada de overlays sobre a árvore tipada. Os overlays-`visitor` são
185
+ * **fundidos num único DFS** (enter na descida, exit na subida); os
186
+ * overlays-`run` rodam depois (podendo `read` os resultados dos visitors). O
187
+ * kernel já rodou fora — aqui não se tipa nada de novo.
188
+ */
189
+ declare function analyze(tree: TypedTree, overlays: readonly Overlay<unknown>[]): Analysis;
190
+
191
+ /** Uma categoria arquitetural (tag): `controller`, `service`, `repo`, … */
192
+ type Category = string;
193
+ /** A decisão de uma aresta entre categorias. */
194
+ type Decision = "allow" | "deny";
195
+ /** A matriz de permissão: `from → to → decisão`. */
196
+ interface PolicyMatrix {
197
+ /** Regras explícitas. Ausência cai no `defaultPolicy`. */
198
+ readonly rules: Readonly<Record<Category, Readonly<Record<Category, Decision>>>>;
199
+ /** Decisão para pares sem regra. Default: `allow` (estilo deny-list). */
200
+ readonly defaultPolicy?: Decision;
201
+ }
202
+ interface PolicyOptions {
203
+ /**
204
+ * A categoria de cada nome — capabilities da DI (`repo`, `repo.save`) ou
205
+ * funções locais (`handle`). Para um callee `obj.m`, tenta `obj.m` e cai em
206
+ * `obj`. Nomes sem categoria não participam de nenhuma regra.
207
+ */
208
+ readonly categories: Readonly<Record<string, Category>>;
209
+ /** A matriz de permissão entre categorias. */
210
+ readonly matrix: PolicyMatrix;
211
+ }
212
+ /** Uma violação de política (erro como dado). */
213
+ interface PolicyViolation {
214
+ readonly call: BaseNode;
215
+ /** Nome da função chamadora (`null` = chamada de topo, fora de função). */
216
+ readonly from: string | null;
217
+ /** Nome/caminho do callee (`repo.save`, `svc`). */
218
+ readonly to: string;
219
+ readonly fromCategory: Category;
220
+ readonly toCategory: Category;
221
+ readonly message: string;
222
+ }
223
+ /**
224
+ * Verifica as **políticas de arquitetura** sobre o call graph de `root`. Para
225
+ * cada chamada `A → B` cujas duas pontas têm categoria, consulta a matriz; o que
226
+ * for `deny` vira uma violação. Por aresta direta — sem propagação.
227
+ */
228
+ declare function checkPolicies(root: BaseNode, opts: PolicyOptions): PolicyViolation[];
229
+
230
+ /** Uma entrada da qual algo depende (uma variável livre). */
231
+ type Dep = string;
232
+ /** O overlay: para cada nó, o conjunto de entradas das quais ele depende. */
233
+ type DepsOverlay = Map<BaseNode, ReadonlySet<Dep>>;
234
+ interface DepsOptions {
235
+ /**
236
+ * Nomes sempre ligados (globals/DI/capabilities): nunca contam como entrada.
237
+ * Útil para focar deps em params/estado e ignorar a borda estável.
238
+ */
239
+ readonly globals?: Iterable<string>;
240
+ }
241
+ /**
242
+ * Computa o overlay de deps de `node`: para cada subárvore, suas variáveis
243
+ * livres. Travessia recursiva (bottom-up) que preenche o `Map`.
244
+ */
245
+ declare function deps(node: BaseNode, opts?: DepsOptions): DepsOverlay;
246
+ /** Conveniência: as variáveis livres (deps) da raiz `node`. */
247
+ declare function freeVars(node: BaseNode, opts?: DepsOptions): ReadonlySet<Dep>;
248
+
249
+ /**
250
+ * `effects` como overlay: o efeito concreto de **cada função** (`Map<fn, Effect>`).
251
+ * Faz a própria travessia (grafo lazy de schemes) — forma `run`.
252
+ */
253
+ declare const effectsOverlay: (env: EffectEnv, rules?: EffectRules) => Overlay<Effect>;
254
+ /**
255
+ * `deps` como overlay-**visitor**: as variáveis livres de cada subárvore. Em
256
+ * pós-ordem (`exit`), combina os filhos (já no map) — partilha o walk do runner.
257
+ */
258
+ declare const depsOverlay: (opts?: DepsOptions) => Overlay<ReadonlySet<Dep>>;
259
+ /**
260
+ * `policies` como overlay-**visitor**: a violação em cada chamada proibida
261
+ * (`Map<callNode, PolicyViolation>`). Por aresta direta, sem propagação.
262
+ */
263
+ declare const policiesOverlay: (opts: PolicyOptions) => Overlay<PolicyViolation>;
264
+
265
+ export { type Analysis, type Category, DEFAULT_RULES, type Decision, type Dep, type DepsOptions, type DepsOverlay, type Effect, type EffectAtom, type EffectDecl, type EffectEnv, type EffectRow, type EffectRules, type EffectScheme, type EffectVar, type EffectViolation, type InferOptions, type Overlay, type OverlayCtx, type OverlayResult, type OverlayVisitor, PURE, PURE_ROW, PURE_SCHEME, type PolicyMatrix, type PolicyOptions, type PolicyViolation, type TypedTree, analyze, checkEffectPolicy, checkEffects, checkPolicies, deps, depsOverlay, effect, effectCeiling, effectEnv, effectsOverlay, effectsOverlayOf, excess, forwardEffect, freeVars, groundRow, inferEffects, instantiateScheme, join, joinRows, policiesOverlay, rowOf, rowOfVar, substituteRow, typedTreeOf, within };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import {forEachChild,rootScope,childScope,lookup}from'@tslite/core';var I=new Set,T=(...e)=>new Set(e),m={atoms:new Set,vars:new Set},M=(...e)=>({atoms:new Set(e),vars:new Set}),C=e=>({atoms:new Set,vars:new Set([e])}),R=e=>{let t=new Set,o=new Set;for(let n of e){for(let r of n.atoms)t.add(r);for(let r of n.vars)o.add(r);}return {atoms:t,vars:o}},Y=(e,t)=>{let o=new Set(e.atoms),n=new Set;for(let r of e.vars){let i=t.get(r);if(i){for(let s of i.atoms)o.add(s);for(let s of i.vars)n.add(s);}else n.add(r);}return {atoms:o,vars:n}},U={params:[],latent:m},V=(e,t)=>{let o=new Map;return e.params.forEach((n,r)=>{let i=t[r];n&&i&&o.set(n,i);}),Y(e.latent,o)},O=e=>new Set(e.atoms),ae=e=>{let t=new Set;for(let o of e)for(let n of o)t.add(n);return t},fe=(e,t)=>{for(let o of e)if(!t.has(o))return false;return true},j=(e,t)=>[...e].filter(o=>!t.has(o));var de=e=>{let t={};for(let[o,n]of Object.entries(e))t[o]=new Set(n);return t},pe=(e,t,o=[])=>{let n=new Array(t).fill(null),r="\u03B5";return n[e]=r,{params:n,latent:{atoms:new Set(o),vars:new Set([r])}}},L={nodeEffects:{ForStatement:"loops",ForInStatement:"loops",ForOfStatement:"loops",WhileStatement:"loops",DoWhileStatement:"loops",AssignmentExpression:"mutates",UpdateExpression:"mutates"}},ye=new Set(["ArrowFunctionExpression","FunctionExpression","FunctionDeclaration"]),h=e=>ye.has(e.type),p=e=>e,ue=e=>p(e).body,me=e=>p(e).params??[],S=e=>e&&e.type==="Identifier"?p(e).name:void 0,Ee=e=>{let t=p(e).object,o=p(e).property,n=S(t);if(!n)return null;let r=p(e).computed?void 0:S(o);return {path:r?`${n}.${r}`:n,root:n}},ge=e=>typeof e=="object"&&e!==null&&"latent"in e,$=(e,t)=>ge(e)?V(e,t):{atoms:new Set(e),vars:new Set},J=(e,t)=>{let o=0;return {rules:e,env:t,schemeOf:new Map,active:new Set,fresh:()=>`\u03B5${o++}`}},Q=e=>{let t={};for(let[o,n]of Object.entries(e))t[o]={kind:"capability",decl:n};return rootScope(t)};function x(e,t,o){let n=o.schemeOf.get(e);if(n)return n;if(o.active.has(e))return U;o.active.add(e);let r=me(e),i=r.map(v=>v.type==="Identifier"?o.fresh():null),s={};r.forEach((v,D)=>{let K=S(v);K&&i[D]&&(s[K]={kind:"param",var:i[D]});});let a=childScope(t,s),f=ue(e),u=f?E(f,a,o):m,d={params:i,latent:u};return o.active.delete(e),o.schemeOf.set(e,d),d}function _(e,t,o){x(e,t,o);}function E(e,t,o){let n=o.rules.nodeEffects[e.type],r=n?M(n):m;if(h(e))return _(e,t,o),r;if(e.type==="CallExpression"||e.type==="NewExpression")return R([r,we(e,t,o)]);if(e.type==="BlockStatement"||e.type==="Program"){let s=p(e).body??[];return R([r,ve(s,t,o)])}let i=[r];return forEachChild(e,s=>i.push(E(s,t,o))),R(i)}function ve(e,t,o){let n=t,r={};for(let s of e)if(s.type==="FunctionDeclaration"){let a=S(p(s).id);a&&(r[a]={kind:"fn",node:s,scope:n});}Object.keys(r).length&&(n=childScope(n,r));for(let s in r)r[s].kind==="fn"&&(r[s].scope=n);for(let s in r){let a=r[s];a.kind==="fn"&&_(a.node,n,o);}let i=[];for(let s of e)if(s.type!=="FunctionDeclaration"){if(s.type==="VariableDeclaration"){let a=p(s).declarations??[];for(let f of a){let u=p(f).id,d=p(f).init,v=S(u);if(v&&d&&h(d)){let D=childScope(n,{[v]:{kind:"fn",node:d,scope:n}});_(d,D,o),n=D;}else d&&i.push(E(d,n,o)),v&&(n=childScope(n,{[v]:{kind:"value"}}));}continue}i.push(E(s,n,o));}return R(i)}function we(e,t,o){let n=p(e).callee,r=p(e).arguments??[],i=r.map(f=>h(f)?m:E(f,t,o)),s=r.map(f=>Re(f,t,o)),a=Se(n,s,t,o);return R([a,...i])}function Re(e,t,o){if(h(e))return x(e,t,o).latent;if(e.type==="Identifier"){let n=lookup(t,S(e));if(!n)return null;if(n.kind==="fn")return x(n.node,n.scope,o).latent;if(n.kind==="param")return C(n.var);if(n.kind==="capability")return $(n.decl,[])}return null}function Se(e,t,o,n){if(!e)return m;if(e.type==="Identifier"){let r=lookup(o,S(e));if(!r)return m;switch(r.kind){case "capability":return $(r.decl,t);case "fn":return V(x(r.node,r.scope,n),t);case "param":return C(r.var);case "value":return m}}if(h(e))return V(x(e,o,n),t);if(e.type==="MemberExpression"){let r=Ee(e),i=r&&(n.env[r.path]??n.env[r.root]);if(i)return $(i,t)}return E(e,o,n)}function W(e,t={}){let o=t.env??{},n=J(t.rules??L,o),r=Q(o);if(t.root&&t.root!==e){E(t.root,r,n);let i=n.schemeOf.get(e);if(i)return O(i.latent)}return h(e)?O(x(e,r,n).latent):O(E(e,r,n))}function z(e,t={}){let o=t.env??{},n=J(t.rules??L,o);E(e,Q(o),n);let r=new Map;for(let[i,s]of n.schemeOf)r.set(i,O(s.latent));return r}var X=e=>e.size?`{ ${[...e].sort().join(", ")} }`:"pure";function G(e,t,o){let n=W(e,o),r=j(n,t);return r.length===0?[]:[{node:e,declared:t,inferred:n,excess:r,message:`Effect contract violated: declared ${X(t)} but infers ${X(n)} \u2014 not allowed: ${r.join(", ")}.`}]}function Z(e){switch(e){case "pure":return I;case "io":return T("io");case "any":return null}}function Be(e,t,o){let n=Z(t);return n===null?[]:G(e,n,o)}var b=e=>e,B=e=>e&&e.type==="Identifier"?b(e).name:void 0,N=(e,t)=>b(e)[t],Ne=new Set(["ArrowFunctionExpression","FunctionExpression","FunctionDeclaration"]),F=e=>Ne.has(e.type);function Oe(e){if(!e)return null;if(e.type==="Identifier"){let t=B(e);return {path:t,root:t}}if(e.type==="MemberExpression"){let t=b(e).object,o=b(e).property,n=b(e).computed,r=B(t);if(!r)return null;let i=n?void 0:B(o);return {path:i?`${r}.${i}`:r,root:r}}return null}function te(e){let{categories:t,matrix:o}=e,n=o.defaultPolicy??"allow",r=s=>t[s.path]??t[s.root],i=(s,a)=>o.rules[s]?.[a]??n;return (s,a)=>{if(!a)return null;let f=t[a];if(f===void 0)return null;let u=Oe(N(s,"callee"));if(!u)return null;let d=r(u);return d===void 0||i(f,d)!=="deny"?null:{call:s,from:a,to:u.path,fromCategory:f,toCategory:d,message:`Policy violated: ${f} '${a}' must not call ${d} '${u.path}' (${f} \u2192 ${d} is deny).`}}}var oe=e=>e.type==="CallExpression"||e.type==="NewExpression";function xe(e,t){let o=te(t),n=[],r=(i,s)=>{if(oe(i)){let a=o(i,s);a&&n.push(a);}if(i.type==="VariableDeclaration"){for(let a of b(i).declarations??[]){let f=N(a,"init");if(!f)continue;let u=F(f)?B(N(a,"id"))??s:s;r(f,u);}return}if(i.type==="FunctionDeclaration"){let a=B(N(i,"id"))??s;forEachChild(i,f=>r(f,a));return}forEachChild(i,a=>r(a,s));};return r(e,null),n}function ne(e){let t=te(e),o=new Map,n=[],r=()=>n.length?n[n.length-1]:null,i=null;return {enter(s){if(s.type==="VariableDeclarator"){let a=N(s,"init"),f=B(N(s,"id"));a&&F(a)&&f&&(i={name:f,fn:a});}if(F(s)){let a=i&&i.fn===s?i.name:s.type==="FunctionDeclaration"?B(N(s,"id"))??r():r();n.push(a),i=null;}if(oe(s)){let a=t(s,r());a&&o.set(s,a);}},exit(s){F(s)&&n.pop();},result:()=>o}}var w=e=>e,be=e=>e&&e.type==="Identifier"?w(e).name:void 0,c=(e,t)=>w(e)[t],g=(e,t)=>w(e)[t]??[];function y(e,t){if(e)switch(e.type){case "Identifier":t.add(w(e).name);return;case "AssignmentPattern":y(c(e,"left"),t);return;case "RestElement":y(c(e,"argument"),t);return;case "ArrayPattern":for(let o of g(e,"elements"))y(o,t);return;case "ObjectPattern":for(let o of g(e,"properties"))o.type==="RestElement"?y(c(o,"argument"),t):y(c(o,"value"),t);return}}function ke(e){let t=new Set;for(let o of e)if(o.type==="VariableDeclaration")for(let n of g(o,"declarations"))y(c(n,"id"),t);else if(o.type==="FunctionDeclaration"){let n=be(c(o,"id"));n&&t.add(n);}return t}var l=(e,t)=>{for(let o of t)e.add(o);},k=(e,t)=>{for(let o of t)e.delete(o);},De=new Set,re=e=>new Set(e);function H(e,t){let o=new Set;switch(e.type){case "Identifier":o.add(w(e).name);break;case "MemberExpression":{l(o,t(c(e,"object"))),w(e).computed&&l(o,t(c(e,"property")));break}case "Property":{w(e).computed&&l(o,t(c(e,"key"))),l(o,t(c(e,"value")));break}case "ArrowFunctionExpression":case "FunctionExpression":case "FunctionDeclaration":{let n=g(e,"params"),r=new Set;for(let i of n)y(i,r),i.type==="AssignmentPattern"&&l(o,t(c(i,"right")));l(o,t(c(e,"body"))),k(o,r);break}case "BlockStatement":case "Program":{let n=g(e,"body");for(let r of n)l(o,t(r));k(o,ke(n));break}case "VariableDeclarator":{l(o,t(c(e,"init")));let n=c(e,"id");n&&n.type!=="Identifier"&&A(n,o,t);break}case "CatchClause":{let n=new Set;y(c(e,"param"),n),l(o,t(c(e,"body"))),k(o,n);break}case "LabeledStatement":l(o,t(c(e,"body")));break;case "BreakStatement":case "ContinueStatement":break;case "ForInStatement":case "ForOfStatement":{let n=c(e,"left");l(o,t(c(e,"right")));let r=new Set;if(n.type==="VariableDeclaration")for(let s of g(n,"declarations"))y(c(s,"id"),r);else y(n,r);let i=re(t(c(e,"body")));k(i,r),l(o,i);break}case "ForStatement":{let n=c(e,"init"),r=new Set;if(n){if(n.type==="VariableDeclaration")for(let i of g(n,"declarations"))y(c(i,"id"),r);l(o,t(n));}for(let i of ["test","update","body"]){let s=re(t(c(e,i)));k(s,r),l(o,s);}break}default:forEachChild(e,n=>l(o,t(n)));break}return o}function se(e,t={}){let o=new Map,n=new Set(t.globals??[]),r=i=>{if(!i)return De;let s=H(i,r);return k(s,n),o.set(i,s),s};return r(e),o}function A(e,t,o){if(e.type==="AssignmentPattern")l(t,o(c(e,"right"))),A(c(e,"left"),t,o);else if(e.type==="ArrayPattern")for(let n of g(e,"elements"))n&&A(n,t,o);else if(e.type==="ObjectPattern")for(let n of g(e,"properties")){w(n).computed&&l(t,o(c(n,"key")));let r=c(n,"value")??c(n,"argument");r&&A(r,t,o);}}function Ve(e,t){return se(e,t).get(e)??new Set}function Pe(e,t){return {root:e,diagnostics:t.diagnostics,typeOf:o=>t.overlay.get(o)?.type,scopeOf:o=>t.overlay.get(o)?.scope}}function Fe(e,t){let o=new Map,n={tree:e,read:s=>o.get(s.name)},r=t.filter(s=>!!s.visitor),i=t.filter(s=>!s.visitor&&s.run);if(r.length){let s=a=>{for(let f of r)f.visitor.enter?.(a,n);forEachChild(a,s);for(let f=r.length-1;f>=0;f--)r[f].visitor.exit?.(a,n);};s(e.root);for(let a of r)o.set(a.name,a.visitor.result());}for(let s of i)o.set(s.name,s.run(n));return o}var ie=new Set,Ae=(e,t)=>({name:"effects",run:o=>z(o.tree.root,{env:e,rules:t})}),Ie=e=>({name:"deps",visitor:(()=>{let t=new Set(e?.globals??[]),o=new Map;return {exit(n){let r=H(n,i=>i?o.get(i)??ie:ie);for(let i of t)r.delete(i);o.set(n,r);},result:()=>o}})()}),Te=e=>({name:"policies",visitor:ne(e)});export{L as DEFAULT_RULES,I as PURE,m as PURE_ROW,U as PURE_SCHEME,Fe as analyze,Be as checkEffectPolicy,G as checkEffects,xe as checkPolicies,se as deps,Ie as depsOverlay,T as effect,Z as effectCeiling,de as effectEnv,Ae as effectsOverlay,z as effectsOverlayOf,j as excess,pe as forwardEffect,Ve as freeVars,O as groundRow,W as inferEffects,V as instantiateScheme,ae as join,R as joinRows,Te as policiesOverlay,M as rowOf,C as rowOfVar,Y as substituteRow,Pe as typedTreeOf,fe as within};
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@tslite/analysis",
3
+ "version": "0.1.0",
4
+ "description": "TSLite analysis — behavior/effect overlays over the AST (effect inference + propagation + contracts)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/andersondrosa/tslite.git",
26
+ "directory": "packages/analysis"
27
+ },
28
+ "keywords": [
29
+ "ast",
30
+ "effects",
31
+ "effect-system",
32
+ "analysis",
33
+ "purity",
34
+ "typescript"
35
+ ],
36
+ "author": "Anderson D. Rosa <andersondrosa@outlook.com>",
37
+ "license": "MIT",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "@tslite/core": "0.1.0",
43
+ "@tslite/checker": "0.1.0",
44
+ "@tslite/capability": "0.1.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "dev": "tsup --watch",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "test:coverage": "vitest run --coverage",
52
+ "typecheck": "tsc --noEmit",
53
+ "lint": "eslint src tests",
54
+ "lint:fix": "eslint src tests --fix",
55
+ "clean": "rm -rf dist",
56
+ "format": "prettier --write \"src/**/*.ts\"",
57
+ "format:check": "prettier --check \"src/**/*.ts\""
58
+ }
59
+ }