| window.searchState = { |
| timeout: null, |
| inputElem: document.getElementById("search-input"), |
| lastSearch: '', |
| clearInput: () => { |
| searchState.inputElem.value = ""; |
| searchState.filterLints(); |
| }, |
| clearInputTimeout: () => { |
| if (searchState.timeout !== null) { |
| clearTimeout(searchState.timeout); |
| searchState.timeout = null |
| } |
| }, |
| resetInputTimeout: () => { |
| searchState.clearInputTimeout(); |
| 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(); |
| if (searchStr.startsWith("clippy::")) { |
| searchStr = searchStr.slice(8); |
| } |
| if (searchState.lastSearch === searchStr) { |
| return; |
| } |
| searchState.lastSearch = searchStr; |
| const terms = searchStr.split(" "); |
| const cleanedSearchStr = searchStr.replaceAll("-", "_"); |
| |
| for (const lint of filters.getAllLints()) { |
| lint.searchFilteredOut = !matchesSearch(lint, terms, cleanedSearchStr); |
| if (lint.filteredOut) { |
| continue; |
| } |
| if (lint.searchFilteredOut) { |
| lint.elem.style.display = "none"; |
| } else { |
| lint.elem.style.display = ""; |
| } |
| } |
| if (searchStr.length > 0) { |
| window.location.hash = `/${searchStr}`; |
| } else { |
| window.location.hash = ''; |
| } |
| }, |
| }; |
| |
| function handleInputChanged(event) { |
| if (event.target !== document.activeElement) { |
| return; |
| } |
| searchState.resetInputTimeout(); |
| } |
| |
| function handleShortcut(ev) { |
| if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) { |
| return; |
| } |
| |
| if (document.activeElement.tagName === "INPUT") { |
| if (ev.key === "Escape") { |
| document.activeElement.blur(); |
| } |
| } else { |
| switch (ev.key) { |
| case "s": |
| case "S": |
| case "/": |
| ev.preventDefault(); // To prevent the key to be put into the input. |
| document.getElementById("search-input").focus(); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| function toggleElements(filter, value) { |
| let needsUpdate = false; |
| let count = 0; |
| |
| const element = document.getElementById(filters[filter].id); |
| onEachLazy( |
| 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) { |
| if (elem.id === "disable-shortcuts") { |
| disableShortcuts = elem.checked; |
| storeValue(elem.id, elem.checked); |
| } |
| } |
| |
| function onEachLazy(lazyArray, func) { |
| const arr = Array.prototype.slice.call(lazyArray); |
| for (const el of arr) { |
| func(el); |
| } |
| } |
| |
| function highlightIfNeeded(lintId) { |
| onEachLazy(document.querySelectorAll(`#${lintId} pre > code:not(.hljs)`), el => { |
| hljs.highlightElement(el.parentElement) |
| el.classList.add("highlighted"); |
| }); |
| } |
| |
| function expandLint(lintId) { |
| const elem = document.querySelector(`#${lintId} > input[type="checkbox"]`); |
| elem.checked = true; |
| highlightIfNeeded(lintId); |
| } |
| |
| function lintAnchor(event) { |
| event.preventDefault(); |
| event.stopPropagation(); |
| |
| const id = event.target.getAttribute("href").replace("#", ""); |
| window.location.hash = id; |
| |
| expandLint(id); |
| } |
| |
| 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, elementId) { |
| const parent = document.getElementById(elementId); |
| if (!parent.contains(document.activeElement) && |
| !parent.contains(event.relatedTarget) |
| ) { |
| parent.classList.remove("open"); |
| } |
| } |
| |
| function toggleExpansion(expand) { |
| onEachLazy( |
| document.querySelectorAll("article"), |
| expand ? el => { |
| el.classList.remove("collapsed"); |
| highlightIfNeeded(el); |
| } : el => el.classList.add("collapsed"), |
| ); |
| } |
| |
| // 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, |
| correctness: true, |
| nursery: true, |
| pedantic: true, |
| perf: true, |
| restriction: true, |
| style: true, |
| suspicious: true, |
| deprecated: false, |
| }; |
| const LEVEL_FILTERS_DEFAULT = { |
| allow: true, |
| warn: true, |
| deny: true, |
| none: true, |
| }; |
| const APPLICABILITIES_FILTER_DEFAULT = { |
| Unspecified: true, |
| MachineApplicable: true, |
| MaybeIncorrect: true, |
| HasPlaceholders: true, |
| }; |
| const URL_PARAMS_CORRESPONDENCE = { |
| "groups_filter": "groups", |
| "levels_filter": "levels", |
| "applicabilities_filter": "applicabilities", |
| "version_filter": "versions", |
| }; |
| const VERSIONS_CORRESPONDENCE = { |
| "lte": "≤", |
| "gte": "≥", |
| "eq": "=", |
| }; |
| |
| 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 => { |
| 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: parseInt(version.split(".")[1]), |
| applicability: elem.querySelector(".label-applicability").innerText, |
| filteredOut: false, |
| searchFilteredOut: false, |
| }; |
| }); |
| } |
| 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_CORRESPONDENCE[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_CORRESPONDENCE["version_filter"], versions.join(",")); |
| } else { |
| urlParams.delete(URL_PARAMS_CORRESPONDENCE["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] |
| || !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 { |
| lint.elem.style.display = ""; |
| } |
| } |
| }, |
| }; |
| |
| 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); |
| if (!skipLintsFiltering) { |
| filters.filterLints(); |
| } |
| } |
| } |
| |
| function updateVersionFilters(elem, skipLintsFiltering) { |
| 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; |
| } |
| |
| 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; |
| if (!skipLintsFiltering) { |
| 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; |
| } |
| }); |
| document.querySelector("#version-filter .badge").innerText = 0; |
| if (needsUpdate) { |
| filters.filterLints(); |
| } |
| } |
| |
| function resetGroupsToDefault() { |
| let needsUpdate = false; |
| let count = 0; |
| |
| onEachLazy(document.querySelectorAll("#lint-groups-selector input"), el => { |
| const key = el.getAttribute("data-value"); |
| const value = GROUPS_FILTER_DEFAULT[key]; |
| if (filters.groups_filter[key] !== value) { |
| filters.groups_filter[key] = value; |
| el.checked = value; |
| needsUpdate = true; |
| } |
| if (value) { |
| count += 1; |
| } |
| }); |
| document.querySelector("#lint-groups .badge").innerText = count; |
| if (needsUpdate) { |
| filters.filterLints(); |
| } |
| } |
| |
| function generateListOfOptions(list, elementId, filter) { |
| let html = ''; |
| let nbEnabled = 0; |
| for (const [key, value] of Object.entries(list)) { |
| const attr = value ? " checked" : ""; |
| html += `\ |
| <li class="checkbox">\ |
| <label class="text-capitalize">\ |
| <input type="checkbox" data-value="${key}" \ |
| onchange="updateFilter(this, '${filter}')"${attr}/>${key}\ |
| </label>\ |
| </li>`; |
| if (value) { |
| nbEnabled += 1; |
| } |
| } |
| |
| const elem = document.getElementById(`${elementId}-selector`); |
| elem.previousElementSibling.querySelector(".badge").innerText = `${nbEnabled}`; |
| elem.innerHTML += html; |
| |
| setupDropdown(elementId); |
| } |
| |
| function setupDropdown(elementId) { |
| const elem = document.getElementById(elementId); |
| const button = document.querySelector(`#${elementId} > button`); |
| button.onclick = () => elem.classList.toggle("open"); |
| |
| const setBlur = child => { |
| child.onblur = event => handleBlur(event, elementId); |
| }; |
| onEachLazy(elem.children, setBlur); |
| onEachLazy(elem.querySelectorAll("select"), setBlur); |
| onEachLazy(elem.querySelectorAll("input"), setBlur); |
| onEachLazy(elem.querySelectorAll("ul button"), setBlur); |
| } |
| |
| function generateSettings() { |
| setupDropdown("settings-dropdown"); |
| |
| 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 ["≥", "≤", "="]) { |
| html += `\ |
| <li class="checkbox">\ |
| <label>${kind}</label>\ |
| <span>1.</span> \ |
| <input type="number" \ |
| min="29" \ |
| class="version-filter-input form-control filter-input" \ |
| maxlength="2" \ |
| data-value="${kind}" \ |
| onchange="updateVersionFilters(this)" \ |
| oninput="updateVersionFilters(this)" \ |
| onkeydown="updateVersionFilters(this)" \ |
| onkeyup="updateVersionFilters(this)" \ |
| onpaste="updateVersionFilters(this)" \ |
| /> |
| <span>.0</span>\ |
| </li>`; |
| } |
| document.getElementById("version-filter-selector").innerHTML += html; |
| setupDropdown("version-filter"); |
| } |
| |
| 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); |
| } |
| |
| function scrollToLint(lintId) { |
| const target = document.getElementById(lintId); |
| if (!target) { |
| return; |
| } |
| target.scrollIntoView(); |
| expandLint(lintId); |
| } |
| |
| // If the page we arrive on has link to a given lint, we scroll to it. |
| function scrollToLintByURL() { |
| const lintId = window.location.hash.substring(1); |
| if (lintId.length > 0) { |
| scrollToLint(lintId); |
| } |
| } |
| |
| 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_CORRESPONDENCE)) { |
| 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_CORRESPONDENCE[kind]}"]`); |
| elem.value = value; |
| updateVersionFilters(elem, true); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| document.getElementById(`theme-choice`).value = loadValue("theme"); |
| let disableShortcuts = loadValue('disable-shortcuts') === "true"; |
| document.getElementById("disable-shortcuts").checked = disableShortcuts; |
| |
| document.addEventListener("keypress", handleShortcut); |
| document.addEventListener("keydown", handleShortcut); |
| |
| generateSettings(); |
| generateSearch(); |
| parseURLFilters(); |
| scrollToLintByURL(); |
| filters.filterLints(); |