Skip to content

Commit 86604fc

Browse files
committed
feat(json): JSON code intelligence - schema LSP, npm hints, vuln squigglies, dep hover
New integrated extension src/extensionsIntegrated/JSONSupport with WebStorm-class intelligence for JSON files, on the existing multi-server LSP framework: 1. JSON language server (desktop): vscode-json-language-server (from vscode-langservers-extracted, resolved via src-node/node_modules/.bin) with lazy start on the first JSON file and repoint-not-restart on project switch. Curated schemastore.org associations (package.json, ts/jsconfig, eslintrc, babelrc, prettierrc, composer, manifests, ...) are pushed via workspace/didChangeConfiguration after every server up-transition; the server downloads the schemas itself. Serves all json docs as jsonc (comment-tolerant, matching real-world tsconfig/.eslintrc), yields Phoenix pref files to PrefsCodeHints via the new documentFilter, and opts into completionSnippetSupport (the server refuses to offer completion without snippet support; insertHint already expands snippets through TabstopManager). 2. npm package intelligence in package.json (all builds): - Name completion in dependency keys: registry search in relevance order, typed-query emphasis via the standard .matched-hint style, descriptions in the reusable side docs popup (showHintDocPopup / hideHintDocPopup now exported from DefaultProviders) instead of widening the rows. Explicit Ctrl-Space searches the whole token under the cursor and skips the typing debounce. - Version completion in values: the package's real version list newest first with ^latest/~latest range shortcuts; typed prefixes filter the FULL list (a 5.x prefix surfaces the 5.x train even when the newest 50 are 7.x) and fall back to newest when nothing matches. Name insertion chains straight into version mode. - Dependency hover (QuickView): registry summary - name, latest version, license, description - with Open homepage and a View docs link pinned bottom-right that opens the npm page anchored at the DECLARED version ("^5.4.11" -> /v/5.4.11, where npm renders that version's README). 3. Vulnerability squigglies: each declared range is resolved to the version npm would install (semver.maxSatisfying over the real version list) and checked against npm's bulk security-advisory endpoint (the npm-audit data source). Severity-mapped (critical/high error, moderate warning, low info), whole-entry underlines, capped and deduped per dep. The endpoint has no CORS headers (verified), so desktop goes straight to the new ph-npm-intel node helper - no doomed browser POST spamming the console; browser builds try fetch and degrade quietly. scanFileAsync never blocks on the network: cached results return immediately and a single-flighted, dep-hash-gated background refresh requestRun()s on change. Framework hardening along the way: - src-node/lsp-client.js answers server-initiated requests (spec-shaped null results for workspace/configuration & friends, -32601 otherwise) so no server can hang awaiting a reply the browser never sends. - LanguageClient.sendCustomNotification; per-server documentFilter and completionSnippetSupport (per-config client capabilities); _notify now logs AND propagates failures so DocumentSync's lost-notification resync hardening actually engages. - Hover typography: the shared .lsp-hover-quickview family (JSON schema hover, JS/TS hover, hint docs popup) moves to the 13px+ readability baseline; popup headings lose their asymmetric browser margins. Tests: unit:JSONSupport npm intelligence 18/18 (dep-range scanner, severity mapping/dedup, registry client with injected fetcher incl. caching + bulk body, hint context detection, full-list version filter + fallback, npm page URLs); integration:JSON LSP 3/3 (server syntax diagnostics, inline-schema validation with no network, advisory squigglies with fake fetcher); regressions integration:TypeScript LSP 20/20 and LegacyInteg:CodeHintManager 13/13.
1 parent d1a823f commit 86604fc

24 files changed

Lines changed: 2359 additions & 21 deletions

