diff --git a/.buildkite/linux/check_contributors b/.buildkite/linux/check_contributors deleted file mode 100755 index e84b4c9fc..000000000 --- a/.buildkite/linux/check_contributors +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -set -eu -o pipefail ${SHELLFLAGS} - -antispam=", at the domain " - -headAuthor=$(git log -1 --pretty=format:'%ae') -authorAt=$(echo "$headAuthor" | sed "s/@/$antispam/") -if [ $headAuthor = "49699333+dependabot[bot]@users.noreply.github.com" ]; then - echo "Dependabot whitelisted." -elif git log --pretty=format:'%ae' CONTRIBUTORS | grep -i "$headAuthor" > /dev/null; then - echo "Author found in CONTRIBUTORS" -else - echo "All contributors:" - git log --pretty=format:' - %ae' CONTRIBUTORS |sort |uniq |sort -f | sed "s/@/$antispam/" - - echo "Author $authorAt NOT found in list" - echo - cat < Result<()> { check_python(build)?; check_proto(build)?; check_sql(build)?; - check_copyright(build)?; + check_minilints(build)?; build.trailing_text = "default pylib/anki qt/aqt\n".into(); diff --git a/build/configure/src/python.rs b/build/configure/src/python.rs index 7f11357fa..9852828fd 100644 --- a/build/configure/src/python.rs +++ b/build/configure/src/python.rs @@ -236,43 +236,3 @@ fn add_pylint(build: &mut Build) -> Result<()> { Ok(()) } - -pub fn check_copyright(build: &mut Build) -> Result<()> { - let script = inputs!["tools/copyright_headers.py"]; - let files = inputs![glob![ - "{build,rslib,pylib,qt,ftl,python,sass,tools,ts}/**/*.{py,rs,ts,svelte,mjs}", - "qt/bundle/PyOxidizer/**" - ]]; - build.add( - "check:copyright", - RunCommand { - command: "$runner", - args: "run --stamp=$out $pyenv_bin $script check", - inputs: hashmap! { - "pyenv_bin" => inputs![":pyenv:bin"], - "script" => script.clone(), - "script" => script.clone(), - "" => files.clone(), - }, - outputs: hashmap! { - "out" => vec!["tests/copyright.check.marker"] - }, - }, - )?; - build.add( - "fix:copyright", - RunCommand { - command: "$runner", - args: "run --stamp=$out $pyenv_bin $script fix", - inputs: hashmap! { - "pyenv_bin" => inputs![":pyenv:bin"], - "script" => script, - "" => files, - }, - outputs: hashmap! { - "out" => vec!["tests/copyright.fix.marker"] - }, - }, - )?; - Ok(()) -} diff --git a/build/configure/src/rust.rs b/build/configure/src/rust.rs index 3ad59564a..4e0ea621a 100644 --- a/build/configure/src/rust.rs +++ b/build/configure/src/rust.rs @@ -1,6 +1,8 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +use ninja_gen::action::BuildAction; +use ninja_gen::build::FilesHandle; use ninja_gen::cargo::CargoBuild; use ninja_gen::cargo::CargoClippy; use ninja_gen::cargo::CargoFormat; @@ -9,6 +11,7 @@ use ninja_gen::cargo::CargoTest; use ninja_gen::cargo::RustOutput; use ninja_gen::git::SyncSubmodule; use ninja_gen::glob; +use ninja_gen::input::BuildInput; use ninja_gen::inputs; use ninja_gen::Build; use ninja_gen::Result; @@ -151,3 +154,57 @@ pub fn check_rust(build: &mut Build) -> Result<()> { Ok(()) } + +pub fn check_minilints(build: &mut Build) -> Result<()> { + struct RunMinilints { + pub deps: BuildInput, + pub fix: bool, + } + + impl BuildAction for RunMinilints { + fn command(&self) -> &str { + "$minilints_bin $fix" + } + + fn files(&mut self, build: &mut impl FilesHandle) { + build.add_inputs("minilints_bin", inputs![":build:minilints"]); + build.add_inputs("", &self.deps); + build.add_variable("fix", if self.fix { "fix" } else { "" }); + build.add_output_stamp(format!("tests/minilints.{}", self.fix)); + } + + fn on_first_instance(&self, build: &mut Build) -> Result<()> { + build.add( + "build:minilints", + CargoBuild { + inputs: inputs![glob!("tools/minilints/**/*")], + outputs: &[RustOutput::Binary("minilints")], + target: None, + extra_args: "-p minilints", + release_override: Some(false), + }, + ) + } + } + + let files = inputs![glob![ + "**/*.{py,rs,ts,svelte,mjs}", + "{node_modules,qt/bundle/PyOxidizer}/**" + ]]; + + build.add( + "check:minilints", + RunMinilints { + deps: files.clone(), + fix: false, + }, + )?; + build.add( + "fix:minilints", + RunMinilints { + deps: files, + fix: true, + }, + )?; + Ok(()) +} diff --git a/rslib/linkchecker/src/lib.rs b/rslib/linkchecker/src/lib.rs index 8b1378917..cf499d047 100644 --- a/rslib/linkchecker/src/lib.rs +++ b/rslib/linkchecker/src/lib.rs @@ -1 +1,2 @@ - +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html diff --git a/tools/copyright_headers.py b/tools/copyright_headers.py deleted file mode 100644 index db448accc..000000000 --- a/tools/copyright_headers.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright: Ankitects Pty Ltd and contributors -# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -import os -import subprocess -import sys -from pathlib import Path - -want_fix = sys.argv[1] == "fix" -checked_for_dirty = False - -nonstandard_header = { - "pylib/anki/_vendor/stringcase.py", - "pylib/anki/importing/pauker.py", - "pylib/anki/importing/supermemo_xml.py", - "pylib/anki/statsbg.py", - "pylib/tools/protoc-gen-mypy.py", - "python/pyqt/install.py", - "python/write_wheel.py", - "qt/aqt/mpv.py", - "qt/aqt/winpaths.py", - "qt/bundle/build.rs", - "qt/bundle/src/main.rs", -} - -ignored_folders = [ - "out", - "node_modules", - "qt/forms", - "tools/workspace-hack", - "qt/bundle/PyOxidizer", -] - - -def fix(path: Path) -> None: - with open(path, "r", encoding="utf8") as f: - existing_text = f.read() - path_str = str(path) - if path_str.endswith(".py"): - header = """\ -# Copyright: Ankitects Pty Ltd and contributors -# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -""" - elif ( - path_str.endswith(".ts") - or path_str.endswith(".rs") - or path_str.endswith(".mjs") - ): - header = """\ -// Copyright: Ankitects Pty Ltd and contributors -// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -""" - elif path_str.endswith(".svelte"): - header = """\ - - -""" - with open(path, "w", encoding="utf8") as f: - f.write(header + existing_text) - - -found = False -if sys.platform == "win32": - ignored_folders = [f.replace("/", "\\") for f in ignored_folders] - nonstandard_header = {f.replace("/", "\\") for f in nonstandard_header} - -for dirpath, dirnames, fnames in os.walk("."): - dir = Path(dirpath) - - # avoid infinite recursion with old symlink - if ".bazel" in dirnames: - dirnames.remove(".bazel") - - ignore = False - for folder in ignored_folders: - if folder in dirpath: - ignore = True - break - if ignore: - continue - - for fname in fnames: - for ext in ".py", ".ts", ".rs", ".svelte", ".mjs": - if fname.endswith(ext): - path = dir / fname - with open(path, encoding="utf8") as f: - top = f.read(256) - if not top.strip(): - continue - if str(path) in nonstandard_header: - continue - if fname.endswith(".d.ts"): - continue - missing = "Ankitects Pty Ltd and contributors" not in top - if missing: - if want_fix: - if not checked_for_dirty: - if subprocess.getoutput("git diff"): - print("stage any changes first") - sys.exit(1) - checked_for_dirty = True - fix(path) - else: - print("missing standard copyright header:", path) - found = True - -if found: - sys.exit(1) diff --git a/tools/minilints/Cargo.toml b/tools/minilints/Cargo.toml new file mode 100644 index 000000000..40e80ac7f --- /dev/null +++ b/tools/minilints/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "minilints" +publish = false + +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true + +[dependencies] +anyhow = "1.0.71" +camino = "1.1.4" +once_cell = "1.17.1" +walkdir = "2.3.3" diff --git a/tools/minilints/src/main.rs b/tools/minilints/src/main.rs new file mode 100644 index 000000000..875d3b6a9 --- /dev/null +++ b/tools/minilints/src/main.rs @@ -0,0 +1,224 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use std::collections::HashSet; +use std::env; +use std::fs; +use std::fs::File; +use std::io::Read; +use std::io::Write; +use std::path::Path; +use std::process::Command; + +use anyhow::Context; +use anyhow::Result; +use camino::Utf8Path; +use once_cell::unsync::Lazy; +use walkdir::WalkDir; + +const NONSTANDARD_HEADER: &[&str] = &[ + "./pylib/anki/_vendor/stringcase.py", + "./pylib/anki/importing/pauker.py", + "./pylib/anki/importing/supermemo_xml.py", + "./pylib/anki/statsbg.py", + "./pylib/tools/protoc-gen-mypy.py", + "./python/pyqt/install.py", + "./python/write_wheel.py", + "./qt/aqt/mpv.py", + "./qt/aqt/winpaths.py", + "./qt/bundle/build.rs", + "./qt/bundle/src/main.rs", +]; + +const IGNORED_FOLDERS: &[&str] = &[ + "./out", + "./node_modules", + "./qt/aqt/forms", + "./tools/workspace-hack", + "./qt/bundle/PyOxidizer", +]; + +fn main() -> Result<()> { + let want_fix = env::args().nth(1) == Some("fix".to_string()); + let mut ctx = LintContext::new(want_fix); + ctx.check_contributors()?; + ctx.walk_folders(Path::new("."))?; + if ctx.found_problems { + std::process::exit(1); + } + Ok(()) +} + +struct LintContext { + want_fix: bool, + unstaged_changes: Lazy<()>, + found_problems: bool, + nonstandard_headers: HashSet<&'static Utf8Path>, +} + +impl LintContext { + pub fn new(want_fix: bool) -> Self { + Self { + want_fix, + unstaged_changes: Lazy::new(check_for_unstaged_changes), + found_problems: false, + nonstandard_headers: NONSTANDARD_HEADER.iter().map(Utf8Path::new).collect(), + } + } + + pub fn walk_folders(&mut self, root: &Path) -> Result<()> { + let ignored_folders: HashSet<_> = IGNORED_FOLDERS.iter().map(Utf8Path::new).collect(); + let walker = WalkDir::new(root).into_iter(); + for entry in walker.filter_entry(|e| { + !ignored_folders.contains(&Utf8Path::from_path(e.path()).expect("utf8")) + }) { + let entry = entry.unwrap(); + let path = Utf8Path::from_path(entry.path()).context("utf8")?; + + let exts: HashSet<_> = ["py", "ts", "rs", "svelte", "mjs"] + .into_iter() + .map(Some) + .collect(); + if exts.contains(&path.extension()) { + self.check_copyright(path)?; + self.check_triple_slash(path)?; + } + } + Ok(()) + } + + fn check_copyright(&mut self, path: &Utf8Path) -> Result<()> { + if path.file_name().unwrap().ends_with(".d.ts") { + return Ok(()); + } + let head = head_of_file(path)?; + if head.is_empty() { + return Ok(()); + } + if self.nonstandard_headers.contains(&path) { + return Ok(()); + } + let missing = !head.contains("Ankitects Pty Ltd and contributors"); + if missing { + if self.want_fix { + Lazy::force(&self.unstaged_changes); + fix_copyright(path)?; + } else { + println!("missing standard copyright header: {:?}", path); + self.found_problems = true; + } + } + Ok(()) + } + + fn check_triple_slash(&mut self, path: &Utf8Path) -> Result<()> { + if !matches!(path.extension(), Some("ts") | Some("svelte")) { + return Ok(()); + } + for line in fs::read_to_string(path)?.lines() { + if line.contains("///") && !line.contains("/// Result<()> { + let antispam = ", at the domain "; + + let last_author = String::from_utf8( + Command::new("git") + .args(["log", "-1", "--pretty=format:%ae"]) + .output()? + .stdout, + )?; + + let all_contributors = String::from_utf8( + Command::new("git") + .args(["log", "--pretty=format:%ae", "CONTRIBUTORS"]) + .output()? + .stdout, + )?; + let all_contributors = all_contributors.lines().collect::>(); + + if last_author == "49699333+dependabot[bot]@users.noreply.github.com" { + println!("Dependabot whitelisted."); + return Ok(()); + } else if all_contributors.contains(last_author.as_str()) { + println!("Author found in CONTRIBUTORS"); + return Ok(()); + } + + println!("All contributors:"); + println!("{}", { + let mut contribs: Vec<_> = all_contributors + .iter() + .map(|s| s.replace('@', antispam)) + .collect(); + contribs.sort(); + contribs.join("\n") + }); + + println!( + "Author {} NOT found in list", + last_author.replace('@', antispam) + ); + + println!( + "\nPlease make sure you modify the CONTRIBUTORS file using the email address you \ + are committing from. If you have GitHub configured to hide your email address, \ + you may need to make a change to the CONTRIBUTORS file using the GitHub UI, \ + then try again." + ); + + std::process::exit(1); + } +} + +fn head_of_file(path: &Utf8Path) -> Result { + let mut file = File::open(path)?; + let mut buffer = vec![0; 256]; + let size = file.read(&mut buffer)?; + buffer.truncate(size); + Ok(String::from_utf8(buffer).unwrap_or_default()) +} + +fn fix_copyright(path: &Utf8Path) -> Result<()> { + let header = match path.extension().unwrap() { + "py" => { + r#"# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +"# + } + "ts" | "rs" | "mjs" => { + r#"// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +"# + } + "svelte" => { + r#" +"# + } + _ => unreachable!(), + }; + + let data = fs::read_to_string(path).with_context(|| format!("reading {path}"))?; + let mut file = fs::OpenOptions::new() + .write(true) + .open(path) + .with_context(|| format!("opening {path}"))?; + write!(file, "{}{}", header, data).with_context(|| format!("writing {path}"))?; + Ok(()) +} + +fn check_for_unstaged_changes() { + let output = Command::new("git").arg("diff").output().unwrap(); + if !output.stdout.is_empty() { + println!("stage any changes first"); + std::process::exit(1); + } +} diff --git a/ts/change-notetype/lib.ts b/ts/change-notetype/lib.ts index ecbf0575f..0a46f2ed5 100644 --- a/ts/change-notetype/lib.ts +++ b/ts/change-notetype/lib.ts @@ -11,12 +11,12 @@ function nullToNegativeOne(list: (number | null)[]): number[] { return list.map((val) => val ?? -1); } -/// Public only for tests. +/** Public only for tests. */ export function negativeOneToNull(list: number[]): (number | null)[] { return list.map((val) => (val === -1 ? null : val)); } -/// Wrapper for the protobuf message to make it more ergonomic. +/** Wrapper for the protobuf message to make it more ergonomic. */ export class ChangeNotetypeInfoWrapper { fields: (number | null)[]; templates?: (number | null)[]; @@ -33,26 +33,26 @@ export class ChangeNotetypeInfoWrapper { this.oldNotetypeName = info.oldNotetypeName; } - /// A list with an entry for each field/template in the new notetype, with - /// the values pointing back to indexes in the original notetype. + /** A list with an entry for each field/template in the new notetype, with + the values pointing back to indexes in the original notetype. */ mapForContext(ctx: MapContext): (number | null)[] { return ctx == MapContext.Template ? this.templates ?? [] : this.fields; } - /// Return index of old fields/templates, with null values mapped to "Nothing" - /// at the end. + /** Return index of old fields/templates, with null values mapped to "Nothing" + at the end.*/ getOldIndex(ctx: MapContext, newIdx: number): number { const map = this.mapForContext(ctx); const val = map[newIdx]; return val ?? this.getOldNamesIncludingNothing(ctx).length - 1; } - /// Return all the old names, with "Nothing" at the end. + /** Return all the old names, with "Nothing" at the end. */ getOldNamesIncludingNothing(ctx: MapContext): string[] { return [...this.getOldNames(ctx), tr.changeNotetypeNothing()]; } - /// Old names without "Nothing" at the end. + /** Old names without "Nothing" at the end. */ getOldNames(ctx: MapContext): string[] { return ctx == MapContext.Template ? this.info.oldTemplateNames @@ -93,7 +93,7 @@ export class ChangeNotetypeInfoWrapper { return this.info.input as Notetypes.ChangeNotetypeRequest; } - /// Pack changes back into input message for saving. + /** Pack changes back into input message for saving. */ intoInput(): Notetypes.ChangeNotetypeRequest { const input = this.info.input as Notetypes.ChangeNotetypeRequest; input.newFields = nullToNegativeOne(this.fields); diff --git a/ts/components/SpinBox.svelte b/ts/components/SpinBox.svelte index e969dc85c..9b951ce1a 100644 --- a/ts/components/SpinBox.svelte +++ b/ts/components/SpinBox.svelte @@ -18,8 +18,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html let input: HTMLInputElement; let focused = false; - /// Set value to a new number, clamping it to a valid range, and - /// leaving it unchanged if `newValue` is NaN. + /** Set value to a new number, clamping it to a valid range, and + leaving it unchanged if `newValue` is NaN. */ function updateValue(newValue: number) { if (Number.isNaN(newValue)) { // avoid updating the value diff --git a/ts/deck-options/SaveButton.svelte b/ts/deck-options/SaveButton.svelte index 6a40b400c..8fc0e1206 100644 --- a/ts/deck-options/SaveButton.svelte +++ b/ts/deck-options/SaveButton.svelte @@ -24,7 +24,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let state: DeckOptionsState; - /// Ensure blur handler has fired so changes get committed. + /** Ensure blur handler has fired so changes get committed. */ async function commitEditing(): Promise { if (document.activeElement instanceof HTMLElement) { document.activeElement.blur(); diff --git a/ts/deck-options/lib.ts b/ts/deck-options/lib.ts index 8c7be2eb9..121a314bc 100644 --- a/ts/deck-options/lib.ts +++ b/ts/deck-options/lib.ts @@ -21,7 +21,7 @@ export interface ParentLimits { reviews: number; } -/// Info for showing the top selector +/** Info for showing the top selector */ export interface ConfigListEntry { idx: number; name: string; @@ -124,17 +124,17 @@ export class DeckOptionsState { this.updateConfigList(); } - /// Adds a new config, making it current. + /** Adds a new config, making it current. */ addConfig(name: string): void { this.addConfigFrom(name, this.defaults); } - /// Clone the current config, making it current. + /** Clone the current config, making it current. */ cloneConfig(name: string): void { this.addConfigFrom(name, this.configs[this.selectedIdx].config.config!); } - /// Clone the current config, making it current. + /** Clone the current config, making it current. */ private addConfigFrom(name: string, source: DeckConfig.DeckConfig.IConfig): void { const uniqueName = this.ensureNewNameUnique(name); const config = DeckConfig.DeckConfig.create({ @@ -158,7 +158,7 @@ export class DeckOptionsState { return this.configs[this.selectedIdx].config.id === 1; } - /// Will throw if the default deck is selected. + /** Will throw if the default deck is selected. */ removeCurrentConfig(): void { const currentId = this.configs[this.selectedIdx].config.id; if (currentId === 1) { @@ -250,12 +250,12 @@ export class DeckOptionsState { this.configListSetter?.(this.getConfigList()); } - /// Returns a copy of the currently selected config. + /** Returns a copy of the currently selected config. */ private getCurrentConfig(): DeckConfig.DeckConfig.Config { return cloneDeep(this.configs[this.selectedIdx].config.config!); } - /// Extra data associated with current config (for add-ons) + /** Extra data associated with current config (for add-ons) */ private getCurrentAuxData(): Record { const conf = this.configs[this.selectedIdx].config.config!; return bytesToObject(conf.other); diff --git a/ts/graphs/buttons.ts b/ts/graphs/buttons.ts index 4e47da3ac..a2d6133fc 100644 --- a/ts/graphs/buttons.ts +++ b/ts/graphs/buttons.ts @@ -25,7 +25,7 @@ import { GraphRange } from "./graph-helpers"; import { setDataAvailable } from "./graph-helpers"; import { hideTooltip, showTooltip } from "./tooltip"; -/// 4 element array +/** 4 element array */ type ButtonCounts = number[]; export interface GraphData { diff --git a/ts/graphs/graph-helpers.ts b/ts/graphs/graph-helpers.ts index a09d392db..018f91583 100644 --- a/ts/graphs/graph-helpers.ts +++ b/ts/graphs/graph-helpers.ts @@ -99,8 +99,8 @@ export type SearchDispatch = void; -/// Convert a protobuf map that protobufjs represents as an object with string -/// keys into a Map with numeric keys. +/** Convert a protobuf map that protobufjs represents as an object with string +keys into a Map with numeric keys. */ export function numericMap(obj: { [k: string]: T }): Map { return new Map(Object.entries(obj).map(([k, v]) => [Number(k), v])); } diff --git a/ts/graphs/reviews.ts b/ts/graphs/reviews.ts index 8f4d2d1c8..eb1359c56 100644 --- a/ts/graphs/reviews.ts +++ b/ts/graphs/reviews.ts @@ -76,7 +76,7 @@ function totalsForBin(bin: BinType): number[] { return total; } -/// eg idx=0 is mature count, idx=1 is mature+young count, etc +/** eg idx=0 is mature count, idx=1 is mature+young count, etc */ function cumulativeBinValue(bin: BinType, idx: number): number { return sum(totalsForBin(bin).slice(0, idx + 1)); } diff --git a/ts/html-filter/styling.ts b/ts/html-filter/styling.ts index db189859e..feb1adec6 100644 --- a/ts/html-filter/styling.ts +++ b/ts/html-filter/styling.ts @@ -1,15 +1,15 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -/// Keep property if true. +/** Keep property if true. */ type StylingPredicate = (property: string, value: string) => boolean; const keep = (_key: string, _value: string) => true; const discard = (_key: string, _value: string) => false; -/// Return a function that filters out certain styles. -/// - If the style is listed in `exceptions`, the provided predicate is used. -/// - If the style is not listed, the default predicate is used instead. +/** Return a function that filters out certain styles. + - If the style is listed in `exceptions`, the provided predicate is used. + - If the style is not listed, the default predicate is used instead. */ function filterStyling( defaultPredicate: StylingPredicate, exceptions: Record, diff --git a/ts/lib/bridgecommand.ts b/ts/lib/bridgecommand.ts index 6b1286770..84bcb4918 100644 --- a/ts/lib/bridgecommand.ts +++ b/ts/lib/bridgecommand.ts @@ -9,7 +9,7 @@ declare global { } } -/// HTML tag pointing to a bridge command. +/** HTML tag pointing to a bridge command. */ export function bridgeLink(command: string, label: string): string { return `${label}`; } diff --git a/ts/lib/cards.ts b/ts/lib/cards.ts index d18d69d56..8490a035f 100644 --- a/ts/lib/cards.ts +++ b/ts/lib/cards.ts @@ -9,17 +9,17 @@ export enum CardType { } export enum CardQueue { - /// due is the order cards are shown in + /** due is the order cards are shown in */ New = 0, - /// due is a unix timestamp + /** due is a unix timestamp */ Learn = 1, - /// due is days since creation date + /** due is days since creation date */ Review = 2, DayLearn = 3, - /// due is a unix timestamp. - /// preview cards only placed here when failed. + /** due is a unix timestamp. */ + /** preview cards only placed here when failed. */ PreviewRepeat = 4, - /// cards are not due in these states + /** cards are not due in these states */ Suspended = -1, SchedBuried = -2, UserBuried = -3, diff --git a/ts/lib/help-page.ts b/ts/lib/help-page.ts index 32c8fdc21..f23f2002c 100644 --- a/ts/lib/help-page.ts +++ b/ts/lib/help-page.ts @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -/// These links are checked in CI to ensure they are valid. +/** These links are checked in CI to ensure they are valid. */ export const HelpPage = { DeckOptions: { maximumInterval: "https://docs.ankiweb.net/deck-options.html#maximum-interval", diff --git a/ts/lib/i18n/utils.ts b/ts/lib/i18n/utils.ts index b6f853183..118cd1230 100644 --- a/ts/lib/i18n/utils.ts +++ b/ts/lib/i18n/utils.ts @@ -64,8 +64,8 @@ export function localeCompare( return first.localeCompare(second, langs, options); } -/// Treat text like HTML, merging multiple spaces and converting -/// newlines to spaces. +/** Treat text like HTML, merging multiple spaces and converting + newlines to spaces. */ export function withCollapsedWhitespace(s: string): string { return s.replace(/\s+/g, " "); } diff --git a/ts/lib/nightmode.ts b/ts/lib/nightmode.ts index b6607e700..c264f4403 100644 --- a/ts/lib/nightmode.ts +++ b/ts/lib/nightmode.ts @@ -1,8 +1,8 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -/// Add night-mode class to documentElement if hash location is #night, and -/// return true if added. +/** Add night-mode class to documentElement if hash location is #night, and + return true if added. */ export function checkNightMode(): boolean { const nightMode = window.location.hash == "#night"; if (nightMode) { diff --git a/ts/lib/runtime-require.ts b/ts/lib/runtime-require.ts index 6a973d436..e0832cf0b 100644 --- a/ts/lib/runtime-require.ts +++ b/ts/lib/runtime-require.ts @@ -31,8 +31,8 @@ type PackageDeprecation> = { [key in keyof T]?: string; }; -/// This can be extended to allow require() calls at runtime, for packages -/// that are not included at bundling time. +/** This can be extended to allow require() calls at runtime, for packages +that are not included at bundling time. */ const runtimePackages: Partial>> = {}; const prohibit = () => false; diff --git a/ts/lib/shortcuts.ts b/ts/lib/shortcuts.ts index 02dbfe9cf..0cb0b2f2d 100644 --- a/ts/lib/shortcuts.ts +++ b/ts/lib/shortcuts.ts @@ -141,8 +141,8 @@ function innerShortcut( export interface RegisterShortcutRestParams { target: EventTarget; - /// There might be no good reason to use `keyup` other - /// than to circumvent Qt bugs + /** There might be no good reason to use `keyup` other + than to circumvent Qt bugs */ event: "keydown" | "keyup"; } diff --git a/ts/lib/time.ts b/ts/lib/time.ts index b074fe4cb..f7cc2c64c 100644 --- a/ts/lib/time.ts +++ b/ts/lib/time.ts @@ -53,7 +53,7 @@ export function naturalUnit(secs: number): TimespanUnit { } } -/// Number of seconds in a given unit. +/** Number of seconds in a given unit. */ export function unitSeconds(unit: TimespanUnit): number { switch (unit) { case TimespanUnit.Seconds: @@ -75,7 +75,7 @@ export function unitAmount(unit: TimespanUnit, secs: number): number { return secs / unitSeconds(unit); } -/// Largest unit provided seconds can be divided by without a remainder. +/** Largest unit provided seconds can be divided by without a remainder. */ export function naturalWholeUnit(secs: number): TimespanUnit { let unit = naturalUnit(secs); while (unit != TimespanUnit.Seconds) { @@ -142,10 +142,10 @@ function i18nFuncForUnit( } } -/// Describe the given seconds using the largest appropriate unit. -/// If precise is true, show to two decimal places, eg -/// eg 70 seconds -> "1.17 minutes" -/// If false, seconds and days are shown without decimals. +/** Describe the given seconds using the largest appropriate unit. +If precise is true, show to two decimal places, eg +eg 70 seconds -> "1.17 minutes" +If false, seconds and days are shown without decimals. */ export function timeSpan(seconds: number, short = false): string { const unit = naturalUnit(seconds); const amount = unitAmount(unit, seconds); diff --git a/ts/sveltelib/theme.ts b/ts/sveltelib/theme.ts index 525d83b05..102c03d90 100644 --- a/ts/sveltelib/theme.ts +++ b/ts/sveltelib/theme.ts @@ -15,9 +15,9 @@ function getThemeFromRoot(): ThemeInfo { } let setPageTheme: ((theme: ThemeInfo) => void) | null = null; -/// The current theme that applies to this document/shadow root. When -/// previewing cards in the card layout screen, this may not match the -/// theme Anki is using in its UI. +/** The current theme that applies to this document/shadow root. When +previewing cards in the card layout screen, this may not match the +theme Anki is using in its UI. */ export const pageTheme = readable(getThemeFromRoot(), (set) => { setPageTheme = set; });