chore: clear mechanical lint warnings (Phase 1) (#12198)

* chore: clear mechanical lint warnings

Drops 44 oxlint warnings (559 → 515) by fixing easy mechanical rules
across the codebase: no-useless-escape, no-duplicate-type-constituents,
no-redundant-type-constituents, no-unused-expressions,
no-meaningless-void-operator, require-array-sort-compare, await-thenable.

* chore: drop callback parameter from useCallback deps

The `open` argument is a parameter of the callback, not a closed-over
variable, so it doesn't belong in the deps array.

* chore: promote cleared lint rules to errors

Promotes the rules cleared in this PR from warn to error so future
violations fail the lint:

- no-unused-expressions
- typescript/await-thenable
- typescript/no-duplicate-type-constituents
- typescript/no-meaningless-void-operator
- typescript/require-array-sort-compare

Removes the override that suppressed no-useless-escape on source
files (the global rule is already error) and fixes the 21 escape
violations that this exposed in regex character classes and template
literals.

* chore: address PR review feedback

- usePinnedDocuments: simplify UrlId to plain string instead of the
  intersection trick.
- PlantUML embed: move - to end of character class so it's a literal
  hyphen rather than a range operator.
- checkboxes: type token params as Token | undefined to match the
  actual call sites that pass tokens[index - 2] etc.
This commit is contained in:
Tom Moor
2026-04-28 20:00:03 -04:00
committed by GitHub
parent cd9e79b1f1
commit adbffc0734
31 changed files with 68 additions and 75 deletions
+5 -1
View File
@@ -73,9 +73,13 @@
"eqeqeq": "error", "eqeqeq": "error",
"curly": "error", "curly": "error",
"no-console": "error", "no-console": "error",
"no-unused-expressions": "error",
"arrow-body-style": ["error", "as-needed"], "arrow-body-style": ["error", "as-needed"],
"no-useless-escape": "off",
"react/react-in-jsx-scope": "off", "react/react-in-jsx-scope": "off",
"typescript/await-thenable": "error",
"typescript/no-duplicate-type-constituents": "error",
"typescript/no-meaningless-void-operator": "error",
"typescript/require-array-sort-compare": "error",
"react/self-closing-comp": [ "react/self-closing-comp": [
"error", "error",
{ {
+1 -1
View File
@@ -47,7 +47,7 @@ export const ContextMenu = observer(
onClose?.(); onClose?.();
} }
}, },
[open, onOpen, onClose] [onOpen, onClose]
); );
const enablePointerEvents = React.useCallback(() => { const enablePointerEvents = React.useCallback(() => {
@@ -143,7 +143,7 @@ export const Suggestions = observer(
); );
React.useEffect(() => { React.useEffect(() => {
void fetchUsersByQuery(query); fetchUsersByQuery(query);
}, [query, fetchUsersByQuery]); }, [query, fetchUsersByQuery]);
function getListItemProps(suggestion: User | Group) { function getListItemProps(suggestion: User | Group) {
+9 -18
View File
@@ -72,8 +72,7 @@ type ContentProps = React.ComponentPropsWithoutRef<
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>; React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>;
const MenuContent = React.forwardRef< const MenuContent = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.Content> React.ElementRef<typeof DropdownMenuPrimitive.Content>,
| React.ElementRef<typeof ContextMenuPrimitive.Content>,
ContentProps ContentProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
@@ -120,8 +119,7 @@ type SubMenuTriggerProps = BaseItemProps &
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger>; React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger>;
const SubMenuTrigger = React.forwardRef< const SubMenuTrigger = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger> React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
| React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
SubMenuTriggerProps SubMenuTriggerProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
@@ -150,8 +148,7 @@ type SubMenuContentProps = React.ComponentPropsWithoutRef<
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>; React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>;
const SubMenuContent = React.forwardRef< const SubMenuContent = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.SubContent> React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
| React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
SubMenuContentProps SubMenuContentProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
@@ -203,8 +200,7 @@ type MenuGroupProps = {
>; >;
const MenuGroup = React.forwardRef< const MenuGroup = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.Group> React.ElementRef<typeof DropdownMenuPrimitive.Group>,
| React.ElementRef<typeof ContextMenuPrimitive.Group>,
MenuGroupProps MenuGroupProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
@@ -275,8 +271,7 @@ type MenuButtonProps = BaseItemProps & {
>; >;
const MenuButton = React.forwardRef< const MenuButton = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.Item> React.ElementRef<typeof DropdownMenuPrimitive.Item>,
| React.ElementRef<typeof ContextMenuPrimitive.Item>,
MenuButtonProps MenuButtonProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
@@ -338,8 +333,7 @@ type MenuInternalLinkProps = BaseItemProps & {
>; >;
const MenuInternalLink = React.forwardRef< const MenuInternalLink = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.Item> React.ElementRef<typeof DropdownMenuPrimitive.Item>,
| React.ElementRef<typeof ContextMenuPrimitive.Item>,
MenuInternalLinkProps MenuInternalLinkProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
@@ -375,8 +369,7 @@ type MenuExternalLinkProps = BaseItemProps & {
>; >;
const MenuExternalLink = React.forwardRef< const MenuExternalLink = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.Item> React.ElementRef<typeof DropdownMenuPrimitive.Item>,
| React.ElementRef<typeof ContextMenuPrimitive.Item>,
MenuExternalLinkProps MenuExternalLinkProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
@@ -409,8 +402,7 @@ type MenuSeparatorProps = React.ComponentPropsWithoutRef<
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>; React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>;
const MenuSeparator = React.forwardRef< const MenuSeparator = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.Separator> React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
| React.ElementRef<typeof ContextMenuPrimitive.Separator>,
MenuSeparatorProps MenuSeparatorProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
@@ -434,8 +426,7 @@ type MenuLabelProps = React.ComponentPropsWithoutRef<
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label>; React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label>;
const MenuLabel = React.forwardRef< const MenuLabel = React.forwardRef<
| React.ElementRef<typeof DropdownMenuPrimitive.Label> React.ElementRef<typeof DropdownMenuPrimitive.Label>,
| React.ElementRef<typeof ContextMenuPrimitive.Label>,
MenuLabelProps MenuLabelProps
>((props, ref) => { >((props, ref) => {
const { variant } = useMenuContext(); const { variant } = useMenuContext();
+10 -2
View File
@@ -144,7 +144,11 @@ const LinkEditor: React.FC<Props> = ({
if (selectedIndex >= 0 && results[selectedIndex]) { if (selectedIndex >= 0 && results[selectedIndex]) {
const selectedDoc = results[selectedIndex]; const selectedDoc = results[selectedIndex];
!mark ? addLink(selectedDoc.url) : updateLink(selectedDoc.url); if (!mark) {
addLink(selectedDoc.url);
} else {
updateLink(selectedDoc.url);
}
} else if (!trimmedQuery) { } else if (!trimmedQuery) {
removeLink(); removeLink();
} else if (!mark) { } else if (!mark) {
@@ -238,7 +242,11 @@ const LinkEditor: React.FC<Props> = ({
{results.map((doc, index) => ( {results.map((doc, index) => (
<SuggestionsMenuItem <SuggestionsMenuItem
onClick={() => { onClick={() => {
!mark ? addLink(doc.path) : updateLink(doc.path); if (!mark) {
addLink(doc.path);
} else {
updateLink(doc.path);
}
}} }}
onPointerMove={() => setSelectedIndex(index)} onPointerMove={() => setSelectedIndex(index)}
selected={index === selectedIndex} selected={index === selectedIndex}
+3 -9
View File
@@ -3,7 +3,7 @@ import { InputRule } from "@shared/editor/lib/InputRule";
const rightArrow = new InputRule(/->$/, "→"); const rightArrow = new InputRule(/->$/, "→");
// Note that the suppression of pipe here prevents conflict with table creation rule. // Note that the suppression of pipe here prevents conflict with table creation rule.
const emdash = new InputRule(/(?:^|[^\|])(--\s)$/, "— "); const emdash = new InputRule(/(?:^|[^|])(--\s)$/, "— ");
const oneHalf = new InputRule(/(?:^|\s)(1\/2)$/, "½"); const oneHalf = new InputRule(/(?:^|\s)(1\/2)$/, "½");
const threeQuarters = new InputRule(/(?:^|\s)(3\/4)$/, "¾"); const threeQuarters = new InputRule(/(?:^|\s)(3\/4)$/, "¾");
const copyright = new InputRule(/\(c\)$/, "©️"); const copyright = new InputRule(/\(c\)$/, "©️");
@@ -12,17 +12,11 @@ const trademarked = new InputRule(/\(tm\)$/, "™️");
const ellipsis = new InputRule(/\.\.\.$/, "…"); const ellipsis = new InputRule(/\.\.\.$/, "…");
// Double quotes // Double quotes
const openDoubleQuote = new InputRule( const openDoubleQuote = new InputRule(/(?:^|[\s{[(<'"\u2018\u201C])(")$/, "“");
/(?:^|[\s\{\[\(\<'"\u2018\u201C])(")$/,
"“"
);
const closeDoubleQuote = new InputRule(/^(?!.*`)[\s\S]*(")$/, "”"); const closeDoubleQuote = new InputRule(/^(?!.*`)[\s\S]*(")$/, "”");
// Single quotes // Single quotes
const openSingleQuote = new InputRule( const openSingleQuote = new InputRule(/(?:^|[\s{[(<'"\u2018\u201C])(')$/, "");
/(?:^|[\s\{\[\(\<'"\u2018\u201C])(')$/,
""
);
const closeSingleQuote = new InputRule(/^(?!.*`)[\s\S]*(')$/, ""); const closeSingleQuote = new InputRule(/^(?!.*`)[\s\S]*(')$/, "");
export default class SmartText extends Extension { export default class SmartText extends Extension {
+1 -1
View File
@@ -27,7 +27,7 @@ export default class Suggestion extends Extension {
: `(?:${triggers.map(escapeRegExp).join("|")})`; : `(?:${triggers.map(escapeRegExp).join("|")})`;
this.openRegex = new RegExp( this.openRegex = new RegExp(
`(?:^|\\s|\\(|[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}])${triggerPattern}(${`[\\p{L}\/\\p{M}\\d${ `(?:^|\\s|\\(|[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}])${triggerPattern}(${`[\\p{L}/\\p{M}\\d${
this.options.allowSpaces ? "\\s{1}" : "" this.options.allowSpaces ? "\\s{1}" : ""
}\\.\\-_]+`})${this.options.requireSearchTerm ? "" : "?"}$`, }\\.\\-_]+`})${this.options.requireSearchTerm ? "" : "?"}$`,
"u" "u"
+1 -1
View File
@@ -31,7 +31,7 @@ const DEFAULT_LIMIT = 10;
* @returns * @returns
*/ */
export default function usePaginatedRequest<T = unknown>( export default function usePaginatedRequest<T = unknown>(
requestFn: (params?: PaginationParams | undefined) => Promise<T[]>, requestFn: (params?: PaginationParams) => Promise<T[]>,
params: PaginationParams = {} params: PaginationParams = {}
): RequestResponse<T> { ): RequestResponse<T> {
const [data, setData] = useState<T[]>(); const [data, setData] = useState<T[]>();
+1 -1
View File
@@ -2,7 +2,7 @@ import { useEffect } from "react";
import usePersistedState from "~/hooks/usePersistedState"; import usePersistedState from "~/hooks/usePersistedState";
import useStores from "./useStores"; import useStores from "./useStores";
type UrlId = "home" | string; type UrlId = string;
export const pinsCacheKey = (urlId: UrlId) => `pins-${urlId}`; export const pinsCacheKey = (urlId: UrlId) => `pins-${urlId}`;
+1 -1
View File
@@ -282,7 +282,7 @@ export function useDocumentSave({
titleRef.current = value; titleRef.current = value;
document.title = value; document.title = value;
updateIsDirtyRef.current(); updateIsDirtyRef.current();
void autosave(); autosave();
}, },
[document, autosave] [document, autosave]
); );
+1 -1
View File
@@ -42,7 +42,7 @@ export class OAuthScopeHelper {
return t("Write all data"); return t("Write all data");
} }
const [namespace, method] = scope.replace("/api/", "").split(/[:\.]/g); const [namespace, method] = scope.replace("/api/", "").split(/[:.]/g);
const readableMethod = const readableMethod =
methodToReadable[method as keyof typeof methodToReadable] ?? method; methodToReadable[method as keyof typeof methodToReadable] ?? method;
if (!readableMethod) { if (!readableMethod) {
+3 -1
View File
@@ -43,7 +43,9 @@ const LoadingState = observer(function LoadingState() {
ui.addActiveModel(template); ui.addActiveModel(template);
} }
return () => { return () => {
template && ui.removeActiveModel(template); if (template) {
ui.removeActiveModel(template);
}
}; };
}, [template, ui]); }, [template, ui]);
+1 -1
View File
@@ -53,7 +53,7 @@ export default class PinsStore extends Store<Pin> {
} }
@action @action
fetchPage = async (params?: FetchParams | undefined): Promise<Pin[]> => { fetchPage = async (params?: FetchParams): Promise<Pin[]> => {
this.isFetching = true; this.isFetching = true;
try { try {
+1 -3
View File
@@ -12,9 +12,7 @@ export default class StarsStore extends Store<Star> {
} }
@action @action
fetchPage = async ( fetchPage = async (params?: PaginationParams): Promise<Star[]> => {
params?: PaginationParams | undefined
): Promise<Star[]> => {
this.isFetching = true; this.isFetching = true;
try { try {
+1 -3
View File
@@ -19,9 +19,7 @@ export default class UserMembershipsStore extends Store<UserMembership> {
} }
@action @action
fetchPage = async ( fetchPage = async (params?: PaginationParams): Promise<UserMembership[]> => {
params?: PaginationParams | undefined
): Promise<UserMembership[]> => {
this.isFetching = true; this.isFetching = true;
try { try {
+1 -1
View File
@@ -401,7 +401,7 @@ export default abstract class Store<T extends Model> {
@action @action
fetchPage = async ( fetchPage = async (
params?: FetchPageParams | undefined params?: FetchPageParams
): Promise<PaginatedResponse<T>> => { ): Promise<PaginatedResponse<T>> => {
if (!this.actions.includes(RPCAction.List)) { if (!this.actions.includes(RPCAction.List)) {
throw new Error(`Cannot list ${this.modelName}`); throw new Error(`Cannot list ${this.modelName}`);
+1 -1
View File
@@ -285,7 +285,7 @@ class ApiClient {
post = <T = any>( post = <T = any>(
path: string, path: string,
data?: JSONObject | FormData | undefined, data?: JSONObject | FormData,
options?: FetchOptions options?: FetchOptions
): Promise<T> => { ): Promise<T> => {
if (data instanceof FormData) { if (data instanceof FormData) {
+1 -1
View File
@@ -54,7 +54,7 @@ class ApiKey extends ParanoidModel<
name: string; name: string;
/** A list of scopes that this API key has access to */ /** A list of scopes that this API key has access to */
@Matches(/[\/\.\w\s]*/, { @Matches(/[/.\w\s]*/, {
each: true, each: true,
}) })
@Column(DataType.ARRAY(DataType.STRING)) @Column(DataType.ARRAY(DataType.STRING))
+1 -1
View File
@@ -83,7 +83,7 @@ class OAuthAuthentication extends ParanoidModel<
grantId: string | null; grantId: string | null;
/** A list of scopes that this authentication has access to */ /** A list of scopes that this authentication has access to */
@Matches(/[\/\.\w\s]*/, { @Matches(/[/.\w\s]*/, {
each: true, each: true,
}) })
@Column(DataType.ARRAY(DataType.STRING)) @Column(DataType.ARRAY(DataType.STRING))
@@ -57,7 +57,7 @@ class OAuthAuthorizationCode extends IdModel<
grantId: string | null; grantId: string | null;
/** A list of scopes that this authorization code has access to */ /** A list of scopes that this authorization code has access to */
@Matches(/[\/\.\w\s]*/, { @Matches(/[/.\w\s]*/, {
each: true, each: true,
}) })
@Column(DataType.ARRAY(DataType.STRING)) @Column(DataType.ARRAY(DataType.STRING))
+1 -1
View File
@@ -155,7 +155,7 @@ export default class ImportJSONTask extends ImportTask {
} }
if (Object.values(item.attachments).length) { if (Object.values(item.attachments).length) {
await mapAttachments(item.attachments); mapAttachments(item.attachments);
} }
} }
+2 -2
View File
@@ -228,11 +228,11 @@ export default class ImportMarkdownZipTask extends ImportTask {
document.text = document.text document.text = document.text
.replace(new RegExp(escapeRegExp(encodedPath), "g"), reference) .replace(new RegExp(escapeRegExp(encodedPath), "g"), reference)
.replace( .replace(
new RegExp(`\\\.?/?${escapeRegExp(normalizedAttachmentPath)}`, "g"), new RegExp(`\\.?/?${escapeRegExp(normalizedAttachmentPath)}`, "g"),
reference reference
) )
.replace( .replace(
new RegExp(`\\\.?/?${escapeRegExp(genericNormalizedPath)}`, "g"), new RegExp(`\\.?/?${escapeRegExp(genericNormalizedPath)}`, "g"),
reference reference
); );
} }
+3 -3
View File
@@ -397,9 +397,9 @@ describe("#comments.list", () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(body.data.length).toEqual(2); expect(body.data.length).toEqual(2);
expect([body.data[0].id, body.data[1].id].sort()).toEqual( expect(
[comment1.id, comment2.id].sort() [body.data[0].id, body.data[1].id].sort((a, b) => a.localeCompare(b))
); ).toEqual([comment1.id, comment2.id].sort((a, b) => a.localeCompare(b)));
expect(body.policies.length).toEqual(2); expect(body.policies.length).toEqual(2);
expect(body.policies[0].abilities.read).toBeTruthy(); expect(body.policies[0].abilities.read).toBeTruthy();
expect(body.policies[1].abilities.read).toBeTruthy(); expect(body.policies[1].abilities.read).toBeTruthy();
+1 -1
View File
@@ -48,7 +48,7 @@ describe("#ValidateKey.sanitize", () => {
const uuid1 = randomUUID(); const uuid1 = randomUUID();
const uuid2 = randomUUID(); const uuid2 = randomUUID();
expect( expect(
ValidateKey.sanitize(`public/${uuid1}/${uuid2}/~\.\u0000\malicious_key`) ValidateKey.sanitize(`public/${uuid1}/${uuid2}/~.\u0000malicious_key`)
).toEqual(`public/${uuid1}/${uuid2}/~.malicious_key`); ).toEqual(`public/${uuid1}/${uuid2}/~.malicious_key`);
}); });
+4 -6
View File
@@ -183,9 +183,7 @@ const embeds: EmbedDescriptor[] = [
id: "canva", id: "canva",
title: "Canva", title: "Canva",
keywords: "design", keywords: "design",
regexMatch: [ regexMatch: [/^https:\/\/(?:www\.)?canva\.com\/design\/([/a-zA-Z0-9_-]*)$/],
/^https:\/\/(?:www\.)?canva\.com\/design\/([\/a-zA-Z0-9_\-]*)$/,
],
transformMatch: (matches: RegExpMatchArray) => { transformMatch: (matches: RegExpMatchArray) => {
const input = matches.input ?? matches[0]; const input = matches.input ?? matches[0];
@@ -634,7 +632,7 @@ const embeds: EmbedDescriptor[] = [
id: "tella", id: "tella",
title: "Tella", title: "Tella",
keywords: "video", keywords: "video",
regexMatch: [/^https?:\/\/(?:www\.)?tella\.tv\/video\/([^\/]+)(?:.*)?$/], regexMatch: [/^https?:\/\/(?:www\.)?tella\.tv\/video\/([^/]+)(?:.*)?$/],
transformMatch: (matches: RegExpMatchArray) => transformMatch: (matches: RegExpMatchArray) =>
`https://www.tella.tv/video/${matches[1]}/embed?b=0&title=1&a=0&loop=0&t=0&muted=0&wt=1`, `https://www.tella.tv/video/${matches[1]}/embed?b=0&title=1&a=0&loop=0&t=0&muted=0&wt=1`,
icon: <Img src="/images/tella.png" alt="Tella" />, icon: <Img src="/images/tella.png" alt="Tella" />,
@@ -719,7 +717,7 @@ const embeds: EmbedDescriptor[] = [
title: "YouTube", title: "YouTube",
keywords: "google video", keywords: "google video",
regexMatch: [ regexMatch: [
/(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([a-zA-Z0-9_-]{11})([\&\?](.*))?$/i, /(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([a-zA-Z0-9_-]{11})([&?](.*))?$/i,
], ],
icon: <Img src="/images/youtube.png" alt="YouTube" />, icon: <Img src="/images/youtube.png" alt="YouTube" />,
component: YouTube, component: YouTube,
@@ -729,7 +727,7 @@ const embeds: EmbedDescriptor[] = [
title: "Plant UML", title: "Plant UML",
keywords: "plant plantuml uml", keywords: "plant plantuml uml",
regexMatch: [ regexMatch: [
/(?:https?:\/\/)?(?:www\.)?editor\.plantuml\.com\/uml\/([a-zA-Z0-9\-_]+)([\&\?].*)?$/i, /(?:https?:\/\/)?(?:www\.)?editor\.plantuml\.com\/uml\/([a-zA-Z0-9_-]+)([&?].*)?$/i,
], ],
icon: <Img src="/images/plantuml.png" alt="PlantUml" />, icon: <Img src="/images/plantuml.png" alt="PlantUml" />,
component: PlantUmlDiagrams, component: PlantUmlDiagrams,
+1 -1
View File
@@ -14,7 +14,7 @@ function safeSlugify(text: string) {
const slug = `h-${escape( const slug = `h-${escape(
slugify(text, { slugify(text, {
remove: /[!"#$%&'\.()*+,\/:;<=>?@\[\]\\^_`{|}~]/g, remove: /[!"#$%&'.()*+,/:;<=>?@[\]\\^_`{|}~]/g,
lower: true, lower: true,
}) })
)}`; )}`;
+3 -3
View File
@@ -25,11 +25,11 @@ this is code
}); });
test("returns true for latex fence", () => { test("returns true for latex fence", () => {
expect(isMarkdown(`\$i\$`)).toBe(true); expect(isMarkdown(`$i$`)).toBe(true);
expect( expect(
isMarkdown(`\$0.00 isMarkdown(`$0.00
random content random content
\$1.00`) $1.00`)
).toBe(false); ).toBe(false);
}); });
+4 -4
View File
@@ -3,19 +3,19 @@ import type MarkdownIt from "markdown-it";
const CHECKBOX_REGEX = /\[(X|\s|_|-)\]\s(.*)?/i; const CHECKBOX_REGEX = /\[(X|\s|_|-)\]\s(.*)?/i;
function matches(token: Token | void) { function matches(token: Token | undefined) {
return token && token.content.match(CHECKBOX_REGEX); return token && token.content.match(CHECKBOX_REGEX);
} }
function isInline(token: Token | void): boolean { function isInline(token: Token | undefined): boolean {
return !!token && token.type === "inline"; return !!token && token.type === "inline";
} }
function isParagraph(token: Token | void): boolean { function isParagraph(token: Token | undefined): boolean {
return !!token && token.type === "paragraph_open"; return !!token && token.type === "paragraph_open";
} }
function isListItem(token: Token | void): boolean { function isListItem(token: Token | undefined): boolean {
// Only match list_item_open, not checkbox_item_open - items that are already // Only match list_item_open, not checkbox_item_open - items that are already
// checkbox_item_open have been processed (e.g., by the tables rule for // checkbox_item_open have been processed (e.g., by the tables rule for
// checkboxes in table cells) and should not be processed again. // checkboxes in table cells) and should not be processed again.
+1 -1
View File
@@ -4,7 +4,7 @@ import { full as emojiPlugin } from "markdown-it-emoji";
import { isUUID } from "validator"; import { isUUID } from "validator";
import { nameToEmoji } from "../lib/emoji"; import { nameToEmoji } from "../lib/emoji";
type Options = MarkdownIt.Options & { type Options = {
emoji: boolean; emoji: boolean;
}; };
+2 -2
View File
@@ -46,8 +46,8 @@ export default class AuthenticationHelper {
const [namespace, method] = resource.split("."); const [namespace, method] = resource.split(".");
return scopes.some((scope) => { return scopes.some((scope) => {
const [scopeNamespace, scopeMethod] = scope.match(/[:\.]/g) const [scopeNamespace, scopeMethod] = scope.match(/[:.]/g)
? scope.replace("/api/", "").split(/[:\.]/g) ? scope.replace("/api/", "").split(/[:.]/g)
: ["*", scope]; : ["*", scope];
const isRouteScope = scope.startsWith("/api/"); const isRouteScope = scope.startsWith("/api/");
+1 -1
View File
@@ -82,7 +82,7 @@ export function isCurrency(value: string): boolean {
} }
// Remove digits, separators, whitespace, and negative indicators // Remove digits, separators, whitespace, and negative indicators
remaining = remaining.replace(/[\d.,\s()\-]/g, ""); remaining = remaining.replace(/[\d.,\s()-]/g, "");
// If anything remains, it's not a valid currency // If anything remains, it's not a valid currency
return remaining.length === 0; return remaining.length === 0;