From b522e7a944341d463bf9091bd72faffcc807d875 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 12 Aug 2024 20:39:25 +0200 Subject: [PATCH 01/16] Generate lint list in HTML directly instead of JS --- .github/deploy.sh | 1 - .gitignore | 1 + Cargo.toml | 2 + clippy_dev/src/serve.rs | 2 +- rinja.toml | 8 ++ tests/compile-test.rs | 33 +++++- .../{index.html => index_template.html} | 103 ++++++++--------- util/gh-pages/script.js | 108 ++++++++++-------- 8 files changed, 152 insertions(+), 106 deletions(-) create mode 100644 rinja.toml rename util/gh-pages/{index.html => index_template.html} (79%) diff --git a/.github/deploy.sh b/.github/deploy.sh index 5b4b4be4e36b..6cebbb7801b0 100644 --- a/.github/deploy.sh +++ b/.github/deploy.sh @@ -9,7 +9,6 @@ echo "Making the docs for master" mkdir out/master/ cp util/gh-pages/index.html out/master cp util/gh-pages/script.js out/master -cp util/gh-pages/lints.json out/master cp util/gh-pages/style.css out/master if [[ -n $TAG_NAME ]]; then diff --git a/.gitignore b/.gitignore index 181b71a658b9..a7c25b29021f 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ out # gh pages docs util/gh-pages/lints.json +util/gh-pages/index.html # rustfmt backups *.rs.bk diff --git a/Cargo.toml b/Cargo.toml index cf810798d8cc..c7383520741b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ toml = "0.7.3" walkdir = "2.3" filetime = "0.2.9" itertools = "0.12" +pulldown-cmark = "0.11" +rinja = { version = "0.3", default-features = false, features = ["config"] } # UI test dependencies clippy_utils = { path = "clippy_utils" } diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index cc14cd8dae69..0216d884e2d5 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -19,7 +19,7 @@ pub fn run(port: u16, lint: Option) -> ! { }); loop { - if mtime("util/gh-pages/lints.json") < mtime("clippy_lints/src") { + if mtime("util/gh-pages/index.html") < mtime("clippy_lints/src") { Command::new(env::var("CARGO").unwrap_or("cargo".into())) .arg("collect-metadata") .spawn() diff --git a/rinja.toml b/rinja.toml new file mode 100644 index 000000000000..5fa682788bd0 --- /dev/null +++ b/rinja.toml @@ -0,0 +1,8 @@ +[general] +dirs = ["util/gh-pages/"] +default_syntax = "mixed" + +[[syntax]] +name = "mixed" +expr_start = "{(" +expr_end = ")}" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index af2aa5192577..0bd6ac677702 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -8,6 +8,8 @@ use clippy_config::ClippyConfiguration; use clippy_lints::LintInfo; use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; +use pulldown_cmark::{Options, Parser, html}; +use rinja::{Template, filters::Safe}; use serde::{Deserialize, Serialize}; use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; @@ -385,6 +387,22 @@ fn ui_cargo_toml_metadata() { } } +#[derive(Template)] +#[template(path = "index_template.html")] +struct Renderer<'a> { + lints: &'a Vec, +} + +impl<'a> Renderer<'a> { + fn markdown(&self, input: &str) -> Safe { + let parser = Parser::new_ext(input, Options::all()); + let mut html_output = String::new(); + html::push_html(&mut html_output, parser); + // Oh deer, what a hack :O + Safe(html_output.replace(", @@ -559,4 +576,14 @@ impl LintMetadata { applicability: Applicability::Unspecified, } } + + fn applicability_str(&self) -> &str { + match self.applicability { + Applicability::MachineApplicable => "MachineApplicable", + Applicability::HasPlaceholders => "HasPlaceholders", + Applicability::MaybeIncorrect => "MaybeIncorrect", + Applicability::Unspecified => "Unspecified", + _ => panic!("needs to update this code"), + } + } } diff --git a/util/gh-pages/index.html b/util/gh-pages/index_template.html similarity index 79% rename from util/gh-pages/index.html rename to util/gh-pages/index_template.html index f3d7e504fdf8..ee134eaaa98e 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index_template.html @@ -55,16 +55,8 @@

Clippy Lints

-
- - - - -
+
+
@@ -188,9 +180,7 @@

Clippy Lints

- +
- -
-
-

-
- {{lint.id}} - - - 📋 - -
+ {% for lint in lints %} +
+
+

+
+ {(lint.id)} + + + 📋 + +
-
- {{lint.group}} +
+ {(lint.group)} - {{lint.level}} + {(lint.level)} - - + -
-

-
+ + + +

