Skip to content

tidy: move rustdoc js stuff into a tidy extra check #142924

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bootstrap.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,15 @@
# Whether to use the precompiled stage0 libtest with compiletest.
#build.compiletest-use-stage0-libtest = true

# Default value for the `--extra-checks` flag of tidy.
#
# See `./x test tidy --help` for details.
#
# Note that if any value is manually given to bootstrap such as
# `./x test tidy --extra-checks=js`, this value is ignored.
# Use `--extra-checks=''` to temporarily disable all extra checks.
#build.tidy-extra-checks = ""

# Indicates whether ccache is used when building certain artifacts (e.g. LLVM).
# Set to `true` to use the first `ccache` in PATH, or set an absolute path to use
# a specific version.
Expand Down
4 changes: 3 additions & 1 deletion src/bootstrap/src/core/build_steps/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,9 @@ impl Step for Tidy {
if builder.config.cmd.bless() {
cmd.arg("--bless");
}
if let Some(s) = builder.config.cmd.extra_checks() {
if let Some(s) =
builder.config.cmd.extra_checks().or(builder.config.tidy_extra_checks.as_deref())
{
cmd.arg(format!("--extra-checks={s}"));
}
let mut args = std::env::args_os();
Expand Down
5 changes: 4 additions & 1 deletion src/bootstrap/src/core/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ pub struct Config {

/// Whether to use the precompiled stage0 libtest with compiletest.
pub compiletest_use_stage0_libtest: bool,

/// Default value for `--extra-checks`
pub tidy_extra_checks: Option<String>,
pub is_running_on_ci: bool,

/// Cache for determining path modifications
Expand Down Expand Up @@ -745,6 +746,7 @@ impl Config {
jobs,
compiletest_diff_tool,
compiletest_use_stage0_libtest,
tidy_extra_checks,
mut ccache,
exclude,
} = toml.build.unwrap_or_default();
Expand Down Expand Up @@ -1013,6 +1015,7 @@ impl Config {
optimized_compiler_builtins.unwrap_or(config.channel != "dev");
config.compiletest_diff_tool = compiletest_diff_tool;
config.compiletest_use_stage0_libtest = compiletest_use_stage0_libtest.unwrap_or(true);
config.tidy_extra_checks = tidy_extra_checks;

let download_rustc = config.download_rustc_commit.is_some();
config.explicit_stage_from_cli = flags_stage.is_some();
Expand Down
2 changes: 1 addition & 1 deletion src/bootstrap/src/core/config/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ pub enum Subcommand {
bless: bool,
#[arg(long)]
/// comma-separated list of other files types to check (accepts py, py:lint,
/// py:fmt, shell)
/// py:fmt, shell, cpp, cpp:fmt, js, js:lint, js:typecheck, js:es-check)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to separate type checking and eslint? It's essentially the same category of "checking code" to me, at least in the world of JavaScript. Unless it runs for too long, I'd just collapse both into "lint" or "check". When you develop on JS, you'd set your bootstrap.toml file to contain both anyway, I imagine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tsc generally checks correctness, while eslint mostly checks style... thought i suppose if you want fast typechecking, it would be best to run tsc directly...

also calling an extra check "check" feels unhelpful.

extra_checks: Option<String>,
#[arg(long)]
/// rerun tests even if the inputs are unchanged
Expand Down
1 change: 1 addition & 0 deletions src/bootstrap/src/core/config/toml/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ define_config! {
jobs: Option<u32> = "jobs",
compiletest_diff_tool: Option<String> = "compiletest-diff-tool",
compiletest_use_stage0_libtest: Option<bool> = "compiletest-use-stage0-libtest",
tidy_extra_checks: Option<String> = "tidy-extra-checks",
ccache: Option<StringOrBool> = "ccache",
exclude: Option<Vec<PathBuf>> = "exclude",
}
Expand Down
5 changes: 5 additions & 0 deletions src/bootstrap/src/utils/change_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
severity: ChangeSeverity::Info,
summary: "Added new option `tool.TOOL_NAME.features` to specify the features to compile a tool with",
},
ChangeInfo {
change_id: 142924,
severity: ChangeSeverity::Info,
summary: "Added new option `build.tidy-extra-checks` to specify a default value for the --extra-checks cli flag.",
},
];
6 changes: 2 additions & 4 deletions src/ci/docker/host-x86_64/mingw-check-1/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ ENV SCRIPT \
python3 ../x.py check compiletest --set build.compiletest-use-stage0-libtest=true && \
python3 ../x.py check --stage 1 --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu && \
python3 ../x.py check --stage 1 --set build.optimized-compiler-builtins=false core alloc std --target=aarch64-unknown-linux-gnu,i686-pc-windows-msvc,i686-unknown-linux-gnu,x86_64-apple-darwin,x86_64-pc-windows-gnu,x86_64-pc-windows-msvc && \
python3 ../x.py test --stage 0 tidy --extra-checks=js && \
/scripts/validate-toolstate.sh && \
reuse --include-submodules lint && \
python3 ../x.py test collect-license-metadata && \
# Runs checks to ensure that there are no issues in our JS code.
es-check es2019 ../src/librustdoc/html/static/js/*.js && \
tsc --project ../src/librustdoc/html/static/js/tsconfig.json
python3 ../x.py test collect-license-metadata
2 changes: 1 addition & 1 deletion src/etc/completions/x.fish
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ complete -c x -n "__fish_x_using_subcommand doc" -l skip-std-check-if-no-downloa
complete -c x -n "__fish_x_using_subcommand doc" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c x -n "__fish_x_using_subcommand test" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r
complete -c x -n "__fish_x_using_subcommand test" -l compiletest-rustc-args -d 'extra options to pass the compiler when running compiletest tests' -r
complete -c x -n "__fish_x_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell)' -r
complete -c x -n "__fish_x_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, cpp, cpp:fmt, js, js:lint, js:typecheck, js:es-check)' -r
complete -c x -n "__fish_x_using_subcommand test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r
complete -c x -n "__fish_x_using_subcommand test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r
complete -c x -n "__fish_x_using_subcommand test" -l run -d 'whether to execute run-* tests' -r
Expand Down
2 changes: 1 addition & 1 deletion src/etc/completions/x.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ Register-ArgumentCompleter -Native -CommandName 'x' -ScriptBlock {
'x;test' {
[CompletionResult]::new('--test-args', '--test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)')
[CompletionResult]::new('--compiletest-rustc-args', '--compiletest-rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running compiletest tests')
[CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell)')
[CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, cpp, cpp:fmt, js, js:lint, js:typecheck, js:es-check)')
[CompletionResult]::new('--compare-mode', '--compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to')
[CompletionResult]::new('--pass', '--pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode')
[CompletionResult]::new('--run', '--run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests')
Expand Down
2 changes: 1 addition & 1 deletion src/etc/completions/x.py.fish
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ complete -c x.py -n "__fish_x.py_using_subcommand doc" -l skip-std-check-if-no-d
complete -c x.py -n "__fish_x.py_using_subcommand doc" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c x.py -n "__fish_x.py_using_subcommand test" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r
complete -c x.py -n "__fish_x.py_using_subcommand test" -l compiletest-rustc-args -d 'extra options to pass the compiler when running compiletest tests' -r
complete -c x.py -n "__fish_x.py_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell)' -r
complete -c x.py -n "__fish_x.py_using_subcommand test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, cpp, cpp:fmt, js, js:lint, js:typecheck, js:es-check)' -r
complete -c x.py -n "__fish_x.py_using_subcommand test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r
complete -c x.py -n "__fish_x.py_using_subcommand test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r
complete -c x.py -n "__fish_x.py_using_subcommand test" -l run -d 'whether to execute run-* tests' -r
Expand Down
2 changes: 1 addition & 1 deletion src/etc/completions/x.py.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock {
'x.py;test' {
[CompletionResult]::new('--test-args', '--test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)')
[CompletionResult]::new('--compiletest-rustc-args', '--compiletest-rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running compiletest tests')
[CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell)')
[CompletionResult]::new('--extra-checks', '--extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell, cpp, cpp:fmt, js, js:lint, js:typecheck, js:es-check)')
[CompletionResult]::new('--compare-mode', '--compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to')
[CompletionResult]::new('--pass', '--pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode')
[CompletionResult]::new('--run', '--run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests')
Expand Down
2 changes: 1 addition & 1 deletion src/etc/completions/x.py.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ _arguments "${_arguments_options[@]}" : \
_arguments "${_arguments_options[@]}" : \
'*--test-args=[extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)]:ARGS:_default' \
'*--compiletest-rustc-args=[extra options to pass the compiler when running compiletest tests]:ARGS:_default' \
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell)]:EXTRA_CHECKS:_default' \
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, cpp, cpp\:fmt, js, js\:lint, js\:typecheck, js\:es-check)]:EXTRA_CHECKS:_default' \
'--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE:_default' \
'--pass=[force {check,build,run}-pass tests to this mode]:check | build | run:_default' \
'--run=[whether to execute run-* tests]:auto | always | never:_default' \
Expand Down
2 changes: 1 addition & 1 deletion src/etc/completions/x.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ _arguments "${_arguments_options[@]}" : \
_arguments "${_arguments_options[@]}" : \
'*--test-args=[extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)]:ARGS:_default' \
'*--compiletest-rustc-args=[extra options to pass the compiler when running compiletest tests]:ARGS:_default' \
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell)]:EXTRA_CHECKS:_default' \
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell, cpp, cpp\:fmt, js, js\:lint, js\:typecheck, js\:es-check)]:EXTRA_CHECKS:_default' \
'--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE:_default' \
'--pass=[force {check,build,run}-pass tests to this mode]:check | build | run:_default' \
'--run=[whether to execute run-* tests]:auto | always | never:_default' \
Expand Down
38 changes: 37 additions & 1 deletion src/tools/tidy/src/ext_tool_checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::{fmt, fs, io};

mod rustdoc_js;

const MIN_PY_REV: (u32, u32) = (3, 9);
const MIN_PY_REV_STR: &str = "≥3.9";

Expand All @@ -39,19 +41,34 @@ const PIP_REQ_PATH: &[&str] = &["src", "tools", "tidy", "config", "requirements.
pub fn check(
root_path: &Path,
outdir: &Path,
librustdoc_path: &Path,
tools_path: &Path,
src_path: &Path,
bless: bool,
extra_checks: Option<&str>,
pos_args: &[String],
bad: &mut bool,
) {
if let Err(e) = check_impl(root_path, outdir, bless, extra_checks, pos_args) {
if let Err(e) = check_impl(
root_path,
outdir,
librustdoc_path,
tools_path,
src_path,
bless,
extra_checks,
pos_args,
) {
tidy_error!(bad, "{e}");
}
}

fn check_impl(
root_path: &Path,
outdir: &Path,
librustdoc_path: &Path,
tools_path: &Path,
src_path: &Path,
bless: bool,
extra_checks: Option<&str>,
pos_args: &[String],
Expand All @@ -65,13 +82,20 @@ fn check_impl(
None => vec![],
};

// FIXME(lolbinarycat): this is getting complex, we should probably
// have more proper handling, including a warning/error
// for unknown extra check names.
let python_all = lint_args.contains(&"py");
let python_lint = lint_args.contains(&"py:lint") || python_all;
let python_fmt = lint_args.contains(&"py:fmt") || python_all;
let shell_all = lint_args.contains(&"shell");
let shell_lint = lint_args.contains(&"shell:lint") || shell_all;
let cpp_all = lint_args.contains(&"cpp");
let cpp_fmt = lint_args.contains(&"cpp:fmt") || cpp_all;
let js_all = lint_args.contains(&"js");
let js_lint = js_all || lint_args.contains(&"js:lint");
let js_typecheck = js_all || lint_args.contains(&"js:typecheck");
let js_es_check = js_all || lint_args.contains(&"js:es-check");

let mut py_path = None;

Expand Down Expand Up @@ -224,6 +248,18 @@ fn check_impl(
shellcheck_runner(&merge_args(&cfg_args, &file_args_shc))?;
}

if js_lint {
rustdoc_js::lint(librustdoc_path, tools_path, src_path)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we say that the checks would be run if the JS files were modified too? Having them never run unless an extra arguments is passed is very inconvenient.

Copy link
Contributor Author

@lolbinarycat lolbinarycat Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it wasn't too long ago that they were never run locally.

i was gonna have a warning for modifying files and not enabling their extra checks....

i suppose i could add a new "auto" value to --extra-checks, and make that the default.

i would prefer a design which still lets people manually disable these checks if they want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if you modify JS files, seems like you do want these checks by default, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you don't have the tools installed, and have limited or no network connectivity, but still want to run tidy?

I agree that running the tools based on modification is a good default, but there's very little cost to letting people disable it.

I think I would want to add 3 new special values, actually:

  1. all, run everything, possibly used in CI (currently shellcheck isn't run in CI, so we would need to do that first).
  2. auto, run checks for modified files
  3. none, run no extra checks, same as the empty string

This would give a robust system that would be easy to add any future checks into, and would also help improve the existing checks, instead of just improving js checks.

}

if js_typecheck {
rustdoc_js::typecheck(librustdoc_path)?;
}

if js_es_check {
rustdoc_js::es_check(librustdoc_path)?;
}

Ok(())
}

Expand Down
142 changes: 142 additions & 0 deletions src/tools/tidy/src/ext_tool_checks/rustdoc_js.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//! Tidy check to ensure that rustdoc templates didn't forget a `{# #}` to strip extra whitespace
//! characters.

use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Command;

use ignore::DirEntry;

use crate::walk::walk_no_read;

fn rustdoc_js_files(librustdoc_path: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();
walk_no_read(
&[&librustdoc_path.join("html/static/js")],
|path, is_dir| is_dir || !path.extension().is_some_and(|ext| ext == OsStr::new("js")),
&mut |path: &DirEntry| {
files.push(path.path().into());
},
);
return files;
}

fn run_eslint(args: &[PathBuf], config_folder: PathBuf) -> Result<(), super::Error> {
let mut child = Command::new("npx")
.arg("eslint")
.arg("-c")
.arg(config_folder.join(".eslintrc.js"))
.args(args)
.spawn()?;
match child.wait() {
Ok(exit_status) => {
if exit_status.success() {
return Ok(());
}
Err(super::Error::FailedCheck("eslint command failed"))
}
Err(error) => Err(super::Error::Generic(format!("eslint command failed: {error:?}"))),
}
}

fn get_eslint_version_inner(global: bool) -> Option<String> {
let mut command = Command::new("npm");
command.arg("list").arg("--parseable").arg("--long").arg("--depth=0");
if global {
command.arg("--global");
}
let output = command.output().ok()?;
let lines = String::from_utf8_lossy(&output.stdout);
lines.lines().find_map(|l| l.split(':').nth(1)?.strip_prefix("eslint@")).map(|v| v.to_owned())
}

fn get_eslint_version() -> Option<String> {
get_eslint_version_inner(false).or_else(|| get_eslint_version_inner(true))
}

pub(super) fn lint(
librustdoc_path: &Path,
tools_path: &Path,
src_path: &Path,
) -> Result<(), super::Error> {
let eslint_version_path =
src_path.join("ci/docker/host-x86_64/mingw-check-tidy/eslint.version");
let eslint_version = match std::fs::read_to_string(&eslint_version_path) {
Ok(version) => version.trim().to_string(),
Err(error) => {
eprintln!("failed to read `{}`: {error:?}", eslint_version_path.display());
return Err(error.into());
}
};
// Having the correct `eslint` version installed via `npm` isn't strictly necessary, since we're invoking it via `npx`,
// but this check allows the vast majority that is not working on the rustdoc frontend to avoid the penalty of running
// `eslint` in tidy. See also: https://github.com/rust-lang/rust/pull/142851
match get_eslint_version() {
Some(version) => {
if version != eslint_version {
// unfortunatly we can't use `Error::Version` here becuse `str::trim` isn't const and
// Version::required must be a static str
return Err(super::Error::Generic(format!(
"⚠️ Installed version of eslint (`{version}`) is different than the \
one used in the CI (`{eslint_version}`)\n\
You can install this version using `npm update eslint` or by using \
`npm install eslint@{eslint_version}`\n
"
)));
}
}
None => {
//eprintln!("`eslint` doesn't seem to be installed. Skipping tidy check for JS files.");
//eprintln!("You can install it using `npm install eslint@{eslint_version}`");
return Err(super::Error::MissingReq(
"eslint",
"js lint checks",
Some(format!("You can install it using `npm install eslint@{eslint_version}`")),
));
}
}
let files_to_check = rustdoc_js_files(librustdoc_path);
println!("Running eslint on rustdoc JS files");
run_eslint(&files_to_check, librustdoc_path.join("html/static"))?;

run_eslint(&[tools_path.join("rustdoc-js/tester.js")], tools_path.join("rustdoc-js"))?;
run_eslint(&[tools_path.join("rustdoc-gui/tester.js")], tools_path.join("rustdoc-gui"))?;
Ok(())
}

pub(super) fn typecheck(librustdoc_path: &Path) -> Result<(), super::Error> {
// use npx to ensure correct version
let mut child = Command::new("npx")
.arg("tsc")
.arg("-p")
.arg(librustdoc_path.join("html/static/js/tsconfig.json"))
.spawn()?;
match child.wait() {
Ok(exit_status) => {
if exit_status.success() {
return Ok(());
}
Err(super::Error::FailedCheck("tsc command failed"))
}
Err(error) => Err(super::Error::Generic(format!("tsc command failed: {error:?}"))),
}
}

pub(super) fn es_check(librustdoc_path: &Path) -> Result<(), super::Error> {
let files_to_check = rustdoc_js_files(librustdoc_path);
// use npx to ensure correct version
let mut cmd = Command::new("npx");
cmd.arg("es-check").arg("es2019");
for f in files_to_check {
cmd.arg(f);
}
match cmd.spawn()?.wait() {
Ok(exit_status) => {
if exit_status.success() {
return Ok(());
}
Err(super::Error::FailedCheck("es-check command failed"))
}
Err(error) => Err(super::Error::Generic(format!("es-check command failed: {error:?}"))),
}
}
1 change: 0 additions & 1 deletion src/tools/tidy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ pub mod mir_opt_tests;
pub mod pal;
pub mod rustdoc_css_themes;
pub mod rustdoc_gui_tests;
pub mod rustdoc_js;
pub mod rustdoc_json;
pub mod rustdoc_templates;
pub mod style;
Expand Down
Loading
Loading