From c7f6f597283df0d3b21c843cab5f09a6114d1be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20=F0=9F=91=A8=F0=9F=8F=BD=E2=80=8D=F0=9F=92=BB=20Copl?= =?UTF-8?q?an?= Date: Sun, 21 Jan 2024 15:00:20 -0800 Subject: [PATCH] =?UTF-8?q?feat(vscode):=20make=20quick-lint=20a=20jerk=20?= =?UTF-8?q?=F0=9F=96=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: It ticks me off 😠 how polite 😇 quick-lint is when I code. If I mess up 😬, I want it to tell me. Stop beating around the bush 🌳 and tell it to me like it is. 💯 Test plan: 📝 1. open TypeScript file with no errors in it ✅ 2. add an error ❌ 3. note how quick-lint tells you what the problem is like a sissy 😭 4. enable settings > quick-lint-js > snarky 😈 5. note how quick-lint is straight up with you. You suck. 🗑️ Closes #1188 --- plugin/vscode/package.json | 6 + .../quick-lint-js/vscode/qljs-document.cpp | 12 ++ .../quick-lint-js/vscode/qljs-document.h | 9 ++ .../quick-lint-js/vscode/qljs-workspace.cpp | 30 ++++- .../quick-lint-js/vscode/qljs-workspace.h | 7 ++ plugin/vscode/test/vscode-tests.js | 109 +++++++++++++++++- src/quick-lint-js/i18n/translation.cpp | 1 + 7 files changed, 172 insertions(+), 2 deletions(-) diff --git a/plugin/vscode/package.json b/plugin/vscode/package.json index b8740a7364..f485ec84bd 100644 --- a/plugin/vscode/package.json +++ b/plugin/vscode/package.json @@ -47,6 +47,12 @@ ], "default": "off", "description": "Log document changes. Useful for quick-lint-js contributors." + }, + "quick-lint-js.snarky": { + "scope": "window", + "type": "boolean", + "default": false, + "description": "Add spice to your failures." } } }, diff --git a/plugin/vscode/quick-lint-js/vscode/qljs-document.cpp b/plugin/vscode/quick-lint-js/vscode/qljs-document.cpp index bbbe6fe10f..e93fbbd927 100644 --- a/plugin/vscode/quick-lint-js/vscode/qljs-document.cpp +++ b/plugin/vscode/quick-lint-js/vscode/qljs-document.cpp @@ -79,6 +79,12 @@ void QLJS_Config_Document::on_config_file_changed( diagnostic_collection); } +void QLJS_Config_Document::on_translator_changed( + ::Napi::Env env, QLJS_Workspace& workspace, + VSCode_Diagnostic_Collection diagnostic_collection) { + this->lint_config_and_publish_diagnostics(env, workspace, + diagnostic_collection); +} void QLJS_Config_Document::lint_config_and_publish_diagnostics( ::Napi::Env env, QLJS_Workspace& workspace, VSCode_Diagnostic_Collection diagnostic_collection) { @@ -106,6 +112,12 @@ void QLJS_Lintable_Document::on_config_file_changed( diagnostic_collection); } +void QLJS_Lintable_Document::on_translator_changed( + ::Napi::Env env, QLJS_Workspace& workspace, + VSCode_Diagnostic_Collection diagnostic_collection) { + this->lint_javascript_and_publish_diagnostics(env, workspace, + diagnostic_collection); +} void QLJS_Lintable_Document::lint_javascript_and_publish_diagnostics( ::Napi::Env env, QLJS_Workspace& workspace, VSCode_Diagnostic_Collection diagnostic_collection) { diff --git a/plugin/vscode/quick-lint-js/vscode/qljs-document.h b/plugin/vscode/quick-lint-js/vscode/qljs-document.h index d3150150af..d4a3019df1 100644 --- a/plugin/vscode/quick-lint-js/vscode/qljs-document.h +++ b/plugin/vscode/quick-lint-js/vscode/qljs-document.h @@ -94,6 +94,10 @@ class QLJS_Document_Base { VSCode_Diagnostic_Collection, Loaded_Config_File* config_file) = 0; + virtual void on_translator_changed( + ::Napi::Env env, QLJS_Workspace& workspace, + VSCode_Diagnostic_Collection diagnostic_collection) = 0; + protected: ::Napi::Value uri() { return this->vscode_document_.Value().uri(); } @@ -118,6 +122,9 @@ class QLJS_Config_Document : public QLJS_Document_Base { void on_config_file_changed(::Napi::Env, QLJS_Workspace&, VSCode_Diagnostic_Collection, Loaded_Config_File* config_file) override; + void on_translator_changed( + ::Napi::Env env, QLJS_Workspace& workspace, + VSCode_Diagnostic_Collection diagnostic_collection) override; private: void lint_config_and_publish_diagnostics(::Napi::Env, QLJS_Workspace&, @@ -142,6 +149,8 @@ class QLJS_Lintable_Document : public QLJS_Document_Base { void on_config_file_changed(::Napi::Env, QLJS_Workspace&, VSCode_Diagnostic_Collection, Loaded_Config_File* config_file) override; + void on_translator_changed(::Napi::Env, QLJS_Workspace&, + VSCode_Diagnostic_Collection) override; void lint_javascript_and_publish_diagnostics(::Napi::Env, QLJS_Workspace&, VSCode_Diagnostic_Collection); diff --git a/plugin/vscode/quick-lint-js/vscode/qljs-workspace.cpp b/plugin/vscode/quick-lint-js/vscode/qljs-workspace.cpp index 62eadefbf2..522c9cedf1 100644 --- a/plugin/vscode/quick-lint-js/vscode/qljs-workspace.cpp +++ b/plugin/vscode/quick-lint-js/vscode/qljs-workspace.cpp @@ -86,6 +86,14 @@ class Extension_Configuration { } } + bool get_snarky(::Napi::Env env) { + ::Napi::Value value = this->get(env, "snarky"); + if (!value.IsBoolean()) { + return false; + } + return value.As<::Napi::Boolean>(); + } + ::Napi::Value get(::Napi::Env env, const char* section) { return this->config_ref_.Get("get").As<::Napi::Function>().Call( this->config_ref_.Value(), {::Napi::String::New(env, section)}); @@ -124,7 +132,8 @@ QLJS_Workspace::QLJS_Workspace(const Napi::CallbackInfo& info) ::Napi::Persistent(info[2].As<::Napi::Object>())), ui_(this) { QLJS_DEBUG_LOG("Workspace %p: created\n", this); - this->update_logging(info.Env()); + configuration_changed(info); + this->fs_change_detection_thread_ = Thread([this]() -> void { this->run_fs_change_detection_thread(); }); } @@ -234,10 +243,29 @@ ::Napi::Value QLJS_Workspace::configuration_changed( ::Napi::Env env = info.Env(); this->update_logging(env); + this->on_translator_changed(env); return env.Undefined(); } +void QLJS_Workspace::on_translator_changed(::Napi::Env env) { + Extension_Configuration config(env, this->vscode_); + bool is_snarky = config.get_snarky(env); + + this->is_snarky_enabled_ = is_snarky; + if (is_snarky) { + this->translator_.use_messages_from_locale("en_US@snarky"); + } else { + // TODO(#529): Use the locale from the VS Code configuration. + this->translator_.use_messages_from_source_code(); + } + this->qljs_documents_.for_each( + [this, is_snarky, env](::Napi::Value value) -> void { + QLJS_Document_Base* doc = QLJS_Document_Base::unwrap(value); + doc->on_translator_changed(env, *this, this->diagnostic_collection()); + }); +} + ::Napi::Value QLJS_Workspace::editor_visibility_changed( const Napi::CallbackInfo& info) { ::Napi::Env env = info.Env(); diff --git a/plugin/vscode/quick-lint-js/vscode/qljs-workspace.h b/plugin/vscode/quick-lint-js/vscode/qljs-workspace.h index b02f6dbb66..f49752ee99 100644 --- a/plugin/vscode/quick-lint-js/vscode/qljs-workspace.h +++ b/plugin/vscode/quick-lint-js/vscode/qljs-workspace.h @@ -73,6 +73,8 @@ class QLJS_Workspace : public ::Napi::ObjectWrap { // Disable logging if logging is enabled. void disable_logging(); + void on_translator_changed(::Napi::Env env); + ~QLJS_Workspace(); ::Napi::Value dispose(const ::Napi::CallbackInfo& info); @@ -248,8 +250,13 @@ class QLJS_Workspace : public ::Napi::ObjectWrap { QLJS_Workspace* workspace_; }; + public: + bool is_snarky_enabled() const; + private: + bool is_snarky_enabled_ = false; Translator translator_; + bool disposed_ = false; VSCode_Tracer tracer_; VSCode_Module vscode_; diff --git a/plugin/vscode/test/vscode-tests.js b/plugin/vscode/test/vscode-tests.js index e735a6b82d..40d2675b3f 100644 --- a/plugin/vscode/test/vscode-tests.js +++ b/plugin/vscode/test/vscode-tests.js @@ -74,6 +74,113 @@ for (let extension of [".js", ".mjs", ".cjs", ".jsx"]) { }; } +// SNARKY tests +for (let testCase of [ + { + fileName: "hello.js", + content: "undeclaredVariable", + englishMessage: "use of undeclared variable: undeclaredVariable", + snarkyEnglishMessage: "did you fail spelling class?", + }, + { + fileName: "quick-lint-js.config", + content: "{", + englishMessage: "JSON syntax error", + snarkyEnglishMessage: "yeah, JSON sucks; try quick-lint-json", + }, +]) { + tests = { + ...tests, + [`snarky enabled at start (${testCase.fileName})`]: async ({ + addCleanup, + }) => { + addCleanup(resetConfigurationAsync); + + await vscode.workspace + .getConfiguration("quick-lint-js") + .update("snarky", true, vscode.ConfigurationTarget.Workspace); + + let scratchDirectory = makeScratchDirectory({ addCleanup }); + let filePath = path.join(scratchDirectory, testCase.fileName); + fs.writeFileSync(filePath, testCase.content); + let helloURI = vscode.Uri.file(filePath); + let helloDocument = await vscode.workspace.openTextDocument(helloURI); + await loadExtensionAsync({ addCleanup }); + let helloEditor = await vscode.window.showTextDocument(helloDocument); + + await waitUntilAnyDiagnosticsAsync(helloURI); + let diags = normalizeDiagnostics(helloURI).map(({ message }) => message); + assert.deepStrictEqual(diags, [testCase.snarkyEnglishMessage]); + }, + + [`enabling snarky re-lints (${testCase.fileName})`]: async ({ + addCleanup, + }) => { + addCleanup(resetConfigurationAsync); + await loadExtensionAsync({ addCleanup }); + let scratchDirectory = makeScratchDirectory({ addCleanup }); + let filePath = path.join(scratchDirectory, testCase.fileName); + fs.writeFileSync(filePath, testCase.content); + let helloURI = vscode.Uri.file(filePath); + let helloDocument = await vscode.workspace.openTextDocument(helloURI); + let helloEditor = await vscode.window.showTextDocument(helloDocument); + + // 1. Make sure we're polite at the start + { + await waitUntilAnyDiagnosticsAsync(helloURI); + let diagMessages = normalizeDiagnostics(helloURI).map( + ({ message }) => message + ); + assert.deepStrictEqual(diagMessages, [testCase.englishMessage]); + } + + // 2. Enable snarky + await vscode.workspace + .getConfiguration("quick-lint-js") + .update("snarky", true, vscode.ConfigurationTarget.Workspace); + + // 3. Make sure we're snarky now + await pollAsync(() => { + let diagMessages = normalizeDiagnostics(helloURI).map( + ({ message }) => message + ); + let want = [testCase.snarkyEnglishMessage]; + assert.deepStrictEqual(diagMessages, want); + }); + }, + + [`disabling snarky re-lints (${testCase.fileName})`]: async ({ + addCleanup, + }) => { + addCleanup(resetConfigurationAsync); + await vscode.workspace + .getConfiguration("quick-lint-js") + .update("snarky", true, vscode.ConfigurationTarget.Workspace); + let scratchDirectory = makeScratchDirectory({ addCleanup }); + let filePath = path.join(scratchDirectory, testCase.fileName); + fs.writeFileSync(filePath, testCase.content); + let helloURI = vscode.Uri.file(filePath); + let helloDocument = await vscode.workspace.openTextDocument(helloURI); + await loadExtensionAsync({ addCleanup }); + let helloEditor = await vscode.window.showTextDocument(helloDocument); + + // 1. Disable snarky + await vscode.workspace + .getConfiguration("quick-lint-js") + .update("snarky", false, vscode.ConfigurationTarget.Workspace); + + // 2. Make sure we're polite now + await pollAsync(() => { + let diagMessages = normalizeDiagnostics(helloURI).map( + ({ message }) => message + ); + let want = [testCase.englishMessage]; + assert.deepStrictEqual(diagMessages, want); + }); + }, + }; +} + tests = { ...tests, @@ -1535,7 +1642,7 @@ async function pollAsync(callback) { } async function resetConfigurationAsync() { - for (let setting of ["logging"]) { + for (let setting of ["logging", "snarky"]) { await vscode.workspace .getConfiguration("quick-lint-js") .update(setting, undefined, vscode.ConfigurationTarget.Workspace); diff --git a/src/quick-lint-js/i18n/translation.cpp b/src/quick-lint-js/i18n/translation.cpp index d050377864..3b1649321a 100644 --- a/src/quick-lint-js/i18n/translation.cpp +++ b/src/quick-lint-js/i18n/translation.cpp @@ -63,6 +63,7 @@ Span get_user_locale_preferences( // TODO(strager): Determine the language using macOS' and Windows' native // APIs. See GNU gettext's _nl_language_preferences_default. + // TODO (#529): Also use VSCode's "Display Language" setting Vector locales("locales", allocator); locales.push_back(locale);