Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/editor/CodeHintManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ define(function (require, exports, module) {
PreferencesManager.definePreference("showCodeHints", "boolean", true, {
description: Strings.DESCRIPTION_SHOW_CODE_HINTS
});
PreferencesManager.definePreference("showCodeHintDocs", "boolean", true, {
description: Strings.DESCRIPTION_SHOW_CODE_HINT_DOCS
});
PreferencesManager.definePreference("insertHintOnTab", "boolean", true, {
description: Strings.DESCRIPTION_INSERT_HINT_ON_TAB
});
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/default/PHPSupport/ServerInstaller.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ define(function (require, exports, module) {
"stale build? Run npm run build.");
}
const INTELEPHENSE_VERSION = _PINS.intelephense || "1.18.5";
const PREF_PHP_CODE_INTELLIGENCE = "php.codeIntelligence";
const PREF_PHP_CODE_INTELLIGENCE = "codeIntelligence.php";

let _onInstalled = null; // main.js callback: ({entryPath, upgraded}) => void
let _inFlight = null; // single-flight install promise
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/default/PythonSupport/ServerInstaller.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ define(function (require, exports, module) {
}
const PYREFLY_VERSION = _PINS.pyrefly || "1.1.1";
const RUFF_VERSION = _PINS.ruff || "0.15.20";
const PREF_PYTHON_CODE_INTELLIGENCE = "python.codeIntelligence";
const PREF_PYTHON_CODE_INTELLIGENCE = "codeIntelligence.python";

// The independently pinned/installed pieces. `pkg` is the PyPI package name; the binary of a
// wheel build lives at <pkg>-<version>.data/scripts/<pkg>[.exe].
Expand Down
21 changes: 21 additions & 0 deletions src/extensions/default/TypeScriptSupport/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,21 @@ define(function (require, exports, module) {
FileSystem = brackets.getModule("filesystem/FileSystem"),
NodeConnector = brackets.getModule("NodeConnector"),
TokenUtils = brackets.getModule("utils/TokenUtils"),
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
Strings = brackets.getModule("strings"),
CodeIntelligence = require("./CodeIntelligence"),
ConfigPanel = require("./ConfigPanel");

const SERVER_ID = "typescript";
const SUPPORTED_LANGUAGES = ["javascript", "typescript", "jsx", "tsx"];
const PREF_TS_CODE_INTELLIGENCE = "codeIntelligence." + SERVER_ID;

// Master switch for JS/TS code intelligence - setting it back to true starts the server on
// the open JS/TS file. A runtime disable of a running server is handled centrally by
// LSPClient's own watcher on this pref.
PreferencesManager.definePreference(PREF_TS_CODE_INTELLIGENCE, "boolean", true, {
description: Strings.DESCRIPTION_TYPESCRIPT_CODE_INTELLIGENCE
});

// Phoenix language id -> LSP languageId
const LANGUAGE_ID_MAP = {
Expand Down Expand Up @@ -417,6 +427,9 @@ define(function (require, exports, module) {
if (!canRun() || !_isServedLanguageActive()) {
return;
}
if (PreferencesManager.get(PREF_TS_CODE_INTELLIGENCE) === false) {
return;
}

// Not running yet: lazily start it (a fresh start already points at the current project root).
if (!registered) {
Expand Down Expand Up @@ -474,6 +487,14 @@ define(function (require, exports, module) {
EditorManager.on("activeEditorChange", _ensureServerForActiveEditor);
_ensureServerForActiveEditor();

// Re-enabling the pref starts the server on the open JS/TS file without needing a file
// switch. The disable direction is handled by LSPClient stopping the running server.
PreferencesManager.on("change", PREF_TS_CODE_INTELLIGENCE, function () {
if (PreferencesManager.get(PREF_TS_CODE_INTELLIGENCE) !== false) {
_ensureServerForActiveEditor();
}
});

// On project switch: re-evaluate checkJs and arm a one-shot repoint. The actual repoint
// (workspace/didChangeWorkspaceFolders, no restart) happens the next time a served-language
// file is active - here if one already is, otherwise on the activeEditorChange as the new
Expand Down
168 changes: 167 additions & 1 deletion src/extensions/default/TypeScriptSupport/unittests.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*
*/

/*global describe, it, expect, beforeAll, afterAll, afterEach, awaitsFor, awaitsForDone, path, jsPromise */
/*global describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, awaitsFor, awaitsForDone, path, jsPromise */

define(function (require, exports, module) {

Expand Down Expand Up @@ -780,5 +780,171 @@
expect($("#reference-in-files-results").is(":visible")).toBe(true);
}, 75000);
});

// ----- codeIntelligence.* server gates + showParameterHints -----
describe("preference gates", function () {
let PreferencesManager, LSPClient;

beforeAll(function () {
PreferencesManager = testWindow.brackets.test.PreferencesManager;
LSPClient = testWindow.require("languageTools/LSPClient");
});

afterEach(function () {
// restore defaults even when a spec failed mid-way
PreferencesManager.set("codeIntelligence.typescript", true);
PreferencesManager.set("showParameterHints", true);
});

it("should gate registerLanguageServer on codeIntelligence.<serverId> and auto-define the pref",
async function () {
PreferencesManager.set("codeIntelligence.fakeGateSpec", false);
const client = await LSPClient.registerLanguageServer({
serverId: "fakeGateSpec",
command: "no-such-lsp-binary",
languages: ["javascript"]
});
expect(client).toBe(null);
// gated before any spawn attempt, but the pref is still auto-defined so
// plugin-supplied servers get a discoverable off-switch with a description
const pref = PreferencesManager.getPreference("codeIntelligence.fakeGateSpec");
expect(pref).toBeTruthy();
expect(pref.description).toContain("fakeGateSpec");
PreferencesManager.set("codeIntelligence.fakeGateSpec", undefined);
});

it("should stop the running server on pref off, block resurrection, and restart on pref on",
async function () {
await _openInProject("ts/", "type-error.ts");
await awaitsFor(function () {
return LSPClient.isLintingProviderActive("typescript");
}, "typescript server to be active", 30000);

PreferencesManager.set("codeIntelligence.typescript", false);
await awaitsFor(function () {
return !LSPClient.isLintingProviderActive("typescript");
}, "typescript server to stop on pref off", 15000);

// neither restart path may resurrect a pref-disabled server
await LSPClient.restartLanguageServer("typescript");
await LSPClient.changeWorkspaceRoot("typescript");
expect(LSPClient.isLintingProviderActive("typescript")).toBe(false);

PreferencesManager.set("codeIntelligence.typescript", true);
await awaitsFor(function () {
return LSPClient.isLintingProviderActive("typescript");
}, "typescript server to restart on pref on", 45000);
}, 90000);

it("should gate the parameter hint popup on showParameterHints", async function () {
await _openInProject("ts/", "type-error.ts");
const editor = EditorManager.getActiveEditor();
editor.document.setText("function add(a: number, b: number) { return a + b; }\nadd(1, 2);\n");
editor.setCursorPos(1, 4); // inside add(|
CommandManager.execute("showParameterHint");
await awaitsFor(function () {
return $("#function-hint-container-new").is(":visible");
}, "parameter hint popup to show", 30000);

// flipping the pref off dismisses the visible popup
PreferencesManager.set("showParameterHints", false);
await awaitsFor(function () {
return !$("#function-hint-container-new").is(":visible");
}, "popup to dismiss on pref off", 5000);

// while off the gate is synchronous - no request is even issued, so the popup
// must still be hidden right after an explicit invocation
CommandManager.execute("showParameterHint");
expect($("#function-hint-container-new").is(":visible")).toBe(false);

PreferencesManager.set("showParameterHints", true);
CommandManager.execute("showParameterHint");
await awaitsFor(function () {
return $("#function-hint-container-new").is(":visible");
}, "parameter hint popup to show again", 30000);
}, 90000);
});

// ----- hint-severity diagnostics: untagged suggestions dropped, tagged ones styled -----
describe("hint diagnostics and tags", function () {
let publishedByFile;
let origSetInspectionResults;

beforeAll(function () {
// Capture what actually reaches the linting layer (post the LSPClient hint filter)
// so the specs can await/assert on publishes deterministically instead of relying
// only on problems-panel text.
const DefaultProviders = testWindow.require("languageTools/DefaultProviders");
const proto = DefaultProviders.LintingProvider.prototype;
publishedByFile = {};
origSetInspectionResults = proto.setInspectionResults;
proto.setInspectionResults = function (msgObj) {
const name = msgObj.uri.substring(msgObj.uri.lastIndexOf("/") + 1);
publishedByFile[name] = publishedByFile[name] || [];
publishedByFile[name].push(msgObj.diagnostics || []);
return origSetInspectionResults.call(this, msgObj);
};
});

afterAll(function () {
const DefaultProviders = testWindow.require("languageTools/DefaultProviders");
DefaultProviders.LintingProvider.prototype.setInspectionResults = origSetInspectionResults;
});

beforeEach(async function () {
// Isolate from whatever earlier specs left behind (dirty documents, an open
// parameter-hint popup, stale panel state).
await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE_ALL, { _forceClose: true }),
"close all files");
});

it("should keep tagged hints (unused/deprecated) in the panel and style the text", async function () {
await _openInProject("ts/", "type-error.ts");
const editor = EditorManager.getActiveEditor();
editor.document.setText(
"const unusedVar: number = 1;\n" +
"/** @deprecated use newFn */\n" +
"function oldFn(): number { return 1; }\n" +
"oldFn();\n" +
"export {};\n"
);
// tagged hints (unused tag 1, deprecated tag 2) must survive the hint filter
await awaitsFor(function () {
const all = (publishedByFile["type-error.ts"] || []).flat();
return all.some(function (d) { return d.tags && d.tags.indexOf(1) !== -1; }) &&

Check warning on line 914 in src/extensions/default/TypeScriptSupport/unittests.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `.includes()`, rather than `.indexOf()`, when checking for existence.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ-Yrfb2BaRu4znBW8kC&open=AZ-Yrfb2BaRu4znBW8kC&pullRequest=3038
all.some(function (d) { return d.tags && d.tags.indexOf(2) !== -1; });

Check warning on line 915 in src/extensions/default/TypeScriptSupport/unittests.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `.includes()`, rather than `.indexOf()`, when checking for existence.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ-Yrfb2BaRu4znBW8kD&open=AZ-Yrfb2BaRu4znBW8kD&pullRequest=3038
}, "tagged unused + deprecated diagnostics to be published", 30000);
await awaitsFor(function () {
return panelText().includes("never read") && panelText().includes("deprecated");
}, "unused + deprecated hint rows in the problems panel", 30000);
// the marked text carries the tag styles on top of the info squiggle
await awaitsFor(function () {
return $(".editor-text-fragment-unnecessary").length > 0 &&
$(".editor-text-fragment-deprecated").length > 0;
}, "faded + strikethrough text marks in the editor", 30000);
}, 90000);

it("should drop untagged hint suggestions like the CommonJS to ESM nag", async function () {
await _openInProject("js-plain/", "implicit.js");
const editor = EditorManager.getActiveEditor();
const publishesBeforeEdit = (publishedByFile["implicit.js"] || []).length;
// "require(" is concatenated so RequireJS's static dependency scan of this module's
// source doesn't read the string literal as an AMD dependency named "http" - that
// fails the whole file's load and silently unregisters every suite in it.
const requireCall = "require";
editor.document.setText("const http = " + requireCall + "('http');\nconsole.log(http);\n");
// the server re-publishes (possibly empty) diagnostics after the edit - wait for
// one, then assert the untagged 80001 CommonJS suggestion never came through
await awaitsFor(function () {
return (publishedByFile["implicit.js"] || []).length > publishesBeforeEdit;
}, "a diagnostics publish for implicit.js after the edit", 30000);
const all = (publishedByFile["implicit.js"] || []).flat();
expect(all.some(function (d) { return d.message.indexOf("CommonJS") !== -1; })).toBe(false);

Check warning on line 942 in src/extensions/default/TypeScriptSupport/unittests.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `.includes()`, rather than `.indexOf()`, when checking for existence.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ-Yrfb2BaRu4znBW8kE&open=AZ-Yrfb2BaRu4znBW8kE&pullRequest=3038
expect(all.some(function (d) {
return d.severity === 4 && !(d.tags && d.tags.length);

Check warning on line 944 in src/extensions/default/TypeScriptSupport/unittests.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ-Yrfb2BaRu4znBW8kF&open=AZ-Yrfb2BaRu4znBW8kF&pullRequest=3038
})).toBe(false);
expect(panelText().includes("CommonJS")).toBe(false);
}, 90000);
});
});
});
20 changes: 20 additions & 0 deletions src/extensionsIntegrated/JSONSupport/JsonLsp.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,20 @@ define(function (require, exports, module) {
const EditorManager = require("editor/EditorManager"),
ProjectManager = require("project/ProjectManager"),
NodeConnector = require("NodeConnector"),
PreferencesManager = require("preferences/PreferencesManager"),
Strings = require("strings"),
SchemaAssociations = require("./schemaAssociations");

const SERVER_ID = "json";
const SUPPORTED_LANGUAGES = ["json"];
const PREF_JSON_CODE_INTELLIGENCE = "codeIntelligence." + SERVER_ID;

// Master switch for JSON code intelligence - setting it back to true starts the server on
// the open JSON file. A runtime disable of a running server is handled centrally by
// LSPClient's own watcher on this pref.
PreferencesManager.definePreference(PREF_JSON_CODE_INTELLIGENCE, "boolean", true, {
description: Strings.DESCRIPTION_JSON_CODE_INTELLIGENCE
});

let lspClientPromise = null;
let client = null; // the LanguageClient once registered
Expand Down Expand Up @@ -181,6 +191,9 @@ define(function (require, exports, module) {
if (!canRun() || !_isServedLanguageActive()) {
return;
}
if (PreferencesManager.get(PREF_JSON_CODE_INTELLIGENCE) === false) {
return;
}
if (!registered) {
if (starting) {
return;
Expand Down Expand Up @@ -219,6 +232,13 @@ define(function (require, exports, module) {
pendingRepoint = true;
_ensureServerForActiveEditor();
});
// Re-enabling the pref starts the server on the open JSON file without needing a file
// switch. The disable direction is handled by LSPClient stopping the running server.
PreferencesManager.on("change", PREF_JSON_CODE_INTELLIGENCE, function () {
if (PreferencesManager.get(PREF_JSON_CODE_INTELLIGENCE) !== false) {
_ensureServerForActiveEditor();
}
});
}

/**
Expand Down
21 changes: 21 additions & 0 deletions src/features/ParameterHintsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ define(function (require, exports, module) {
EditorManager = require("editor/EditorManager"),
Menus = require("command/Menus"),
Strings = require("strings"),
PreferencesManager = require("preferences/PreferencesManager"),
WorkspaceManager = require("view/WorkspaceManager"),
ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler;

Expand Down Expand Up @@ -89,6 +90,19 @@ define(function (require, exports, module) {
registerHintProvider = _providerRegistrationHandler.registerProvider.bind(_providerRegistrationHandler),
removeHintProvider = _providerRegistrationHandler.removeProvider.bind(_providerRegistrationHandler);

let paramHintsEnabled = true;

PreferencesManager.definePreference("showParameterHints", "boolean", true, {
description: Strings.DESCRIPTION_SHOW_PARAMETER_HINTS
});

PreferencesManager.on("change", "showParameterHints", function () {
paramHintsEnabled = PreferencesManager.get("showParameterHints");
if (!paramHintsEnabled) {
dismissHint();
}
});

/**
* Keep the active parameter visible inside the single-line, width-capped popup: scroll it
* into view (centered) when the signature overflows, and fade whichever edge is clipped so
Expand Down Expand Up @@ -322,6 +336,13 @@ define(function (require, exports, module) {
let $deferredPopUp = $.Deferred();
let sessionProvider = null;

// Gates implicit ("("/"," typed, cursor tracking) and explicit (Ctrl-Shift-Space)
// invocation alike - same semantics as showCodeHints gating code hint sessions.
if (!paramHintsEnabled) {
dismissHint(editor);
return $deferredPopUp;
}

popupShown = true;
// Find a suitable provider, if any
let language = editor.getLanguageForSelection(),
Expand Down
19 changes: 16 additions & 3 deletions src/language/CodeInspection.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,11 +423,24 @@
}

function _getMarkOptions(error){
let options;
switch (error.type) {
case Type.ERROR: return Editor.getMarkOptionUnderlineError();
case Type.WARNING: return Editor.getMarkOptionUnderlineWarn();
case Type.META: return Editor.getMarkOptionUnderlineInfo();
case Type.ERROR: options = Editor.getMarkOptionUnderlineError(); break;
case Type.WARNING: options = Editor.getMarkOptionUnderlineWarn(); break;
case Type.META: options = Editor.getMarkOptionUnderlineInfo(); break;
}
// LSP DiagnosticTag values on the error (1 Unnecessary - unused symbol, 2 Deprecated)
// add a text style on top of the squiggle: faded for unused, strikethrough for
// deprecated. Panel row and squiggle treatment stay unchanged.
if (options && Array.isArray(error.tags)) {
if (error.tags.indexOf(1) !== -1) {

Check warning on line 436 in src/language/CodeInspection.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `.includes()`, rather than `.indexOf()`, when checking for existence.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ-YrfgOBaRu4znBW8kG&open=AZ-YrfgOBaRu4znBW8kG&pullRequest=3038
options.className += " editor-text-fragment-unnecessary";
}
if (error.tags.indexOf(2) !== -1) {

Check warning on line 439 in src/language/CodeInspection.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `.includes()`, rather than `.indexOf()`, when checking for existence.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ-YrfgOBaRu4znBW8kH&open=AZ-YrfgOBaRu4znBW8kH&pullRequest=3038
options.className += " editor-text-fragment-deprecated";
}
}
return options;
}

function _getMarkTypePriority(type){
Expand Down
Loading
Loading