tina4-nodejs 3.13.90 → 3.13.92

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 CHANGED
@@ -1,10 +1,10 @@
1
- # CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.90)
1
+ # CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.92)
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.90 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.13.92 - 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
 
@@ -1244,7 +1244,7 @@ When adding new features, add a corresponding `test/<feature>.test.ts` file.
1244
1244
  ## v3 Features Summary
1245
1245
 
1246
1246
  - **98 built-in features**, zero third-party dependencies
1247
- - **5,923 tests** passing across 189 files (build + typecheck green)
1247
+ - **6,005 tests** passing across 191 files (build + typecheck green)
1248
1248
  - **Race-safe `getNextId()`** with atomic sequence table (`tina4_sequences`) for SQLite/MySQL/MSSQL; PostgreSQL auto-creates sequences
1249
1249
  - **Frond template engine optimizations**: pre-compiled regexes, lazy loop context (copy-on-write), filter chain caching, path split caching, inline common filters (11-15% speedup)
1250
1250
  - **Production server auto-detect**: `npx tina4nodejs serve --production` auto-uses cluster mode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.13.90",
3
+ "version": "3.13.92",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript - 54 built-in features, zero dependencies",
6
6
  "keywords": [
@@ -12122,6 +12122,7 @@ var init_logger = __esm({
12122
12122
  var auth_exports = {};
12123
12123
  __export(auth_exports, {
12124
12124
  Auth: () => Auth,
12125
+ JWT_LEEWAY_SECONDS: () => JWT_LEEWAY_SECONDS,
12125
12126
  authMiddleware: () => authMiddleware,
12126
12127
  authenticateRequest: () => authenticateRequest,
12127
12128
  checkPassword: () => checkPassword,
@@ -12130,6 +12131,7 @@ __export(auth_exports, {
12130
12131
  getToken: () => getToken,
12131
12132
  hashPassword: () => hashPassword,
12132
12133
  refreshToken: () => refreshToken,
12134
+ resolveAlgorithm: () => resolveAlgorithm,
12133
12135
  validToken: () => validToken,
12134
12136
  validateApiKey: () => validateApiKey
12135
12137
  });
@@ -12190,6 +12192,18 @@ function ensureDevSecret(cwd) {
12190
12192
  }
12191
12193
  return newSecret;
12192
12194
  }
12195
+ function unsupportedAlgorithmError(algorithm) {
12196
+ return new Error(
12197
+ `Unsupported JWT algorithm "${algorithm}". Tina4 signs with ${SUPPORTED_ALGORITHMS.join(", ")} (HMAC via node:crypto; RS256 needs a PEM key pair). Set TINA4_JWT_ALGORITHM to one of those.`
12198
+ );
12199
+ }
12200
+ function resolveAlgorithm(algorithm) {
12201
+ const chosen = (algorithm || process.env.TINA4_JWT_ALGORITHM || "HS256").trim();
12202
+ if (!HMAC_DIGESTS.has(chosen) && !RSA_SIGN_ALGORITHMS.has(chosen)) {
12203
+ throw unsupportedAlgorithmError(chosen);
12204
+ }
12205
+ return chosen;
12206
+ }
12193
12207
  function base64urlEncode(data) {
12194
12208
  return data.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
12195
12209
  }
@@ -12212,7 +12226,7 @@ function getToken(payload, secretOrExpiresIn, expiresIn = 60, algorithm) {
12212
12226
  if (!resolvedSecret) {
12213
12227
  _warnBlankSecret();
12214
12228
  }
12215
- const resolvedAlgorithm = algorithm ?? process.env.TINA4_JWT_ALGORITHM ?? "HS256";
12229
+ const resolvedAlgorithm = resolveAlgorithm(algorithm);
12216
12230
  const header = { alg: resolvedAlgorithm, typ: "JWT" };
12217
12231
  const now = Math.floor(Date.now() / 1e3);
12218
12232
  const claims = { ...payload, iat: now };
@@ -12230,19 +12244,27 @@ function validToken(token, secret, algorithm) {
12230
12244
  if (!resolvedSecret) {
12231
12245
  _warnBlankSecret();
12232
12246
  }
12233
- const resolvedAlgorithm = algorithm ?? process.env.TINA4_JWT_ALGORITHM ?? "HS256";
12247
+ const resolvedAlgorithm = resolveAlgorithm(algorithm);
12234
12248
  try {
12235
12249
  const parts = token.split(".");
12236
12250
  if (parts.length !== 3) return null;
12237
12251
  const [h, p, sig] = parts;
12252
+ const header = JSON.parse(base64urlDecode(h).toString());
12253
+ if (header.alg !== resolvedAlgorithm) return null;
12238
12254
  const signingInput = `${h}.${p}`;
12239
12255
  if (!verifySignature(signingInput, sig, resolvedSecret, resolvedAlgorithm)) {
12240
12256
  return null;
12241
12257
  }
12242
12258
  const payload = JSON.parse(base64urlDecode(p).toString());
12243
- if (typeof payload.exp === "number" && Date.now() / 1e3 > payload.exp) {
12259
+ const now = Date.now() / 1e3;
12260
+ if (typeof payload.exp === "number" && now > payload.exp) {
12244
12261
  return null;
12245
12262
  }
12263
+ if (Object.hasOwn(payload, "nbf")) {
12264
+ const notBefore = payload.nbf;
12265
+ if (typeof notBefore !== "number" || !Number.isFinite(notBefore)) return null;
12266
+ if (now + JWT_LEEWAY_SECONDS < notBefore) return null;
12267
+ }
12246
12268
  return payload;
12247
12269
  } catch {
12248
12270
  return null;
@@ -12258,32 +12280,33 @@ function getPayload(token) {
12258
12280
  }
12259
12281
  }
12260
12282
  function sign(input, secret, algorithm) {
12261
- if (algorithm === "HS256") {
12262
- const sig = createHmac2("sha256", secret).update(input).digest();
12263
- return base64urlEncode(sig);
12283
+ const digest = HMAC_DIGESTS.get(algorithm);
12284
+ if (digest) {
12285
+ return base64urlEncode(createHmac2(digest, secret).update(input).digest());
12264
12286
  }
12265
- if (algorithm === "RS256") {
12266
- const signer = createSign("RSA-SHA256");
12287
+ const rsaAlgorithm = RSA_SIGN_ALGORITHMS.get(algorithm);
12288
+ if (rsaAlgorithm) {
12289
+ const signer = createSign(rsaAlgorithm);
12267
12290
  signer.update(input);
12268
- const sig = signer.sign(secret);
12269
- return base64urlEncode(sig);
12291
+ return base64urlEncode(signer.sign(secret));
12270
12292
  }
12271
- throw new Error(`Unsupported algorithm: ${algorithm}`);
12293
+ throw unsupportedAlgorithmError(algorithm);
12272
12294
  }
12273
12295
  function verifySignature(input, sig, secret, algorithm) {
12274
- if (algorithm === "HS256") {
12296
+ if (HMAC_DIGESTS.has(algorithm)) {
12275
12297
  const expected = sign(input, secret, algorithm);
12276
12298
  const a = Buffer.from(sig);
12277
12299
  const b = Buffer.from(expected);
12278
12300
  if (a.length !== b.length) return false;
12279
12301
  return timingSafeEqual(a, b);
12280
12302
  }
12281
- if (algorithm === "RS256") {
12282
- const verifier = createVerify("RSA-SHA256");
12303
+ const rsaAlgorithm = RSA_SIGN_ALGORITHMS.get(algorithm);
12304
+ if (rsaAlgorithm) {
12305
+ const verifier = createVerify(rsaAlgorithm);
12283
12306
  verifier.update(input);
12284
12307
  return verifier.verify(secret, base64urlDecode(sig));
12285
12308
  }
12286
- throw new Error(`Unsupported algorithm: ${algorithm}`);
12309
+ throw unsupportedAlgorithmError(algorithm);
12287
12310
  }
12288
12311
  function hashPassword(password, salt, iterations = 26e4) {
12289
12312
  const actualSalt = salt ?? randomBytes3(16).toString("hex");
@@ -12308,7 +12331,7 @@ function checkPassword(password, hash) {
12308
12331
  return false;
12309
12332
  }
12310
12333
  }
12311
- function authMiddleware(secret, algorithm = "HS256") {
12334
+ function authMiddleware(secret, algorithm) {
12312
12335
  return (req2, res, next) => {
12313
12336
  const authHeader = req2.headers.authorization ?? "";
12314
12337
  if (!authHeader.startsWith("Bearer ")) {
@@ -12331,11 +12354,11 @@ function refreshToken(token, expiresIn = 60) {
12331
12354
  const { iat: _iat, exp: _exp, ...claims } = payload;
12332
12355
  return getToken(claims, expiresIn);
12333
12356
  }
12334
- function authenticateRequest(headers, secret, algorithm = "HS256") {
12357
+ function authenticateRequest(headers, secret, algorithm) {
12335
12358
  const authHeader = headers.authorization ?? headers.Authorization ?? "";
12336
12359
  if (!authHeader.startsWith("Bearer ")) return null;
12337
12360
  const token = authHeader.slice(7);
12338
- if (validToken(token)) return getPayload(token);
12361
+ if (validToken(token, secret, algorithm)) return getPayload(token);
12339
12362
  if (validateApiKey(token)) {
12340
12363
  return { _auth: "api_key" };
12341
12364
  }
@@ -12349,12 +12372,23 @@ function validateApiKey(provided, expected) {
12349
12372
  if (a.length !== b.length) return false;
12350
12373
  return timingSafeEqual(a, b);
12351
12374
  }
12352
- var BLANK_SECRET_WARNING, Auth;
12375
+ var BLANK_SECRET_WARNING, HMAC_DIGESTS, RSA_SIGN_ALGORITHMS, SUPPORTED_ALGORITHMS, JWT_LEEWAY_SECONDS, Auth;
12353
12376
  var init_auth = __esm({
12354
12377
  "../core/src/auth.ts"() {
12355
12378
  "use strict";
12356
12379
  init_dotenv();
12357
12380
  BLANK_SECRET_WARNING = "Auth: TINA4_SECRET is not set \u2014 JWT signing is insecure. Set TINA4_SECRET to a random value (e.g. `openssl rand -hex 32`) in your environment or .env before serving traffic. For LOCAL DEV, set TINA4_DEBUG=true and a per-machine secret is generated automatically into .env.local (gitignored). Seeing this warning means the run was NOT detected as dev \u2014 typically a container or CI without TINA4_DEBUG set, or TINA4_ENV=production.";
12381
+ HMAC_DIGESTS = /* @__PURE__ */ new Map([
12382
+ ["HS256", "sha256"],
12383
+ ["HS384", "sha384"],
12384
+ ["HS512", "sha512"]
12385
+ ]);
12386
+ RSA_SIGN_ALGORITHMS = /* @__PURE__ */ new Map([["RS256", "RSA-SHA256"]]);
12387
+ SUPPORTED_ALGORITHMS = [
12388
+ ...HMAC_DIGESTS.keys(),
12389
+ ...RSA_SIGN_ALGORITHMS.keys()
12390
+ ];
12391
+ JWT_LEEWAY_SECONDS = 60;
12358
12392
  Auth = class {
12359
12393
  static getToken = getToken;
12360
12394
  static validToken = validToken;
@@ -17938,7 +17972,7 @@ function extractFunctions(source, filePath, root = ".") {
17938
17972
  if (funcName !== null && !NON_FUNCTION_WORDS.has(funcName)) {
17939
17973
  const displayName = funcName === "constructor" && currentClass ? `${currentClass}.constructor` : currentClass !== null && !isTopLevelDecl ? `${currentClass}.${funcName}` : funcName;
17940
17974
  const funcBody = extractFunctionBody(lines, i);
17941
- const funcLoc = funcBody.split("\n").length;
17975
+ const funcLoc = Math.max(1, countLines(funcBody).loc);
17942
17976
  const complexity = cycloMaticComplexity(funcBody);
17943
17977
  const args = argsStr.split(",").map((a) => a.trim().split(":")[0].split("=")[0].replace("?", "").trim()).filter((a) => a && a !== "this");
17944
17978
  functions.push({
@@ -17954,6 +17988,24 @@ function extractFunctions(source, filePath, root = ".") {
17954
17988
  currentClass = null;
17955
17989
  }
17956
17990
  }
17991
+ return chargeNestedComplexityToTheNestedFunction(functions);
17992
+ }
17993
+ function chargeNestedComplexityToTheNestedFunction(functions) {
17994
+ if (functions.length < 2) return functions;
17995
+ const lastLine = (f) => f.line + Math.max(1, f.loc) - 1;
17996
+ const contains = (outer, inner) => inner.line > outer.line && lastLine(inner) <= lastLine(outer);
17997
+ const raw = functions.map((f) => f.complexity);
17998
+ functions.forEach((outer, i) => {
17999
+ let subtract = 0;
18000
+ functions.forEach((inner, j) => {
18001
+ if (i === j || !contains(outer, inner)) return;
18002
+ const nestedDeeper = functions.some(
18003
+ (mid, k) => k !== i && k !== j && contains(outer, mid) && contains(mid, inner)
18004
+ );
18005
+ if (!nestedDeeper) subtract += raw[j] - 1;
18006
+ });
18007
+ outer.complexity = Math.max(1, raw[i] - subtract);
18008
+ });
17957
18009
  return functions;
17958
18010
  }
17959
18011
  function extractFunctionBody(lines, startLine) {
@@ -12121,6 +12121,7 @@ var init_logger = __esm({
12121
12121
  var auth_exports = {};
12122
12122
  __export(auth_exports, {
12123
12123
  Auth: () => Auth,
12124
+ JWT_LEEWAY_SECONDS: () => JWT_LEEWAY_SECONDS,
12124
12125
  authMiddleware: () => authMiddleware,
12125
12126
  authenticateRequest: () => authenticateRequest,
12126
12127
  checkPassword: () => checkPassword,
@@ -12129,6 +12130,7 @@ __export(auth_exports, {
12129
12130
  getToken: () => getToken,
12130
12131
  hashPassword: () => hashPassword,
12131
12132
  refreshToken: () => refreshToken,
12133
+ resolveAlgorithm: () => resolveAlgorithm,
12132
12134
  validToken: () => validToken,
12133
12135
  validateApiKey: () => validateApiKey
12134
12136
  });
@@ -12189,6 +12191,18 @@ function ensureDevSecret(cwd) {
12189
12191
  }
12190
12192
  return newSecret;
12191
12193
  }
12194
+ function unsupportedAlgorithmError(algorithm) {
12195
+ return new Error(
12196
+ `Unsupported JWT algorithm "${algorithm}". Tina4 signs with ${SUPPORTED_ALGORITHMS.join(", ")} (HMAC via node:crypto; RS256 needs a PEM key pair). Set TINA4_JWT_ALGORITHM to one of those.`
12197
+ );
12198
+ }
12199
+ function resolveAlgorithm(algorithm) {
12200
+ const chosen = (algorithm || process.env.TINA4_JWT_ALGORITHM || "HS256").trim();
12201
+ if (!HMAC_DIGESTS.has(chosen) && !RSA_SIGN_ALGORITHMS.has(chosen)) {
12202
+ throw unsupportedAlgorithmError(chosen);
12203
+ }
12204
+ return chosen;
12205
+ }
12192
12206
  function base64urlEncode(data) {
12193
12207
  return data.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
12194
12208
  }
@@ -12211,7 +12225,7 @@ function getToken(payload, secretOrExpiresIn, expiresIn = 60, algorithm) {
12211
12225
  if (!resolvedSecret) {
12212
12226
  _warnBlankSecret();
12213
12227
  }
12214
- const resolvedAlgorithm = algorithm ?? process.env.TINA4_JWT_ALGORITHM ?? "HS256";
12228
+ const resolvedAlgorithm = resolveAlgorithm(algorithm);
12215
12229
  const header = { alg: resolvedAlgorithm, typ: "JWT" };
12216
12230
  const now = Math.floor(Date.now() / 1e3);
12217
12231
  const claims = { ...payload, iat: now };
@@ -12229,19 +12243,27 @@ function validToken(token, secret, algorithm) {
12229
12243
  if (!resolvedSecret) {
12230
12244
  _warnBlankSecret();
12231
12245
  }
12232
- const resolvedAlgorithm = algorithm ?? process.env.TINA4_JWT_ALGORITHM ?? "HS256";
12246
+ const resolvedAlgorithm = resolveAlgorithm(algorithm);
12233
12247
  try {
12234
12248
  const parts = token.split(".");
12235
12249
  if (parts.length !== 3) return null;
12236
12250
  const [h, p, sig] = parts;
12251
+ const header = JSON.parse(base64urlDecode(h).toString());
12252
+ if (header.alg !== resolvedAlgorithm) return null;
12237
12253
  const signingInput = `${h}.${p}`;
12238
12254
  if (!verifySignature(signingInput, sig, resolvedSecret, resolvedAlgorithm)) {
12239
12255
  return null;
12240
12256
  }
12241
12257
  const payload = JSON.parse(base64urlDecode(p).toString());
12242
- if (typeof payload.exp === "number" && Date.now() / 1e3 > payload.exp) {
12258
+ const now = Date.now() / 1e3;
12259
+ if (typeof payload.exp === "number" && now > payload.exp) {
12243
12260
  return null;
12244
12261
  }
12262
+ if (Object.hasOwn(payload, "nbf")) {
12263
+ const notBefore = payload.nbf;
12264
+ if (typeof notBefore !== "number" || !Number.isFinite(notBefore)) return null;
12265
+ if (now + JWT_LEEWAY_SECONDS < notBefore) return null;
12266
+ }
12245
12267
  return payload;
12246
12268
  } catch {
12247
12269
  return null;
@@ -12257,32 +12279,33 @@ function getPayload(token) {
12257
12279
  }
12258
12280
  }
12259
12281
  function sign(input, secret, algorithm) {
12260
- if (algorithm === "HS256") {
12261
- const sig = createHmac2("sha256", secret).update(input).digest();
12262
- return base64urlEncode(sig);
12282
+ const digest = HMAC_DIGESTS.get(algorithm);
12283
+ if (digest) {
12284
+ return base64urlEncode(createHmac2(digest, secret).update(input).digest());
12263
12285
  }
12264
- if (algorithm === "RS256") {
12265
- const signer = createSign("RSA-SHA256");
12286
+ const rsaAlgorithm = RSA_SIGN_ALGORITHMS.get(algorithm);
12287
+ if (rsaAlgorithm) {
12288
+ const signer = createSign(rsaAlgorithm);
12266
12289
  signer.update(input);
12267
- const sig = signer.sign(secret);
12268
- return base64urlEncode(sig);
12290
+ return base64urlEncode(signer.sign(secret));
12269
12291
  }
12270
- throw new Error(`Unsupported algorithm: ${algorithm}`);
12292
+ throw unsupportedAlgorithmError(algorithm);
12271
12293
  }
12272
12294
  function verifySignature(input, sig, secret, algorithm) {
12273
- if (algorithm === "HS256") {
12295
+ if (HMAC_DIGESTS.has(algorithm)) {
12274
12296
  const expected = sign(input, secret, algorithm);
12275
12297
  const a = Buffer.from(sig);
12276
12298
  const b = Buffer.from(expected);
12277
12299
  if (a.length !== b.length) return false;
12278
12300
  return timingSafeEqual(a, b);
12279
12301
  }
12280
- if (algorithm === "RS256") {
12281
- const verifier = createVerify("RSA-SHA256");
12302
+ const rsaAlgorithm = RSA_SIGN_ALGORITHMS.get(algorithm);
12303
+ if (rsaAlgorithm) {
12304
+ const verifier = createVerify(rsaAlgorithm);
12282
12305
  verifier.update(input);
12283
12306
  return verifier.verify(secret, base64urlDecode(sig));
12284
12307
  }
12285
- throw new Error(`Unsupported algorithm: ${algorithm}`);
12308
+ throw unsupportedAlgorithmError(algorithm);
12286
12309
  }
12287
12310
  function hashPassword(password, salt, iterations = 26e4) {
12288
12311
  const actualSalt = salt ?? randomBytes3(16).toString("hex");
@@ -12307,7 +12330,7 @@ function checkPassword(password, hash) {
12307
12330
  return false;
12308
12331
  }
12309
12332
  }
12310
- function authMiddleware(secret, algorithm = "HS256") {
12333
+ function authMiddleware(secret, algorithm) {
12311
12334
  return (req2, res, next) => {
12312
12335
  const authHeader = req2.headers.authorization ?? "";
12313
12336
  if (!authHeader.startsWith("Bearer ")) {
@@ -12330,11 +12353,11 @@ function refreshToken(token, expiresIn = 60) {
12330
12353
  const { iat: _iat, exp: _exp, ...claims } = payload;
12331
12354
  return getToken(claims, expiresIn);
12332
12355
  }
12333
- function authenticateRequest(headers, secret, algorithm = "HS256") {
12356
+ function authenticateRequest(headers, secret, algorithm) {
12334
12357
  const authHeader = headers.authorization ?? headers.Authorization ?? "";
12335
12358
  if (!authHeader.startsWith("Bearer ")) return null;
12336
12359
  const token = authHeader.slice(7);
12337
- if (validToken(token)) return getPayload(token);
12360
+ if (validToken(token, secret, algorithm)) return getPayload(token);
12338
12361
  if (validateApiKey(token)) {
12339
12362
  return { _auth: "api_key" };
12340
12363
  }
@@ -12348,12 +12371,23 @@ function validateApiKey(provided, expected) {
12348
12371
  if (a.length !== b.length) return false;
12349
12372
  return timingSafeEqual(a, b);
12350
12373
  }
12351
- var BLANK_SECRET_WARNING, Auth;
12374
+ var BLANK_SECRET_WARNING, HMAC_DIGESTS, RSA_SIGN_ALGORITHMS, SUPPORTED_ALGORITHMS, JWT_LEEWAY_SECONDS, Auth;
12352
12375
  var init_auth = __esm({
12353
12376
  "src/auth.ts"() {
12354
12377
  "use strict";
12355
12378
  init_dotenv();
12356
12379
  BLANK_SECRET_WARNING = "Auth: TINA4_SECRET is not set \u2014 JWT signing is insecure. Set TINA4_SECRET to a random value (e.g. `openssl rand -hex 32`) in your environment or .env before serving traffic. For LOCAL DEV, set TINA4_DEBUG=true and a per-machine secret is generated automatically into .env.local (gitignored). Seeing this warning means the run was NOT detected as dev \u2014 typically a container or CI without TINA4_DEBUG set, or TINA4_ENV=production.";
12380
+ HMAC_DIGESTS = /* @__PURE__ */ new Map([
12381
+ ["HS256", "sha256"],
12382
+ ["HS384", "sha384"],
12383
+ ["HS512", "sha512"]
12384
+ ]);
12385
+ RSA_SIGN_ALGORITHMS = /* @__PURE__ */ new Map([["RS256", "RSA-SHA256"]]);
12386
+ SUPPORTED_ALGORITHMS = [
12387
+ ...HMAC_DIGESTS.keys(),
12388
+ ...RSA_SIGN_ALGORITHMS.keys()
12389
+ ];
12390
+ JWT_LEEWAY_SECONDS = 60;
12357
12391
  Auth = class {
12358
12392
  static getToken = getToken;
12359
12393
  static validToken = validToken;
@@ -17937,7 +17971,7 @@ function extractFunctions(source, filePath, root = ".") {
17937
17971
  if (funcName !== null && !NON_FUNCTION_WORDS.has(funcName)) {
17938
17972
  const displayName = funcName === "constructor" && currentClass ? `${currentClass}.constructor` : currentClass !== null && !isTopLevelDecl ? `${currentClass}.${funcName}` : funcName;
17939
17973
  const funcBody = extractFunctionBody(lines, i);
17940
- const funcLoc = funcBody.split("\n").length;
17974
+ const funcLoc = Math.max(1, countLines(funcBody).loc);
17941
17975
  const complexity = cycloMaticComplexity(funcBody);
17942
17976
  const args = argsStr.split(",").map((a) => a.trim().split(":")[0].split("=")[0].replace("?", "").trim()).filter((a) => a && a !== "this");
17943
17977
  functions.push({
@@ -17953,6 +17987,24 @@ function extractFunctions(source, filePath, root = ".") {
17953
17987
  currentClass = null;
17954
17988
  }
17955
17989
  }
17990
+ return chargeNestedComplexityToTheNestedFunction(functions);
17991
+ }
17992
+ function chargeNestedComplexityToTheNestedFunction(functions) {
17993
+ if (functions.length < 2) return functions;
17994
+ const lastLine = (f) => f.line + Math.max(1, f.loc) - 1;
17995
+ const contains = (outer, inner) => inner.line > outer.line && lastLine(inner) <= lastLine(outer);
17996
+ const raw = functions.map((f) => f.complexity);
17997
+ functions.forEach((outer, i) => {
17998
+ let subtract = 0;
17999
+ functions.forEach((inner, j) => {
18000
+ if (i === j || !contains(outer, inner)) return;
18001
+ const nestedDeeper = functions.some(
18002
+ (mid, k) => k !== i && k !== j && contains(outer, mid) && contains(mid, inner)
18003
+ );
18004
+ if (!nestedDeeper) subtract += raw[j] - 1;
18005
+ });
18006
+ outer.complexity = Math.max(1, raw[i] - subtract);
18007
+ });
17956
18008
  return functions;
17957
18009
  }
17958
18010
  function extractFunctionBody(lines, startLine) {