+ + -
-
-
- -
- Applicability: - {{lint.applicability}} - (?) -
- -
- {{lint.group == "deprecated" ? "Deprecated" : "Added"}} in: - {{lint.version}} -
- - - -
- View Source +
+
{(markdown(lint.docs))}
+
+ {# Applicability #} +
+ Applicability: + {( lint.applicability_str() )} + (?) +
+ +
+ {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif %} in: + {(lint.version)} +
+ + + +
-
- + + {% endfor %}
diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 1a5330bc0e57..8942628d5da0 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -1,21 +1,4 @@ (function () { - const md = window.markdownit({ - html: true, - linkify: true, - typographer: true, - highlight: function (str, lang) { - if (lang && hljs.getLanguage(lang)) { - try { - return '
' +
-                        hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
-                        '
'; - } catch (__) {} - } - - return '
' + md.utils.escapeHtml(str) + '
'; - } - }); - function scrollToLint(lintId) { const target = document.getElementById(lintId); if (!target) { @@ -41,15 +24,6 @@ } angular.module("clippy", []) - .filter('markdown', function ($sce) { - return function (text) { - return $sce.trustAsHtml( - md.render(text || '') - // Oh deer, what a hack :O - .replace(' { + if (searchState.timeout !== null) { + clearTimeout(searchState.timeout); + searchState.timeout = null + } + }, + resetInputTimeout: () => { + searchState.clearInputTimeout(); + setTimeout(searchState.filterLints, 50); + }, + filterLints: () => { + let searchStr = searchState.value.trim().toLowerCase(); + if (searchStr.startsWith("clippy::")) { + searchStr = searchStr.slice(8); + } + const terms = searchStr.split(" "); + + onEachLazy(document.querySelectorAll("article"), lint => { + // Search by id + if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { + el.style.display = ""; + return; + } + // Search the description + // The use of `for`-loops instead of `foreach` enables us to return early + const docsLowerCase = lint.docs.toLowerCase(); + for (index = 0; index < terms.length; index++) { + // This is more likely and will therefore be checked first + if (docsLowerCase.indexOf(terms[index]) !== -1) { + continue; + } + + if (lint.id.indexOf(terms[index]) !== -1) { + continue; + } + + return false; + } + }); + }, +}; + +function handleInputChanged(event) { + if (event.target !== document.activeElement) { + return; + } + searchState.resetInputTimeout(); +} + function storeValue(settingName, value) { try { localStorage.setItem(`clippy-lint-list-${settingName}`, value); @@ -627,3 +643,5 @@ if (prefersDark.matches && !theme) { } let disableShortcuts = loadValue('disable-shortcuts') === "true"; document.getElementById("disable-shortcuts").checked = disableShortcuts; + +hljs.highlightAll(); From 9661ba07408c901d9521e27ee8453f769ab12744 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Aug 2024 20:47:42 +0200 Subject: [PATCH 02/16] Update `cargo dev serve` command to look over the correct files --- clippy_dev/src/serve.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index 0216d884e2d5..d367fefec619 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -19,7 +19,9 @@ pub fn run(port: u16, lint: Option) -> ! { }); loop { - if mtime("util/gh-pages/index.html") < mtime("clippy_lints/src") { + let index_time = mtime("util/gh-pages/index.html"); + + if index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html") { Command::new(env::var("CARGO").unwrap_or("cargo".into())) .arg("collect-metadata") .spawn() From 0055cebaa3c0ab3cb0921efa20b18b0f291ae3ea Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Aug 2024 20:47:54 +0200 Subject: [PATCH 03/16] Replace search with vanilla JS --- tests/compile-test.rs | 2 +- util/gh-pages/index_template.html | 29 +++++++------ util/gh-pages/script.js | 68 ++++++++++++------------------- 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 0bd6ac677702..00627dc0bb1f 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -10,7 +10,7 @@ use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; use pulldown_cmark::{Options, Parser, html}; use rinja::{Template, filters::Safe}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; use ui_test::custom_flags::rustfix::RustfixMode; diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index ee134eaaa98e..5baa03da2ea4 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -180,9 +180,9 @@

Clippy Lints

- + - @@ -199,22 +199,22 @@

Clippy Lints

{% for lint in lints %} -
+

{(lint.id)} - - + 📋
- {(lint.group)} + {(lint.group)} - {(lint.level)} + {(lint.level)} @@ -223,7 +223,7 @@

-
+
{(markdown(lint.docs))}
{# Applicability #} @@ -232,18 +232,21 @@

{( lint.applicability_str() )} (?)

- + {# Clippy version #}
{% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif %} in: {(lint.version)}
- + {# Open related issues #} - -
- View Source + + {# Jump to source #} + {% if let Some(id_location) = lint.id_location %} +
+ View Source + {% endif %}
diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 8942628d5da0..eb563a0f242c 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -361,42 +361,6 @@ return $scope.groups[lint.group]; }; - $scope.bySearch = function (lint, index, array) { - let searchStr = $scope.search; - // It can be `null` I haven't missed this value - if (searchStr == null) { - return true; - } - searchStr = searchStr.toLowerCase(); - if (searchStr.startsWith("clippy::")) { - searchStr = searchStr.slice(8); - } - - // Search by id - if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { - return true; - } - - // Search the description - // The use of `for`-loops instead of `foreach` enables us to return early - const terms = searchStr.split(" "); - const docsLowerCase = lint.docs.toLowerCase(); - for (index = 0; index < terms.length; index++) { - // This is more likely and will therefore be checked first - if (docsLowerCase.indexOf(terms[index]) !== -1) { - continue; - } - - if (lint.id.indexOf(terms[index]) !== -1) { - continue; - } - - return false; - } - - return true; - } - $scope.byApplicabilities = function (lint) { return $scope.applicabilities[lint.applicability]; }; @@ -472,6 +436,11 @@ function getQueryVariable(variable) { window.searchState = { timeout: null, inputElem: document.getElementById("search-input"), + lastSearch: '', + clearInput: () => { + searchState.inputElem.value = ""; + searchState.filterLints(); + }, clearInputTimeout: () => { if (searchState.timeout !== null) { clearTimeout(searchState.timeout); @@ -483,32 +452,38 @@ window.searchState = { setTimeout(searchState.filterLints, 50); }, filterLints: () => { - let searchStr = searchState.value.trim().toLowerCase(); + searchState.clearInputTimeout(); + + let searchStr = searchState.inputElem.value.trim().toLowerCase(); if (searchStr.startsWith("clippy::")) { searchStr = searchStr.slice(8); } + if (searchState.lastSearch === searchStr) { + return; + } + searchState.lastSearch = searchStr; const terms = searchStr.split(" "); onEachLazy(document.querySelectorAll("article"), lint => { // Search by id if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { - el.style.display = ""; + lint.style.display = ""; return; } // Search the description // The use of `for`-loops instead of `foreach` enables us to return early - const docsLowerCase = lint.docs.toLowerCase(); + const docsLowerCase = lint.textContent.toLowerCase(); for (index = 0; index < terms.length; index++) { // This is more likely and will therefore be checked first if (docsLowerCase.indexOf(terms[index]) !== -1) { - continue; + return; } if (lint.id.indexOf(terms[index]) !== -1) { - continue; + return; } - return false; + lint.style.display = "none"; } }); }, @@ -631,7 +606,16 @@ function generateSettings() { ); } +function generateSearch() { + searchState.inputElem.addEventListener("change", handleInputChanged); + searchState.inputElem.addEventListener("input", handleInputChanged); + searchState.inputElem.addEventListener("keydown", handleInputChanged); + searchState.inputElem.addEventListener("keyup", handleInputChanged); + searchState.inputElem.addEventListener("paste", handleInputChanged); +} + generateSettings(); +generateSearch(); // loading the theme after the initial load const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); From 574e3dd9226774d5a11cec900ec5322fbf7d6f28 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Aug 2024 22:34:14 +0200 Subject: [PATCH 04/16] Finish porting lint functionalities to vanilla JS --- util/gh-pages/index_template.html | 19 ++- util/gh-pages/script.js | 230 +++++++++++++++++------------- util/gh-pages/style.css | 4 + 3 files changed, 140 insertions(+), 113 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 5baa03da2ea4..6571283cbf4d 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -199,14 +199,13 @@

Clippy Lints

{% for lint in lints %} -
-
+
{# Open related issues #} {# Jump to source #} {% if let Some(id_location) = lint.id_location %}
- View Source + View Source {% endif %}
@@ -315,7 +313,6 @@

- diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index eb563a0f242c..902827b97e49 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -1,19 +1,4 @@ (function () { - function scrollToLint(lintId) { - const target = document.getElementById(lintId); - if (!target) { - return; - } - target.scrollIntoView(); - } - - function scrollToLintByURL($scope, $location) { - const removeListener = $scope.$on('ngRepeatFinished', function (ngRepeatFinishedEvent) { - scrollToLint($location.path().substring(1)); - removeListener(); - }); - } - function selectGroup($scope, selectedGroup) { const groups = $scope.groups; for (const group in groups) { @@ -365,39 +350,12 @@ return $scope.applicabilities[lint.applicability]; }; - // Show details for one lint - $scope.openLint = function (lint) { - $scope.open[lint.id] = true; - $location.path(lint.id); - }; - $scope.toggleExpansion = function(lints, isExpanded) { lints.forEach(lint => { $scope.open[lint.id] = isExpanded; }); } - $scope.copyToClipboard = function (lint) { - const clipboard = document.getElementById("clipboard-" + lint.id); - if (clipboard) { - let resetClipboardTimeout = null; - const resetClipboardIcon = clipboard.innerHTML; - - function resetClipboard() { - resetClipboardTimeout = null; - clipboard.innerHTML = resetClipboardIcon; - } - - navigator.clipboard.writeText("clippy::" + lint.id); - - clipboard.innerHTML = "✓"; - if (resetClipboardTimeout !== null) { - clearTimeout(resetClipboardTimeout); - } - resetClipboardTimeout = setTimeout(resetClipboard, 1000); - } - } - // Get data $scope.open = {}; $scope.loading = true; @@ -413,8 +371,6 @@ selectGroup($scope, selectedGroup.toLowerCase()); } - scrollToLintByURL($scope, $location); - setTimeout(function () { const el = document.getElementById('filter-input'); if (el) { el.focus() } @@ -433,6 +389,65 @@ function getQueryVariable(variable) { } } +function storeValue(settingName, value) { + try { + localStorage.setItem(`clippy-lint-list-${settingName}`, value); + } catch (e) { } +} + +function loadValue(settingName) { + return localStorage.getItem(`clippy-lint-list-${settingName}`); +} + +function setTheme(theme, store) { + let enableHighlight = false; + let enableNight = false; + let enableAyu = false; + + switch(theme) { + case "ayu": + enableAyu = true; + break; + case "coal": + case "navy": + enableNight = true; + break; + case "rust": + enableHighlight = true; + break; + default: + enableHighlight = true; + theme = "light"; + break; + } + + document.getElementsByTagName("body")[0].className = theme; + + document.getElementById("githubLightHighlight").disabled = enableNight || !enableHighlight; + document.getElementById("githubDarkHighlight").disabled = !enableNight && !enableAyu; + + document.getElementById("styleHighlight").disabled = !enableHighlight; + document.getElementById("styleNight").disabled = !enableNight; + document.getElementById("styleAyu").disabled = !enableAyu; + + if (store) { + storeValue("theme", theme); + } else { + document.getElementById(`theme-choice`).value = theme; + } +} + +// loading the theme after the initial load +const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); +const theme = loadValue('theme'); +if (prefersDark.matches && !theme) { + setTheme("coal", false); +} else { + setTheme(theme, false); +} +let disableShortcuts = loadValue('disable-shortcuts') === "true"; +document.getElementById("disable-shortcuts").checked = disableShortcuts; + window.searchState = { timeout: null, inputElem: document.getElementById("search-input"), @@ -486,6 +501,11 @@ window.searchState = { lint.style.display = "none"; } }); + if (searchStr.length > 0) { + window.location.hash = `/${searchStr}`; + } else { + window.location.hash = ''; + } }, }; @@ -496,54 +516,6 @@ function handleInputChanged(event) { searchState.resetInputTimeout(); } -function storeValue(settingName, value) { - try { - localStorage.setItem(`clippy-lint-list-${settingName}`, value); - } catch (e) { } -} - -function loadValue(settingName) { - return localStorage.getItem(`clippy-lint-list-${settingName}`); -} - -function setTheme(theme, store) { - let enableHighlight = false; - let enableNight = false; - let enableAyu = false; - - switch(theme) { - case "ayu": - enableAyu = true; - break; - case "coal": - case "navy": - enableNight = true; - break; - case "rust": - enableHighlight = true; - break; - default: - enableHighlight = true; - theme = "light"; - break; - } - - document.getElementsByTagName("body")[0].className = theme; - - document.getElementById("githubLightHighlight").disabled = enableNight || !enableHighlight; - document.getElementById("githubDarkHighlight").disabled = !enableNight && !enableAyu; - - document.getElementById("styleHighlight").disabled = !enableHighlight; - document.getElementById("styleNight").disabled = !enableNight; - document.getElementById("styleAyu").disabled = !enableAyu; - - if (store) { - storeValue("theme", theme); - } else { - document.getElementById(`theme-choice`).value = theme; - } -} - function handleShortcut(ev) { if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) { return; @@ -584,6 +556,52 @@ function onEachLazy(lazyArray, func) { } } +function expandLintId(lintId) { + searchState.inputElem.value = lintId; + searchState.filterLints(); + + // Expand the lint. + const lintElem = document.getElementById(lintId); + const isCollapsed = lintElem.classList.remove("collapsed"); + lintElem.querySelector(".label-doc-folding").innerText = "-"; +} + +// Show details for one lint +function openLint(event) { + event.preventDefault(); + event.stopPropagation(); + expandLintId(event.target.getAttribute("href").slice(1)); +} + +function expandLint(lintId) { + const lintElem = document.getElementById(lintId); + const isCollapsed = lintElem.classList.toggle("collapsed"); + lintElem.querySelector(".label-doc-folding").innerText = isCollapsed ? "+" : "-"; +} + +function copyToClipboard(event) { + event.preventDefault(); + event.stopPropagation(); + + const clipboard = event.target; + + let resetClipboardTimeout = null; + const resetClipboardIcon = clipboard.innerHTML; + + function resetClipboard() { + resetClipboardTimeout = null; + clipboard.innerHTML = resetClipboardIcon; + } + + navigator.clipboard.writeText("clippy::" + clipboard.parentElement.id.slice(5)); + + clipboard.innerHTML = "✓"; + if (resetClipboardTimeout !== null) { + clearTimeout(resetClipboardTimeout); + } + resetClipboardTimeout = setTimeout(resetClipboard, 1000); +} + function handleBlur(event) { const parent = document.getElementById("settings-dropdown"); if (!parent.contains(document.activeElement) && @@ -617,15 +635,23 @@ function generateSearch() { generateSettings(); generateSearch(); -// loading the theme after the initial load -const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); -const theme = loadValue('theme'); -if (prefersDark.matches && !theme) { - setTheme("coal", false); -} else { - setTheme(theme, false); +function scrollToLint(lintId) { + const target = document.getElementById(lintId); + if (!target) { + return; + } + target.scrollIntoView(); + expandLintId(lintId); } -let disableShortcuts = loadValue('disable-shortcuts') === "true"; -document.getElementById("disable-shortcuts").checked = disableShortcuts; -hljs.highlightAll(); +// If the page we arrive on has link to a given lint, we scroll to it. +function scrollToLintByURL() { + const lintId = window.location.hash.substring(2); + if (lintId.length > 0) { + scrollToLint(lintId); + } +} + +scrollToLintByURL(); + +onEachLazy(document.querySelectorAll("pre > code.language-rust"), el => hljs.highlightElement(el)); diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index a9485d511047..9af566b5aa9e 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -396,3 +396,7 @@ body { background: var(--bg); color: var(--fg); } + +article.collapsed .lint-docs { + display: none; +} \ No newline at end of file From f2193c680cc5e2d9cf711af0a9bb92b15887c892 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Aug 2024 23:43:35 +0200 Subject: [PATCH 05/16] Completely remove angular and generate parts of settings in JS --- util/gh-pages/index_template.html | 80 ++--- util/gh-pages/script.js | 469 +++++------------------------- 2 files changed, 102 insertions(+), 447 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 6571283cbf4d..5a7af7d562ed 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -25,7 +25,7 @@ - +
@@ -59,121 +59,90 @@

Clippy Lints

-
+
-
-
+
-
-
+
-
+
-
@@ -189,10 +158,10 @@

Clippy Lints

- -
@@ -315,7 +284,6 @@

- diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 902827b97e49..6f506956e7c9 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -1,394 +1,3 @@ -(function () { - function selectGroup($scope, selectedGroup) { - const groups = $scope.groups; - for (const group in groups) { - if (groups.hasOwnProperty(group)) { - groups[group] = group === selectedGroup; - } - } - } - - angular.module("clippy", []) - .directive('filterDropdown', function ($document) { - return { - restrict: 'A', - link: function ($scope, $element, $attr) { - $element.bind('click', function (event) { - if (event.target.closest('button')) { - $element.toggleClass('open'); - } else { - $element.addClass('open'); - } - $element.addClass('open-recent'); - }); - - $document.bind('click', function () { - if (!$element.hasClass('open-recent')) { - $element.removeClass('open'); - } - $element.removeClass('open-recent'); - }) - } - } - }) - .directive('onFinishRender', function ($timeout) { - return { - restrict: 'A', - link: function (scope, element, attr) { - if (scope.$last === true) { - $timeout(function () { - scope.$emit(attr.onFinishRender); - }); - } - } - }; - }) - .controller("lintList", function ($scope, $http, $location, $timeout) { - // Level filter - const LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true}; - $scope.levels = { ...LEVEL_FILTERS_DEFAULT }; - $scope.byLevels = function (lint) { - return $scope.levels[lint.level]; - }; - - const GROUPS_FILTER_DEFAULT = { - cargo: true, - complexity: true, - correctness: true, - nursery: true, - pedantic: true, - perf: true, - restriction: true, - style: true, - suspicious: true, - deprecated: false, - } - - $scope.groups = { - ...GROUPS_FILTER_DEFAULT - }; - - $scope.versionFilters = { - "≥": {enabled: false, minorVersion: null }, - "≤": {enabled: false, minorVersion: null }, - "=": {enabled: false, minorVersion: null }, - }; - - // Map the versionFilters to the query parameters in a way that is easier to work with in a URL - const versionFilterKeyMap = { - "≥": "gte", - "≤": "lte", - "=": "eq" - }; - const reverseVersionFilterKeyMap = Object.fromEntries( - Object.entries(versionFilterKeyMap).map(([key, value]) => [value, key]) - ); - - const APPLICABILITIES_FILTER_DEFAULT = { - MachineApplicable: true, - MaybeIncorrect: true, - HasPlaceholders: true, - Unspecified: true, - }; - - $scope.applicabilities = { - ...APPLICABILITIES_FILTER_DEFAULT - } - - // loadFromURLParameters retrieves filter settings from the URL parameters and assigns them - // to corresponding $scope variables. - function loadFromURLParameters() { - // Extract parameters from URL - const urlParameters = $location.search(); - - // Define a helper function that assigns URL parameters to a provided scope variable - const handleParameter = (parameter, scopeVariable, defaultValues) => { - if (urlParameters[parameter]) { - const items = urlParameters[parameter].split(','); - for (const key in scopeVariable) { - if (scopeVariable.hasOwnProperty(key)) { - scopeVariable[key] = items.includes(key); - } - } - } else if (defaultValues) { - for (const key in defaultValues) { - if (scopeVariable.hasOwnProperty(key)) { - scopeVariable[key] = defaultValues[key]; - } - } - } - }; - - handleParameter('levels', $scope.levels, LEVEL_FILTERS_DEFAULT); - handleParameter('groups', $scope.groups, GROUPS_FILTER_DEFAULT); - handleParameter('applicabilities', $scope.applicabilities, APPLICABILITIES_FILTER_DEFAULT); - - // Handle 'versions' parameter separately because it needs additional processing - if (urlParameters.versions) { - const versionFilters = urlParameters.versions.split(','); - for (const versionFilter of versionFilters) { - const [key, minorVersion] = versionFilter.split(':'); - const parsedMinorVersion = parseInt(minorVersion); - - // Map the key from the URL parameter to its original form - const originalKey = reverseVersionFilterKeyMap[key]; - - if (originalKey in $scope.versionFilters && !isNaN(parsedMinorVersion)) { - $scope.versionFilters[originalKey].enabled = true; - $scope.versionFilters[originalKey].minorVersion = parsedMinorVersion; - } - } - } - - // Load the search parameter from the URL path - const searchParameter = $location.path().substring(1); // Remove the leading slash - if (searchParameter) { - $scope.search = searchParameter; - $scope.open[searchParameter] = true; - scrollToLintByURL($scope, $location); - } - } - - // updateURLParameter updates the URL parameter with the given key to the given value - function updateURLParameter(filterObj, urlKey, defaultValue = {}, processFilter = filter => filter) { - const parameter = Object.keys(filterObj) - .filter(filter => filterObj[filter]) - .sort() - .map(processFilter) - .filter(Boolean) // Filters out any falsy values, including null - .join(','); - - const defaultParameter = Object.keys(defaultValue) - .filter(filter => defaultValue[filter]) - .sort() - .map(processFilter) - .filter(Boolean) // Filters out any falsy values, including null - .join(','); - - // if we ended up back at the defaults, just remove it from the URL - if (parameter === defaultParameter) { - $location.search(urlKey, null); - } else { - $location.search(urlKey, parameter || null); - } - } - - // updateVersionURLParameter updates the version URL parameter with the given version filters - function updateVersionURLParameter(versionFilters) { - updateURLParameter( - versionFilters, - 'versions', {}, - versionFilter => versionFilters[versionFilter].enabled && versionFilters[versionFilter].minorVersion != null - ? `${versionFilterKeyMap[versionFilter]}:${versionFilters[versionFilter].minorVersion}` - : null - ); - } - - // updateAllURLParameters updates all the URL parameters with the current filter settings - function updateAllURLParameters() { - updateURLParameter($scope.levels, 'levels', LEVEL_FILTERS_DEFAULT); - updateURLParameter($scope.groups, 'groups', GROUPS_FILTER_DEFAULT); - updateVersionURLParameter($scope.versionFilters); - updateURLParameter($scope.applicabilities, 'applicabilities', APPLICABILITIES_FILTER_DEFAULT); - } - - // Add $watches to automatically update URL parameters when the data changes - $scope.$watch('levels', function (newVal, oldVal) { - if (newVal !== oldVal) { - updateURLParameter(newVal, 'levels', LEVEL_FILTERS_DEFAULT); - } - }, true); - - $scope.$watch('groups', function (newVal, oldVal) { - if (newVal !== oldVal) { - updateURLParameter(newVal, 'groups', GROUPS_FILTER_DEFAULT); - } - }, true); - - $scope.$watch('versionFilters', function (newVal, oldVal) { - if (newVal !== oldVal) { - updateVersionURLParameter(newVal); - } - }, true); - - $scope.$watch('applicabilities', function (newVal, oldVal) { - if (newVal !== oldVal) { - updateURLParameter(newVal, 'applicabilities', APPLICABILITIES_FILTER_DEFAULT) - } - }, true); - - // Watch for changes in the URL path and update the search and lint display - $scope.$watch(function () { return $location.path(); }, function (newPath) { - const searchParameter = newPath.substring(1); - if ($scope.search !== searchParameter) { - $scope.search = searchParameter; - $scope.open[searchParameter] = true; - scrollToLintByURL($scope, $location); - } - }); - - let debounceTimeout; - $scope.$watch('search', function (newVal, oldVal) { - if (newVal !== oldVal) { - if (debounceTimeout) { - $timeout.cancel(debounceTimeout); - } - - debounceTimeout = $timeout(function () { - $location.path(newVal); - }, 1000); - } - }); - - $scope.$watch(function () { return $location.search(); }, function (newParameters) { - loadFromURLParameters(); - }, true); - - $scope.updatePath = function () { - if (debounceTimeout) { - $timeout.cancel(debounceTimeout); - } - - $location.path($scope.search); - } - - $scope.toggleLevels = function (value) { - const levels = $scope.levels; - for (const key in levels) { - if (levels.hasOwnProperty(key)) { - levels[key] = value; - } - } - }; - - $scope.toggleGroups = function (value) { - const groups = $scope.groups; - for (const key in groups) { - if (groups.hasOwnProperty(key)) { - groups[key] = value; - } - } - }; - - $scope.toggleApplicabilities = function (value) { - const applicabilities = $scope.applicabilities; - for (const key in applicabilities) { - if (applicabilities.hasOwnProperty(key)) { - applicabilities[key] = value; - } - } - } - - $scope.resetGroupsToDefault = function () { - $scope.groups = { - ...GROUPS_FILTER_DEFAULT - }; - }; - - $scope.selectedValuesCount = function (obj) { - return Object.values(obj).filter(x => x).length; - } - - $scope.clearVersionFilters = function () { - for (const filter in $scope.versionFilters) { - $scope.versionFilters[filter] = { enabled: false, minorVersion: null }; - } - } - - $scope.versionFilterCount = function(obj) { - return Object.values(obj).filter(x => x.enabled).length; - } - - $scope.updateVersionFilters = function() { - for (const filter in $scope.versionFilters) { - const minorVersion = $scope.versionFilters[filter].minorVersion; - - // 1.29.0 and greater - if (minorVersion && minorVersion > 28) { - $scope.versionFilters[filter].enabled = true; - continue; - } - - $scope.versionFilters[filter].enabled = false; - } - } - - $scope.byVersion = function(lint) { - const filters = $scope.versionFilters; - for (const filter in filters) { - if (filters[filter].enabled) { - const minorVersion = filters[filter].minorVersion; - - // Strip the "pre " prefix for pre 1.29.0 lints - const lintVersion = lint.version.startsWith("pre ") ? lint.version.substring(4, lint.version.length) : lint.version; - const lintMinorVersion = lintVersion.substring(2, 4); - - switch (filter) { - // "=" gets the highest priority, since all filters are inclusive - case "=": - return (lintMinorVersion == minorVersion); - case "≥": - if (lintMinorVersion < minorVersion) { return false; } - break; - case "≤": - if (lintMinorVersion > minorVersion) { return false; } - break; - default: - return true - } - } - } - - return true; - } - - $scope.byGroups = function (lint) { - return $scope.groups[lint.group]; - }; - - $scope.byApplicabilities = function (lint) { - return $scope.applicabilities[lint.applicability]; - }; - - $scope.toggleExpansion = function(lints, isExpanded) { - lints.forEach(lint => { - $scope.open[lint.id] = isExpanded; - }); - } - - // Get data - $scope.open = {}; - $scope.loading = true; - - // This will be used to jump into the source code of the version that this documentation is for. - $scope.docVersion = window.location.pathname.split('/')[2] || "master"; - - // Set up the filters from the URL parameters before we start loading the data - loadFromURLParameters(); - - const selectedGroup = getQueryVariable("sel"); - if (selectedGroup) { - selectGroup($scope, selectedGroup.toLowerCase()); - } - - setTimeout(function () { - const el = document.getElementById('filter-input'); - if (el) { el.focus() } - }, 0); - }); -})(); - -function getQueryVariable(variable) { - const query = window.location.search.substring(1); - const vars = query.split('&'); - for (const entry of vars) { - const pair = entry.split('='); - if (decodeURIComponent(pair[0]) == variable) { - return decodeURIComponent(pair[1]); - } - } -} - function storeValue(settingName, value) { try { localStorage.setItem(`clippy-lint-list-${settingName}`, value); @@ -611,6 +220,34 @@ function handleBlur(event) { } } +function toggleExpansion(expand) { + onEachLazy( + document.querySelectorAll("article"), + expand ? el => el.classList.remove("collapsed") : el => el.classList.add("collapsed"), + ); +} + +function generateListOfOptions(list, elementId) { + let html = ''; + let nbEnabled = 0; + for (const [key, value] of Object.entries(list)) { + const attr = value ? " checked" : ""; + html += `\ +
  • \ + \ +
  • `; + if (value) { + nbEnabled += 1; + } + } + + const elem = document.getElementById(elementId); + elem.previousElementSibling.querySelector(".badge").innerText = `${nbEnabled}`; + elem.innerHTML += html; +} + function generateSettings() { const settings = document.getElementById("settings-dropdown"); const settingsButton = settings.querySelector(".settings-icon") @@ -622,6 +259,56 @@ function generateSettings() { settingsMenu.querySelectorAll("input"), el => el.onblur = handleBlur, ); + + const LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true}; + generateListOfOptions(LEVEL_FILTERS_DEFAULT, "lint-levels"); + + // Generate lint groups. + const GROUPS_FILTER_DEFAULT = { + cargo: true, + complexity: true, + correctness: true, + deprecated: false, + nursery: true, + pedantic: true, + perf: true, + restriction: true, + style: true, + suspicious: true, + }; + generateListOfOptions(GROUPS_FILTER_DEFAULT, "lint-groups"); + + const APPLICABILITIES_FILTER_DEFAULT = { + Unspecified: true, + Unresolved: true, + MachineApplicable: true, + MaybeIncorrect: true, + HasPlaceholders: true + }; + generateListOfOptions(APPLICABILITIES_FILTER_DEFAULT, "lint-applicabilities"); + + const VERSIONS_FILTERS = { + "≥": {enabled: false, minorVersion: null }, + "≤": {enabled: false, minorVersion: null }, + "=": {enabled: false, minorVersion: null }, + }; + + let html = ''; + for (const kind of ["≥", "≤", "="]) { + html += `\ +
  • \ + \ + 1. \ + \ + .0\ +
  • `; + } + document.getElementById("version-filter-selector").innerHTML += html; } function generateSearch() { From aeb548a4f76c742608c2b7154461814440048506 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 14 Aug 2024 16:40:00 +0200 Subject: [PATCH 06/16] Put back interactions with settings menus --- util/gh-pages/index_template.html | 78 ++++++++++------------------ util/gh-pages/script.js | 85 +++++++++++++++++++------------ util/gh-pages/style.css | 14 ++++- 3 files changed, 91 insertions(+), 86 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 5a7af7d562ed..a18948a20604 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -27,7 +27,7 @@
    -
    +
    Theme
    - All - +
  • - +
  • -
    +
    -
    -
    -
    - - -
    +
    + +
    -
    +
    -

    diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 7dce72c18590..4c61c5129df2 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -76,6 +76,29 @@ window.searchState = { setTimeout(searchState.filterLints, 50); }, filterLints: () => { + function matchesSearch(lint, terms, searchStr) { + // Search by id + if (lint.elem.id.indexOf(searchStr) !== -1) { + return true; + } + // Search the description + // The use of `for`-loops instead of `foreach` enables us to return early + const docsLowerCase = lint.elem.textContent.toLowerCase(); + for (const term of terms) { + // This is more likely and will therefore be checked first + if (docsLowerCase.indexOf(term) !== -1) { + return true; + } + + if (lint.elem.id.indexOf(term) !== -1) { + return true; + } + + return false; + } + return true; + } + searchState.clearInputTimeout(); let searchStr = searchState.inputElem.value.trim().toLowerCase(); @@ -87,29 +110,19 @@ window.searchState = { } searchState.lastSearch = searchStr; const terms = searchStr.split(" "); + const cleanedSearchStr = searchStr.replaceAll("-", "_"); - onEachLazy(document.querySelectorAll("article"), lint => { - // Search by id - if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { - lint.style.display = ""; - return; + for (const lint of filters.getAllLints()) { + lint.searchFilteredOut = !matchesSearch(lint, terms, cleanedSearchStr); + if (lint.filteredOut) { + continue; } - // Search the description - // The use of `for`-loops instead of `foreach` enables us to return early - const docsLowerCase = lint.textContent.toLowerCase(); - for (index = 0; index < terms.length; index++) { - // This is more likely and will therefore be checked first - if (docsLowerCase.indexOf(terms[index]) !== -1) { - return; - } - - if (lint.id.indexOf(terms[index]) !== -1) { - return; - } - - lint.style.display = "none"; + if (lint.searchFilteredOut) { + lint.elem.style.display = "none"; + } else { + lint.elem.style.display = ""; } - }); + } if (searchStr.length > 0) { window.location.hash = `/${searchStr}`; } else { @@ -151,12 +164,26 @@ function handleShortcut(ev) { document.addEventListener("keypress", handleShortcut); document.addEventListener("keydown", handleShortcut); -function toggleElements(element, value) { - // `element` is always a button in a `li` in a `ul`. We want the `input` in the `ul`. +function toggleElements(filter, value) { + let needsUpdate = false; + let count = 0; + + const element = document.getElementById(filters[filter].id); onEachLazy( - element.parentElement.parentElement.getElementsByTagName("input"), - el => el.checked = value, + element.querySelectorAll("ul input"), + el => { + if (el.checked !== value) { + el.checked = value; + filters[filter][el.getAttribute("data-value")] = value; + needsUpdate = true; + } + count += 1; + } ); + element.querySelector(".badge").innerText = value ? count : 0; + if (needsUpdate) { + filters.filterLints(); + } } function changeSetting(elem) { @@ -251,15 +278,90 @@ const GROUPS_FILTER_DEFAULT = { style: true, suspicious: true, }; +const LEVEL_FILTERS_DEFAULT = { + allow: true, + warn: true, + deny: true, + none: true, +}; +const APPLICABILITIES_FILTER_DEFAULT = { + Unspecified: true, + Unresolved: true, + MachineApplicable: true, + MaybeIncorrect: true, + HasPlaceholders: true, +}; + +window.filters = { + groups_filter: { id: "lint-groups", ...GROUPS_FILTER_DEFAULT }, + levels_filter: { id: "lint-levels", ...LEVEL_FILTERS_DEFAULT }, + applicabilities_filter: { id: "lint-applicabilities", ...APPLICABILITIES_FILTER_DEFAULT }, + version_filter: { + "≥": null, + "≤": null, + "=": null, + }, + allLints: null, + getAllLints: () => { + if (filters.allLints === null) { + filters.allLints = Array.prototype.slice.call( + document.getElementsByTagName("article"), + ).map(elem => { + return { + elem: elem, + group: elem.querySelector(".label-lint-group").innerText, + level: elem.querySelector(".label-lint-level").innerText, + version: elem.querySelector(".label-version").innerText, + applicability: elem.querySelector(".label-applicability").innerText, + filteredOut: false, + searchFilteredOut: false, + }; + }); + } + return filters.allLints; + }, + filterLints: () => { + for (const lint of filters.getAllLints()) { + lint.filteredOut = (!filters.groups_filter[lint.group] + || !filters.levels_filter[lint.level] + || !filters.applicabilities_filter[lint.applicability]); + if (lint.filteredOut || lint.searchFilteredOut) { + lint.elem.style.display = "none"; + } else { + lint.elem.style.display = ""; + } + } + }, +}; + +function updateFilter(elem, filter) { + const value = elem.getAttribute("data-value"); + if (filters[filter][value] !== elem.checked) { + filters[filter][value] = elem.checked; + const counter = document.querySelector(`#${filters[filter].id} .badge`); + counter.innerText = parseInt(counter.innerText) + (elem.checked ? 1 : -1); + filters.filterLints(); + } +} function resetGroupsToDefault() { + let needsUpdate = false; + onEachLazy(document.querySelectorAll("#lint-groups-selector input"), el => { const key = el.getAttribute("data-value"); - el.checked = GROUPS_FILTER_DEFAULT[key]; + const value = GROUPS_FILTER_DEFAULT[key]; + if (filters.groups_filter[key] !== value) { + filters.groups_filter[key] = value; + el.checked = value; + needsUpdate = true; + } }); + if (needsUpdate) { + filters.filterLints(); + } } -function generateListOfOptions(list, elementId) { +function generateListOfOptions(list, elementId, filter) { let html = ''; let nbEnabled = 0; for (const [key, value] of Object.entries(list)) { @@ -267,7 +369,8 @@ function generateListOfOptions(list, elementId) { html += `\
  • \ \
  • `; if (value) { @@ -298,20 +401,10 @@ function setupDropdown(elementId) { function generateSettings() { setupDropdown("settings-dropdown"); - const LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true}; - generateListOfOptions(LEVEL_FILTERS_DEFAULT, "lint-levels"); - - // Generate lint groups. - generateListOfOptions(GROUPS_FILTER_DEFAULT, "lint-groups"); - - const APPLICABILITIES_FILTER_DEFAULT = { - Unspecified: true, - Unresolved: true, - MachineApplicable: true, - MaybeIncorrect: true, - HasPlaceholders: true - }; - generateListOfOptions(APPLICABILITIES_FILTER_DEFAULT, "lint-applicabilities"); + generateListOfOptions(LEVEL_FILTERS_DEFAULT, "lint-levels", "levels_filter"); + generateListOfOptions(GROUPS_FILTER_DEFAULT, "lint-groups", "groups_filter"); + generateListOfOptions( + APPLICABILITIES_FILTER_DEFAULT, "lint-applicabilities", "applicabilities_filter"); let html = ''; for (const kind of ["≥", "≤", "="]) { @@ -361,5 +454,5 @@ function scrollToLintByURL() { } scrollToLintByURL(); - +filters.filterLints(); onEachLazy(document.querySelectorAll("pre > code.language-rust"), el => hljs.highlightElement(el)); From 00c11df107e27b8f37c63efbdf359b0d62e9f54c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 15 Aug 2024 00:47:20 +0200 Subject: [PATCH 08/16] Add support for version filtering --- util/gh-pages/script.js | 58 +++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 4c61c5129df2..6b25bfa48bbe 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -262,10 +262,6 @@ function toggleExpansion(expand) { ); } -function clearVersionFilters() { - onEachLazy(document.querySelectorAll("#version-filter-count input"), el => el.value = ""); -} - const GROUPS_FILTER_DEFAULT = { cargo: true, complexity: true, @@ -307,11 +303,16 @@ window.filters = { filters.allLints = Array.prototype.slice.call( document.getElementsByTagName("article"), ).map(elem => { + let version = elem.querySelector(".label-version").innerText; + // Strip the "pre " prefix for pre 1.29.0 lints + if (version.startsWith("pre ")) { + version = version.slice(4); + } return { elem: elem, group: elem.querySelector(".label-lint-group").innerText, level: elem.querySelector(".label-lint-level").innerText, - version: elem.querySelector(".label-version").innerText, + version: parseInt(version.split(".")[1]), applicability: elem.querySelector(".label-applicability").innerText, filteredOut: false, searchFilteredOut: false, @@ -324,7 +325,11 @@ window.filters = { for (const lint of filters.getAllLints()) { lint.filteredOut = (!filters.groups_filter[lint.group] || !filters.levels_filter[lint.level] - || !filters.applicabilities_filter[lint.applicability]); + || !filters.applicabilities_filter[lint.applicability] + || !(filters.version_filter["="] === null || lint.version === filters.version_filter["="]) + || !(filters.version_filter["≥"] === null || lint.version > filters.version_filter["≥"]) + || !(filters.version_filter["≤"] === null || lint.version < filters.version_filter["≤"]) + ); if (lint.filteredOut || lint.searchFilteredOut) { lint.elem.style.display = "none"; } else { @@ -344,6 +349,38 @@ function updateFilter(elem, filter) { } } +function updateVersionFilters(elem, comparisonKind) { + let value = elem.value.trim(); + if (value.length === 0) { + value = null; + } else if (/^\d+$/.test(value)) { + value = parseInt(value); + } else { + console.error(`Failed to get version number from "${value}"`); + return; + } + if (filters.version_filter[comparisonKind] !== value) { + filters.version_filter[comparisonKind] = value; + filters.filterLints(); + } +} + +function clearVersionFilters() { + let needsUpdate = false; + + onEachLazy(document.querySelectorAll("#version-filter input"), el => { + el.value = ""; + const comparisonKind = el.getAttribute("data-value"); + if (filters.version_filter[comparisonKind] !== null) { + needsUpdate = true; + filters.version_filter[comparisonKind] = null; + } + }); + if (needsUpdate) { + filters.filterLints(); + } +} + function resetGroupsToDefault() { let needsUpdate = false; @@ -414,10 +451,15 @@ function generateSettings() { 1. \ \ + data-value="${kind}" \ + onchange="updateVersionFilters(this, '${kind}')" \ + oninput="updateVersionFilters(this, '${kind}')" \ + onkeydown="updateVersionFilters(this, '${kind}')" \ + onkeyup="updateVersionFilters(this, '${kind}')" \ + onpaste="updateVersionFilters(this, '${kind}')" \ + /> .0\ `; } From 4bb4cc6fcbc0fe1bacd44ab9004405c19ae52781 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 15 Aug 2024 01:56:53 +0200 Subject: [PATCH 09/16] Add support for URL arguments --- util/gh-pages/script.js | 124 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 6b25bfa48bbe..cbe2a79934f7 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -262,6 +262,11 @@ function toggleExpansion(expand) { ); } +// Returns the current URL without any query parameter or hash. +function getNakedUrl() { + return window.location.href.split("?")[0].split("#")[0]; +} + const GROUPS_FILTER_DEFAULT = { cargo: true, complexity: true, @@ -287,6 +292,17 @@ const APPLICABILITIES_FILTER_DEFAULT = { MaybeIncorrect: true, HasPlaceholders: true, }; +const URL_PARAMS_CORRESPONDANCE = { + "groups_filter": "groups", + "levels_filter": "levels", + "applicabilities_filter": "applicabilities", + "version_filter": "versions", +}; +const VERSIONS_CORRESPONDANCE = { + "lte": "≤", + "gte": "≥", + "eq": "=", +}; window.filters = { groups_filter: { id: "lint-groups", ...GROUPS_FILTER_DEFAULT }, @@ -321,7 +337,65 @@ window.filters = { } return filters.allLints; }, + regenerateURLparams: () => { + const urlParams = new URLSearchParams(window.location.search); + + function compareObjects(obj1, obj2) { + return (JSON.stringify(obj1) === JSON.stringify({ id: obj1.id, ...obj2 })); + } + function updateIfNeeded(filterName, obj2) { + const obj1 = filters[filterName]; + const name = URL_PARAMS_CORRESPONDANCE[filterName]; + if (!compareObjects(obj1, obj2)) { + urlParams.set( + name, + Object.entries(obj1).filter( + ([key, value]) => value && key !== "id" + ).map( + ([key, _]) => key + ).join(","), + ); + } else { + urlParams.delete(name); + } + } + + updateIfNeeded("groups_filter", GROUPS_FILTER_DEFAULT); + updateIfNeeded("levels_filter", LEVEL_FILTERS_DEFAULT); + updateIfNeeded( + "applicabilities_filter", APPLICABILITIES_FILTER_DEFAULT); + + const versions = []; + if (filters.version_filter["="] !== null) { + versions.push(`eq:${filters.version_filter["="]}`); + } + if (filters.version_filter["≥"] !== null) { + versions.push(`gte:${filters.version_filter["≥"]}`); + } + if (filters.version_filter["≤"] !== null) { + versions.push(`lte:${filters.version_filter["≤"]}`); + } + if (versions.length !== 0) { + urlParams.set(URL_PARAMS_CORRESPONDANCE["version_filter"], versions.join(",")); + } else { + urlParams.delete(URL_PARAMS_CORRESPONDANCE["version_filter"]); + } + + let params = urlParams.toString(); + if (params.length !== 0) { + params = `?${params}`; + } + + const url = getNakedUrl() + params + window.location.hash + if (!history.state) { + history.pushState(null, "", url); + } else { + history.replaceState(null, "", url); + } + }, filterLints: () => { + // First we regenerate the URL parameters. + filters.regenerateURLparams(); for (const lint of filters.getAllLints()) { lint.filteredOut = (!filters.groups_filter[lint.group] || !filters.levels_filter[lint.level] @@ -339,17 +413,19 @@ window.filters = { }, }; -function updateFilter(elem, filter) { +function updateFilter(elem, filter, skipLintsFiltering) { const value = elem.getAttribute("data-value"); if (filters[filter][value] !== elem.checked) { filters[filter][value] = elem.checked; const counter = document.querySelector(`#${filters[filter].id} .badge`); counter.innerText = parseInt(counter.innerText) + (elem.checked ? 1 : -1); - filters.filterLints(); + if (!skipLintsFiltering) { + filters.filterLints(); + } } } -function updateVersionFilters(elem, comparisonKind) { +function updateVersionFilters(elem, skipLintsFiltering) { let value = elem.value.trim(); if (value.length === 0) { value = null; @@ -359,9 +435,12 @@ function updateVersionFilters(elem, comparisonKind) { console.error(`Failed to get version number from "${value}"`); return; } + const comparisonKind = elem.getAttribute("data-value"); if (filters.version_filter[comparisonKind] !== value) { filters.version_filter[comparisonKind] = value; - filters.filterLints(); + if (!skipLintsFiltering) { + filters.filterLints(); + } } } @@ -454,11 +533,11 @@ function generateSettings() { class="version-filter-input form-control filter-input" \ maxlength="2" \ data-value="${kind}" \ - onchange="updateVersionFilters(this, '${kind}')" \ - oninput="updateVersionFilters(this, '${kind}')" \ - onkeydown="updateVersionFilters(this, '${kind}')" \ - onkeyup="updateVersionFilters(this, '${kind}')" \ - onpaste="updateVersionFilters(this, '${kind}')" \ + onchange="updateVersionFilters(this)" \ + oninput="updateVersionFilters(this)" \ + onkeydown="updateVersionFilters(this)" \ + onkeyup="updateVersionFilters(this)" \ + onpaste="updateVersionFilters(this)" \ /> .0\ `; @@ -495,6 +574,33 @@ function scrollToLintByURL() { } } +function parseURLFilters() { + const urlParams = new URLSearchParams(window.location.search); + + for (const [key, value] of urlParams.entries()) { + for (const [corres_key, corres_value] of Object.entries(URL_PARAMS_CORRESPONDANCE)) { + if (corres_value === key) { + if (key !== "versions") { + const settings = new Set(value.split(",")); + onEachLazy(document.querySelectorAll(`#lint-${key} ul input`), elem => { + elem.checked = settings.has(elem.getAttribute("data-value")); + updateFilter(elem, corres_key, true); + }); + } else { + const settings = value.split(",").map(elem => elem.split(":")); + + for (const [kind, value] of settings) { + const elem = document.querySelector( + `#version-filter input[data-value="${VERSIONS_CORRESPONDANCE[kind]}"]`); + updateVersionFilters(elem, true); + } + } + } + } + } +} + +parseURLFilters(); scrollToLintByURL(); filters.filterLints(); onEachLazy(document.querySelectorAll("pre > code.language-rust"), el => hljs.highlightElement(el)); From e0b0851ba8a83d4f258a9d86ba5579c4f2c61b46 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 15 Aug 2024 16:08:48 +0200 Subject: [PATCH 10/16] Support version filter URL parameters --- util/gh-pages/script.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index cbe2a79934f7..d3bc6f6fc997 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -435,6 +435,16 @@ function updateVersionFilters(elem, skipLintsFiltering) { console.error(`Failed to get version number from "${value}"`); return; } + + const counter = document.querySelector("#version-filter .badge"); + let count = 0; + onEachLazy(document.querySelectorAll("#version-filter input"), el => { + if (el.value.trim().length !== 0) { + count += 1; + } + }); + counter.innerText = count; + const comparisonKind = elem.getAttribute("data-value"); if (filters.version_filter[comparisonKind] !== value) { filters.version_filter[comparisonKind] = value; @@ -455,6 +465,7 @@ function clearVersionFilters() { filters.version_filter[comparisonKind] = null; } }); + document.querySelector("#version-filter .badge").innerText = 0; if (needsUpdate) { filters.filterLints(); } @@ -462,6 +473,7 @@ function clearVersionFilters() { function resetGroupsToDefault() { let needsUpdate = false; + let count = 0; onEachLazy(document.querySelectorAll("#lint-groups-selector input"), el => { const key = el.getAttribute("data-value"); @@ -471,7 +483,11 @@ function resetGroupsToDefault() { el.checked = value; needsUpdate = true; } + if (value) { + count += 1; + } }); + document.querySelector("#lint-groups .badge").innerText = count; if (needsUpdate) { filters.filterLints(); } @@ -592,6 +608,7 @@ function parseURLFilters() { for (const [kind, value] of settings) { const elem = document.querySelector( `#version-filter input[data-value="${VERSIONS_CORRESPONDANCE[kind]}"]`); + elem.value = value; updateVersionFilters(elem, true); } } From 47f40d468acae7e37d101cee9a6d79bff3461c64 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 15 Aug 2024 16:13:53 +0200 Subject: [PATCH 11/16] Improve rendering speed by moving settings generation after theme rendering --- tests/compile-test.rs | 11 +++++-- util/gh-pages/index_template.html | 4 +-- util/gh-pages/script.js | 49 ++++++++++++++++--------------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 00627dc0bb1f..162aed393c46 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -9,7 +9,8 @@ use clippy_lints::LintInfo; use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; use pulldown_cmark::{Options, Parser, html}; -use rinja::{Template, filters::Safe}; +use rinja::Template; +use rinja::filters::Safe; use serde::Deserialize; use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; @@ -394,7 +395,7 @@ struct Renderer<'a> { } impl<'a> Renderer<'a> { - fn markdown(&self, input: &str) -> Safe { + fn markdown(input: &str) -> Safe { let parser = Parser::new_ext(input, Options::all()); let mut html_output = String::new(); html::push_html(&mut html_output, parser); @@ -465,7 +466,11 @@ impl DiagnosticCollector { .collect(); metadata.sort_unstable_by(|a, b| a.id.cmp(&b.id)); - fs::write("util/gh-pages/index.html", Renderer { lints: &metadata }.render().unwrap()).unwrap(); + fs::write( + "util/gh-pages/index.html", + Renderer { lints: &metadata }.render().unwrap(), + ) + .unwrap(); }); (Self { sender }, handle) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 774c7b487c16..d942cbe39e72 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -1,7 +1,7 @@ - - - - - + {# #} + {# #} + {# #} + {# #} + {# #} - Clippy Lints + Clippy Lints {# #} - - - + {# #} + {# #} + {# #} - - - - - - - - -
    - -
    -
    Theme
    - - -
    -
    + {# #} + {# #} + {# #} + {# #} + {# #} + {# #} + {# #} + {# #} +
    {# #} + {# #} +
    {# #} +
    Theme
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} -
    - +
    {# #} + {# #} - + {# #} -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - - - - -
    -
    -
    - - -
    -
    +
    {# #} +
    {# #} +
    {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} +
    {# #} +
    {# #} + {# #} + {# #} + {# #} + {# #} + {# #} +
    {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #}
    {% for lint in lints %} -
    {# #} +

    {# #} + {# #} -
    -
    {{Self::markdown(lint.docs)}}
    +
    {# #} +
    {{Self::markdown(lint.docs)}}
    {# #}
    {# Applicability #} -
    - Applicability: - {{ lint.applicability_str() }} - (?) +
    {# #} + Applicability: {# #} + {{ lint.applicability_str() }} {# #} + (?) {# #}
    {# Clippy version #} -
    - {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif %} in: - {{lint.version}} +
    {# #} + {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: {# #} + {{lint.version}} {# #}
    {# Open related issues #} -
    - Related Issues +
    {# #} + Related Issues {# #}
    {# Jump to source #} {% if let Some(id_location) = lint.id_location %} -
    - View Source +
    {# #} + View Source {# #} +
    {% endif %} -
    -
    -
    +
    {# #} +
    {# #} {% endfor %} -
    -
    +
    {# #} +
    {# #} - - - + {# #} + {# #} + {# #} - - - - - + {# #} + {# #} + {# #} + {# #} + {# #} diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 7f36021e1aa3..cc22a39b3d19 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -140,19 +140,15 @@ function onEachLazy(lazyArray, func) { function highlightIfNeeded(elem) { onEachLazy(elem.querySelectorAll("pre > code.language-rust:not(.highlighted)"), el => { - hljs.highlightElement(el) + hljs.highlightElement(el.parentElement) el.classList.add("highlighted"); }); } -function expandLintId(lintId) { - searchState.inputElem.value = lintId; - searchState.filterLints(); - - // Expand the lint. +function expandLint(lintId) { const lintElem = document.getElementById(lintId); - const isCollapsed = lintElem.classList.remove("collapsed"); - lintElem.querySelector(".label-doc-folding").innerText = "-"; + const isCollapsed = lintElem.classList.toggle("collapsed"); + lintElem.querySelector(".label-doc-folding").innerText = isCollapsed ? "+" : "−"; highlightIfNeeded(lintElem); } @@ -160,14 +156,7 @@ function expandLintId(lintId) { function openLint(event) { event.preventDefault(); event.stopPropagation(); - expandLintId(event.target.getAttribute("href").slice(1)); -} - -function expandLint(lintId) { - const lintElem = document.getElementById(lintId); - const isCollapsed = lintElem.classList.toggle("collapsed"); - lintElem.querySelector(".label-doc-folding").innerText = isCollapsed ? "+" : "-"; - highlightIfNeeded(lintElem); + expandLint(event.target.getAttribute("href").slice(1)); } function copyToClipboard(event) { @@ -526,7 +515,7 @@ function scrollToLint(lintId) { return; } target.scrollIntoView(); - expandLintId(lintId); + expandLint(lintId); } // If the page we arrive on has link to a given lint, we scroll to it. diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index 43c626422076..a68a10b14011 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -272,6 +272,9 @@ L4.75,12h2.5l0.5393066-2.1572876 c0.2276001-0.1062012,0.4459839-0.2269287,0.649 height: 18px; display: block; filter: invert(0.7); + position: absolute; + top: 4px; + left: 5px; } .settings-menu * {