docs/API-Reference/language/CodeInspection.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ Each error object in the results should have the following structure:
163163
htmlMessage:string,
164164
type:?Type ,
165165
fix: { // an optional fix, if present will show the fix button
166-
replace: "text to replace the offset given below",
166+
replaceText: "text to replace the offset given below",
167167
rangeOffset: {
168168
start: number,
169169
end: number
@@ -194,7 +194,7 @@ Each error object in the results should have the following structure:
194194
| htmlMessage | <code>string</code> | The error message to be displayed as HTML. |
195195
| type | [<code>Type</code>](#Type) | The type of the error. Defaults to `Type.WARNING` if unspecified. |
196196
| fix | <code>Object</code> | An optional fix object. |
197-
| fix.replace | <code>string</code> | The text to replace the error with. |
197+
| fix.replaceText | <code>string</code> | The text to replace the error with. |
198198
| fix.rangeOffset | <code>Object</code> | The range within the text to replace. |
199199
| fix.rangeOffset.start | <code>number</code> | The start offset of the range. |
200200
| fix.rangeOffset.end | <code>number</code> | The end offset of the range. If no errors are found, return either `null`(treated as file is problem free) or an object with a zero-length `errors` array. Always use `message` to safely display the error as text. If you want to display HTML error message, then explicitly use `htmlMessage` to display it. Both `message` and `htmlMessage` can be used simultaneously. After scanning the file, if you need to omit the lint result, return or resolve with `{isIgnored: true}`. This prevents the file from being marked with a no errors tick mark in the status bar and excludes the linter from the problems panel. |

docs/API-Reference/widgets/NotificationUI.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ The `createFromTemplate` API can be configured with numerous options. See API op
5252
* [.createToastFromTemplate(title, template, [options])](#module_widgets/NotificationUI..createToastFromTemplate) ⇒ <code>Notification</code>
5353
* [.showToastOn(containerOrSelector, template, [options])](#module_widgets/NotificationUI..showToastOn) ⇒ <code>Notification</code>
5454
* [.showHUD(iconClass, label, [options])](#module_widgets/NotificationUI..showHUD) ⇒ <code>Notification</code>
55+
* [.hideRichTooltip()](#module_widgets/NotificationUI..hideRichTooltip) : <code>function</code>
56+
* [.attachRichTooltip(elements, html, [options])](#module_widgets/NotificationUI..attachRichTooltip) ⇒ <code>Object</code>
5557

5658
<a name="module_widgets/NotificationUI..API"></a>
5759

@@ -188,3 +190,31 @@ NotificationUI.showHUD("fa-solid fa-magnifying-glass-plus", "110%");
188190
| label | <code>string</code> | Text to display below the icon (e.g. "110%"). |
189191
| [options] | <code>Object</code> | optional, supported options: * `autoCloseTimeS` - Time in seconds after which the HUD auto-closes. Default is 1. |
190192

193+
<a name="module_widgets/NotificationUI..hideRichTooltip"></a>
194+
195+
### widgets/NotificationUI.hideRichTooltip() : <code>function</code>
196+
Hide the currently showing rich tooltip (if any).
197+
198+
**Kind**: inner method of [<code>widgets/NotificationUI</code>](#module_widgets/NotificationUI)
199+
<a name="module_widgets/NotificationUI..attachRichTooltip"></a>
200+
201+
### widgets/NotificationUI.attachRichTooltip(elements, html, [options]) ⇒ <code>Object</code>
202+
Attaches a rich (HTML-capable) hover tooltip to the given element(s). The tooltip is
203+
Phoenix-themed for both light and dark themes, positioned beside the element, clamped to the
204+
viewport, and attached to `<body>` so scrolling containers cannot clip it.
205+
206+
```js
207+
NotificationUI.attachRichTooltip($(".my-info-icon"), "<b>Hello</b> world");
208+
// or compute content per element on show:
209+
NotificationUI.attachRichTooltip($(".my-info-icon"), el => $(el).attr("data-info"));
210+
```
211+
212+
**Kind**: inner method of [<code>widgets/NotificationUI</code>](#module_widgets/NotificationUI)
213+
**Returns**: <code>Object</code> - call `detach()` to unbind the handlers and hide the tooltip
214+
215+
| Param | Type | Description |
216+
| --- | --- | --- |
217+
| elements | <code>jQuery</code> \| <code>Element</code> \| <code>string</code> | element(s) or selector to attach to |
218+
| html | <code>string</code> \| <code>function</code> | TRUSTED html string (escape untrusted parts yourself), or a function returning it for the hovered element |
219+
| [options] | <code>Object</code> | optional, supported options: * `showDelayMs` - hover delay before the tooltip appears. Default 250. |
220+

src-node/lsp-client.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,54 @@ function handleMessage(serverId, msg) {
142142
} else {
143143
resolve(msg.result);
144144
}
145+
} else if (msg.method && msg.id !== undefined) {
146+
// Server-initiated REQUEST: answer it right here so the server never awaits a reply
147+
// forever (the browser side has no response path; an unanswered request can stall the
148+
// server's own processing - e.g. vscode-json-language-server pulling configuration).
149+
_respondToServerRequest(serverId, server, msg);
145150
} else if (msg.method) {
146-
// Notification or server-initiated request - forward to the browser.
151+
// Notification - forward to the browser (e.g. textDocument/publishDiagnostics).
147152
nodeConnector.triggerPeer('lspNotification', { serverId, ...msg });
148153
}
149154
}
150155

156+
/**
157+
* Answer a server-initiated request with a benign, spec-shaped reply. We advertise minimal client
158+
* capabilities (no dynamic registration, workspace.configuration=false), so servers should rarely
159+
* send these - this is the safety net that guarantees no server hangs awaiting a reply.
160+
* @param {string} serverId - The server identifier (for logging)
161+
* @param {Object} server - The server state object
162+
* @param {Object} msg - The incoming JSON-RPC request (method + id)
163+
*/
164+
function _respondToServerRequest(serverId, server, msg) {
165+
let response;
166+
switch (msg.method) {
167+
case 'workspace/configuration':
168+
// Result must be an array matching params.items length; null entries mean "no config".
169+
response = {
170+
jsonrpc: '2.0', id: msg.id,
171+
result: ((msg.params && msg.params.items) || []).map(() => null)
172+
};
173+
break;
174+
case 'client/registerCapability':
175+
case 'client/unregisterCapability':
176+
case 'window/workDoneProgress/create':
177+
case 'window/showMessageRequest':
178+
response = { jsonrpc: '2.0', id: msg.id, result: null };
179+
break;
180+
default:
181+
response = {
182+
jsonrpc: '2.0', id: msg.id,
183+
error: { code: -32601, message: `Method not handled by Phoenix LSP client: ${msg.method}` }
184+
};
185+
}
186+
try {
187+
server.process.stdin.write(encode(response));
188+
} catch (e) {
189+
console.error(`[lsp-client][${serverId}] failed to answer ${msg.method}:`, e.message);
190+
}
191+
}
192+
151193
/**
152194
* Ping endpoint to verify the LSP connector is alive.
153195
* @returns {Promise<Object>} Status and list of active servers

src-node/npm-intel.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2021 - present core.ai . All rights reserved.
5+
*
6+
* This program is free software: you can redistribute it and/or modify it
7+
* under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
14+
* for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
18+
*
19+
*/
20+
21+
/**
22+
* npm-intel - node-side helper for npm security-advisory lookups. The registry's bulk advisory
23+
* endpoint is POST-only without CORS headers, so the browser context cannot call it directly;
24+
* the JSONSupport extension routes the request here on desktop builds.
25+
*
26+
* Lazy-loaded via NodeUtils._loadNodeExtensionModule("./npm-intel") on first use - keep this
27+
* module free of heavyweight requires so it adds nothing to node boot.
28+
*/
29+
30+
const ADVISORY_BULK_URL = "https://registry.npmjs.org/-/npm/v1/security/advisories/bulk";
31+
32+
/**
33+
* POST the bulk advisory query to the npm registry.
34+
* @param {Object} params
35+
* @param {Object<string, string[]>} params.body - map of package name -> array of exact versions
36+
* @returns {Promise<Object>} the registry's response: package name -> advisory array
37+
*/
38+
async function fetchAdvisoriesBulk({ body }) {
39+
const response = await fetch(ADVISORY_BULK_URL, {
40+
method: "POST",
41+
headers: { "content-type": "application/json" },
42+
body: JSON.stringify(body)
43+
});
44+
if (!response.ok) {
45+
throw new Error(`advisory fetch failed: ${response.status}`);
46+
}
47+
return response.json();
48+
}
49+
50+
exports.fetchAdvisoriesBulk = fetchAdvisoriesBulk;
51+
52+
global.createNodeConnector("ph-npm-intel", exports);

0 commit comments

Comments
 (0)