@nudojs/lsp 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -240131,6 +240131,10 @@ var _bForkBudgetLimit = MAX_B_TOTAL_FORKS;
240131
240131
  var _bForkCount = 0;
240132
240132
  var _bForkTruncNoted = false;
240133
240133
  var FORK_TRUNCATION_LABEL = "#fork-budget";
240134
+ var HOST_EFFECT_LABEL_PREFIX = "#host-effect:";
240135
+ function noteHostEffectBlocked(name) {
240136
+ noteAbsTruncation(`${HOST_EFFECT_LABEL_PREFIX}${name}`);
240137
+ }
240134
240138
  function noteBForkTruncation() {
240135
240139
  noteAbsTruncation(FORK_TRUNCATION_LABEL);
240136
240140
  }
@@ -242176,6 +242180,26 @@ var GLOBAL_FNS = /* @__PURE__ */ new Set([
242176
242180
  "eval",
242177
242181
  "Symbol"
242178
242182
  ]);
242183
+ var NEVER_EXEC_HOST_FN_NAMES = [
242184
+ "fetch",
242185
+ "XMLHttpRequest",
242186
+ "WebSocket",
242187
+ "EventSource",
242188
+ "setTimeout",
242189
+ "setInterval",
242190
+ "setImmediate",
242191
+ "queueMicrotask",
242192
+ "requestAnimationFrame",
242193
+ "requestIdleCallback"
242194
+ ];
242195
+ function neverExecHostName(fn2) {
242196
+ if (typeof fn2 !== "function") return null;
242197
+ const g = globalThis;
242198
+ for (const n of NEVER_EXEC_HOST_FN_NAMES) {
242199
+ if (fn2 === g[n]) return n;
242200
+ }
242201
+ return null;
242202
+ }
242179
242203
  function noteBCallRecord(r) {
242180
242204
  if (!bCallCollector) return;
242181
242205
  try {
@@ -242257,8 +242281,12 @@ function $callNamed(name, fn2, args, loc, argLocs) {
242257
242281
  try {
242258
242282
  if (typeof fn2 === "function") {
242259
242283
  const g = GLOBAL_FNS.has(name) && fn2 === globalThis[name] ? evalGlobalFn(name, args) : void 0;
242284
+ const blockedHost = g === void 0 ? neverExecHostName(fn2) : null;
242260
242285
  if (g !== void 0) {
242261
242286
  result = g;
242287
+ } else if (blockedHost !== null) {
242288
+ noteHostEffectBlocked(blockedHost);
242289
+ result = bTruncatedAbs();
242262
242290
  } else {
242263
242291
  const entered = bEnterCall(name, fn2, args);
242264
242292
  if (!entered.ok) {
@@ -242357,7 +242385,13 @@ function execRegexBrand(re, method, args) {
242357
242385
  if (method === "toString") return strLit(`/${parts.pat}/${parts.flags}`);
242358
242386
  const subject = args[0] ? litValue(args[0]) : void 0;
242359
242387
  if (typeof subject !== "string") {
242360
- return method === "test" ? abs({ k: "prim", type: "boolean" }, void 0, void 0, "partial") : void 0;
242388
+ if (method === "test") {
242389
+ return abs({ k: "prim", type: "boolean" }, void 0, void 0, "partial");
242390
+ }
242391
+ const element = joinAbs(str(), undefAbs());
242392
+ const matchAbs = abs({ k: "arr", element }, void 0, void 0, "path");
242393
+ const nullAbs = abs({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
242394
+ return joinAbs(nullAbs, matchAbs);
242361
242395
  }
242362
242396
  try {
242363
242397
  return regexExecWithState(re, method, subject).result;
package/dist/server.js CHANGED
@@ -250531,6 +250531,10 @@ var _bForkBudgetLimit = MAX_B_TOTAL_FORKS;
250531
250531
  var _bForkCount = 0;
250532
250532
  var _bForkTruncNoted = false;
250533
250533
  var FORK_TRUNCATION_LABEL = "#fork-budget";
250534
+ var HOST_EFFECT_LABEL_PREFIX = "#host-effect:";
250535
+ function noteHostEffectBlocked(name) {
250536
+ noteAbsTruncation(`${HOST_EFFECT_LABEL_PREFIX}${name}`);
250537
+ }
250534
250538
  function noteBForkTruncation() {
250535
250539
  noteAbsTruncation(FORK_TRUNCATION_LABEL);
250536
250540
  }
@@ -252576,6 +252580,26 @@ var GLOBAL_FNS = /* @__PURE__ */ new Set([
252576
252580
  "eval",
252577
252581
  "Symbol"
252578
252582
  ]);
252583
+ var NEVER_EXEC_HOST_FN_NAMES = [
252584
+ "fetch",
252585
+ "XMLHttpRequest",
252586
+ "WebSocket",
252587
+ "EventSource",
252588
+ "setTimeout",
252589
+ "setInterval",
252590
+ "setImmediate",
252591
+ "queueMicrotask",
252592
+ "requestAnimationFrame",
252593
+ "requestIdleCallback"
252594
+ ];
252595
+ function neverExecHostName(fn2) {
252596
+ if (typeof fn2 !== "function") return null;
252597
+ const g = globalThis;
252598
+ for (const n of NEVER_EXEC_HOST_FN_NAMES) {
252599
+ if (fn2 === g[n]) return n;
252600
+ }
252601
+ return null;
252602
+ }
252579
252603
  function noteBCallRecord(r) {
252580
252604
  if (!bCallCollector) return;
252581
252605
  try {
@@ -252657,8 +252681,12 @@ function $callNamed(name, fn2, args, loc, argLocs) {
252657
252681
  try {
252658
252682
  if (typeof fn2 === "function") {
252659
252683
  const g = GLOBAL_FNS.has(name) && fn2 === globalThis[name] ? evalGlobalFn(name, args) : void 0;
252684
+ const blockedHost = g === void 0 ? neverExecHostName(fn2) : null;
252660
252685
  if (g !== void 0) {
252661
252686
  result = g;
252687
+ } else if (blockedHost !== null) {
252688
+ noteHostEffectBlocked(blockedHost);
252689
+ result = bTruncatedAbs();
252662
252690
  } else {
252663
252691
  const entered = bEnterCall(name, fn2, args);
252664
252692
  if (!entered.ok) {
@@ -252757,7 +252785,13 @@ function execRegexBrand(re, method, args) {
252757
252785
  if (method === "toString") return strLit(`/${parts.pat}/${parts.flags}`);
252758
252786
  const subject = args[0] ? litValue(args[0]) : void 0;
252759
252787
  if (typeof subject !== "string") {
252760
- return method === "test" ? abs({ k: "prim", type: "boolean" }, void 0, void 0, "partial") : void 0;
252788
+ if (method === "test") {
252789
+ return abs({ k: "prim", type: "boolean" }, void 0, void 0, "partial");
252790
+ }
252791
+ const element = joinAbs(str(), undefAbs());
252792
+ const matchAbs = abs({ k: "arr", element }, void 0, void 0, "path");
252793
+ const nullAbs = abs({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
252794
+ return joinAbs(nullAbs, matchAbs);
252761
252795
  }
252762
252796
  try {
252763
252797
  return regexExecWithState(re, method, subject).result;
@@ -258057,6 +258091,17 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
258057
258091
  });
258058
258092
  continue;
258059
258093
  }
258094
+ if (label.startsWith(HOST_EFFECT_LABEL_PREFIX)) {
258095
+ const hostName = label.slice(HOST_EFFECT_LABEL_PREFIX.length);
258096
+ issues.push({
258097
+ severity: "info",
258098
+ code: "nudo:host-effect-blocked",
258099
+ message: `Host function '${hostName}' is not executed during analysis (network/timer side effect); result widened to unknown#opaque`,
258100
+ suggestion: "optional: mock it with @nudo:mock / @nudo:env, or pass the value in from a call site",
258101
+ fn: hostName
258102
+ });
258103
+ continue;
258104
+ }
258060
258105
  issues.push({
258061
258106
  severity: "info",
258062
258107
  code: "nudo:recursion-truncated",
@@ -258186,6 +258231,19 @@ function checkReturnConstraint(fnName, cName, constraint, ret) {
258186
258231
  const out = [];
258187
258232
  if (ret.shape.k === "unknown" && !ret.term) return out;
258188
258233
  if (ret.shape.k === "any") return out;
258234
+ if (isNullishLitAbs(ret)) return out;
258235
+ if (ret.shape.k === "sum" && constraint.fields) {
258236
+ const seen = /* @__PURE__ */ new Set();
258237
+ for (const m of ret.shape.members) {
258238
+ for (const issue of checkReturnConstraint(fnName, cName, constraint, m)) {
258239
+ const key = `${issue.message}\0${issue.actual ?? ""}\0${issue.expected ?? ""}`;
258240
+ if (seen.has(key)) continue;
258241
+ seen.add(key);
258242
+ out.push(issue);
258243
+ }
258244
+ }
258245
+ return out;
258246
+ }
258189
258247
  const push = (actual, expected, suggestion) => {
258190
258248
  out.push({
258191
258249
  severity: "error",
@@ -258354,6 +258412,11 @@ function actionsForIssue(i) {
258354
258412
  label: "results widened to unknown \u2014 raise budget or narrow control flow"
258355
258413
  }
258356
258414
  ];
258415
+ case "nudo:host-effect-blocked":
258416
+ return [
258417
+ { kind: "mock", label: "pin the host API with @nudo:mock / @nudo:env" },
258418
+ { kind: "info", label: "results widened to unknown \u2014 or feed the value in from a call site" }
258419
+ ];
258357
258420
  case "nudo:interface-drift":
258358
258421
  return [
258359
258422
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nudojs/lsp",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -49,9 +49,9 @@
49
49
  "registry": "https://registry.npmjs.org"
50
50
  },
51
51
  "dependencies": {
52
- "@nudojs/service": "1.1.0",
53
- "@nudojs/core": "1.1.0",
54
- "@nudojs/parser": "1.1.0",
52
+ "@nudojs/service": "1.1.1",
53
+ "@nudojs/core": "1.1.1",
54
+ "@nudojs/parser": "1.1.1",
55
55
  "@babel/traverse": "^8.0.6",
56
56
  "@babel/types": "^8.0.6",
57
57
  "vscode-languageserver": "^10.1.2",