binja 0.9.0 → 0.9.2

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.
@@ -0,0 +1,115 @@
1
+ // @bun
2
+ var $=import.meta.require;var F={and:"AND",or:"OR",not:"NOT",true:"NAME",false:"NAME",True:"NAME",False:"NAME",None:"NAME",none:"NAME",is:"NAME",in:"NAME"};var w={red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",gray:"\x1B[90m",bold:"\x1B[1m",dim:"\x1B[2m",reset:"\x1B[0m"},W=process.stdout?.isTTY!==!1;function _(A,E){return W?`${w[A]}${E}${w.reset}`:E}class B extends Error{line;column;source;templateName;suggestion;constructor(A,E){let O=V("TemplateSyntaxError",A,E);super(O);this.name="TemplateSyntaxError",this.line=E.line,this.column=E.column,this.source=E.source,this.templateName=E.templateName,this.suggestion=E.suggestion}}function V(A,E,O){let I=[],P=O.templateName?`${O.templateName}:${O.line}:${O.column}`:`line ${O.line}, column ${O.column}`;if(I.push(`${_("red",_("bold",A))}: ${E} at ${_("cyan",P)}`),O.source)I.push(""),I.push(j(O.source,O.line,O.column));if(O.suggestion)I.push(""),I.push(`${_("yellow","Did you mean")}: ${_("cyan",O.suggestion)}?`);if(O.availableOptions&&O.availableOptions.length>0){I.push("");let R=O.availableOptions.slice(0,8),M=O.availableOptions.length>8?` ${_("gray",`... and ${O.availableOptions.length-8} more`)}`:"";I.push(`${_("gray","Available")}: ${R.join(", ")}${M}`)}return I.join(`
3
+ `)}function j(A,E,O){let I=A.split(`
4
+ `),P=[],R=Math.max(1,E-2),M=Math.min(I.length,E+1),L=String(M).length;for(let N=R;N<=M;N++){let C=I[N-1]||"",K=String(N).padStart(L," ");if(N===E){P.push(`${_("red"," \u2192")} ${_("gray",K)} ${_("dim","\u2502")} ${C}`);let Z=" ".repeat(L+4+Math.max(0,O-1)),q=_("red","^");P.push(`${Z}${q}`)}else P.push(` ${_("gray",K)} ${_("dim","\u2502")} ${_("gray",C)}`)}return P.join(`
5
+ `)}class D{state;variableStart;variableEnd;blockStart;blockEnd;commentStart;commentEnd;constructor(A,E={}){this.state={source:A,pos:0,line:1,column:1,tokens:[]},this.variableStart=E.variableStart??"{{",this.variableEnd=E.variableEnd??"}}",this.blockStart=E.blockStart??"{%",this.blockEnd=E.blockEnd??"%}",this.commentStart=E.commentStart??"{#",this.commentEnd=E.commentEnd??"#}"}tokenize(){while(!this.isAtEnd())this.scanToken();return this.addToken("EOF",""),this.state.tokens}scanToken(){if(this.match(this.variableStart)){this.addToken("VARIABLE_START",this.variableStart),this.scanExpression(this.variableEnd,"VARIABLE_END");return}if(this.match(this.blockStart)){let A=this.peek()==="-";if(A)this.advance();let E=this.state.pos;if(this.skipWhitespace(),this.checkWord("raw")||this.checkWord("verbatim")){let O=this.checkWord("raw")?"raw":"verbatim";this.scanRawBlock(O,A);return}this.state.pos=E,this.addToken("BLOCK_START",this.blockStart+(A?"-":"")),this.scanExpression(this.blockEnd,"BLOCK_END");return}if(this.match(this.commentStart)){this.scanComment();return}this.scanText()}checkWord(A){let E=this.state.pos;for(let I=0;I<A.length;I++)if(this.state.source[E+I]?.toLowerCase()!==A[I])return!1;let O=this.state.source[E+A.length];return!O||!this.isAlphaNumeric(O)}scanRawBlock(A,E){let O=this.state.line,I=this.state.column;for(let M=0;M<A.length;M++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new B(`Expected '${this.blockEnd}' after '${A}'`,{line:this.state.line,column:this.state.column,source:this.state.source});let P=`end${A}`,R=this.state.pos;while(!this.isAtEnd()){if(this.check(this.blockStart)){let M=this.state.pos,L=this.state.line,N=this.state.column;if(this.match(this.blockStart),this.peek()==="-")this.advance();if(this.skipWhitespace(),this.checkWord(P)){let C=this.state.source.slice(R,M);if(C.length>0)this.state.tokens.push({type:"TEXT",value:C,line:O,column:I});for(let K=0;K<P.length;K++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new B(`Expected '${this.blockEnd}' after '${P}'`,{line:this.state.line,column:this.state.column,source:this.state.source});return}this.state.pos=M,this.state.line=L,this.state.column=N}if(this.peek()===`
6
+ `)this.state.line++,this.state.column=0;this.advance()}throw new B(`Unclosed '${A}' block`,{line:O,column:I,source:this.state.source,suggestion:`Add {% end${A} %} to close the block`})}scanText(){let A=this.state.pos,E=this.state.line,O=this.state.column,I=this.state.source;while(!this.isAtEnd()){let P=I.indexOf("{",this.state.pos);if(P===-1){this.advanceToEnd();break}if(P>this.state.pos)this.advanceTo(P);if(this.check(this.variableStart)||this.check(this.blockStart)||this.check(this.commentStart))break;if(this.peek()===`
7
+ `)this.state.line++,this.state.column=0;this.state.pos++,this.state.column++}if(this.state.pos>A){let P=I.slice(A,this.state.pos);this.state.tokens.push({type:"TEXT",value:P,line:E,column:O})}}advanceTo(A){let E=this.state.source;while(this.state.pos<A){if(E[this.state.pos]===`
8
+ `)this.state.line++,this.state.column=0;this.state.pos++,this.state.column++}}advanceToEnd(){let A=this.state.source,E=A.length;while(this.state.pos<E){if(A[this.state.pos]===`
9
+ `)this.state.line++,this.state.column=0;this.state.pos++,this.state.column++}}scanExpression(A,E){this.skipWhitespace();while(!this.isAtEnd()){if(this.skipWhitespace(),this.peek()==="-"&&this.check(A,1))this.advance();if(this.match(A)){this.addToken(E,A);return}this.scanExpressionToken()}throw new B("Unclosed template tag",{line:this.state.line,column:this.state.column,source:this.state.source,suggestion:`Add closing delimiter '${A}'`})}scanExpressionToken(){if(this.skipWhitespace(),this.isAtEnd())return;let A=this.peek();if(A==='"'||A==="'"){this.scanString(A);return}if(this.isDigit(A)){this.scanNumber();return}if(this.isAlpha(A)||A==="_"){this.scanIdentifier();return}this.scanOperator()}scanString(A){this.advance();let E=this.state.pos;while(!this.isAtEnd()&&this.peek()!==A){if(this.peek()==="\\"&&this.peekNext()===A)this.advance();if(this.peek()===`
10
+ `)this.state.line++,this.state.column=0;this.advance()}if(this.isAtEnd())throw new B("Unterminated string literal",{line:this.state.line,column:this.state.column,source:this.state.source,suggestion:`Add closing quote '${A}'`});let O=this.state.source.slice(E,this.state.pos);this.advance(),this.addToken("STRING",O)}scanNumber(){let A=this.state.pos;while(this.isDigit(this.peek()))this.advance();if(this.peek()==="."&&this.isDigit(this.peekNext())){this.advance();while(this.isDigit(this.peek()))this.advance()}let E=this.state.source.slice(A,this.state.pos);this.addToken("NUMBER",E)}scanIdentifier(){let A=this.state.pos;while(this.isAlphaNumeric(this.peek())||this.peek()==="_")this.advance();let E=this.state.source.slice(A,this.state.pos),O=F[E]??"NAME";this.addToken(O,E)}scanOperator(){let A=this.advance();switch(A){case".":this.addToken("DOT",A);break;case",":this.addToken("COMMA",A);break;case":":this.addToken("COLON",A);break;case"|":this.addToken("PIPE",A);break;case"(":this.addToken("LPAREN",A);break;case")":this.addToken("RPAREN",A);break;case"[":this.addToken("LBRACKET",A);break;case"]":this.addToken("RBRACKET",A);break;case"{":this.addToken("LBRACE",A);break;case"}":this.addToken("RBRACE",A);break;case"+":this.addToken("ADD",A);break;case"-":this.addToken("SUB",A);break;case"*":this.addToken("MUL",A);break;case"/":this.addToken("DIV",A);break;case"%":this.addToken("MOD",A);break;case"~":this.addToken("TILDE",A);break;case"=":if(this.match("="))this.addToken("EQ","==");else this.addToken("ASSIGN","=");break;case"!":if(this.match("="))this.addToken("NE","!=");else throw new B("Unexpected character '!'",{line:this.state.line,column:this.state.column-1,source:this.state.source,suggestion:"Use '!=' for not-equal comparison or 'not' for negation"});break;case"<":if(this.match("="))this.addToken("LE","<=");else this.addToken("LT","<");break;case">":if(this.match("="))this.addToken("GE",">=");else this.addToken("GT",">");break;case"?":if(this.match("?"))this.addToken("NULLCOALESCE","??");else this.addToken("QUESTION","?");break;default:if(!this.isWhitespace(A))throw new B(`Unexpected character '${A}'`,{line:this.state.line,column:this.state.column-1,source:this.state.source})}}scanComment(){while(!this.isAtEnd()&&!this.check(this.commentEnd)){if(this.peek()===`
11
+ `)this.state.line++,this.state.column=0;this.advance()}if(!this.isAtEnd())this.match(this.commentEnd)}isAtEnd(){return this.state.pos>=this.state.source.length}peek(){if(this.isAtEnd())return"\x00";return this.state.source[this.state.pos]}peekNext(){if(this.state.pos+1>=this.state.source.length)return"\x00";return this.state.source[this.state.pos+1]}advance(){let A=this.state.source[this.state.pos];return this.state.pos++,this.state.column++,A}match(A,E=0){let O=this.state.source,I=this.state.pos+E,P=A.length;if(I+P>O.length)return!1;for(let R=0;R<P;R++)if(O[I+R]!==A[R])return!1;if(E===0)this.state.pos+=P,this.state.column+=P;return!0}check(A,E=0){let O=this.state.source,I=this.state.pos+E,P=A.length;if(I+P>O.length)return!1;for(let R=0;R<P;R++)if(O[I+R]!==A[R])return!1;return!0}skipWhitespace(){while(!this.isAtEnd()&&this.isWhitespace(this.peek())){if(this.peek()===`
12
+ `)this.state.line++,this.state.column=0;this.advance()}}isWhitespace(A){return A===" "||A==="\t"||A===`
13
+ `||A==="\r"}isDigit(A){let E=A.charCodeAt(0);return E>=48&&E<=57}isAlpha(A){let E=A.charCodeAt(0);return E>=97&&E<=122||E>=65&&E<=90}isAlphaNumeric(A){let E=A.charCodeAt(0);return E>=48&&E<=57||E>=97&&E<=122||E>=65&&E<=90}addToken(A,E){this.state.tokens.push({type:A,value:E,line:this.state.line,column:this.state.column-E.length})}}class Q{tokens;current=0;source;constructor(A,E){this.tokens=A,this.source=E}parse(){let A=[];while(!this.isAtEnd()){let E=this.parseStatement();if(E)A.push(E)}return{type:"Template",body:A,line:1,column:1}}parseStatement(){switch(this.peek().type){case"TEXT":return this.parseText();case"VARIABLE_START":return this.parseOutput();case"BLOCK_START":return this.parseBlock();case"EOF":return null;default:return this.advance(),null}}parseText(){let A=this.advance();return{type:"Text",value:A.value,line:A.line,column:A.column}}parseOutput(){let A=this.advance(),E=this.parseExpression();return this.expect("VARIABLE_END"),{type:"Output",expression:E,line:A.line,column:A.column}}parseBlock(){let A=this.advance(),E=this.expect("NAME");switch(E.value){case"if":return this.parseIf(A);case"for":return this.parseFor(A);case"block":return this.parseBlockTag(A);case"extends":return this.parseExtends(A);case"include":return this.parseInclude(A);case"set":return this.parseSet(A);case"with":return this.parseWith(A);case"load":return this.parseLoad(A);case"url":return this.parseUrl(A);case"static":return this.parseStatic(A);case"now":return this.parseNow(A);case"comment":return this.parseComment(A);case"spaceless":case"autoescape":case"verbatim":return this.parseSimpleBlock(A,E.value);case"cycle":return this.parseCycle(A);case"firstof":return this.parseFirstof(A);case"ifchanged":return this.parseIfchanged(A);case"regroup":return this.parseRegroup(A);case"widthratio":return this.parseWidthratio(A);case"lorem":return this.parseLorem(A);case"csrf_token":return this.parseCsrfToken(A);case"debug":return this.parseDebug(A);case"templatetag":return this.parseTemplatetag(A);case"ifequal":return this.parseIfequal(A,!1);case"ifnotequal":return this.parseIfequal(A,!0);default:return this.skipToBlockEnd(),null}}parseIf(A){let E=this.parseExpression();this.expect("BLOCK_END");let O=[],I=[],P=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let R=this.parseStatement();if(R)O.push(R)}while(this.checkBlockTag("elif")){this.advance(),this.advance();let R=this.parseExpression();this.expect("BLOCK_END");let M=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let L=this.parseStatement();if(L)M.push(L)}I.push({test:R,body:M})}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endif"))break;let R=this.parseStatement();if(R)P.push(R)}}return this.expectBlockTag("endif"),{type:"If",test:E,body:O,elifs:I,else_:P,line:A.line,column:A.column}}parseFor(A){let E,O=this.expect("NAME").value;if(this.check("COMMA")){let N=[O];while(this.match("COMMA"))N.push(this.expect("NAME").value);E=N}else E=O;let I=this.expect("NAME");if(I.value!=="in")throw this.error(`Expected 'in' in for loop, got '${I.value}'`);let P=this.parseExpression(),R=this.check("NAME")&&this.peek().value==="recursive";if(R)this.advance();this.expect("BLOCK_END");let M=[],L=[];while(!this.isAtEnd()){if(this.checkBlockTag("empty")||this.checkBlockTag("else")||this.checkBlockTag("endfor"))break;let N=this.parseStatement();if(N)M.push(N)}if(this.checkBlockTag("empty")||this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endfor"))break;let N=this.parseStatement();if(N)L.push(N)}}return this.expectBlockTag("endfor"),{type:"For",target:E,iter:P,body:M,else_:L,recursive:R,line:A.line,column:A.column}}parseBlockTag(A){let E=this.expect("NAME").value,O=this.check("NAME")&&this.peek().value==="scoped";if(O)this.advance();this.expect("BLOCK_END");let I=[];while(!this.isAtEnd()){if(this.checkBlockTag("endblock"))break;let P=this.parseStatement();if(P)I.push(P)}if(this.advance(),this.advance(),this.check("NAME"))this.advance();return this.expect("BLOCK_END"),{type:"Block",name:E,body:I,scoped:O,line:A.line,column:A.column}}parseExtends(A){let E=this.parseExpression();return this.expect("BLOCK_END"),{type:"Extends",template:E,line:A.line,column:A.column}}parseInclude(A){let E=this.parseExpression(),O=null,I=!1,P=!1;while(this.check("NAME")){let R=this.peek().value;if(R==="ignore"&&this.peekNext()?.value==="missing")this.advance(),this.advance(),P=!0;else if(R==="with")this.advance(),O=this.parseKeywordArgs();else if(R==="only")this.advance(),I=!0;else if(R==="without"){if(this.advance(),this.check("NAME")&&this.peek().value==="context")this.advance(),I=!0}else break}return this.expect("BLOCK_END"),{type:"Include",template:E,context:O,only:I,ignoreMissing:P,line:A.line,column:A.column}}parseSet(A){let E=this.expect("NAME").value;this.expect("ASSIGN");let O=this.parseExpression();return this.expect("BLOCK_END"),{type:"Set",target:E,value:O,line:A.line,column:A.column}}parseWith(A){let E=[];do{let I=this.expect("NAME").value;this.expect("ASSIGN");let P=this.parseExpression();E.push({target:I,value:P})}while(this.match("COMMA")||this.check("NAME")&&this.peekNext()?.type==="ASSIGN");this.expect("BLOCK_END");let O=[];while(!this.isAtEnd()){if(this.checkBlockTag("endwith"))break;let I=this.parseStatement();if(I)O.push(I)}return this.expectBlockTag("endwith"),{type:"With",assignments:E,body:O,line:A.line,column:A.column}}parseLoad(A){let E=[];while(this.check("NAME"))E.push(this.advance().value);return this.expect("BLOCK_END"),{type:"Load",names:E,line:A.line,column:A.column}}parseUrl(A){let E=this.parseExpression(),O=[],I={},P=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),P=this.expect("NAME").value;break}if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let R=this.advance().value;this.advance(),I[R]=this.parseExpression()}else O.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Url",name:E,args:O,kwargs:I,asVar:P,line:A.line,column:A.column}}parseStatic(A){let E=this.parseExpression(),O=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),O=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Static",path:E,asVar:O,line:A.line,column:A.column}}parseNow(A){let E=this.parseExpression(),O=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),O=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Now",format:E,asVar:O,line:A.line,column:A.column}}parseComment(A){this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endcomment"))break;this.advance()}return this.expectBlockTag("endcomment"),null}parseSimpleBlock(A,E){this.skipToBlockEnd();let O=`end${E}`;while(!this.isAtEnd()){if(this.checkBlockTag(O))break;this.advance()}if(this.checkBlockTag(O))this.advance(),this.advance(),this.expect("BLOCK_END");return null}parseCycle(A){let E=[],O=null,I=!1;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){if(this.advance(),O=this.expect("NAME").value,this.check("NAME")&&this.peek().value==="silent")this.advance(),I=!0;break}E.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Cycle",values:E,asVar:O,silent:I,line:A.line,column:A.column}}parseFirstof(A){let E=[],O=null,I=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),I=this.expect("NAME").value;break}E.push(this.parseExpression())}if(E.length>0){let P=E[E.length-1];if(P.type==="Literal"&&typeof P.value==="string")O=E.pop()}return this.expect("BLOCK_END"),{type:"Firstof",values:E,fallback:O,asVar:I,line:A.line,column:A.column}}parseIfchanged(A){let E=[];while(!this.check("BLOCK_END"))E.push(this.parseExpression());this.expect("BLOCK_END");let O=[],I=[];while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag("endifchanged"))break;let P=this.parseStatement();if(P)O.push(P)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endifchanged"))break;let P=this.parseStatement();if(P)I.push(P)}}return this.expectBlockTag("endifchanged"),{type:"Ifchanged",values:E,body:O,else_:I,line:A.line,column:A.column}}parseRegroup(A){let E=this.parseExpression();this.expectName("by");let O=this.expect("NAME").value;this.expectName("as");let I=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Regroup",target:E,key:O,asVar:I,line:A.line,column:A.column}}parseWidthratio(A){let E=this.parseExpression(),O=this.parseExpression(),I=this.parseExpression(),P=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),P=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Widthratio",value:E,maxValue:O,maxWidth:I,asVar:P,line:A.line,column:A.column}}parseLorem(A){let E=null,O="p",I=!1;if(this.check("NUMBER"))E={type:"Literal",value:parseInt(this.advance().value,10),line:A.line,column:A.column};if(this.check("NAME")){let P=this.peek().value.toLowerCase();if(P==="w"||P==="p"||P==="b")O=P,this.advance()}if(this.check("NAME")&&this.peek().value==="random")I=!0,this.advance();return this.expect("BLOCK_END"),{type:"Lorem",count:E,method:O,random:I,line:A.line,column:A.column}}parseCsrfToken(A){return this.expect("BLOCK_END"),{type:"CsrfToken",line:A.line,column:A.column}}parseDebug(A){return this.expect("BLOCK_END"),{type:"Debug",line:A.line,column:A.column}}parseTemplatetag(A){let E=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Templatetag",tagType:E,line:A.line,column:A.column}}parseIfequal(A,E){let O=this.parseExpression(),I=this.parseExpression();this.expect("BLOCK_END");let P={type:"Compare",left:O,ops:[{operator:E?"!=":"==",right:I}],line:A.line,column:A.column},R=[],M=[],L=E?"endifnotequal":"endifequal";while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag(L))break;let N=this.parseStatement();if(N)R.push(N)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag(L))break;let N=this.parseStatement();if(N)M.push(N)}}return this.expectBlockTag(L),{type:"If",test:P,body:R,elifs:[],else_:M,line:A.line,column:A.column}}parseExpression(){return this.parseConditional()}parseConditional(){let A=this.parseOr();if(this.check("NAME")&&this.peek().value==="if"){this.advance();let E=this.parseOr();this.expectName("else");let O=this.parseConditional();A={type:"Conditional",test:E,trueExpr:A,falseExpr:O,line:A.line,column:A.column}}return A}parseOr(){let A=this.parseAnd();while(this.check("OR")||this.check("NAME")&&this.peek().value==="or"){this.advance();let E=this.parseAnd();A={type:"BinaryOp",operator:"or",left:A,right:E,line:A.line,column:A.column}}return A}parseAnd(){let A=this.parseNot();while(this.check("AND")||this.check("NAME")&&this.peek().value==="and"){this.advance();let E=this.parseNot();A={type:"BinaryOp",operator:"and",left:A,right:E,line:A.line,column:A.column}}return A}parseNot(){if(this.check("NOT")||this.check("NAME")&&this.peek().value==="not"){let A=this.advance();return{type:"UnaryOp",operator:"not",operand:this.parseNot(),line:A.line,column:A.column}}return this.parseCompare()}parseCompare(){let A=this.parseAddSub(),E=[];while(!0){let O=null;if(this.match("EQ"))O="==";else if(this.match("NE"))O="!=";else if(this.match("LT"))O="<";else if(this.match("GT"))O=">";else if(this.match("LE"))O="<=";else if(this.match("GE"))O=">=";else if(this.check("NAME")){let P=this.peek().value;if(P==="in")this.advance(),O="in";else if(P==="not"&&this.peekNext()?.value==="in")this.advance(),this.advance(),O="not in";else if(P==="is"){this.advance();let R=this.check("NOT");if(R)this.advance();let L=this.expect("NAME").value,N=[];if(this.match("LPAREN")){while(!this.check("RPAREN"))if(N.push(this.parseExpression()),!this.check("RPAREN"))this.expect("COMMA");this.expect("RPAREN")}A={type:"TestExpr",node:A,test:L,args:N,negated:R,line:A.line,column:A.column};continue}}if(!O)break;let I=this.parseAddSub();E.push({operator:O,right:I})}if(E.length===0)return A;return{type:"Compare",left:A,ops:E,line:A.line,column:A.column}}parseAddSub(){let A=this.parseMulDiv();while(this.check("ADD")||this.check("SUB")||this.check("TILDE")){let E=this.advance(),O=this.parseMulDiv();A={type:"BinaryOp",operator:E.value,left:A,right:O,line:A.line,column:A.column}}return A}parseMulDiv(){let A=this.parseUnary();while(this.check("MUL")||this.check("DIV")||this.check("MOD")){let E=this.advance(),O=this.parseUnary();A={type:"BinaryOp",operator:E.value,left:A,right:O,line:A.line,column:A.column}}return A}parseUnary(){if(this.check("SUB")||this.check("ADD")){let A=this.advance(),E=this.parseUnary();return{type:"UnaryOp",operator:A.value,operand:E,line:A.line,column:A.column}}return this.parseFilter()}parseFilter(){let A=this.parsePostfix();while(this.match("PIPE")){let E=this.expect("NAME").value,O=[],I={};if(this.match("COLON"))if(this.check("SUB")||this.check("ADD")){let P=this.advance(),R=this.parsePostfix();O.push({type:"UnaryOp",operator:P.value,operand:R,line:P.line,column:P.column})}else O.push(this.parsePostfix());else if(this.match("LPAREN")){while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let P=this.advance().value;this.advance(),I[P]=this.parseExpression()}else O.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN")}A={type:"FilterExpr",node:A,filter:E,args:O,kwargs:I,line:A.line,column:A.column}}return A}parsePostfix(){let A=this.parsePrimary();while(!0)if(this.match("DOT")){let E;if(this.check("NUMBER"))E=this.advance().value;else E=this.expect("NAME").value;A={type:"GetAttr",object:A,attribute:E,line:A.line,column:A.column}}else if(this.match("LBRACKET")){let E=this.parseExpression();this.expect("RBRACKET"),A={type:"GetItem",object:A,index:E,line:A.line,column:A.column}}else if(this.match("LPAREN")){let E=[],O={};while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let I=this.advance().value;this.advance(),O[I]=this.parseExpression()}else E.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN"),A={type:"FunctionCall",callee:A,args:E,kwargs:O,line:A.line,column:A.column}}else break;return A}parsePrimary(){let A=this.peek();if(this.match("STRING"))return{type:"Literal",value:A.value,line:A.line,column:A.column};if(this.match("NUMBER"))return{type:"Literal",value:A.value.includes(".")?parseFloat(A.value):parseInt(A.value,10),line:A.line,column:A.column};if(this.check("NAME")){let E=this.advance().value;if(E==="true"||E==="True")return{type:"Literal",value:!0,line:A.line,column:A.column};if(E==="false"||E==="False")return{type:"Literal",value:!1,line:A.line,column:A.column};if(E==="none"||E==="None"||E==="null")return{type:"Literal",value:null,line:A.line,column:A.column};return{type:"Name",name:E,line:A.line,column:A.column}}if(this.match("LPAREN")){let E=this.parseExpression();return this.expect("RPAREN"),E}if(this.match("LBRACKET")){let E=[];while(!this.check("RBRACKET"))if(E.push(this.parseExpression()),!this.check("RBRACKET"))this.expect("COMMA");return this.expect("RBRACKET"),{type:"Array",elements:E,line:A.line,column:A.column}}if(this.match("LBRACE")){let E=[];while(!this.check("RBRACE")){let O=this.parseExpression();this.expect("COLON");let I=this.parseExpression();if(E.push({key:O,value:I}),!this.check("RBRACE"))this.expect("COMMA")}return this.expect("RBRACE"),{type:"Object",pairs:E,line:A.line,column:A.column}}throw this.error(`Unexpected token: ${A.type} (${A.value})`)}parseKeywordArgs(){let A={};while(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let E=this.advance().value;this.advance(),A[E]=this.parseExpression()}return A}checkBlockTag(A){if(this.peek().type!=="BLOCK_START")return!1;let E=this.current+1;if(E>=this.tokens.length)return!1;let O=this.tokens[E];return O.type==="NAME"&&O.value===A}expectBlockTag(A){this.advance();let E=this.expect("NAME");if(E.value!==A)throw this.error(`Expected '${A}', got '${E.value}'`);this.expect("BLOCK_END")}expectName(A){let E=this.expect("NAME");if(E.value!==A)throw this.error(`Expected '${A}', got '${E.value}'`)}skipToBlockEnd(){while(!this.isAtEnd()&&!this.check("BLOCK_END"))this.advance();if(this.check("BLOCK_END"))this.advance()}isAtEnd(){return this.peek().type==="EOF"}peek(){return this.tokens[this.current]}peekNext(){if(this.current+1>=this.tokens.length)return null;return this.tokens[this.current+1]}advance(){if(!this.isAtEnd())this.current++;return this.tokens[this.current-1]}check(A){if(this.isAtEnd())return!1;return this.peek().type===A}match(A){if(this.check(A))return this.advance(),!0;return!1}expect(A){if(this.check(A))return this.advance();let E=this.peek();throw this.error(`Expected ${A}, got ${E.type} (${E.value})`)}error(A){let E=this.peek();return new B(A,{line:E.line,column:E.column,source:this.source})}}function G(A,E){let O=E||process.env.ANTHROPIC_API_KEY;return{name:"anthropic",async available(){return!!O},async analyze(I,P){let N=(await new(await import("@anthropic-ai/sdk")).default({apiKey:O}).messages.create({model:A||"claude-sonnet-4-20250514",max_tokens:1500,messages:[{role:"user",content:P.replace("{{TEMPLATE}}",I)}]})).content[0];if(N.type==="text")return N.text;throw Error("Unexpected response type from Anthropic")}}}function U(A,E){let O=E||process.env.OPENAI_API_KEY;return{name:"openai",async available(){return!!O},async analyze(I,P){return(await new(await import("openai")).default({apiKey:O}).chat.completions.create({model:A||"gpt-4o-mini",max_tokens:1500,messages:[{role:"user",content:P.replace("{{TEMPLATE}}",I)}]})).choices[0]?.message?.content||""}}}function S(A,E){let O=E||"http://localhost:11434";return{name:"ollama",async available(){try{return(await fetch(`${O}/api/tags`,{signal:AbortSignal.timeout(2000)})).ok}catch{return!1}},async analyze(I,P){let R=await fetch(`${O}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:A||"llama3.1",prompt:P.replace("{{TEMPLATE}}",I),stream:!1})});if(!R.ok)throw Error(`Ollama error: ${R.statusText}`);return(await R.json()).response}}}function Y(A,E){let O=E||process.env.GROQ_API_KEY;return{name:"groq",async available(){return!!O},async analyze(I,P){if(!O)throw Error("Groq API key not provided");let R=await fetch("https://api.groq.com/openai/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${O}`},body:JSON.stringify({model:A||"llama-3.1-70b-versatile",max_tokens:1500,messages:[{role:"user",content:P.replace("{{TEMPLATE}}",I)}]})});if(!R.ok){let L=await R.text();throw Error(`Groq error: ${L}`)}return(await R.json()).choices[0]?.message?.content||""}}}function h(A,E={}){switch(A){case"anthropic":return G(E.model,E.apiKey);case"openai":return U(E.model,E.apiKey);case"ollama":return S(E.model,E.ollamaUrl);case"groq":return Y(E.model,E.apiKey);default:throw Error(`Unknown provider: ${A}`)}}async function H(A={}){let E=[{name:"anthropic",create:()=>G(A.model,A.apiKey)},{name:"openai",create:()=>U(A.model,A.apiKey)},{name:"groq",create:()=>Y(A.model,A.apiKey)},{name:"ollama",create:()=>S(A.model,A.ollamaUrl)}];for(let{name:O,create:I}of E){let P=I();if(await P.available())return P}throw Error(`No AI provider available.
14
+
15
+ Configure one of the following:
16
+ - ANTHROPIC_API_KEY (Claude)
17
+ - OPENAI_API_KEY (GPT-4)
18
+ - GROQ_API_KEY (Llama, free tier)
19
+ - Ollama running locally (http://localhost:11434)
20
+
21
+ Install SDK if needed:
22
+ bun add @anthropic-ai/sdk # for Claude
23
+ bun add openai # for OpenAI`)}async function J(A={}){let E=A.provider||"auto";if(E==="auto")return H(A);let O=h(E,A);if(!await O.available())throw Error(`Provider '${E}' is not available. Check your API key or configuration.`);return O}function z(A){if(!A||A.length===0)return`Analyze this Jinja2/Django template for issues.
24
+
25
+ TEMPLATE:
26
+ \`\`\`jinja
27
+ {{TEMPLATE}}
28
+ \`\`\`
29
+
30
+ Check for:
31
+
32
+ 1. SECURITY
33
+ - XSS vulnerabilities (unescaped user input, |safe on untrusted data)
34
+ - Variables in onclick/onerror/javascript: without escapejs
35
+ - Sensitive data exposure (passwords, tokens, API keys)
36
+ - SQL/command injection patterns
37
+
38
+ 2. PERFORMANCE
39
+ - Heavy filters inside loops (date, filesizeformat)
40
+ - Repeated filter calls on same value (use {% with %})
41
+ - N+1 query patterns (accessing relations in loops)
42
+
43
+ 3. ACCESSIBILITY
44
+ - Images without alt text
45
+ - Forms without labels
46
+ - Missing ARIA attributes on interactive elements
47
+ - Poor heading hierarchy
48
+
49
+ 4. BEST PRACTICES
50
+ - {% for %} without {% empty %}
51
+ - Deeply nested conditionals (>3 levels)
52
+ - Magic numbers/strings (should be variables)
53
+ - Deprecated filter usage
54
+
55
+ Respond ONLY with valid JSON (no markdown, no explanation):
56
+ {
57
+ "issues": [
58
+ {
59
+ "line": 1,
60
+ "type": "security|performance|accessibility|best-practice",
61
+ "severity": "error|warning|suggestion",
62
+ "message": "Brief description",
63
+ "suggestion": "How to fix"
64
+ }
65
+ ]
66
+ }
67
+
68
+ If no issues found, return: {"issues": []}`;let E={syntax:"SYNTAX",security:"SECURITY",performance:"PERFORMANCE",accessibility:"ACCESSIBILITY","best-practice":"BEST PRACTICES",deprecated:"DEPRECATED"},O=A.map((I)=>E[I]).filter(Boolean);return`Analyze this Jinja2/Django template for issues.
69
+
70
+ TEMPLATE:
71
+ \`\`\`jinja
72
+ {{TEMPLATE}}
73
+ \`\`\`
74
+
75
+ Check for:
76
+
77
+ 1. SECURITY
78
+ - XSS vulnerabilities (unescaped user input, |safe on untrusted data)
79
+ - Variables in onclick/onerror/javascript: without escapejs
80
+ - Sensitive data exposure (passwords, tokens, API keys)
81
+ - SQL/command injection patterns
82
+
83
+ 2. PERFORMANCE
84
+ - Heavy filters inside loops (date, filesizeformat)
85
+ - Repeated filter calls on same value (use {% with %})
86
+ - N+1 query patterns (accessing relations in loops)
87
+
88
+ 3. ACCESSIBILITY
89
+ - Images without alt text
90
+ - Forms without labels
91
+ - Missing ARIA attributes on interactive elements
92
+ - Poor heading hierarchy
93
+
94
+ 4. BEST PRACTICES
95
+ - {% for %} without {% empty %}
96
+ - Deeply nested conditionals (>3 levels)
97
+ - Magic numbers/strings (should be variables)
98
+ - Deprecated filter usage
99
+
100
+ Respond ONLY with valid JSON (no markdown, no explanation):
101
+ {
102
+ "issues": [
103
+ {
104
+ "line": 1,
105
+ "type": "security|performance|accessibility|best-practice",
106
+ "severity": "error|warning|suggestion",
107
+ "message": "Brief description",
108
+ "suggestion": "How to fix"
109
+ }
110
+ ]
111
+ }
112
+
113
+ If no issues found, return: {"issues": []}`.replace(/Check for:[\s\S]*?Respond ONLY/,`Check ONLY for: ${O.join(", ")}
114
+
115
+ Respond ONLY`)}async function b(A,E={}){let O=Date.now(),I={valid:!0,errors:[],warnings:[],suggestions:[]};try{let R=new D(A).tokenize();new Q(R,A).parse()}catch(P){return I.valid=!1,I.errors.push({line:P.line||1,type:"syntax",severity:"error",message:P.message}),I.duration=Date.now()-O,I}try{let P=await J(E);I.provider=P.name;let R=z(E.categories),M=await P.analyze(A,R),L=v(M);for(let N of L){if(E.maxIssues&&I.errors.length+I.warnings.length+I.suggestions.length>=E.maxIssues)break;switch(N.severity){case"error":I.errors.push(N);break;case"warning":I.warnings.push(N);break;case"suggestion":I.suggestions.push(N);break}}I.valid=I.errors.length===0}catch(P){I.warnings.push({line:0,type:"best-practice",severity:"warning",message:`AI analysis failed: ${P.message}`})}return I.duration=Date.now()-O,I}function v(A){try{let E=A.trim(),O=E.match(/```(?:json)?\s*([\s\S]*?)```/);if(O)E=O[1].trim();let I=E.match(/\{[\s\S]*\}/);if(I)E=I[0];let P=JSON.parse(E);if(!P.issues||!Array.isArray(P.issues))return[];return P.issues.filter((R)=>R&&typeof R==="object").map((R)=>({line:typeof R.line==="number"?R.line:1,type:f(R.type),severity:x(R.severity),message:String(R.message||"Unknown issue"),suggestion:R.suggestion?String(R.suggestion):void 0}))}catch{return[]}}function f(A){return{security:"security",performance:"performance",accessibility:"accessibility",a11y:"accessibility","best-practice":"best-practice","best-practices":"best-practice",bestpractice:"best-practice",deprecated:"deprecated",syntax:"syntax"}[String(A).toLowerCase()]||"best-practice"}function x(A){return{error:"error",warning:"warning",warn:"warning",suggestion:"suggestion",info:"suggestion",hint:"suggestion"}[String(A).toLowerCase()]||"warning"}function g(A){let E={valid:!0,errors:[],warnings:[],suggestions:[]};try{let I=new D(A).tokenize();new Q(I,A).parse()}catch(O){E.valid=!1,E.errors.push({line:O.line||1,type:"syntax",severity:"error",message:O.message})}return E}export{g as syntaxCheck,J as resolveProvider,b as lint,h as getProvider,H as detectProvider,U as createOpenAIProvider,S as createOllamaProvider,Y as createGroqProvider,G as createAnthropicProvider};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * AI-powered Template Linting
3
+ */
4
+ import type { LintResult, LintOptions } from './types';
5
+ /**
6
+ * Lint a template using AI analysis
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { lint } from 'binja/ai'
11
+ *
12
+ * const result = await lint(`
13
+ * {% for p in products %}
14
+ * <div onclick="buy({{ p.id }})">{{ p.name }}</div>
15
+ * {% endfor %}
16
+ * `)
17
+ *
18
+ * // result.warnings = [{ type: 'security', message: 'XSS: unescaped in onclick' }]
19
+ * ```
20
+ */
21
+ export declare function lint(template: string, options?: LintOptions): Promise<LintResult>;
22
+ /**
23
+ * Quick syntax check without AI (fast, sync)
24
+ */
25
+ export declare function syntaxCheck(template: string): LintResult;
26
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * AI Lint Prompt Engineering
3
+ */
4
+ import type { IssueType } from './types';
5
+ export declare const LINT_PROMPT = "Analyze this Jinja2/Django template for issues.\n\nTEMPLATE:\n```jinja\n{{TEMPLATE}}\n```\n\nCheck for:\n\n1. SECURITY\n - XSS vulnerabilities (unescaped user input, |safe on untrusted data)\n - Variables in onclick/onerror/javascript: without escapejs\n - Sensitive data exposure (passwords, tokens, API keys)\n - SQL/command injection patterns\n\n2. PERFORMANCE\n - Heavy filters inside loops (date, filesizeformat)\n - Repeated filter calls on same value (use {% with %})\n - N+1 query patterns (accessing relations in loops)\n\n3. ACCESSIBILITY\n - Images without alt text\n - Forms without labels\n - Missing ARIA attributes on interactive elements\n - Poor heading hierarchy\n\n4. BEST PRACTICES\n - {% for %} without {% empty %}\n - Deeply nested conditionals (>3 levels)\n - Magic numbers/strings (should be variables)\n - Deprecated filter usage\n\nRespond ONLY with valid JSON (no markdown, no explanation):\n{\n \"issues\": [\n {\n \"line\": 1,\n \"type\": \"security|performance|accessibility|best-practice\",\n \"severity\": \"error|warning|suggestion\",\n \"message\": \"Brief description\",\n \"suggestion\": \"How to fix\"\n }\n ]\n}\n\nIf no issues found, return: {\"issues\": []}";
6
+ /**
7
+ * Build prompt with optional category filter
8
+ */
9
+ export declare function buildPrompt(categories?: IssueType[]): string;
10
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Anthropic Claude Provider
3
+ */
4
+ import type { AIProvider } from '../types';
5
+ export declare function createAnthropicProvider(model?: string, apiKey?: string): AIProvider;
6
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Groq Provider (Fast & Free tier available)
3
+ */
4
+ import type { AIProvider } from '../types';
5
+ export declare function createGroqProvider(model?: string, apiKey?: string): AIProvider;
6
+ //# sourceMappingURL=groq.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * AI Provider Manager - Auto-detect and create providers
3
+ */
4
+ import type { AIProvider, LintOptions } from '../types';
5
+ /**
6
+ * Get a specific provider by name
7
+ */
8
+ export declare function getProvider(name: 'anthropic' | 'openai' | 'ollama' | 'groq', options?: LintOptions): AIProvider;
9
+ /**
10
+ * Auto-detect available provider
11
+ * Priority: Anthropic > OpenAI > Groq > Ollama
12
+ */
13
+ export declare function detectProvider(options?: LintOptions): Promise<AIProvider>;
14
+ /**
15
+ * Get provider based on options (auto or specific)
16
+ */
17
+ export declare function resolveProvider(options?: LintOptions): Promise<AIProvider>;
18
+ export { createAnthropicProvider } from './anthropic';
19
+ export { createOpenAIProvider } from './openai';
20
+ export { createOllamaProvider } from './ollama';
21
+ export { createGroqProvider } from './groq';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Ollama Provider (Local)
3
+ */
4
+ import type { AIProvider } from '../types';
5
+ export declare function createOllamaProvider(model?: string, baseUrl?: string): AIProvider;
6
+ //# sourceMappingURL=ollama.d.ts.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * OpenAI Provider
3
+ */
4
+ import type { AIProvider } from '../types';
5
+ export declare function createOpenAIProvider(model?: string, apiKey?: string): AIProvider;
6
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * AI Linting Types
3
+ */
4
+ export type IssueSeverity = 'error' | 'warning' | 'suggestion';
5
+ export type IssueType = 'syntax' | 'security' | 'performance' | 'accessibility' | 'best-practice' | 'deprecated';
6
+ export interface Issue {
7
+ line: number;
8
+ column?: number;
9
+ type: IssueType;
10
+ severity: IssueSeverity;
11
+ message: string;
12
+ suggestion?: string;
13
+ code?: string;
14
+ }
15
+ export interface LintResult {
16
+ valid: boolean;
17
+ errors: Issue[];
18
+ warnings: Issue[];
19
+ suggestions: Issue[];
20
+ provider?: string;
21
+ duration?: number;
22
+ }
23
+ export interface LintOptions {
24
+ /** AI provider to use: 'auto', 'anthropic', 'openai', 'ollama', 'groq' */
25
+ provider?: 'auto' | 'anthropic' | 'openai' | 'ollama' | 'groq';
26
+ /** Model to use (provider-specific) */
27
+ model?: string;
28
+ /** API key (alternative to environment variable) */
29
+ apiKey?: string;
30
+ /** Ollama server URL (default: http://localhost:11434) */
31
+ ollamaUrl?: string;
32
+ /** Categories to check */
33
+ categories?: IssueType[];
34
+ /** Maximum issues to return */
35
+ maxIssues?: number;
36
+ }
37
+ export interface AIProvider {
38
+ name: string;
39
+ available: () => Promise<boolean>;
40
+ analyze: (template: string, prompt: string) => Promise<string>;
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map