@nudojs/lsp 1.1.0 → 1.1.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.
- package/dist/index.js +44 -1
- package/dist/server.js +73 -1
- package/package.json +4 -4
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
|
}
|
|
@@ -242127,7 +242131,9 @@ __export(calls_exports, {
|
|
|
242127
242131
|
$recordBinding: () => $recordBinding,
|
|
242128
242132
|
MAX_B_CALL_DEPTH: () => MAX_B_CALL_DEPTH,
|
|
242129
242133
|
MAX_B_TOTAL_CALLS: () => MAX_B_TOTAL_CALLS,
|
|
242134
|
+
blockHostSideEffect: () => blockHostSideEffect,
|
|
242130
242135
|
getBCallCollector: () => getBCallCollector,
|
|
242136
|
+
neverExecHostName: () => neverExecHostName,
|
|
242131
242137
|
noteBCallRecord: () => noteBCallRecord,
|
|
242132
242138
|
resetBCallBudget: () => resetBCallBudget,
|
|
242133
242139
|
setBAssignCollector: () => setBAssignCollector,
|
|
@@ -242176,6 +242182,32 @@ var GLOBAL_FNS = /* @__PURE__ */ new Set([
|
|
|
242176
242182
|
"eval",
|
|
242177
242183
|
"Symbol"
|
|
242178
242184
|
]);
|
|
242185
|
+
var NEVER_EXEC_HOST_FN_NAMES = [
|
|
242186
|
+
"fetch",
|
|
242187
|
+
"XMLHttpRequest",
|
|
242188
|
+
"WebSocket",
|
|
242189
|
+
"EventSource",
|
|
242190
|
+
"setTimeout",
|
|
242191
|
+
"setInterval",
|
|
242192
|
+
"setImmediate",
|
|
242193
|
+
"queueMicrotask",
|
|
242194
|
+
"requestAnimationFrame",
|
|
242195
|
+
"requestIdleCallback"
|
|
242196
|
+
];
|
|
242197
|
+
function neverExecHostName(fn2) {
|
|
242198
|
+
if (typeof fn2 !== "function") return null;
|
|
242199
|
+
const g = globalThis;
|
|
242200
|
+
for (const n of NEVER_EXEC_HOST_FN_NAMES) {
|
|
242201
|
+
if (fn2 === g[n]) return n;
|
|
242202
|
+
}
|
|
242203
|
+
return null;
|
|
242204
|
+
}
|
|
242205
|
+
function blockHostSideEffect(fn2) {
|
|
242206
|
+
const hostName = neverExecHostName(fn2);
|
|
242207
|
+
if (hostName === null) return null;
|
|
242208
|
+
noteHostEffectBlocked(hostName);
|
|
242209
|
+
return abs({ k: "unknown" }, void 0, void 0, "opaque");
|
|
242210
|
+
}
|
|
242179
242211
|
function noteBCallRecord(r) {
|
|
242180
242212
|
if (!bCallCollector) return;
|
|
242181
242213
|
try {
|
|
@@ -242257,8 +242289,11 @@ function $callNamed(name, fn2, args, loc, argLocs) {
|
|
|
242257
242289
|
try {
|
|
242258
242290
|
if (typeof fn2 === "function") {
|
|
242259
242291
|
const g = GLOBAL_FNS.has(name) && fn2 === globalThis[name] ? evalGlobalFn(name, args) : void 0;
|
|
242292
|
+
const blockedHost = g === void 0 ? blockHostSideEffect(fn2) : null;
|
|
242260
242293
|
if (g !== void 0) {
|
|
242261
242294
|
result = g;
|
|
242295
|
+
} else if (blockedHost !== null) {
|
|
242296
|
+
result = blockedHost;
|
|
242262
242297
|
} else {
|
|
242263
242298
|
const entered = bEnterCall(name, fn2, args);
|
|
242264
242299
|
if (!entered.ok) {
|
|
@@ -242357,7 +242392,13 @@ function execRegexBrand(re, method, args) {
|
|
|
242357
242392
|
if (method === "toString") return strLit(`/${parts.pat}/${parts.flags}`);
|
|
242358
242393
|
const subject = args[0] ? litValue(args[0]) : void 0;
|
|
242359
242394
|
if (typeof subject !== "string") {
|
|
242360
|
-
|
|
242395
|
+
if (method === "test") {
|
|
242396
|
+
return abs({ k: "prim", type: "boolean" }, void 0, void 0, "partial");
|
|
242397
|
+
}
|
|
242398
|
+
const element = joinAbs(str(), undefAbs());
|
|
242399
|
+
const matchAbs = abs({ k: "arr", element }, void 0, void 0, "path");
|
|
242400
|
+
const nullAbs = abs({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
|
|
242401
|
+
return joinAbs(nullAbs, matchAbs);
|
|
242361
242402
|
}
|
|
242362
242403
|
try {
|
|
242363
242404
|
return regexExecWithState(re, method, subject).result;
|
|
@@ -242523,6 +242564,8 @@ function $new(cls, args) {
|
|
|
242523
242564
|
}
|
|
242524
242565
|
}
|
|
242525
242566
|
if (typeof cls === "function") {
|
|
242567
|
+
const blockedHost = blockHostSideEffect(cls);
|
|
242568
|
+
if (blockedHost) return blockedHost;
|
|
242526
242569
|
if (cls === Array) {
|
|
242527
242570
|
return makeArrayCtorAbs(args);
|
|
242528
242571
|
}
|
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
|
}
|
|
@@ -252527,7 +252531,9 @@ __export(calls_exports, {
|
|
|
252527
252531
|
$recordBinding: () => $recordBinding,
|
|
252528
252532
|
MAX_B_CALL_DEPTH: () => MAX_B_CALL_DEPTH,
|
|
252529
252533
|
MAX_B_TOTAL_CALLS: () => MAX_B_TOTAL_CALLS,
|
|
252534
|
+
blockHostSideEffect: () => blockHostSideEffect,
|
|
252530
252535
|
getBCallCollector: () => getBCallCollector,
|
|
252536
|
+
neverExecHostName: () => neverExecHostName,
|
|
252531
252537
|
noteBCallRecord: () => noteBCallRecord,
|
|
252532
252538
|
resetBCallBudget: () => resetBCallBudget,
|
|
252533
252539
|
setBAssignCollector: () => setBAssignCollector,
|
|
@@ -252576,6 +252582,32 @@ var GLOBAL_FNS = /* @__PURE__ */ new Set([
|
|
|
252576
252582
|
"eval",
|
|
252577
252583
|
"Symbol"
|
|
252578
252584
|
]);
|
|
252585
|
+
var NEVER_EXEC_HOST_FN_NAMES = [
|
|
252586
|
+
"fetch",
|
|
252587
|
+
"XMLHttpRequest",
|
|
252588
|
+
"WebSocket",
|
|
252589
|
+
"EventSource",
|
|
252590
|
+
"setTimeout",
|
|
252591
|
+
"setInterval",
|
|
252592
|
+
"setImmediate",
|
|
252593
|
+
"queueMicrotask",
|
|
252594
|
+
"requestAnimationFrame",
|
|
252595
|
+
"requestIdleCallback"
|
|
252596
|
+
];
|
|
252597
|
+
function neverExecHostName(fn2) {
|
|
252598
|
+
if (typeof fn2 !== "function") return null;
|
|
252599
|
+
const g = globalThis;
|
|
252600
|
+
for (const n of NEVER_EXEC_HOST_FN_NAMES) {
|
|
252601
|
+
if (fn2 === g[n]) return n;
|
|
252602
|
+
}
|
|
252603
|
+
return null;
|
|
252604
|
+
}
|
|
252605
|
+
function blockHostSideEffect(fn2) {
|
|
252606
|
+
const hostName = neverExecHostName(fn2);
|
|
252607
|
+
if (hostName === null) return null;
|
|
252608
|
+
noteHostEffectBlocked(hostName);
|
|
252609
|
+
return abs({ k: "unknown" }, void 0, void 0, "opaque");
|
|
252610
|
+
}
|
|
252579
252611
|
function noteBCallRecord(r) {
|
|
252580
252612
|
if (!bCallCollector) return;
|
|
252581
252613
|
try {
|
|
@@ -252657,8 +252689,11 @@ function $callNamed(name, fn2, args, loc, argLocs) {
|
|
|
252657
252689
|
try {
|
|
252658
252690
|
if (typeof fn2 === "function") {
|
|
252659
252691
|
const g = GLOBAL_FNS.has(name) && fn2 === globalThis[name] ? evalGlobalFn(name, args) : void 0;
|
|
252692
|
+
const blockedHost = g === void 0 ? blockHostSideEffect(fn2) : null;
|
|
252660
252693
|
if (g !== void 0) {
|
|
252661
252694
|
result = g;
|
|
252695
|
+
} else if (blockedHost !== null) {
|
|
252696
|
+
result = blockedHost;
|
|
252662
252697
|
} else {
|
|
252663
252698
|
const entered = bEnterCall(name, fn2, args);
|
|
252664
252699
|
if (!entered.ok) {
|
|
@@ -252757,7 +252792,13 @@ function execRegexBrand(re, method, args) {
|
|
|
252757
252792
|
if (method === "toString") return strLit(`/${parts.pat}/${parts.flags}`);
|
|
252758
252793
|
const subject = args[0] ? litValue(args[0]) : void 0;
|
|
252759
252794
|
if (typeof subject !== "string") {
|
|
252760
|
-
|
|
252795
|
+
if (method === "test") {
|
|
252796
|
+
return abs({ k: "prim", type: "boolean" }, void 0, void 0, "partial");
|
|
252797
|
+
}
|
|
252798
|
+
const element = joinAbs(str(), undefAbs());
|
|
252799
|
+
const matchAbs = abs({ k: "arr", element }, void 0, void 0, "path");
|
|
252800
|
+
const nullAbs = abs({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
|
|
252801
|
+
return joinAbs(nullAbs, matchAbs);
|
|
252761
252802
|
}
|
|
252762
252803
|
try {
|
|
252763
252804
|
return regexExecWithState(re, method, subject).result;
|
|
@@ -252923,6 +252964,8 @@ function $new(cls, args) {
|
|
|
252923
252964
|
}
|
|
252924
252965
|
}
|
|
252925
252966
|
if (typeof cls === "function") {
|
|
252967
|
+
const blockedHost = blockHostSideEffect(cls);
|
|
252968
|
+
if (blockedHost) return blockedHost;
|
|
252926
252969
|
if (cls === Array) {
|
|
252927
252970
|
return makeArrayCtorAbs(args);
|
|
252928
252971
|
}
|
|
@@ -258057,6 +258100,17 @@ function checkSourceInner(filePath, source, file, phi2, opts, issues, signatures
|
|
|
258057
258100
|
});
|
|
258058
258101
|
continue;
|
|
258059
258102
|
}
|
|
258103
|
+
if (label.startsWith(HOST_EFFECT_LABEL_PREFIX)) {
|
|
258104
|
+
const hostName = label.slice(HOST_EFFECT_LABEL_PREFIX.length);
|
|
258105
|
+
issues.push({
|
|
258106
|
+
severity: "info",
|
|
258107
|
+
code: "nudo:host-effect-blocked",
|
|
258108
|
+
message: `Host function '${hostName}' is not executed during analysis (network/timer side effect); result widened to unknown#opaque`,
|
|
258109
|
+
suggestion: "optional: mock it with @nudo:mock / @nudo:env, or pass the value in from a call site",
|
|
258110
|
+
fn: hostName
|
|
258111
|
+
});
|
|
258112
|
+
continue;
|
|
258113
|
+
}
|
|
258060
258114
|
issues.push({
|
|
258061
258115
|
severity: "info",
|
|
258062
258116
|
code: "nudo:recursion-truncated",
|
|
@@ -258186,6 +258240,19 @@ function checkReturnConstraint(fnName, cName, constraint, ret) {
|
|
|
258186
258240
|
const out = [];
|
|
258187
258241
|
if (ret.shape.k === "unknown" && !ret.term) return out;
|
|
258188
258242
|
if (ret.shape.k === "any") return out;
|
|
258243
|
+
if (isNullishLitAbs(ret)) return out;
|
|
258244
|
+
if (ret.shape.k === "sum" && constraint.fields) {
|
|
258245
|
+
const seen = /* @__PURE__ */ new Set();
|
|
258246
|
+
for (const m of ret.shape.members) {
|
|
258247
|
+
for (const issue of checkReturnConstraint(fnName, cName, constraint, m)) {
|
|
258248
|
+
const key = `${issue.message}\0${issue.actual ?? ""}\0${issue.expected ?? ""}`;
|
|
258249
|
+
if (seen.has(key)) continue;
|
|
258250
|
+
seen.add(key);
|
|
258251
|
+
out.push(issue);
|
|
258252
|
+
}
|
|
258253
|
+
}
|
|
258254
|
+
return out;
|
|
258255
|
+
}
|
|
258189
258256
|
const push = (actual, expected, suggestion) => {
|
|
258190
258257
|
out.push({
|
|
258191
258258
|
severity: "error",
|
|
@@ -258354,6 +258421,11 @@ function actionsForIssue(i) {
|
|
|
258354
258421
|
label: "results widened to unknown \u2014 raise budget or narrow control flow"
|
|
258355
258422
|
}
|
|
258356
258423
|
];
|
|
258424
|
+
case "nudo:host-effect-blocked":
|
|
258425
|
+
return [
|
|
258426
|
+
{ kind: "mock", label: "pin the host API with @nudo:mock / @nudo:env" },
|
|
258427
|
+
{ kind: "info", label: "results widened to unknown \u2014 or feed the value in from a call site" }
|
|
258428
|
+
];
|
|
258357
258429
|
case "nudo:interface-drift":
|
|
258358
258430
|
return [
|
|
258359
258431
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nudojs/lsp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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.
|
|
53
|
-
"@nudojs/core": "1.1.
|
|
54
|
-
"@nudojs/parser": "1.1.
|
|
52
|
+
"@nudojs/service": "1.1.2",
|
|
53
|
+
"@nudojs/core": "1.1.2",
|
|
54
|
+
"@nudojs/parser": "1.1.2",
|
|
55
55
|
"@babel/traverse": "^8.0.6",
|
|
56
56
|
"@babel/types": "^8.0.6",
|
|
57
57
|
"vscode-languageserver": "^10.1.2",
|