Auto merge of #13269 - GuillaumeGomez:rewrite-lints-page, r=Alexendoo

Rewrite lints page

This PR has multiple goals:

* Make lints page to work without needing a web server by removing the json file.
* Prepare the field to also make the page work with JS (not done in this PR but should be straightforward).
* Remove angular dependency.

r? `@Alexendoo`

changelog: make lint page work without web server
This commit is contained in:
bors
2024-10-11 14:18:54 +00:00
11 changed files with 887 additions and 904 deletions
+1 -1
View File
@@ -8,8 +8,8 @@ rm -rf out/master/ || exit 0
echo "Making the docs for master"
mkdir out/master/
cp util/gh-pages/index.html out/master
cp util/gh-pages/theme.js 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
+1
View File
@@ -34,6 +34,7 @@ out
# gh pages docs
util/gh-pages/lints.json
util/gh-pages/index.html
# rustfmt backups
*.rs.bk
+2
View File
@@ -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" }
+3 -1
View File
@@ -19,7 +19,9 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
});
loop {
if mtime("util/gh-pages/lints.json") < 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()
+3
View File
@@ -0,0 +1,3 @@
[general]
dirs = ["util/gh-pages/"]
whitespace = "suppress"
+36 -4
View File
@@ -8,7 +8,10 @@
use clippy_lints::LintInfo;
use clippy_lints::declared_lints::LINTS;
use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED};
use serde::{Deserialize, Serialize};
use pulldown_cmark::{Options, Parser, html};
use rinja::Template;
use rinja::filters::Safe;
use serde::Deserialize;
use test_utils::IS_RUSTC_TEST_SUITE;
use ui_test::custom_flags::Flag;
use ui_test::custom_flags::rustfix::RustfixMode;
@@ -385,6 +388,22 @@ fn ui_cargo_toml_metadata() {
}
}
#[derive(Template)]
#[template(path = "index_template.html")]
struct Renderer<'a> {
lints: &'a Vec<LintMetadata>,
}
impl Renderer<'_> {
fn markdown(input: &str) -> Safe<String> {
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("<table", "<table class=\"table\""))
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum DiagnosticOrMessage {
@@ -448,8 +467,11 @@ fn spawn() -> (Self, thread::JoinHandle<()>) {
metadata.sort_unstable_by(|a, b| a.id.cmp(&b.id));
let json = serde_json::to_string_pretty(&metadata).unwrap();
fs::write("util/gh-pages/lints.json", json).unwrap();
fs::write(
"util/gh-pages/index.html",
Renderer { lints: &metadata }.render().unwrap(),
)
.unwrap();
});
(Self { sender }, handle)
@@ -488,7 +510,7 @@ fn must_be_unique(&self) -> bool {
}
}
#[derive(Debug, Serialize)]
#[derive(Debug)]
struct LintMetadata {
id: String,
id_location: Option<&'static str>,
@@ -560,4 +582,14 @@ fn new_deprecated(name: &str, reason: &str, version: &'static str) -> Self {
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"),
}
}
}
-330
View File
@@ -1,330 +0,0 @@
<!DOCTYPE html>
<!--
Welcome to a Clippy's lint list, at least the source code of it. If you are
interested in contributing to this website checkout `util/gh-pages/index.html`
inside the rust-clippy repository.
Otherwise, have a great day =^.^=
-->
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="description" content="A collection of lints to catch common mistakes and improve your Rust code.">
<title>Clippy Lints</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css"/>
<link id="githubLightHighlight" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/github.min.css" disabled="true" />
<link id="githubDarkHighlight" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/github-dark.min.css" disabled="true" />
<!-- The files are not copied over into the Clippy project since they use the MPL-2.0 License -->
<link rel="stylesheet" href="https://rust-lang.github.io/mdBook/css/variables.css"/>
<link id="styleHighlight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/highlight.css">
<link id="styleNight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/tomorrow-night.css" disabled="true">
<link id="styleAyu" rel="stylesheet" href="https://rust-lang.github.io/mdBook/ayu-highlight.css" disabled="true">
<link rel="stylesheet" href="style.css">
</head>
<body ng-app="clippy" ng-controller="lintList">
<div id="settings-dropdown">
<div class="settings-icon" tabindex="-1"></div>
<div class="settings-menu" tabindex="-1">
<div class="setting-radio-name">Theme</div>
<select id="theme-choice" onchange="setTheme(this.value, true)">
<option value="ayu">Ayu</option>
<option value="coal">Coal</option>
<option value="light">Light</option>
<option value="navy">Navy</option>
<option value="rust">Rust</option>
</select>
<label>
<input type="checkbox" id="disable-shortcuts" onchange="changeSetting(this)">
<span>Disable keyboard shortcuts</span>
</label>
</div>
</div>
<div class="container">
<div class="page-header">
<h1>Clippy Lints</h1>
</div>
<noscript>
<div class="alert alert-danger" role="alert">
Sorry, this site only works with JavaScript! :(
</div>
</noscript>
<div ng-cloak>
<div class="alert alert-info" role="alert" ng-if="loading">
Loading&#x2026;
</div>
<div class="alert alert-danger" role="alert" ng-if="error">
Error loading lints!
</div>
<div class="panel panel-default" ng-show="data">
<div class="panel-body row">
<div id="upper-filters" class="col-12 col-md-5">
<div class="btn-group" filter-dropdown>
<button type="button" class="btn btn-default dropdown-toggle">
Lint levels <span class="badge">{{selectedValuesCount(levels)}}</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="checkbox">
<label ng-click="toggleLevels(true)">
<input type="checkbox" class="invisible" />
All
</label>
</li>
<li class="checkbox">
<label ng-click="toggleLevels(false)">
<input type="checkbox" class="invisible" />
None
</label>
</li>
<li role="separator" class="divider"></li>
<li class="checkbox" ng-repeat="(level, enabled) in levels">
<label class="text-capitalize">
<input type="checkbox" ng-model="levels[level]" />
{{level}}
</label>
</li>
</ul>
</div>
<div class="btn-group" filter-dropdown>
<button type="button" class="btn btn-default dropdown-toggle">
Lint groups <span class="badge">{{selectedValuesCount(groups)}}</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="checkbox">
<label ng-click="toggleGroups(true)">
<input type="checkbox" class="invisible" />
All
</label>
</li>
<li class="checkbox">
<label ng-click="resetGroupsToDefault()">
<input type="checkbox" class="invisible" />
Default
</label>
</li>
<li class="checkbox">
<label ng-click="toggleGroups(false)">
<input type="checkbox" class="invisible" />
None
</label>
</li>
<li role="separator" class="divider"></li>
<li class="checkbox" ng-repeat="(group, enabled) in groups">
<label class="text-capitalize">
<input type="checkbox" ng-model="groups[group]" />
{{group}}
</label>
</li>
</ul>
</div>
<div id="version-filter">
<div class="btn-group" filter-dropdown>
<button type="button" class="btn btn-default dropdown-toggle">
Version
<span id="version-filter-count" class="badge">
{{versionFilterCount(versionFilters)}}
</span>
<span class="caret"></span>
</button>
<ul id="version-filter-selector" class="dropdown-menu">
<li class="checkbox">
<label ng-click="clearVersionFilters()">
<input type="checkbox" class="invisible" />
Clear filters
</label>
</li>
<li role="separator" class="divider"></li>
<li class="checkbox" ng-repeat="(filter, vars) in versionFilters">
<label ng-attr-for="filter-{filter}">{{filter}}</label>
<span>1.</span>
<input type="number"
min="29"
ng-attr-id="filter-{filter}"
class="version-filter-input form-control filter-input"
maxlength="2"
ng-model="versionFilters[filter].minorVersion"
ng-model-options="{debounce: 50}"
ng-change="updateVersionFilters()" />
<span>.0</span>
</li>
</ul>
</div>
</div>
<div class="btn-group" filter-dropdown>
<button type="button" class="btn btn-default dropdown-toggle">
Applicability <span class="badge">{{selectedValuesCount(applicabilities)}}</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="checkbox">
<label ng-click="toggleApplicabilities(true)">
<input type="checkbox" class="invisible" />
All
</label>
</li>
<li class="checkbox">
<label ng-click="toggleApplicabilities(false)">
<input type="checkbox" class="invisible" />
None
</label>
</li>
<li role="separator" class="divider"></li>
<li class="checkbox" ng-repeat="(applicability, enabled) in applicabilities">
<label class="text-capitalize">
<input type="checkbox" ng-model="applicabilities[applicability]" />
{{applicability}}
</label>
</li>
</ul>
</div>
</div>
<div class="col-12 col-md-5 search-control">
<div class="input-group">
<label class="input-group-addon" id="filter-label" for="search-input">Filter:</label>
<input type="text" class="form-control filter-input" placeholder="Keywords or search string (`S` or `/` to focus)" id="search-input"
ng-model="search" ng-blur="updatePath()" ng-keyup="$event.keyCode == 13 && updatePath()"
ng-model-options="{debounce: 50}" />
<span class="input-group-btn">
<button class="filter-clear btn" type="button" ng-click="search = ''; updatePath();">
Clear
</button>
</span>
</div>
</div>
<div class="col-12 col-md-2 btn-group expansion-group">
<button title="Collapse All" class="btn btn-default expansion-control" type="button" ng-click="toggleExpansion(data, false)">
<span class="glyphicon glyphicon-collapse-up"></span>
</button>
<button title="Expand All" class="btn btn-default expansion-control" type="button" ng-click="toggleExpansion(data, true)">
<span class="glyphicon glyphicon-collapse-down"></span>
</button>
</div>
</div>
</div>
<!-- The order of the filters should be from most likely to remove a lint to least likely to improve performance. -->
<article class="panel panel-default" id="{{lint.id}}" ng-repeat="lint in data | filter:bySearch | filter:byGroups | filter:byLevels | filter:byVersion | filter:byApplicabilities">
<header class="panel-heading" ng-click="open[lint.id] = !open[lint.id]">
<h2 class="panel-title">
<div class="panel-title-name">
<span>{{lint.id}}</span>
<a href="#{{lint.id}}" class="anchor label label-default"
ng-click="openLint(lint); $event.preventDefault(); $event.stopPropagation()">&para;</a>
<a href="" id="clipboard-{{lint.id}}" class="anchor label label-default" ng-click="copyToClipboard(lint); $event.stopPropagation()">
&#128203;
</a>
</div>
<div class="panel-title-addons">
<span class="label label-lint-group label-default label-group-{{lint.group}}">{{lint.group}}</span>
<span class="label label-lint-level label-lint-level-{{lint.level}}">{{lint.level}}</span>
<span class="label label-doc-folding" ng-show="open[lint.id]">&minus;</span>
<span class="label label-doc-folding" ng-hide="open[lint.id]">&plus;</span>
</div>
</h2>
</header>
<div class="list-group lint-docs" ng-if="open[lint.id]" ng-class="{collapse: true, in: open[lint.id]}">
<div class="list-group-item lint-doc-md" ng-bind-html="lint.docs | markdown"></div>
<div class="lint-additional-info-container">
<!-- Applicability -->
<div class="lint-additional-info-item">
<span> Applicability: </span>
<span class="label label-default label-applicability">{{lint.applicability}}</span>
<a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.Applicability.html#variants">(?)</a>
</div>
<!-- Clippy version -->
<div class="lint-additional-info-item">
<span>{{lint.group == "deprecated" ? "Deprecated" : "Added"}} in: </span>
<span class="label label-default label-version">{{lint.version}}</span>
</div>
<!-- Open related issues -->
<div class="lint-additional-info-item">
<a href="https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+{{lint.id}}">Related Issues</a>
</div>
<!-- Jump to source -->
<div class="lint-additional-info-item" ng-if="lint.id_location">
<a href="https://github.com/rust-lang/rust-clippy/blob/{{docVersion}}/{{lint.id_location}}">View Source</a>
</div>
</div>
</div>
</article>
</div>
</div>
<a
aria-label="View source on GitHub"
class="github-corner"
href="https://github.com/rust-lang/rust-clippy"
rel="noopener noreferrer"
target="_blank"
>
<svg
width="80"
height="80"
viewBox="0 0 250 250"
style="position: absolute; top: 0; border: 0; right: 0"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" fill="var(--theme-color)"></path>
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px"
class="octo-arm"
></path>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
></path>
</svg>
<style>
.github-corner svg {
fill: var(--fg);
color: var(--bg);
}
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg);
}
}
@media (max-width: 500px) {
.github-corner:hover .octo-arm {
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
</style>
</a>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.3.2/markdown-it.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/languages/rust.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.12/angular.min.js"></script>
<script src="script.js"></script>
</body>
</html>
+232
View File
@@ -0,0 +1,232 @@
<!DOCTYPE html>
<!--
Welcome to a Clippy's lint list, at least the source code of it. If you are
interested in contributing to this website checkout `util/gh-pages/index_template.html`
inside the rust-clippy repository.
Otherwise, have a great day =^.^=
-->
<html lang="en"> {# #}
<head> {# #}
<meta charset="UTF-8"/> {# #}
<meta name="viewport" content="width=device-width, initial-scale=1"/> {# #}
<meta name="description" content="A collection of lints to catch common mistakes and improve your Rust code."> {# #}
<title>Clippy Lints</title> {# #}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css"/> {# #}
<link id="githubLightHighlight" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/github.min.css" disabled="true" /> {# #}
<link id="githubDarkHighlight" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/github-dark.min.css" disabled="true" /> {# #}
<!-- The files are not copied over into the Clippy project since they use the MPL-2.0 License -->
<link rel="stylesheet" href="https://rust-lang.github.io/mdBook/css/variables.css"/> {# #}
<link id="styleHighlight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/highlight.css"> {# #}
<link id="styleNight" rel="stylesheet" href="https://rust-lang.github.io/mdBook/tomorrow-night.css" disabled="true"> {# #}
<link id="styleAyu" rel="stylesheet" href="https://rust-lang.github.io/mdBook/ayu-highlight.css" disabled="true"> {# #}
<link rel="stylesheet" href="style.css"> {# #}
</head> {# #}
<body> {# #}
<script src="theme.js"></script> {# #}
<div id="settings-dropdown"> {# #}
<button class="settings-icon" tabindex="-1"></button> {# #}
<div class="settings-menu" tabindex="-1"> {# #}
<div class="setting-radio-name">Theme</div> {# #}
<select id="theme-choice" onchange="setTheme(this.value, true)"> {# #}
<option value="ayu">Ayu</option> {# #}
<option value="coal">Coal</option> {# #}
<option value="light">Light</option> {# #}
<option value="navy">Navy</option> {# #}
<option value="rust">Rust</option> {# #}
</select> {# #}
<label> {# #}
<input type="checkbox" id="disable-shortcuts" onchange="changeSetting(this)"> {#+ #}
<span>Disable keyboard shortcuts</span> {# #}
</label> {# #}
</div> {# #}
</div> {# #}
<div class="container"> {# #}
<div class="page-header"> {# #}
<h1>Clippy Lints</h1> {# #}
</div> {# #}
<noscript> {# #}
<div class="alert alert-danger" role="alert"> {# #}
Sorry, this site only works with JavaScript! :( {# #}
</div> {# #}
</noscript> {# #}
<div> {# #}
<div class="panel panel-default"> {# #}
<div class="panel-body row"> {# #}
<div id="upper-filters" class="col-12 col-md-5"> {# #}
<div class="btn-group" id="lint-levels" tabindex="-1"> {# #}
<button type="button" class="btn btn-default dropdown-toggle"> {# #}
Lint levels <span class="badge">4</span> <span class="caret"></span> {# #}
</button> {# #}
<ul class="dropdown-menu" id="lint-levels-selector"> {# #}
<li class="checkbox"> {# #}
<button onclick="toggleElements('levels_filter', true)">All</button> {# #}
</li> {# #}
<li class="checkbox"> {# #}
<button onclick="toggleElements('levels_filter', false)">None</button> {# #}
</li> {# #}
<li role="separator" class="divider"></li> {# #}
</ul> {# #}
</div> {# #}
<div class="btn-group" id="lint-groups" tabindex="-1"> {# #}
<button type="button" class="btn btn-default dropdown-toggle"> {# #}
Lint groups <span class="badge">9</span> <span class="caret"></span> {# #}
</button> {# #}
<ul class="dropdown-menu" id="lint-groups-selector"> {# #}
<li class="checkbox"> {# #}
<button onclick="toggleElements('groups_filter', true)">All</button> {# #}
</li> {# #}
<li class="checkbox"> {# #}
<button onclick="resetGroupsToDefault()">Default</button> {# #}
</li> {# #}
<li class="checkbox"> {# #}
<button onclick="toggleElements('groups_filter', false)">None</button> {# #}
</li> {# #}
<li role="separator" class="divider"></li> {# #}
</ul> {# #}
</div> {# #}
<div class="btn-group" id="version-filter" tabindex="-1"> {# #}
<button type="button" class="btn btn-default dropdown-toggle"> {# #}
Version {#+ #}
<span id="version-filter-count" class="badge">0</span> {#+ #}
<span class="caret"></span> {# #}
</button> {# #}
<ul id="version-filter-selector" class="dropdown-menu"> {# #}
<li class="checkbox"> {# #}
<button onclick="clearVersionFilters()">Clear filters</button> {# #}
</li> {# #}
<li role="separator" class="divider"></li> {# #}
</ul> {# #}
</div> {# #}
<div class="btn-group", id="lint-applicabilities" tabindex="-1"> {# #}
<button type="button" class="btn btn-default dropdown-toggle"> {# #}
Applicability {#+ #}
<span class="badge">4</span> {#+ #}
<span class="caret"></span> {# #}
</button> {# #}
<ul class="dropdown-menu" id="lint-applicabilities-selector"> {# #}
<li class="checkbox"> {# #}
<button onclick="toggleElements('applicabilities_filter', true)">All</button> {# #}
</li> {# #}
<li class="checkbox"> {# #}
<button onclick="toggleElements('applicabilities_filter', false)">None</button> {# #}
</li> {# #}
<li role="separator" class="divider"></li> {# #}
</ul> {# #}
</div> {# #}
</div> {# #}
<div class="col-12 col-md-5 search-control"> {# #}
<div class="input-group"> {# #}
<label class="input-group-addon" id="filter-label" for="search-input">Filter:</label> {# #}
<input type="text" class="form-control filter-input" placeholder="Keywords or search string (`S` or `/` to focus)" id="search-input" /> {# #}
<span class="input-group-btn"> {# #}
<button class="filter-clear btn" type="button" onclick="searchState.clearInput(event)"> {# #}
Clear {# #}
</button> {# #}
</span> {# #}
</div> {# #}
</div> {# #}
<div class="col-12 col-md-2 btn-group expansion-group"> {# #}
<button title="Collapse All" class="btn btn-default expansion-control" type="button" onclick="toggleExpansion(false)"> {# #}
<span class="glyphicon glyphicon-collapse-up"></span> {# #}
</button> {# #}
<button title="Expand All" class="btn btn-default expansion-control" type="button" onclick="toggleExpansion(true)"> {# #}
<span class="glyphicon glyphicon-collapse-down"></span> {# #}
</button> {# #}
</div> {# #}
</div> {# #}
</div>
{% for lint in lints %}
<article class="panel panel-default collapsed" id="{{lint.id}}"> {# #}
<header class="panel-heading" onclick="expandLint('{{lint.id}}')"> {# #}
<h2 class="panel-title"> {# #}
<div class="panel-title-name" id="lint-{{lint.id}}"> {# #}
<span>{{lint.id}}</span> {#+ #}
<a href="#{{lint.id}}" class="anchor label label-default" onclick="openLint(event)">&para;</a> {#+ #}
<a href="" class="anchor label label-default" onclick="copyToClipboard(event)"> {# #}
&#128203; {# #}
</a> {# #}
</div> {# #}
<div class="panel-title-addons"> {# #}
<span class="label label-lint-group label-default label-group-{{lint.group}}">{{lint.group}}</span> {#+ #}
<span class="label label-lint-level label-lint-level-{{lint.level}}">{{lint.level}}</span> {#+ #}
<span class="label label-doc-folding">&plus;</span> {# #}
</div> {# #}
</h2> {# #}
</header> {# #}
<div class="list-group lint-docs"> {# #}
<div class="list-group-item lint-doc-md">{{Self::markdown(lint.docs)}}</div> {# #}
<div class="lint-additional-info-container">
{# Applicability #}
<div class="lint-additional-info-item"> {# #}
<span> Applicability: </span> {# #}
<span class="label label-default label-applicability">{{ lint.applicability_str() }}</span> {# #}
<a href="https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.Applicability.html#variants">(?)</a> {# #}
</div>
{# Clippy version #}
<div class="lint-additional-info-item"> {# #}
<span>{% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: </span> {# #}
<span class="label label-default label-version">{{lint.version}}</span> {# #}
</div>
{# Open related issues #}
<div class="lint-additional-info-item"> {# #}
<a href="https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+{{lint.id}}">Related Issues</a> {# #}
</div>
{# Jump to source #}
{% if let Some(id_location) = lint.id_location %}
<div class="lint-additional-info-item"> {# #}
<a href="https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/{{id_location}}">View Source</a> {# #}
</div>
{% endif %}
</div> {# #}
</div> {# #}
</article>
{% endfor %}
</div> {# #}
</div> {# #}
<a {#+ #}
aria-label="View source on GitHub" {#+ #}
class="github-corner" {#+ #}
href="https://github.com/rust-lang/rust-clippy" {#+ #}
rel="noopener noreferrer" {#+ #}
target="_blank" {# #}
> {# #}
<svg {#+ #}
width="80" {#+ #}
height="80" {#+ #}
viewBox="0 0 250 250" {#+ #}
style="position: absolute; top: 0; border: 0; right: 0" {#+ #}
aria-hidden="true" {# #}
> {# #}
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" fill="var(--theme-color)"></path> {# #}
<path {#+ #}
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" {#+ #}
fill="currentColor" {#+ #}
style="transform-origin: 130px 106px" {#+ #}
class="octo-arm" {# #}
></path> {# #}
<path {#+ #}
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" {#+ #}
fill="currentColor" {#+ #}
class="octo-body" {# #}
></path> {# #}
</svg> {# #}
</a> {# #}
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script> {# #}
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/languages/rust.min.js"></script> {# #}
<script src="script.js"></script> {# #}
</body> {# #}
</html> {# #}
+504 -566
View File
@@ -1,556 +1,82 @@
(function () {
const md = window.markdownit({
html: true,
linkify: true,
typographer: true,
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return '<pre class="hljs"><code>' +
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
'</code></pre>';
} catch (__) {}
}
return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
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;
}
function scrollToLint(lintId) {
const target = document.getElementById(lintId);
if (!target) {
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;
}
target.scrollIntoView();
}
searchState.lastSearch = searchStr;
const terms = searchStr.split(" ");
const cleanedSearchStr = searchStr.replaceAll("-", "_");
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) {
if (groups.hasOwnProperty(group)) {
groups[group] = group === selectedGroup;
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 = "";
}
}
}
angular.module("clippy", [])
.filter('markdown', function ($sce) {
return function (text) {
return $sce.trustAsHtml(
md.render(text || '')
// Oh deer, what a hack :O
.replace('<table', '<table class="table"')
);
};
})
.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.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];
};
// 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 = "&#10003;";
if (resetClipboardTimeout !== null) {
clearTimeout(resetClipboardTimeout);
}
resetClipboardTimeout = setTimeout(resetClipboard, 1000);
}
}
// 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();
$http.get('./lints.json')
.success(function (data) {
$scope.data = data;
$scope.loading = false;
const selectedGroup = getQueryVariable("sel");
if (selectedGroup) {
selectGroup($scope, selectedGroup.toLowerCase());
}
scrollToLintByURL($scope, $location);
setTimeout(function () {
const el = document.getElementById('filter-input');
if (el) { el.focus() }
}, 0);
})
.error(function (data) {
$scope.error = data;
$scope.loading = false;
});
});
})();
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]);
if (searchStr.length > 0) {
window.location.hash = `/${searchStr}`;
} else {
window.location.hash = '';
}
},
};
function handleInputChanged(event) {
if (event.target !== document.activeElement) {
return;
}
}
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;
}
searchState.resetInputTimeout();
}
function handleShortcut(ev) {
@@ -576,8 +102,27 @@ function handleShortcut(ev) {
}
}
document.addEventListener("keypress", handleShortcut);
document.addEventListener("keydown", handleShortcut);
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") {
@@ -593,8 +138,52 @@ function onEachLazy(lazyArray, func) {
}
}
function handleBlur(event) {
const parent = document.getElementById("settings-dropdown");
function highlightIfNeeded(elem) {
onEachLazy(elem.querySelectorAll("pre > code.language-rust:not(.highlighted)"), el => {
hljs.highlightElement(el.parentElement)
el.classList.add("highlighted");
});
}
function expandLint(lintId) {
const lintElem = document.getElementById(lintId);
const isCollapsed = lintElem.classList.toggle("collapsed");
lintElem.querySelector(".label-doc-folding").innerText = isCollapsed ? "+" : "";
highlightIfNeeded(lintElem);
}
// Show details for one lint
function openLint(event) {
event.preventDefault();
event.stopPropagation();
expandLint(event.target.getAttribute("href").slice(1));
}
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 = "&#10003;";
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)
) {
@@ -602,28 +191,377 @@ function handleBlur(event) {
}
}
function generateSettings() {
const settings = document.getElementById("settings-dropdown");
const settingsButton = settings.querySelector(".settings-icon")
settingsButton.onclick = () => settings.classList.toggle("open");
settingsButton.onblur = handleBlur;
const settingsMenu = settings.querySelector(".settings-menu");
settingsMenu.onblur = handleBlur;
function toggleExpansion(expand) {
onEachLazy(
settingsMenu.querySelectorAll("input"),
el => el.onblur = handleBlur,
document.querySelectorAll("article"),
expand ? el => {
el.classList.remove("collapsed");
highlightIfNeeded(el);
} : el => el.classList.add("collapsed"),
);
}
generateSettings();
// 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);
// 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_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 },
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_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]
|| !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(2);
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_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]}"]`);
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();
+49 -2
View File
@@ -272,8 +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);
padding-left: 4px;
padding-top: 3px;
position: absolute;
top: 4px;
left: 5px;
}
.settings-menu * {
@@ -329,6 +330,18 @@ L4.75,12h2.5l0.5393066-2.1572876 c0.2276001-0.1062012,0.4459839-0.2269287,0.649
display: flex;
}
ul.dropdown-menu li.checkbox > button {
border: 0;
width: 100%;
background: var(--theme-popup-bg);
color: var(--fg);
}
ul.dropdown-menu li.checkbox > button:hover {
background: var(--theme-hover);
box-shadow: none;
}
#version-filter {
min-width: available;
}
@@ -396,3 +409,37 @@ body {
background: var(--bg);
color: var(--fg);
}
article.collapsed .lint-docs {
display: none;
}
.github-corner svg {
fill: var(--fg);
color: var(--bg);
}
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg);
}
}
@media (max-width: 500px) {
.github-corner:hover .octo-arm {
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
+56
View File
@@ -0,0 +1,56 @@
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.body.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);
}
}
(function() {
// 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);
}
})();