tina4-nodejs 3.13.101 → 3.13.103
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/CLAUDE.md +2 -2
- package/package.json +2 -2
- package/packages/cli/dist/bin.js +117 -112
- package/packages/core/dist/index.js +117 -112
- package/packages/core/public/js/tina4-dev-admin.min.js +2 -2
- package/packages/core/src/metrics.ts +1 -1
- package/packages/frond/dist/index.js +113 -109
- package/packages/frond/src/engine.ts +128 -130
- package/packages/orm/dist/index.js +117 -112
- package/types/frond/src/engine.d.ts +4 -2
package/CLAUDE.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.
|
|
1
|
+
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.103)
|
|
2
2
|
|
|
3
3
|
> This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
|
|
4
4
|
|
|
5
5
|
## What This Project Is
|
|
6
6
|
|
|
7
|
-
Tina4 for Node.js/TypeScript v3.13.
|
|
7
|
+
Tina4 for Node.js/TypeScript v3.13.103 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
|
|
8
8
|
|
|
9
9
|
The philosophy: zero ceremony, batteries included, file system as source of truth.
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tina4-nodejs",
|
|
3
|
-
"version": "3.13.
|
|
3
|
+
"version": "3.13.103",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Tina4 for Node.js/TypeScript -
|
|
5
|
+
"description": "Tina4 for Node.js/TypeScript - native TypeScript conventions and shared Tina4 contracts",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tina4",
|
|
8
8
|
"framework",
|
package/packages/cli/dist/bin.js
CHANGED
|
@@ -1463,6 +1463,7 @@ __export(engine_exports, {
|
|
|
1463
1463
|
Frond: () => Frond,
|
|
1464
1464
|
MEMO_CACHE_MAX: () => MEMO_CACHE_MAX,
|
|
1465
1465
|
TEMPLATE_CACHE_MAX: () => TEMPLATE_CACHE_MAX,
|
|
1466
|
+
expressionFormCache: () => expressionFormCache,
|
|
1466
1467
|
filterChainCache: () => filterChainCache,
|
|
1467
1468
|
pathParseCache: () => pathParseCache,
|
|
1468
1469
|
setFormTokenSessionId: () => setFormTokenSessionId
|
|
@@ -1871,62 +1872,58 @@ function splitOutsideQuotes(expr, sep6) {
|
|
|
1871
1872
|
parts.push(expr.slice(currentStart));
|
|
1872
1873
|
return parts;
|
|
1873
1874
|
}
|
|
1874
|
-
function
|
|
1875
|
-
expr
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
if (
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
}
|
|
1882
|
-
if (expr.length >= 2 && expr[0] === "(" && expr.endsWith(")")) {
|
|
1883
|
-
let depth = 0;
|
|
1884
|
-
let matched = true;
|
|
1885
|
-
for (let pi = 0; pi < expr.length; pi++) {
|
|
1886
|
-
if (expr[pi] === "(") depth++;
|
|
1887
|
-
else if (expr[pi] === ")") depth--;
|
|
1888
|
-
if (depth === 0 && pi < expr.length - 1) {
|
|
1889
|
-
matched = false;
|
|
1890
|
-
break;
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
if (matched) {
|
|
1894
|
-
return evalExpr(expr.slice(1, -1), context);
|
|
1895
|
-
}
|
|
1875
|
+
function parenthesizedInner(expr) {
|
|
1876
|
+
if (expr.length < 2 || expr[0] !== "(" || !expr.endsWith(")")) return null;
|
|
1877
|
+
let depth = 0;
|
|
1878
|
+
for (let index = 0; index < expr.length; index++) {
|
|
1879
|
+
if (expr[index] === "(") depth++;
|
|
1880
|
+
else if (expr[index] === ")") depth--;
|
|
1881
|
+
if (depth === 0 && index < expr.length - 1) return null;
|
|
1896
1882
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
const truePart = rest.slice(0, colonIdx).trim();
|
|
1904
|
-
const falsePart = rest.slice(colonIdx + 1).trim();
|
|
1905
|
-
const cond = evalExpr(condPart, context);
|
|
1906
|
-
return cond ? evalExpr(truePart, context) : evalExpr(falsePart, context);
|
|
1907
|
-
}
|
|
1883
|
+
return expr.slice(1, -1);
|
|
1884
|
+
}
|
|
1885
|
+
function evalPrimary(expr, context) {
|
|
1886
|
+
const quote = expr[0];
|
|
1887
|
+
if (expr.length >= 2 && (quote === '"' || quote === "'") && expr.endsWith(quote) && !expr.slice(1, -1).includes(quote)) {
|
|
1888
|
+
return expr.slice(1, -1);
|
|
1908
1889
|
}
|
|
1890
|
+
const inner = parenthesizedInner(expr);
|
|
1891
|
+
if (inner !== null) return evalExpr(inner, context);
|
|
1892
|
+
return EXPR_NOT_MATCHED;
|
|
1893
|
+
}
|
|
1894
|
+
function evalTernaryExpression(expr, context) {
|
|
1895
|
+
const ternaryIdx = findTernary(expr);
|
|
1896
|
+
if (ternaryIdx === -1) return EXPR_NOT_MATCHED;
|
|
1897
|
+
const rest = expr.slice(ternaryIdx + 1);
|
|
1898
|
+
const colonIdx = findColon(rest);
|
|
1899
|
+
if (colonIdx === -1) return EXPR_NOT_MATCHED;
|
|
1900
|
+
const condition = evalExpr(expr.slice(0, ternaryIdx).trim(), context);
|
|
1901
|
+
const branch = condition ? rest.slice(0, colonIdx) : rest.slice(colonIdx + 1);
|
|
1902
|
+
return evalExpr(branch.trim(), context);
|
|
1903
|
+
}
|
|
1904
|
+
function evalInlineIfExpression(expr, context) {
|
|
1909
1905
|
const ifIdx = findOutsideQuotes(expr, " if ");
|
|
1910
|
-
if (ifIdx
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1906
|
+
if (ifIdx < 0) return EXPR_NOT_MATCHED;
|
|
1907
|
+
const elseIdx = findOutsideQuotes(expr, " else ");
|
|
1908
|
+
if (elseIdx < 0 || elseIdx <= ifIdx) return EXPR_NOT_MATCHED;
|
|
1909
|
+
const condition = evalExpr(expr.slice(ifIdx + 4, elseIdx).trim(), context);
|
|
1910
|
+
const branch = condition ? expr.slice(0, ifIdx) : expr.slice(elseIdx + 6);
|
|
1911
|
+
return evalExpr(branch.trim(), context);
|
|
1912
|
+
}
|
|
1913
|
+
function evalCoalesceExpression(expr, context) {
|
|
1920
1914
|
const qqIdx = findOutsideQuotes(expr, "??");
|
|
1921
|
-
if (qqIdx
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
return
|
|
1915
|
+
if (qqIdx === -1) return EXPR_NOT_MATCHED;
|
|
1916
|
+
const value = evalExpr(expr.slice(0, qqIdx).trim(), context);
|
|
1917
|
+
return value === null || value === void 0 ? evalExpr(expr.slice(qqIdx + 2).trim(), context) : value;
|
|
1918
|
+
}
|
|
1919
|
+
function evalConditional(expr, context) {
|
|
1920
|
+
for (const evaluator of [evalTernaryExpression, evalInlineIfExpression, evalCoalesceExpression]) {
|
|
1921
|
+
const result = evaluator(expr, context);
|
|
1922
|
+
if (result !== EXPR_NOT_MATCHED) return result;
|
|
1929
1923
|
}
|
|
1924
|
+
return EXPR_NOT_MATCHED;
|
|
1925
|
+
}
|
|
1926
|
+
function evalConcatOrComparison(expr, context) {
|
|
1930
1927
|
if (findOutsideQuotes(expr, "~") >= 0) {
|
|
1931
1928
|
const parts = splitOutsideQuotes(expr, "~");
|
|
1932
1929
|
if (parts.length > 1) {
|
|
@@ -1944,6 +1941,9 @@ function evalExpr(expr, context) {
|
|
|
1944
1941
|
return evalComparison(expr, context);
|
|
1945
1942
|
}
|
|
1946
1943
|
}
|
|
1944
|
+
return EXPR_NOT_MATCHED;
|
|
1945
|
+
}
|
|
1946
|
+
function evalArithmeticExpression(expr, context) {
|
|
1947
1947
|
for (const op of [" + ", " - ", " * ", " // ", " / ", " % ", " ** "]) {
|
|
1948
1948
|
const pos = findOutsideQuotes(expr, op);
|
|
1949
1949
|
if (pos >= 0) {
|
|
@@ -1956,40 +1956,15 @@ function evalExpr(expr, context) {
|
|
|
1956
1956
|
let rNum = rVal != null ? Number(rVal) : 0;
|
|
1957
1957
|
if (isNaN(lNum)) lNum = 0;
|
|
1958
1958
|
if (isNaN(rNum)) rNum = 0;
|
|
1959
|
-
|
|
1960
|
-
const bothInt = Number.isInteger(lNum) && Number.isInteger(rNum) && opS !== "/";
|
|
1961
|
-
let result;
|
|
1962
|
-
switch (opS) {
|
|
1963
|
-
case "+":
|
|
1964
|
-
result = lNum + rNum;
|
|
1965
|
-
break;
|
|
1966
|
-
case "-":
|
|
1967
|
-
result = lNum - rNum;
|
|
1968
|
-
break;
|
|
1969
|
-
case "*":
|
|
1970
|
-
result = lNum * rNum;
|
|
1971
|
-
break;
|
|
1972
|
-
case "//":
|
|
1973
|
-
result = rNum !== 0 ? Math.floor(lNum / rNum) : 0;
|
|
1974
|
-
break;
|
|
1975
|
-
case "/":
|
|
1976
|
-
result = rNum !== 0 ? lNum / rNum : 0;
|
|
1977
|
-
break;
|
|
1978
|
-
case "%":
|
|
1979
|
-
result = rNum !== 0 ? lNum % rNum : 0;
|
|
1980
|
-
break;
|
|
1981
|
-
case "**":
|
|
1982
|
-
result = lNum ** rNum;
|
|
1983
|
-
break;
|
|
1984
|
-
default:
|
|
1985
|
-
result = 0;
|
|
1986
|
-
}
|
|
1987
|
-
return bothInt && Number.isInteger(result) ? result : result;
|
|
1959
|
+
return ARITHMETIC_OPERATIONS[op.trim()](lNum, rNum);
|
|
1988
1960
|
} catch {
|
|
1989
1961
|
return null;
|
|
1990
1962
|
}
|
|
1991
1963
|
}
|
|
1992
1964
|
}
|
|
1965
|
+
return EXPR_NOT_MATCHED;
|
|
1966
|
+
}
|
|
1967
|
+
function evalFilterExpression(expr, context) {
|
|
1993
1968
|
if (findOutsideQuotes(expr, "|") >= 0) {
|
|
1994
1969
|
const [baseExpr, filters] = parseFilterChain(expr);
|
|
1995
1970
|
if (filters.length > 0) {
|
|
@@ -2008,38 +1983,49 @@ function evalExpr(expr, context) {
|
|
|
2008
1983
|
return value;
|
|
2009
1984
|
}
|
|
2010
1985
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
1986
|
+
return EXPR_NOT_MATCHED;
|
|
1987
|
+
}
|
|
1988
|
+
function evaluateCallArgs(rawArgs, context) {
|
|
1989
|
+
return rawArgs.trim() ? splitArgs(rawArgs).map((arg) => evalExpr(arg.trim(), context)) : [];
|
|
1990
|
+
}
|
|
1991
|
+
function evalDottedFunction(name, rawArgs, context) {
|
|
1992
|
+
const lastDot = name.lastIndexOf(".");
|
|
1993
|
+
const owner = resolveVar(name.slice(0, lastDot), context);
|
|
1994
|
+
const member = name.slice(lastDot + 1);
|
|
1995
|
+
if (!owner || typeof owner !== "object" || !(member in owner)) {
|
|
1996
|
+
return EXPR_NOT_MATCHED;
|
|
1997
|
+
}
|
|
1998
|
+
const method = owner[member];
|
|
1999
|
+
return typeof method === "function" ? method.apply(owner, evaluateCallArgs(rawArgs, context)) : EXPR_NOT_MATCHED;
|
|
2000
|
+
}
|
|
2001
|
+
function evalFunctionExpression(expr, context) {
|
|
2002
|
+
const match = expr.match(FN_CALL_RE);
|
|
2003
|
+
if (!match) return EXPR_NOT_MATCHED;
|
|
2004
|
+
const name = match[1];
|
|
2005
|
+
const rawArgs = match[2] || "";
|
|
2006
|
+
if (name.includes(".")) return evalDottedFunction(name, rawArgs, context);
|
|
2007
|
+
const fn = context[name] ?? resolveVar(name, context);
|
|
2008
|
+
if (typeof fn === "function") return fn(...evaluateCallArgs(rawArgs, context));
|
|
2009
|
+
return EXPR_NOT_MATCHED;
|
|
2010
|
+
}
|
|
2011
|
+
function evalExpr(expr, context) {
|
|
2012
|
+
expr = expr.trim();
|
|
2013
|
+
const cachedForm = expressionFormCache.get(expr);
|
|
2014
|
+
if (cachedForm !== void 0) {
|
|
2015
|
+
if (cachedForm === -1) return resolveVar(expr, context);
|
|
2016
|
+
const result = EXPR_EVALUATORS[cachedForm](expr, context);
|
|
2017
|
+
return result === EXPR_NOT_MATCHED ? resolveVar(expr, context) : result;
|
|
2018
|
+
}
|
|
2019
|
+
for (let index = 0; index < EXPR_EVALUATORS.length; index++) {
|
|
2020
|
+
const result = EXPR_EVALUATORS[index](expr, context);
|
|
2021
|
+
if (result !== EXPR_NOT_MATCHED) {
|
|
2022
|
+
capCache(expressionFormCache, MEMO_CACHE_MAX);
|
|
2023
|
+
expressionFormCache.set(expr, index);
|
|
2024
|
+
return result;
|
|
2041
2025
|
}
|
|
2042
2026
|
}
|
|
2027
|
+
capCache(expressionFormCache, MEMO_CACHE_MAX);
|
|
2028
|
+
expressionFormCache.set(expr, FN_CALL_RE.test(expr) ? EXPR_EVALUATORS.length - 1 : -1);
|
|
2043
2029
|
return resolveVar(expr, context);
|
|
2044
2030
|
}
|
|
2045
2031
|
function findTernary(expr) {
|
|
@@ -2495,7 +2481,7 @@ function _generateFormToken(descriptor = "") {
|
|
|
2495
2481
|
function _generateFormTokenValue(descriptor = "") {
|
|
2496
2482
|
return new SafeString(_buildFormTokenJwt(descriptor));
|
|
2497
2483
|
}
|
|
2498
|
-
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
|
|
2484
|
+
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
|
|
2499
2485
|
var init_engine = __esm({
|
|
2500
2486
|
"../frond/src/engine.ts"() {
|
|
2501
2487
|
"use strict";
|
|
@@ -2597,6 +2583,25 @@ var init_engine = __esm({
|
|
|
2597
2583
|
MEMO_CACHE_MAX = 1024;
|
|
2598
2584
|
TOKEN_RE = /(\{%-?\s*[\s\S]*?\s*-?%\})|(\{\{-?\s*[\s\S]*?\s*-?\}\})|(\{#[\s\S]*?#\})/g;
|
|
2599
2585
|
RAW_BLOCK_RE = /\{%-?\s*raw\s*-?%\}([\s\S]*?)\{%-?\s*endraw\s*-?%\}/g;
|
|
2586
|
+
EXPR_NOT_MATCHED = Symbol("frond-expression-not-matched");
|
|
2587
|
+
ARITHMETIC_OPERATIONS = {
|
|
2588
|
+
"+": (left, right) => left + right,
|
|
2589
|
+
"-": (left, right) => left - right,
|
|
2590
|
+
"*": (left, right) => left * right,
|
|
2591
|
+
"//": (left, right) => right !== 0 ? Math.floor(left / right) : 0,
|
|
2592
|
+
"/": (left, right) => right !== 0 ? left / right : 0,
|
|
2593
|
+
"%": (left, right) => right !== 0 ? left % right : 0,
|
|
2594
|
+
"**": (left, right) => left ** right
|
|
2595
|
+
};
|
|
2596
|
+
EXPR_EVALUATORS = [
|
|
2597
|
+
evalPrimary,
|
|
2598
|
+
evalConditional,
|
|
2599
|
+
evalConcatOrComparison,
|
|
2600
|
+
evalArithmeticExpression,
|
|
2601
|
+
evalFilterExpression,
|
|
2602
|
+
evalFunctionExpression
|
|
2603
|
+
];
|
|
2604
|
+
expressionFormCache = /* @__PURE__ */ new Map();
|
|
2600
2605
|
VarRef = class {
|
|
2601
2606
|
constructor(name) {
|
|
2602
2607
|
this.name = name;
|
|
@@ -23000,7 +23005,7 @@ var init_metrics = __esm({
|
|
|
23000
23005
|
};
|
|
23001
23006
|
INSTALL_HINT = "update the native tina4 CLI: https://tina4.com/cli";
|
|
23002
23007
|
SUMMARY_KEYS = ["files_analyzed", "total_functions", "avg_complexity", "avg_maintainability"];
|
|
23003
|
-
FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "
|
|
23008
|
+
FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_referencing_test"];
|
|
23004
23009
|
FUNCTION_KEYS = ["name", "file", "line", "complexity", "loc"];
|
|
23005
23010
|
}
|
|
23006
23011
|
});
|
|
@@ -1462,6 +1462,7 @@ __export(engine_exports, {
|
|
|
1462
1462
|
Frond: () => Frond,
|
|
1463
1463
|
MEMO_CACHE_MAX: () => MEMO_CACHE_MAX,
|
|
1464
1464
|
TEMPLATE_CACHE_MAX: () => TEMPLATE_CACHE_MAX,
|
|
1465
|
+
expressionFormCache: () => expressionFormCache,
|
|
1465
1466
|
filterChainCache: () => filterChainCache,
|
|
1466
1467
|
pathParseCache: () => pathParseCache,
|
|
1467
1468
|
setFormTokenSessionId: () => setFormTokenSessionId
|
|
@@ -1870,62 +1871,58 @@ function splitOutsideQuotes(expr, sep6) {
|
|
|
1870
1871
|
parts.push(expr.slice(currentStart));
|
|
1871
1872
|
return parts;
|
|
1872
1873
|
}
|
|
1873
|
-
function
|
|
1874
|
-
expr
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
if (
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
}
|
|
1881
|
-
if (expr.length >= 2 && expr[0] === "(" && expr.endsWith(")")) {
|
|
1882
|
-
let depth = 0;
|
|
1883
|
-
let matched = true;
|
|
1884
|
-
for (let pi = 0; pi < expr.length; pi++) {
|
|
1885
|
-
if (expr[pi] === "(") depth++;
|
|
1886
|
-
else if (expr[pi] === ")") depth--;
|
|
1887
|
-
if (depth === 0 && pi < expr.length - 1) {
|
|
1888
|
-
matched = false;
|
|
1889
|
-
break;
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
if (matched) {
|
|
1893
|
-
return evalExpr(expr.slice(1, -1), context);
|
|
1894
|
-
}
|
|
1874
|
+
function parenthesizedInner(expr) {
|
|
1875
|
+
if (expr.length < 2 || expr[0] !== "(" || !expr.endsWith(")")) return null;
|
|
1876
|
+
let depth = 0;
|
|
1877
|
+
for (let index = 0; index < expr.length; index++) {
|
|
1878
|
+
if (expr[index] === "(") depth++;
|
|
1879
|
+
else if (expr[index] === ")") depth--;
|
|
1880
|
+
if (depth === 0 && index < expr.length - 1) return null;
|
|
1895
1881
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
const truePart = rest.slice(0, colonIdx).trim();
|
|
1903
|
-
const falsePart = rest.slice(colonIdx + 1).trim();
|
|
1904
|
-
const cond = evalExpr(condPart, context);
|
|
1905
|
-
return cond ? evalExpr(truePart, context) : evalExpr(falsePart, context);
|
|
1906
|
-
}
|
|
1882
|
+
return expr.slice(1, -1);
|
|
1883
|
+
}
|
|
1884
|
+
function evalPrimary(expr, context) {
|
|
1885
|
+
const quote = expr[0];
|
|
1886
|
+
if (expr.length >= 2 && (quote === '"' || quote === "'") && expr.endsWith(quote) && !expr.slice(1, -1).includes(quote)) {
|
|
1887
|
+
return expr.slice(1, -1);
|
|
1907
1888
|
}
|
|
1889
|
+
const inner = parenthesizedInner(expr);
|
|
1890
|
+
if (inner !== null) return evalExpr(inner, context);
|
|
1891
|
+
return EXPR_NOT_MATCHED;
|
|
1892
|
+
}
|
|
1893
|
+
function evalTernaryExpression(expr, context) {
|
|
1894
|
+
const ternaryIdx = findTernary(expr);
|
|
1895
|
+
if (ternaryIdx === -1) return EXPR_NOT_MATCHED;
|
|
1896
|
+
const rest = expr.slice(ternaryIdx + 1);
|
|
1897
|
+
const colonIdx = findColon(rest);
|
|
1898
|
+
if (colonIdx === -1) return EXPR_NOT_MATCHED;
|
|
1899
|
+
const condition = evalExpr(expr.slice(0, ternaryIdx).trim(), context);
|
|
1900
|
+
const branch = condition ? rest.slice(0, colonIdx) : rest.slice(colonIdx + 1);
|
|
1901
|
+
return evalExpr(branch.trim(), context);
|
|
1902
|
+
}
|
|
1903
|
+
function evalInlineIfExpression(expr, context) {
|
|
1908
1904
|
const ifIdx = findOutsideQuotes(expr, " if ");
|
|
1909
|
-
if (ifIdx
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1905
|
+
if (ifIdx < 0) return EXPR_NOT_MATCHED;
|
|
1906
|
+
const elseIdx = findOutsideQuotes(expr, " else ");
|
|
1907
|
+
if (elseIdx < 0 || elseIdx <= ifIdx) return EXPR_NOT_MATCHED;
|
|
1908
|
+
const condition = evalExpr(expr.slice(ifIdx + 4, elseIdx).trim(), context);
|
|
1909
|
+
const branch = condition ? expr.slice(0, ifIdx) : expr.slice(elseIdx + 6);
|
|
1910
|
+
return evalExpr(branch.trim(), context);
|
|
1911
|
+
}
|
|
1912
|
+
function evalCoalesceExpression(expr, context) {
|
|
1919
1913
|
const qqIdx = findOutsideQuotes(expr, "??");
|
|
1920
|
-
if (qqIdx
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
return
|
|
1914
|
+
if (qqIdx === -1) return EXPR_NOT_MATCHED;
|
|
1915
|
+
const value = evalExpr(expr.slice(0, qqIdx).trim(), context);
|
|
1916
|
+
return value === null || value === void 0 ? evalExpr(expr.slice(qqIdx + 2).trim(), context) : value;
|
|
1917
|
+
}
|
|
1918
|
+
function evalConditional(expr, context) {
|
|
1919
|
+
for (const evaluator of [evalTernaryExpression, evalInlineIfExpression, evalCoalesceExpression]) {
|
|
1920
|
+
const result = evaluator(expr, context);
|
|
1921
|
+
if (result !== EXPR_NOT_MATCHED) return result;
|
|
1928
1922
|
}
|
|
1923
|
+
return EXPR_NOT_MATCHED;
|
|
1924
|
+
}
|
|
1925
|
+
function evalConcatOrComparison(expr, context) {
|
|
1929
1926
|
if (findOutsideQuotes(expr, "~") >= 0) {
|
|
1930
1927
|
const parts = splitOutsideQuotes(expr, "~");
|
|
1931
1928
|
if (parts.length > 1) {
|
|
@@ -1943,6 +1940,9 @@ function evalExpr(expr, context) {
|
|
|
1943
1940
|
return evalComparison(expr, context);
|
|
1944
1941
|
}
|
|
1945
1942
|
}
|
|
1943
|
+
return EXPR_NOT_MATCHED;
|
|
1944
|
+
}
|
|
1945
|
+
function evalArithmeticExpression(expr, context) {
|
|
1946
1946
|
for (const op of [" + ", " - ", " * ", " // ", " / ", " % ", " ** "]) {
|
|
1947
1947
|
const pos = findOutsideQuotes(expr, op);
|
|
1948
1948
|
if (pos >= 0) {
|
|
@@ -1955,40 +1955,15 @@ function evalExpr(expr, context) {
|
|
|
1955
1955
|
let rNum = rVal != null ? Number(rVal) : 0;
|
|
1956
1956
|
if (isNaN(lNum)) lNum = 0;
|
|
1957
1957
|
if (isNaN(rNum)) rNum = 0;
|
|
1958
|
-
|
|
1959
|
-
const bothInt = Number.isInteger(lNum) && Number.isInteger(rNum) && opS !== "/";
|
|
1960
|
-
let result;
|
|
1961
|
-
switch (opS) {
|
|
1962
|
-
case "+":
|
|
1963
|
-
result = lNum + rNum;
|
|
1964
|
-
break;
|
|
1965
|
-
case "-":
|
|
1966
|
-
result = lNum - rNum;
|
|
1967
|
-
break;
|
|
1968
|
-
case "*":
|
|
1969
|
-
result = lNum * rNum;
|
|
1970
|
-
break;
|
|
1971
|
-
case "//":
|
|
1972
|
-
result = rNum !== 0 ? Math.floor(lNum / rNum) : 0;
|
|
1973
|
-
break;
|
|
1974
|
-
case "/":
|
|
1975
|
-
result = rNum !== 0 ? lNum / rNum : 0;
|
|
1976
|
-
break;
|
|
1977
|
-
case "%":
|
|
1978
|
-
result = rNum !== 0 ? lNum % rNum : 0;
|
|
1979
|
-
break;
|
|
1980
|
-
case "**":
|
|
1981
|
-
result = lNum ** rNum;
|
|
1982
|
-
break;
|
|
1983
|
-
default:
|
|
1984
|
-
result = 0;
|
|
1985
|
-
}
|
|
1986
|
-
return bothInt && Number.isInteger(result) ? result : result;
|
|
1958
|
+
return ARITHMETIC_OPERATIONS[op.trim()](lNum, rNum);
|
|
1987
1959
|
} catch {
|
|
1988
1960
|
return null;
|
|
1989
1961
|
}
|
|
1990
1962
|
}
|
|
1991
1963
|
}
|
|
1964
|
+
return EXPR_NOT_MATCHED;
|
|
1965
|
+
}
|
|
1966
|
+
function evalFilterExpression(expr, context) {
|
|
1992
1967
|
if (findOutsideQuotes(expr, "|") >= 0) {
|
|
1993
1968
|
const [baseExpr, filters] = parseFilterChain(expr);
|
|
1994
1969
|
if (filters.length > 0) {
|
|
@@ -2007,38 +1982,49 @@ function evalExpr(expr, context) {
|
|
|
2007
1982
|
return value;
|
|
2008
1983
|
}
|
|
2009
1984
|
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
1985
|
+
return EXPR_NOT_MATCHED;
|
|
1986
|
+
}
|
|
1987
|
+
function evaluateCallArgs(rawArgs, context) {
|
|
1988
|
+
return rawArgs.trim() ? splitArgs(rawArgs).map((arg) => evalExpr(arg.trim(), context)) : [];
|
|
1989
|
+
}
|
|
1990
|
+
function evalDottedFunction(name, rawArgs, context) {
|
|
1991
|
+
const lastDot = name.lastIndexOf(".");
|
|
1992
|
+
const owner = resolveVar(name.slice(0, lastDot), context);
|
|
1993
|
+
const member = name.slice(lastDot + 1);
|
|
1994
|
+
if (!owner || typeof owner !== "object" || !(member in owner)) {
|
|
1995
|
+
return EXPR_NOT_MATCHED;
|
|
1996
|
+
}
|
|
1997
|
+
const method = owner[member];
|
|
1998
|
+
return typeof method === "function" ? method.apply(owner, evaluateCallArgs(rawArgs, context)) : EXPR_NOT_MATCHED;
|
|
1999
|
+
}
|
|
2000
|
+
function evalFunctionExpression(expr, context) {
|
|
2001
|
+
const match = expr.match(FN_CALL_RE);
|
|
2002
|
+
if (!match) return EXPR_NOT_MATCHED;
|
|
2003
|
+
const name = match[1];
|
|
2004
|
+
const rawArgs = match[2] || "";
|
|
2005
|
+
if (name.includes(".")) return evalDottedFunction(name, rawArgs, context);
|
|
2006
|
+
const fn = context[name] ?? resolveVar(name, context);
|
|
2007
|
+
if (typeof fn === "function") return fn(...evaluateCallArgs(rawArgs, context));
|
|
2008
|
+
return EXPR_NOT_MATCHED;
|
|
2009
|
+
}
|
|
2010
|
+
function evalExpr(expr, context) {
|
|
2011
|
+
expr = expr.trim();
|
|
2012
|
+
const cachedForm = expressionFormCache.get(expr);
|
|
2013
|
+
if (cachedForm !== void 0) {
|
|
2014
|
+
if (cachedForm === -1) return resolveVar(expr, context);
|
|
2015
|
+
const result = EXPR_EVALUATORS[cachedForm](expr, context);
|
|
2016
|
+
return result === EXPR_NOT_MATCHED ? resolveVar(expr, context) : result;
|
|
2017
|
+
}
|
|
2018
|
+
for (let index = 0; index < EXPR_EVALUATORS.length; index++) {
|
|
2019
|
+
const result = EXPR_EVALUATORS[index](expr, context);
|
|
2020
|
+
if (result !== EXPR_NOT_MATCHED) {
|
|
2021
|
+
capCache(expressionFormCache, MEMO_CACHE_MAX);
|
|
2022
|
+
expressionFormCache.set(expr, index);
|
|
2023
|
+
return result;
|
|
2040
2024
|
}
|
|
2041
2025
|
}
|
|
2026
|
+
capCache(expressionFormCache, MEMO_CACHE_MAX);
|
|
2027
|
+
expressionFormCache.set(expr, FN_CALL_RE.test(expr) ? EXPR_EVALUATORS.length - 1 : -1);
|
|
2042
2028
|
return resolveVar(expr, context);
|
|
2043
2029
|
}
|
|
2044
2030
|
function findTernary(expr) {
|
|
@@ -2494,7 +2480,7 @@ function _generateFormToken(descriptor = "") {
|
|
|
2494
2480
|
function _generateFormTokenValue(descriptor = "") {
|
|
2495
2481
|
return new SafeString(_buildFormTokenJwt(descriptor));
|
|
2496
2482
|
}
|
|
2497
|
-
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
|
|
2483
|
+
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, BUILTIN_FILTERS, _formTokenSessionId, Frond;
|
|
2498
2484
|
var init_engine = __esm({
|
|
2499
2485
|
"../frond/src/engine.ts"() {
|
|
2500
2486
|
"use strict";
|
|
@@ -2596,6 +2582,25 @@ var init_engine = __esm({
|
|
|
2596
2582
|
MEMO_CACHE_MAX = 1024;
|
|
2597
2583
|
TOKEN_RE = /(\{%-?\s*[\s\S]*?\s*-?%\})|(\{\{-?\s*[\s\S]*?\s*-?\}\})|(\{#[\s\S]*?#\})/g;
|
|
2598
2584
|
RAW_BLOCK_RE = /\{%-?\s*raw\s*-?%\}([\s\S]*?)\{%-?\s*endraw\s*-?%\}/g;
|
|
2585
|
+
EXPR_NOT_MATCHED = Symbol("frond-expression-not-matched");
|
|
2586
|
+
ARITHMETIC_OPERATIONS = {
|
|
2587
|
+
"+": (left, right) => left + right,
|
|
2588
|
+
"-": (left, right) => left - right,
|
|
2589
|
+
"*": (left, right) => left * right,
|
|
2590
|
+
"//": (left, right) => right !== 0 ? Math.floor(left / right) : 0,
|
|
2591
|
+
"/": (left, right) => right !== 0 ? left / right : 0,
|
|
2592
|
+
"%": (left, right) => right !== 0 ? left % right : 0,
|
|
2593
|
+
"**": (left, right) => left ** right
|
|
2594
|
+
};
|
|
2595
|
+
EXPR_EVALUATORS = [
|
|
2596
|
+
evalPrimary,
|
|
2597
|
+
evalConditional,
|
|
2598
|
+
evalConcatOrComparison,
|
|
2599
|
+
evalArithmeticExpression,
|
|
2600
|
+
evalFilterExpression,
|
|
2601
|
+
evalFunctionExpression
|
|
2602
|
+
];
|
|
2603
|
+
expressionFormCache = /* @__PURE__ */ new Map();
|
|
2599
2604
|
VarRef = class {
|
|
2600
2605
|
constructor(name) {
|
|
2601
2606
|
this.name = name;
|
|
@@ -22979,7 +22984,7 @@ var init_metrics = __esm({
|
|
|
22979
22984
|
};
|
|
22980
22985
|
INSTALL_HINT = "update the native tina4 CLI: https://tina4.com/cli";
|
|
22981
22986
|
SUMMARY_KEYS = ["files_analyzed", "total_functions", "avg_complexity", "avg_maintainability"];
|
|
22982
|
-
FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "
|
|
22987
|
+
FILE_KEYS = ["path", "loc", "avg_complexity", "maintainability", "has_referencing_test"];
|
|
22983
22988
|
FUNCTION_KEYS = ["name", "file", "line", "complexity", "loc"];
|
|
22984
22989
|
}
|
|
22985
22990
|
});
|