nudojs 1.0.0 → 1.0.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.
Files changed (2) hide show
  1. package/dist/index.js +78 -3
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -27989,6 +27989,9 @@ function exitCall() {
27989
27989
  _absCallDepth--;
27990
27990
  _activeCallKeys.pop();
27991
27991
  }
27992
+ function noteHostEffectBlocked(name) {
27993
+ noteAbsTruncation(`${HOST_EFFECT_LABEL_PREFIX}${name}`);
27994
+ }
27992
27995
  function noteBForkTruncation() {
27993
27996
  noteAbsTruncation(FORK_TRUNCATION_LABEL);
27994
27997
  }
@@ -28018,7 +28021,7 @@ function bumpBForkBudget() {
28018
28021
  }
28019
28022
  return false;
28020
28023
  }
28021
- var MAX_CALL_DEPTH, MAX_TOTAL_CALLS, _absCallDepth, _absTotalCalls, _activeCallKeys, _fnCallIds, _fnCallIdSeq, _callTruncated, absTruncCollector, MAX_B_TOTAL_FORKS, _bForkBudgetLimit, _bForkCount, _bForkTruncNoted, FORK_TRUNCATION_LABEL;
28024
+ var MAX_CALL_DEPTH, MAX_TOTAL_CALLS, _absCallDepth, _absTotalCalls, _activeCallKeys, _fnCallIds, _fnCallIdSeq, _callTruncated, absTruncCollector, MAX_B_TOTAL_FORKS, _bForkBudgetLimit, _bForkCount, _bForkTruncNoted, FORK_TRUNCATION_LABEL, HOST_EFFECT_LABEL_PREFIX;
28022
28025
  var init_call_budget = __esm({
28023
28026
  "../core/src/algebra/call-budget.ts"() {
28024
28027
  "use strict";
@@ -28038,6 +28041,7 @@ var init_call_budget = __esm({
28038
28041
  _bForkCount = 0;
28039
28042
  _bForkTruncNoted = false;
28040
28043
  FORK_TRUNCATION_LABEL = "#fork-budget";
28044
+ HOST_EFFECT_LABEL_PREFIX = "#host-effect:";
28041
28045
  }
28042
28046
  });
28043
28047
 
@@ -30215,7 +30219,9 @@ __export(calls_exports, {
30215
30219
  $recordBinding: () => $recordBinding,
30216
30220
  MAX_B_CALL_DEPTH: () => MAX_B_CALL_DEPTH,
30217
30221
  MAX_B_TOTAL_CALLS: () => MAX_B_TOTAL_CALLS,
30222
+ blockHostSideEffect: () => blockHostSideEffect,
30218
30223
  getBCallCollector: () => getBCallCollector,
30224
+ neverExecHostName: () => neverExecHostName,
30219
30225
  noteBCallRecord: () => noteBCallRecord,
30220
30226
  resetBCallBudget: () => resetBCallBudget,
30221
30227
  setBAssignCollector: () => setBAssignCollector,
@@ -30247,6 +30253,20 @@ function setBBindingSink(sink) {
30247
30253
  function $recordBinding(name, value) {
30248
30254
  if (bindingSink) bindingSink.set(name, value);
30249
30255
  }
30256
+ function neverExecHostName(fn2) {
30257
+ if (typeof fn2 !== "function") return null;
30258
+ const g = globalThis;
30259
+ for (const n of NEVER_EXEC_HOST_FN_NAMES) {
30260
+ if (fn2 === g[n]) return n;
30261
+ }
30262
+ return null;
30263
+ }
30264
+ function blockHostSideEffect(fn2) {
30265
+ const hostName = neverExecHostName(fn2);
30266
+ if (hostName === null) return null;
30267
+ noteHostEffectBlocked(hostName);
30268
+ return abs({ k: "unknown" }, void 0, void 0, "opaque");
30269
+ }
30250
30270
  function noteBCallRecord(r) {
30251
30271
  if (!bCallCollector) return;
30252
30272
  try {
@@ -30321,8 +30341,11 @@ function $callNamed(name, fn2, args, loc, argLocs) {
30321
30341
  try {
30322
30342
  if (typeof fn2 === "function") {
30323
30343
  const g = GLOBAL_FNS.has(name) && fn2 === globalThis[name] ? evalGlobalFn(name, args) : void 0;
30344
+ const blockedHost = g === void 0 ? blockHostSideEffect(fn2) : null;
30324
30345
  if (g !== void 0) {
30325
30346
  result = g;
30347
+ } else if (blockedHost !== null) {
30348
+ result = blockedHost;
30326
30349
  } else {
30327
30350
  const entered = bEnterCall(name, fn2, args);
30328
30351
  if (!entered.ok) {
@@ -30375,7 +30398,7 @@ function $callNamed(name, fn2, args, loc, argLocs) {
30375
30398
  }
30376
30399
  return result;
30377
30400
  }
30378
- var pureCallMemo, bCallCollector, bAssignCollector, bindingSink, GLOBAL_FNS, MAX_B_CALL_DEPTH, MAX_B_TOTAL_CALLS, bCallDepth, bTotalCalls, bActiveCallKeys, bFnCallIds, bFnCallIdSeq;
30401
+ var pureCallMemo, bCallCollector, bAssignCollector, bindingSink, GLOBAL_FNS, NEVER_EXEC_HOST_FN_NAMES, MAX_B_CALL_DEPTH, MAX_B_TOTAL_CALLS, bCallDepth, bTotalCalls, bActiveCallKeys, bFnCallIds, bFnCallIdSeq;
30379
30402
  var init_calls = __esm({
30380
30403
  "../core/src/algebra/exec/calls.ts"() {
30381
30404
  "use strict";
@@ -30403,6 +30426,18 @@ var init_calls = __esm({
30403
30426
  "eval",
30404
30427
  "Symbol"
30405
30428
  ]);
30429
+ NEVER_EXEC_HOST_FN_NAMES = [
30430
+ "fetch",
30431
+ "XMLHttpRequest",
30432
+ "WebSocket",
30433
+ "EventSource",
30434
+ "setTimeout",
30435
+ "setInterval",
30436
+ "setImmediate",
30437
+ "queueMicrotask",
30438
+ "requestAnimationFrame",
30439
+ "requestIdleCallback"
30440
+ ];
30406
30441
  MAX_B_CALL_DEPTH = 64;
30407
30442
  MAX_B_TOTAL_CALLS = 2e4;
30408
30443
  bCallDepth = 0;
@@ -30458,7 +30493,13 @@ function execRegexBrand(re, method, args) {
30458
30493
  if (method === "toString") return strLit(`/${parts.pat}/${parts.flags}`);
30459
30494
  const subject = args[0] ? litValue(args[0]) : void 0;
30460
30495
  if (typeof subject !== "string") {
30461
- return method === "test" ? abs({ k: "prim", type: "boolean" }, void 0, void 0, "partial") : void 0;
30496
+ if (method === "test") {
30497
+ return abs({ k: "prim", type: "boolean" }, void 0, void 0, "partial");
30498
+ }
30499
+ const element = joinAbs(str(), undefAbs());
30500
+ const matchAbs = abs({ k: "arr", element }, void 0, void 0, "path");
30501
+ const nullAbs = abs({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
30502
+ return joinAbs(nullAbs, matchAbs);
30462
30503
  }
30463
30504
  try {
30464
30505
  return regexExecWithState(re, method, subject).result;
@@ -30657,6 +30698,8 @@ function $new(cls, args) {
30657
30698
  }
30658
30699
  }
30659
30700
  if (typeof cls === "function") {
30701
+ const blockedHost = blockHostSideEffect(cls);
30702
+ if (blockedHost) return blockedHost;
30660
30703
  if (cls === Array) {
30661
30704
  return makeArrayCtorAbs(args);
30662
30705
  }
@@ -36245,6 +36288,17 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
36245
36288
  });
36246
36289
  continue;
36247
36290
  }
36291
+ if (label.startsWith(HOST_EFFECT_LABEL_PREFIX)) {
36292
+ const hostName = label.slice(HOST_EFFECT_LABEL_PREFIX.length);
36293
+ issues.push({
36294
+ severity: "info",
36295
+ code: "nudo:host-effect-blocked",
36296
+ message: `Host function '${hostName}' is not executed during analysis (network/timer side effect); result widened to unknown#opaque`,
36297
+ suggestion: "optional: mock it with @nudo:mock / @nudo:env, or pass the value in from a call site",
36298
+ fn: hostName
36299
+ });
36300
+ continue;
36301
+ }
36248
36302
  issues.push({
36249
36303
  severity: "info",
36250
36304
  code: "nudo:recursion-truncated",
@@ -36374,6 +36428,19 @@ function checkReturnConstraint(fnName, cName, constraint, ret) {
36374
36428
  const out = [];
36375
36429
  if (ret.shape.k === "unknown" && !ret.term) return out;
36376
36430
  if (ret.shape.k === "any") return out;
36431
+ if (isNullishLitAbs(ret)) return out;
36432
+ if (ret.shape.k === "sum" && constraint.fields) {
36433
+ const seen = /* @__PURE__ */ new Set();
36434
+ for (const m of ret.shape.members) {
36435
+ for (const issue of checkReturnConstraint(fnName, cName, constraint, m)) {
36436
+ const key = `${issue.message}\0${issue.actual ?? ""}\0${issue.expected ?? ""}`;
36437
+ if (seen.has(key)) continue;
36438
+ seen.add(key);
36439
+ out.push(issue);
36440
+ }
36441
+ }
36442
+ return out;
36443
+ }
36377
36444
  const push = (actual, expected, suggestion) => {
36378
36445
  out.push({
36379
36446
  severity: "error",
@@ -36475,6 +36542,7 @@ var init_check = __esm({
36475
36542
  "../core/src/algebra/check.ts"() {
36476
36543
  "use strict";
36477
36544
  init_parse_source();
36545
+ init_surface();
36478
36546
  init_call_budget();
36479
36547
  init_refine();
36480
36548
  init_interface();
@@ -36574,6 +36642,11 @@ function actionsForIssue(i) {
36574
36642
  label: "results widened to unknown \u2014 raise budget or narrow control flow"
36575
36643
  }
36576
36644
  ];
36645
+ case "nudo:host-effect-blocked":
36646
+ return [
36647
+ { kind: "mock", label: "pin the host API with @nudo:mock / @nudo:env" },
36648
+ { kind: "info", label: "results widened to unknown \u2014 or feed the value in from a call site" }
36649
+ ];
36577
36650
  case "nudo:interface-drift":
36578
36651
  return [
36579
36652
  {
@@ -46028,6 +46101,7 @@ __export(internal_exports, {
46028
46101
  $tryReleaseSoft: () => $tryReleaseSoft,
46029
46102
  ERROR_FAMILY: () => ERROR_FAMILY,
46030
46103
  FORK_TRUNCATION_LABEL: () => FORK_TRUNCATION_LABEL,
46104
+ HOST_EFFECT_LABEL_PREFIX: () => HOST_EFFECT_LABEL_PREFIX,
46031
46105
  MAX_B_TOTAL_FORKS: () => MAX_B_TOTAL_FORKS,
46032
46106
  MAX_CALL_DEPTH: () => MAX_CALL_DEPTH,
46033
46107
  MAX_TOTAL_CALLS: () => MAX_TOTAL_CALLS,
@@ -46105,6 +46179,7 @@ __export(internal_exports, {
46105
46179
  noteBForkTruncation: () => noteBForkTruncation,
46106
46180
  noteDerivationAdd: () => noteDerivationAdd,
46107
46181
  noteDerivationJoin: () => noteDerivationJoin,
46182
+ noteHostEffectBlocked: () => noteHostEffectBlocked,
46108
46183
  noteMemberDispatchMiss: () => noteMemberDispatchMiss,
46109
46184
  noteNullishMemberThrows: () => noteNullishMemberThrows,
46110
46185
  noteObjSlotMissing: () => noteObjSlotMissing,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nudojs",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -46,10 +46,10 @@
46
46
  "dependencies": {
47
47
  "@babel/generator": "^8.0.6",
48
48
  "@babel/types": "^8.0.6",
49
- "@nudojs/core": "1.1.0",
50
- "@nudojs/parser": "1.1.0",
51
- "@nudojs/service": "1.1.0",
52
- "@nudojs/harvester": "0.2.8",
49
+ "@nudojs/core": "1.1.2",
50
+ "@nudojs/parser": "1.1.2",
51
+ "@nudojs/service": "1.1.2",
52
+ "@nudojs/harvester": "0.2.10",
53
53
  "commander": "^15.0.0"
54
54
  },
55
55
  "devDependencies": {