diff --git a/.eslintrc.js b/.eslintrc.js index edadc674..df746746 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,10 @@ module.exports = { "browser": true, "es6": true }, - "extends": "eslint:recommended", + "extends": ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + "parser": '@typescript-eslint/parser', + "plugins": ['@typescript-eslint'], + "root": true, "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" @@ -13,5 +16,13 @@ module.exports = { "sourceType": "module" }, "rules": { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/ban-types":"off", + "no-case-declarations": "off", + "prefer-const": "off", + "no-empty": "off", + "curly": "error" } }; \ No newline at end of file diff --git a/README.md b/README.md index 2603a814..642ef23c 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ directly to the correct folder during development. ### Testing -Tests in a specific workspace can be run via `npx jest` in that workspace. These for the whole project can be run via `npm run tests` in the +Tests in a specific workspace can be run via `npx jest` in that workspace. These for the whole project can be run via `npm run test` in the root directory. ### Readme Generation and Consistency diff --git a/package-lock.json b/package-lock.json old mode 100644 new mode 100755 diff --git a/package.json b/package.json index 195d3bb5..31404ee5 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,13 @@ "package": "npm run package -ws", "publish": "npm run publish -ws", "test": "vitest ./packages", - "test:ci": "vitest ./packages --maxWorkers=1" + "test:ci": "vitest ./packages --maxWorkers=1", + "prettier": "prettier '**/*.ts' --write", + "eslint": "eslint '**/*.ts' --ignore-pattern dist" + }, + "prettier": { + "tabWidth": 4, + "trailingComma": "es5" }, "devDependencies": { "@types/node": "^20.5.9", diff --git a/packages/structured-clone/index.ts b/packages/structured-clone/index.ts index 15b6b3a0..e8adc53f 100644 --- a/packages/structured-clone/index.ts +++ b/packages/structured-clone/index.ts @@ -1,8 +1,8 @@ -// @ts-nocheck - // globalThis polyfill from https://mathiasbynens.be/notes/globalthis (function () { - if (typeof globalThis === "object") return; + if (typeof globalThis === "object") { + return; + } Object.defineProperty(Object.prototype, "__magic__", { get: function () { return this; @@ -25,3 +25,10 @@ const clone = export function structuredClone(obj: T): T { return clone(obj); } + +declare global { + const __magic__: any; + interface Object { + __magic__?: any; + } +} diff --git a/packages/unified-latex-builder/index.ts b/packages/unified-latex-builder/index.ts index dfd8b1cd..9314d5d0 100644 --- a/packages/unified-latex-builder/index.ts +++ b/packages/unified-latex-builder/index.ts @@ -10,5 +10,5 @@ export * from "./libs/builders"; * ## When should I use this? * * If you want to programmatically create `Ast.Node` nodes. - * + * */ diff --git a/packages/unified-latex-cli/libs/unified-args/schema.ts b/packages/unified-latex-cli/libs/unified-args/schema.ts index 7617552a..e209256b 100644 --- a/packages/unified-latex-cli/libs/unified-args/schema.ts +++ b/packages/unified-latex-cli/libs/unified-args/schema.ts @@ -104,7 +104,7 @@ export const schema: Option[] = [ "Expand the specified macro which is defined in the document. You can use --stats to list all macros defined in the document.", short: "d", type: "string", - value: "" + value: "", }, { long: "frail", diff --git a/packages/unified-latex-ctan/package/cleveref/provides.ts b/packages/unified-latex-ctan/package/cleveref/provides.ts index 4e15e41b..51b67612 100644 --- a/packages/unified-latex-ctan/package/cleveref/provides.ts +++ b/packages/unified-latex-ctan/package/cleveref/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { cref: { signature: "s m" }, diff --git a/packages/unified-latex-ctan/package/exam/provides.ts b/packages/unified-latex-ctan/package/exam/provides.ts index bcb9a921..921326c7 100644 --- a/packages/unified-latex-ctan/package/exam/provides.ts +++ b/packages/unified-latex-ctan/package/exam/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; import { cleanEnumerateBody } from "../../utils/enumerate"; export const macros: MacroInfoRecord = { diff --git a/packages/unified-latex-ctan/package/geometry/provides.ts b/packages/unified-latex-ctan/package/geometry/provides.ts index 3aa0e019..affe1e25 100644 --- a/packages/unified-latex-ctan/package/geometry/provides.ts +++ b/packages/unified-latex-ctan/package/geometry/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { geometry: { diff --git a/packages/unified-latex-ctan/package/hyperref/provides.ts b/packages/unified-latex-ctan/package/hyperref/provides.ts index 073407a7..349d6dc9 100644 --- a/packages/unified-latex-ctan/package/hyperref/provides.ts +++ b/packages/unified-latex-ctan/package/hyperref/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { hypersetup: { diff --git a/packages/unified-latex-ctan/package/listings/libs/argument-parser.ts b/packages/unified-latex-ctan/package/listings/libs/argument-parser.ts index a358e55a..6ba45bed 100644 --- a/packages/unified-latex-ctan/package/listings/libs/argument-parser.ts +++ b/packages/unified-latex-ctan/package/listings/libs/argument-parser.ts @@ -18,7 +18,7 @@ export const argumentParser: ArgumentParser = (nodes, startPos) => { const { argument: optionalArg, nodesRemoved: optionalArgNodesRemoved } = gobbleSingleArgument(nodes, argSpecO, startPos); - let codeArg: Argument | null = null; + let codeArg: Argument | Argument[] | null = null; let codeArgNodesRemoved: number = 0; const nextNode = nodes[startPos]; if (match.group(nextNode)) { diff --git a/packages/unified-latex-ctan/package/makeidx/provides.ts b/packages/unified-latex-ctan/package/makeidx/provides.ts index bc8634fa..2c769e72 100644 --- a/packages/unified-latex-ctan/package/makeidx/provides.ts +++ b/packages/unified-latex-ctan/package/makeidx/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { see: { signature: "m m" }, diff --git a/packages/unified-latex-ctan/package/mathtools/provides.ts b/packages/unified-latex-ctan/package/mathtools/provides.ts index 86762ecf..860afcdf 100644 --- a/packages/unified-latex-ctan/package/mathtools/provides.ts +++ b/packages/unified-latex-ctan/package/mathtools/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { mathtoolsset: { diff --git a/packages/unified-latex-ctan/package/minted/libs/argument-parser.ts b/packages/unified-latex-ctan/package/minted/libs/argument-parser.ts index efd0f527..4bb2afba 100644 --- a/packages/unified-latex-ctan/package/minted/libs/argument-parser.ts +++ b/packages/unified-latex-ctan/package/minted/libs/argument-parser.ts @@ -21,7 +21,7 @@ export const argumentParser: ArgumentParser = (nodes, startPos) => { const { argument: languageArg, nodesRemoved: languageArgNodesRemoved } = gobbleSingleArgument(nodes, argSpecM, startPos); - let codeArg: Argument | null = null; + let codeArg: Argument | Argument[] | null = null; let codeArgNodesRemoved: number = 0; const nextNode = nodes[startPos]; if (match.group(nextNode)) { diff --git a/packages/unified-latex-ctan/package/nicematrix/provides.ts b/packages/unified-latex-ctan/package/nicematrix/provides.ts index 23933392..b1d2b179 100644 --- a/packages/unified-latex-ctan/package/nicematrix/provides.ts +++ b/packages/unified-latex-ctan/package/nicematrix/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { NiceMatrixOptions: { diff --git a/packages/unified-latex-ctan/package/systeme/provides.ts b/packages/unified-latex-ctan/package/systeme/provides.ts index ac71ba96..6d01b09a 100644 --- a/packages/unified-latex-ctan/package/systeme/provides.ts +++ b/packages/unified-latex-ctan/package/systeme/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { systeme: { diff --git a/packages/unified-latex-ctan/package/tabularx/provides.ts b/packages/unified-latex-ctan/package/tabularx/provides.ts index 52d47cc7..edf72842 100644 --- a/packages/unified-latex-ctan/package/tabularx/provides.ts +++ b/packages/unified-latex-ctan/package/tabularx/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = {}; diff --git a/packages/unified-latex-ctan/package/tikz/libs/tikz-command-argument-parser.ts b/packages/unified-latex-ctan/package/tikz/libs/tikz-command-argument-parser.ts index 672201dd..ef2429db 100644 --- a/packages/unified-latex-ctan/package/tikz/libs/tikz-command-argument-parser.ts +++ b/packages/unified-latex-ctan/package/tikz/libs/tikz-command-argument-parser.ts @@ -4,7 +4,7 @@ import { ArgSpecAst as ArgSpec, parse as parseArgspec, } from "@unified-latex/unified-latex-util-argspec"; -import { ArgumentParser } from "@unified-latex/unified-latex-types"; +import { Argument, ArgumentParser } from "@unified-latex/unified-latex-types"; import { gobbleSingleArgument } from "@unified-latex/unified-latex-util-arguments"; import { match } from "@unified-latex/unified-latex-util-match"; import { scan } from "@unified-latex/unified-latex-util-scan"; @@ -44,7 +44,10 @@ export const tikzCommandArgumentParser: ArgumentParser = (nodes, startPos) => { const { argument: _optionalArgument, nodesRemoved: optionalArgumentNodesRemoved, - } = gobbleSingleArgument(nodes, OPTIONAL_ARGUMENT_ARG_SPEC, pos); + } = gobbleSingleArgument(nodes, OPTIONAL_ARGUMENT_ARG_SPEC, pos) as { + argument: Argument; + nodesRemoved: number; + }; nodesRemoved += optionalArgumentNodesRemoved; const optionalArg = _optionalArgument || blankArg(); diff --git a/packages/unified-latex-ctan/package/xcolor/libs/color-to-textcolor-macro.ts b/packages/unified-latex-ctan/package/xcolor/libs/color-to-textcolor-macro.ts index 12aa85ac..85357247 100644 --- a/packages/unified-latex-ctan/package/xcolor/libs/color-to-textcolor-macro.ts +++ b/packages/unified-latex-ctan/package/xcolor/libs/color-to-textcolor-macro.ts @@ -25,4 +25,4 @@ export function colorToTextcolorMacro( args, _renderInfo: { inParMode: true }, }; -} \ No newline at end of file +} diff --git a/packages/unified-latex-ctan/package/xcolor/provides.ts b/packages/unified-latex-ctan/package/xcolor/provides.ts index a709a130..ed603277 100644 --- a/packages/unified-latex-ctan/package/xcolor/provides.ts +++ b/packages/unified-latex-ctan/package/xcolor/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { substitutecolormodel: { diff --git a/packages/unified-latex-ctan/package/xparse/provides.ts b/packages/unified-latex-ctan/package/xparse/provides.ts index 91631bff..33d03f9d 100644 --- a/packages/unified-latex-ctan/package/xparse/provides.ts +++ b/packages/unified-latex-ctan/package/xparse/provides.ts @@ -1,4 +1,7 @@ -import { MacroInfoRecord, EnvInfoRecord } from "@unified-latex/unified-latex-types"; +import { + MacroInfoRecord, + EnvInfoRecord, +} from "@unified-latex/unified-latex-types"; export const macros: MacroInfoRecord = { NewDocumentCommand: { diff --git a/packages/unified-latex-ctan/tests/beamer.test.ts b/packages/unified-latex-ctan/tests/beamer.test.ts index d0c4642c..edf8414a 100644 --- a/packages/unified-latex-ctan/tests/beamer.test.ts +++ b/packages/unified-latex-ctan/tests/beamer.test.ts @@ -32,15 +32,46 @@ describe("unified-latex-ctan:beamer", () => { const EXAMPLES: [string, Ast.Node[]][] = [ [ "\\onslide+", - [m("onslide", args([arg("+", {openMark:"", closeMark:""}), null, "foo", null], { braces: "[][]<>{}" }))], + [ + m( + "onslide", + args( + [ + arg("+", { openMark: "", closeMark: "" }), + null, + "foo", + null, + ], + { braces: "[][]<>{}" } + ) + ), + ], ], [ "\\onslide+{bar}", - [m("onslide", args([arg("+", {openMark:"", closeMark:""}), null, "foo", "bar"], { braces: "[][]<>{}" }))], + [ + m( + "onslide", + args( + [ + arg("+", { openMark: "", closeMark: "" }), + null, + "foo", + "bar", + ], + { braces: "[][]<>{}" } + ) + ), + ], ], [ "\\onslide{bar}", - [m("onslide", args([null, null, null, "bar"], { braces: "[][]<>{}" }))], + [ + m( + "onslide", + args([null, null, null, "bar"], { braces: "[][]<>{}" }) + ), + ], ], ]; diff --git a/packages/unified-latex-ctan/tests/tabular.test.ts b/packages/unified-latex-ctan/tests/tabular.test.ts index fc4606d5..05b83cad 100644 --- a/packages/unified-latex-ctan/tests/tabular.test.ts +++ b/packages/unified-latex-ctan/tests/tabular.test.ts @@ -1,6 +1,9 @@ import util from "util"; import { parse } from "../../unified-latex-util-parse"; -import { parseTabularSpec, printRaw as tabularPrintRaw } from "../package/tabularx"; +import { + parseTabularSpec, + printRaw as tabularPrintRaw, +} from "../package/tabularx"; /* eslint-env jest */ diff --git a/packages/unified-latex-ctan/tests/tikz.test.ts b/packages/unified-latex-ctan/tests/tikz.test.ts index d3561bbb..008266d5 100644 --- a/packages/unified-latex-ctan/tests/tikz.test.ts +++ b/packages/unified-latex-ctan/tests/tikz.test.ts @@ -97,7 +97,10 @@ describe("unified-latex-ctan:tikz", () => { ["\\tikz foo baz; bar", "\\tikz{foo baz;} bar"], ["\\tikz [xxx] {foo} bar", "\\tikz[xxx]{foo} bar"], ["\\tikz [xxx] foo baz; bar", "\\tikz[xxx]{foo baz;} bar"], - ["\\tikz :fill = {aaa} :rotate = {bbb} [xxx] foo baz; bar", "\\tikz :fill = {aaa} :rotate = {bbb} [xxx]{foo baz;} bar"], + [ + "\\tikz :fill = {aaa} :rotate = {bbb} [xxx] foo baz; bar", + "\\tikz :fill = {aaa} :rotate = {bbb} [xxx]{foo baz;} bar", + ], ]; for (const [inStr, outStr] of EXAMPLES) { diff --git a/packages/unified-latex-lint/rules/unified-latex-lint-no-plaintext-operators/index.ts b/packages/unified-latex-lint/rules/unified-latex-lint-no-plaintext-operators/index.ts index acab30bd..525d6708 100644 --- a/packages/unified-latex-lint/rules/unified-latex-lint-no-plaintext-operators/index.ts +++ b/packages/unified-latex-lint/rules/unified-latex-lint-no-plaintext-operators/index.ts @@ -1,7 +1,7 @@ import { pointStart, pointEnd } from "unist-util-position"; import { lintRule } from "unified-lint-rule"; import * as Ast from "@unified-latex/unified-latex-types"; -import { match} from "@unified-latex/unified-latex-util-match"; +import { match } from "@unified-latex/unified-latex-util-match"; import { prefixMatch, Trie } from "@unified-latex/unified-latex-util-scan"; import { visit } from "@unified-latex/unified-latex-util-visit"; diff --git a/packages/unified-latex-lint/rules/unified-latex-lint-no-tex-font-shaping-commands/index.ts b/packages/unified-latex-lint/rules/unified-latex-lint-no-tex-font-shaping-commands/index.ts index f566eed8..12420f56 100644 --- a/packages/unified-latex-lint/rules/unified-latex-lint-no-tex-font-shaping-commands/index.ts +++ b/packages/unified-latex-lint/rules/unified-latex-lint-no-tex-font-shaping-commands/index.ts @@ -44,22 +44,25 @@ CTAN l2tabuen Section 2.`; export const unifiedLatexLintNoTexFontShapingCommands = lintRule< Ast.Root, PluginOptions ->({ origin: "unified-latex-lint:no-tex-font-shaping-commands" }, (tree, file, options) => { - visit( - tree, - (node, info) => { - const macroName = node.content; - file.message( - `Replace "${printRaw(node)}" with "${printRaw( - m(REPLACEMENTS[macroName]) - )}"`, - node - ); +>( + { origin: "unified-latex-lint:no-tex-font-shaping-commands" }, + (tree, file, options) => { + visit( + tree, + (node, info) => { + const macroName = node.content; + file.message( + `Replace "${printRaw(node)}" with "${printRaw( + m(REPLACEMENTS[macroName]) + )}"`, + node + ); - if (options?.fix) { - replaceNodeDuringVisit(m(REPLACEMENTS[macroName]), info); - } - }, - { test: isReplaceable } - ); -}); + if (options?.fix) { + replaceNodeDuringVisit(m(REPLACEMENTS[macroName]), info); + } + }, + { test: isReplaceable } + ); + } +); diff --git a/packages/unified-latex-prettier/index.ts b/packages/unified-latex-prettier/index.ts index 68de57da..0ed5810b 100644 --- a/packages/unified-latex-prettier/index.ts +++ b/packages/unified-latex-prettier/index.ts @@ -10,6 +10,6 @@ export * from "./libs/prettier-plugin-latex"; * ## When should I use this? * * If you want to construct a `Prettier` instance that has LaTeX parsing abilities. - * + * * You should probably use the `prettier-plugin-latex` package instead of directly accessing this package. */ diff --git a/packages/unified-latex-prettier/libs/printer/common.ts b/packages/unified-latex-prettier/libs/printer/common.ts index f1f90124..4e4d89f6 100644 --- a/packages/unified-latex-prettier/libs/printer/common.ts +++ b/packages/unified-latex-prettier/libs/printer/common.ts @@ -42,10 +42,8 @@ function isLineType(elm: Doc): boolean { return isLineType(elm[0]); } // Perhaps we can sneak by with Prettier v2 compatibility? - // @ts-ignore - if (elm.type === "concat") { - // @ts-ignore - return isLineType(elm.parts); + if ((elm.type as any) === "concat") { + return isLineType((elm as any).parts); } return elm.type === "line"; } diff --git a/packages/unified-latex-prettier/libs/printer/tikz.ts b/packages/unified-latex-prettier/libs/printer/tikz.ts index 6015ad44..31d1d7b6 100644 --- a/packages/unified-latex-prettier/libs/printer/tikz.ts +++ b/packages/unified-latex-prettier/libs/printer/tikz.ts @@ -14,9 +14,7 @@ import { softline, fill, } from "./common"; -import { - printRaw, -} from "@unified-latex/unified-latex-util-print-raw"; +import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; import { match } from "@unified-latex/unified-latex-util-match"; import { trim } from "@unified-latex/unified-latex-util-trim"; import { diff --git a/packages/unified-latex-prettier/tests/formatting.test.ts b/packages/unified-latex-prettier/tests/formatting.test.ts index e9a321f7..829be7de 100644 --- a/packages/unified-latex-prettier/tests/formatting.test.ts +++ b/packages/unified-latex-prettier/tests/formatting.test.ts @@ -719,5 +719,4 @@ c await expect(inStr).toFormatAs(outStr, formatter); } }); - }); diff --git a/packages/unified-latex-to-hast/libs/pre-html-subs/environment-subs.ts b/packages/unified-latex-to-hast/libs/pre-html-subs/environment-subs.ts index 52d6da09..daea542c 100644 --- a/packages/unified-latex-to-hast/libs/pre-html-subs/environment-subs.ts +++ b/packages/unified-latex-to-hast/libs/pre-html-subs/environment-subs.ts @@ -16,7 +16,10 @@ import { wrapPars } from "../wrap-pars"; const ITEM_ARG_NAMES_REG = ["label"] as const; const ITEM_ARG_NAMES_BEAMER = [null, "label", null] as const; -type ItemArgs = Record & { +type ItemArgs = Record< + (typeof ITEM_ARG_NAMES_REG)[number], + Ast.Node[] | null +> & { body: Ast.Node[]; }; diff --git a/packages/unified-latex-to-hast/libs/pre-html-subs/katex-subs.ts b/packages/unified-latex-to-hast/libs/pre-html-subs/katex-subs.ts index 78b75c0e..2c673e35 100644 --- a/packages/unified-latex-to-hast/libs/pre-html-subs/katex-subs.ts +++ b/packages/unified-latex-to-hast/libs/pre-html-subs/katex-subs.ts @@ -29,8 +29,8 @@ export const katexSpecificMacroReplacements: Record< // If we have information about the sysdelims, then apply them if (node._renderInfo?.sysdelims) { - const [frontDelim, backDelim]: [Ast.Node, Ast.Node] = - node._renderInfo?.sysdelims as any; + const [frontDelim, backDelim]: [Ast.Node, Ast.Node] = node + ._renderInfo?.sysdelims as any; return [LEFT, frontDelim, ret, RIGHT, backDelim]; } diff --git a/packages/unified-latex-to-hast/tests/unified-latex-to-hast.test.ts b/packages/unified-latex-to-hast/tests/unified-latex-to-hast.test.ts index 9131f0f0..ffd3459d 100644 --- a/packages/unified-latex-to-hast/tests/unified-latex-to-hast.test.ts +++ b/packages/unified-latex-to-hast/tests/unified-latex-to-hast.test.ts @@ -271,9 +271,9 @@ describe("unified-latex-to-hast:unified-latex-to-hast", () => { ast = process(`\\begin{enumerate}\\item\\bfseries b\\end{enumerate}`); expect(normalizeHtml(ast)).toEqual( - normalizeHtml(`
    + normalizeHtml(`
    1. -

      b

      +

      b

    `) ); diff --git a/packages/unified-latex-util-argspec/libs/argspec-parser.ts b/packages/unified-latex-util-argspec/libs/argspec-parser.ts index a3e32599..d32b3d52 100644 --- a/packages/unified-latex-util-argspec/libs/argspec-parser.ts +++ b/packages/unified-latex-util-argspec/libs/argspec-parser.ts @@ -29,7 +29,7 @@ export function printRaw(node: ArgSpec.Ast, root = false): string { const decorators = getDecorators(node); const defaultArg = (node as ArgSpec.DefaultArgument).defaultArg - ? printRaw((node as ArgSpec.DefaultArgument).defaultArg) + ? printRaw((node as ArgSpec.DefaultArgument).defaultArg!) : ""; let spec = decorators; diff --git a/packages/unified-latex-util-argspec/libs/argspec-types.ts b/packages/unified-latex-util-argspec/libs/argspec-types.ts index ddff8ba3..998aa470 100644 --- a/packages/unified-latex-util-argspec/libs/argspec-types.ts +++ b/packages/unified-latex-util-argspec/libs/argspec-types.ts @@ -19,7 +19,7 @@ export interface LeadingWhitespace { noLeadingWhitespace: boolean | undefined; } export interface DefaultArgument { - defaultArg: Group; + defaultArg?: Group; } interface Verbatim extends Arg { type: "verbatim"; @@ -34,14 +34,14 @@ interface OptionalToken extends LeadingWhitespace, AstNode { type: "optionalToken"; token: string; } -interface Embellishment extends DefaultArgument, AstNode { +export interface Embellishment extends DefaultArgument, AstNode { type: "embellishment"; - embellishmentTokens: string[]; + embellishmentTokens: (Group | string)[]; } interface Mandatory extends LeadingWhitespace, DefaultArgument, Arg { type: "mandatory"; } -interface Group extends AstNode { +export interface Group extends AstNode { type: "group"; content: (Group | string)[]; } diff --git a/packages/unified-latex-util-arguments/libs/attach-arguments.ts b/packages/unified-latex-util-arguments/libs/attach-arguments.ts index a97e1a79..3b8e9d99 100644 --- a/packages/unified-latex-util-arguments/libs/attach-arguments.ts +++ b/packages/unified-latex-util-arguments/libs/attach-arguments.ts @@ -55,7 +55,8 @@ export function attachMacroArgsInArray( // Add `._renderInfo` if we have any updateRenderInfo(macro, macroInfo.renderInfo); - const signatureOrParser = macroInfo.argumentParser || macroInfo.signature + const signatureOrParser = + macroInfo.argumentParser || macroInfo.signature; // If the macro has no signature, it shouldn't consume any arguments. Just move along. // Note: It is important that this happens *after* `updateRenderInfo` is called, since diff --git a/packages/unified-latex-util-arguments/libs/gobble-arguments.ts b/packages/unified-latex-util-arguments/libs/gobble-arguments.ts index e3cb90f3..d4cd86c1 100644 --- a/packages/unified-latex-util-arguments/libs/gobble-arguments.ts +++ b/packages/unified-latex-util-arguments/libs/gobble-arguments.ts @@ -1,11 +1,12 @@ +import { structuredClone } from "@unified-latex/structured-clone"; +import { arg } from "@unified-latex/unified-latex-builder"; +import * as Ast from "@unified-latex/unified-latex-types"; +import { ArgumentParser } from "@unified-latex/unified-latex-types"; import { ArgSpecAst as ArgSpec, parse as parseArgspec, } from "@unified-latex/unified-latex-util-argspec"; -import * as Ast from "@unified-latex/unified-latex-types"; -import { arg } from "@unified-latex/unified-latex-builder"; import { gobbleSingleArgument } from "./gobble-single-argument"; -import { ArgumentParser } from "@unified-latex/unified-latex-types"; /** * Gobbles an argument of whose type is specified @@ -28,21 +29,63 @@ export function gobbleArguments( argSpec = parseArgspec(argSpec); } + // argSpec may be mutated below. + argSpec = structuredClone(argSpec); + const args: Ast.Argument[] = []; - let nodesRemoved = 0; + let totalNodesRemoved = 0; + for (const spec of argSpec) { - const { argument, nodesRemoved: removed } = gobbleSingleArgument( - nodes, - spec, - startPos - ); - if (argument) { - args.push(argument); - nodesRemoved += removed; - } else { - args.push(arg([], { openMark: "", closeMark: "" })); + const innerArgs: Ast.Argument[] = []; + let argument: Ast.Argument | null; + let nodesRemoved: number, matchNum: number | undefined; + do { + ({ argument, nodesRemoved, matchNum } = gobbleSingleArgument( + nodes, + spec, + startPos + )); + if (argument) { + innerArgs[nthHoleIndex(innerArgs, matchNum || 1)] = argument; + totalNodesRemoved += nodesRemoved; + } + // Usual ArgSpec ends this loop by returning `matchNum === undefined`. + // Embellishment argspecs always return matchNum. They end this loop + // by returning falsy `argument` value. + } while (argument && matchNum !== undefined); + + // Fill out missing arguments. + if (matchNum === undefined) { + matchNum = argument ? 0 : 1; + } + let i = -1; + while (matchNum--) { + i = nextHoleIndex(innerArgs, i); + innerArgs[i] = arg([], { openMark: "", closeMark: "" }); } + args.push(...innerArgs); } - return { args, nodesRemoved }; + return { args, nodesRemoved: totalNodesRemoved }; +} + +function nextHoleIndex( + arr: (NonNullable | undefined)[], + startPos: number +) { + do { + startPos++; + } while (typeof arr[startPos] !== "undefined"); + return startPos; +} +/** + * Get n-th left-most hole in `arr`. `n` is a 1-based integer, + * so putting ([], 1) would return 0. + */ +function nthHoleIndex(arr: (NonNullable | undefined)[], n: number) { + let i = -1; + while (n--) { + i = nextHoleIndex(arr, i); + } + return i; } diff --git a/packages/unified-latex-util-arguments/libs/gobble-single-argument.ts b/packages/unified-latex-util-arguments/libs/gobble-single-argument.ts index b90c34d2..2167e709 100644 --- a/packages/unified-latex-util-arguments/libs/gobble-single-argument.ts +++ b/packages/unified-latex-util-arguments/libs/gobble-single-argument.ts @@ -1,13 +1,22 @@ -import { ArgSpecAst as ArgSpec } from "@unified-latex/unified-latex-util-argspec"; +/* eslint-disable no-fallthrough */ +import { arg } from "@unified-latex/unified-latex-builder"; import * as Ast from "@unified-latex/unified-latex-types"; +import { + ArgSpecAst as ArgSpec, + printRaw, +} from "@unified-latex/unified-latex-util-argspec"; import { match } from "@unified-latex/unified-latex-util-match"; import { scan } from "@unified-latex/unified-latex-util-scan"; -import { arg } from "@unified-latex/unified-latex-builder"; /** * Gobbles an argument of whose type is specified - * by `argSpec` starting at the position `startPos`. If an argument couldn't be found, - * `argument` will be `null`. + * by `argSpec` starting at the position `startPos`. + * If an argument couldn't be found, `argument` will be `null`. + * `matchNum` is undefined in most cases. It is optionally provided + * if the provided `argSpec` may match multiple arguments. In such cases, + * if there is a matched `argument`, `matchNum` represents a 1-based index of + * that argument, and if there's no `argument`, it represents the count of + * missing arguments. */ export function gobbleSingleArgument( nodes: Ast.Node[], @@ -16,6 +25,7 @@ export function gobbleSingleArgument( ): { argument: Ast.Argument | null; nodesRemoved: number; + matchNum?: number; } { if (typeof argSpec === "string" || !argSpec.type) { throw new Error( @@ -29,6 +39,8 @@ export function gobbleSingleArgument( let currPos = startPos; + let matchNum: number | undefined = undefined; + // Gobble whitespace from `currPos` onward, updating `currPos`. // If `argSpec` specifies leading whitespace is not allowed, // this function does nothing. @@ -56,32 +68,6 @@ export function gobbleSingleArgument( openMark === "{" && closeMark === "}"; - // Find the position of the open brace and the closing brace. - // The position(s) are null if the brace isn't found. - function findBracePositions(): [number | null, number | null] { - let openMarkPos: number | null = null; - if (openMark) { - openMarkPos = nodes.findIndex( - (node, i) => i >= currPos && match.string(node, openMark) - ); - if (openMarkPos < currPos) { - openMarkPos = null; - } - } - let closeMarkPos: number | null = null; - if (openMarkPos != null) { - closeMarkPos = nodes.findIndex( - (node, i) => - i >= (openMarkPos as number) + 1 && - match.string(node, closeMark) - ); - if (closeMarkPos < openMarkPos + 1) { - closeMarkPos = null; - } - } - return [openMarkPos, closeMarkPos]; - } - // Do the actual matching gobbleWhitespace(); const currNode = nodes[currPos]; @@ -90,7 +76,14 @@ export function gobbleSingleArgument( match.comment(currNode) || match.parbreak(currNode) ) { - return { argument, nodesRemoved: 0 }; + const ret: { argument: null; nodesRemoved: number; matchNum?: number } = + { argument, nodesRemoved: 0 }; + if (argSpec.type === "embellishment") { + ret.matchNum = normalizeEmbellishmentTokens( + argSpec.embellishmentTokens + ).length; + } + return ret; } switch (argSpec.type) { @@ -109,6 +102,21 @@ export function gobbleSingleArgument( }); currPos++; break; + } else { + const bracePos = findBracePositions( + nodes, + currPos, + openMark, + closeMark + ); + if (bracePos) { + argument = arg(nodes.slice(bracePos[0] + 1, bracePos[1]), { + openMark, + closeMark, + }); + currPos = bracePos[1] + 1; + break; + } } // NOTE: Fallthrough is on purpose. // Matching a mandatory argument and an optional argument is the same for our purposes @@ -123,32 +131,37 @@ export function gobbleSingleArgument( currPos++; break; } - if (match.string(currNode, openMark)) { - // If we're here, we have custom braces to match - const [openMarkPos, closeMarkPos] = findBracePositions(); - if (openMarkPos != null && closeMarkPos != null) { - argument = arg(nodes.slice(openMarkPos + 1, closeMarkPos), { - openMark, - closeMark, - }); - currPos = closeMarkPos + 1; - break; - } + // If we're here, we have custom braces to match + const bracePos = findBracePositions( + nodes, + currPos, + openMark, + closeMark + ); + if (bracePos) { + argument = arg(nodes.slice(bracePos[0] + 1, bracePos[1]), { + openMark, + closeMark, + }); + currPos = bracePos[1] + 1; + break; } break; case "optionalStar": - case "optionalToken": - if ( - match.string( - currNode, - argSpec.type === "optionalStar" ? "*" : argSpec.token - ) - ) { - argument = arg([currNode], { openMark: "", closeMark: "" }); - currPos++; - break; + case "optionalToken": { + const bracePos = findBracePositions( + nodes, + currPos, + argSpec.type === "optionalStar" ? "*" : argSpec.token + ); + if (bracePos) { + argument = arg(currNode, { openMark: "", closeMark: "" }); + // Instead of `closeMarkPos` returned from findBracePositions, + // one should use `openMarkPos + ` because there's no argument + currPos = bracePos[0] + 1; } break; + } case "until": { if (argSpec.stopTokens.length > 1) { console.warn( @@ -157,37 +170,60 @@ export function gobbleSingleArgument( break; } const rawToken = argSpec.stopTokens[0]; - const stopToken: Ast.String | Ast.Whitespace = - rawToken === " " - ? { type: "whitespace" } - : { type: "string", content: argSpec.stopTokens[0] }; - let matchPos = scan(nodes, stopToken, { - startIndex: startPos, - allowSubstringMatches: true, - }); - if ( - matchPos != null && - partialStringMatch(nodes[matchPos], stopToken) - ) { - console.warn( - `"until" arguments that stop at non-punctuation symbols is not yet implemented` - ); - break; - } + const stopToken: string | Ast.Whitespace = + rawToken === " " ? { type: "whitespace" } : rawToken; + + let bracePos = findBracePositions( + nodes, + startPos, + undefined, + stopToken + ); // If the corresponding token is not found, eat nothing; - if (matchPos == null) { + if (!bracePos) { break; } - argument = arg(nodes.slice(startPos, matchPos), { + + argument = arg(nodes.slice(startPos, bracePos[1]), { openMark: "", closeMark: rawToken, }); - currPos = matchPos; + currPos = bracePos[1]; if (currPos < nodes.length) { currPos++; } break; } + case "embellishment": { + // Split tokens into single characters + const tokens = normalizeEmbellishmentTokens( + argSpec.embellishmentTokens + ); + argSpec.embellishmentTokens = tokens; // ArgSpec is mutated here + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + const bracePos = findBracePositions(nodes, currPos, token); + if (!bracePos) { + continue; + } + let argNode = nodes[bracePos[0] + 1]; + argument = arg( + match.group(argNode) ? argNode.content : argNode, + { + openMark: token, + closeMark: "", + } + ); + currPos = bracePos[1] + 1; + matchNum = i + 1; // 1-based indices + tokens.splice(i, 1); + break; + } + if (!argument) { + matchNum = tokens.length; + } + break; + } default: console.warn( `Don't know how to find an argument of argspec type "${argSpec.type}"` @@ -198,17 +234,126 @@ export function gobbleSingleArgument( // if we did not consume an argument, we don't want to consume the whitespace. const nodesRemoved = argument ? currPos - startPos : 0; nodes.splice(startPos, nodesRemoved); - return { argument, nodesRemoved }; + return { argument, nodesRemoved, matchNum }; +} + +function cloneStringNode(node: Ast.String, content: string): Ast.String { + return Object.assign({}, node, { content }); } /** - * Returns whether the presumed match "node" contains "token" as a strict - * substring. + * Find the position of the open brace and the closing brace. + * Returns undefined if the brace isn't found. + * This may mutate `nodes`, if braces are not a kind of characters that are + * always parsed as a separate token */ -function partialStringMatch(node: Ast.Node, token: Ast.Node): boolean { - return ( - match.anyString(node) && - match.anyString(token) && - node.content.length > token.content.length - ); +function findBracePositions( + nodes: Ast.Node[], + startPos: number, + openMark?: string, + closeMark?: string | Ast.Node +): [number, number] | undefined { + const currNode = nodes[startPos]; + let openMarkPos = startPos; + let closeMarkPos: number | null = startPos; + if (openMark) { + if (!match.anyString(currNode)) { + return; + } + const nodeContent = currNode.content; + // The first node we encounter must contain the opening brace. + if (!nodeContent.startsWith(openMark)) { + return; + } + openMarkPos = startPos; + if (currNode.content.length > openMark.length) { + const nodeContent = currNode.content; + currNode.content = openMark; + nodes.splice( + openMarkPos + 1, + 0, + cloneStringNode(currNode, nodeContent.slice(openMark.length)) + ); + } + closeMarkPos = openMarkPos + 1; + } + if (!closeMark) { + // In such a case, the token immediately preceding the opening brace + // will be treated as an argument. If the next token is a string node, + // only its first character is picked up. + const argNode = nodes[closeMarkPos]; + if (!argNode) { + return; + } + if (match.anyString(argNode) && argNode.content.length > 1) { + const argContent = argNode.content; + argNode.content = argContent[0]; + nodes.splice( + closeMarkPos + 1, + 0, + cloneStringNode(argNode, argContent.slice(1)) + ); + } + return [openMarkPos, closeMarkPos]; + } + // scan for closing marks + closeMarkPos = scan(nodes, closeMark, { + startIndex: closeMarkPos, + allowSubstringMatches: true, + }); + if (closeMarkPos === null) { + return; + } + const closingNode = nodes[closeMarkPos]; + if (match.anyString(closingNode) && typeof closeMark === "string") { + const closingNodeContent = closingNode.content; + let closeMarkIndex = closingNodeContent.indexOf(closeMark); + if (closingNodeContent.length > closeMark.length) { + closingNode.content = closeMark; + const prev = closingNodeContent.slice(0, closeMarkIndex); + const next = closingNodeContent.slice( + closeMarkIndex + closeMark.length + ); + if (prev) { + nodes.splice( + closeMarkPos, + 0, + cloneStringNode(closingNode, prev) + ); + closeMarkPos++; + } + if (next) { + nodes.splice( + closeMarkPos + 1, + 0, + cloneStringNode(closingNode, next) + ); + } + } + } + return [openMarkPos, closeMarkPos]; +} + +function normalizeEmbellishmentTokens( + tokens: (ArgSpec.Group | string)[] +): string[] { + return tokens.flatMap((token) => { + if (typeof token === "string") { + return token.split(""); + } + // xparse (as of 2023-02-02) accepts single character enclosed in braces {}. + // It does not allow more nesting, e.g. e{{{_}}} produces an error. + if (token.content.length === 1) { + const bracedToken = token.content[0]; + if (typeof bracedToken === "string" && bracedToken.length === 1) { + return bracedToken; + } + } + console.warn( + `Embellishment token should be a single character, but got ${printRaw( + token + )}` + ); + return []; + }); } diff --git a/packages/unified-latex-util-arguments/tests/attach-arguments-in-array.test.ts b/packages/unified-latex-util-arguments/tests/attach-arguments-in-array.test.ts index 7dfd7766..421e19e5 100644 --- a/packages/unified-latex-util-arguments/tests/attach-arguments-in-array.test.ts +++ b/packages/unified-latex-util-arguments/tests/attach-arguments-in-array.test.ts @@ -197,5 +197,143 @@ describe("unified-latex-util-arguments", () => { ], }, ]); + + // multiple delimiters + nodes = strToNodes("\\xxx_a^_^\\xxx__"); + attachMacroArgsInArray(nodes, { xxx: { signature: "r__" } }); + expect(nodes).toEqual([ + { + type: "macro", + content: "xxx", + args: [ + { + type: "argument", + content: [{ type: "string", content: "a^" }], + openMark: "_", + closeMark: "_", + }, + ], + }, + { type: "string", content: "^" }, + { + type: "macro", + content: "xxx", + args: [ + { + type: "argument", + content: [], + openMark: "_", + closeMark: "_", + }, + ], + }, + ]); + + // embellishments + nodes = strToNodes("\\xxx^a\\xxx_b\\xxx_b^a"); + attachMacroArgsInArray(nodes, { xxx: { signature: "e{^_}" } }); + expect(nodes).toEqual([ + { + type: "macro", + content: "xxx", + args: [ + { + type: "argument", + content: [{ type: "string", content: "a" }], + openMark: "^", + closeMark: "", + }, + { + type: "argument", + content: [], + openMark: "", + closeMark: "", + }, + ], + }, + { + type: "macro", + content: "xxx", + args: [ + { + type: "argument", + content: [], + openMark: "", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "b" }], + openMark: "_", + closeMark: "", + }, + ], + }, + { + type: "macro", + content: "xxx", + args: [ + { + type: "argument", + content: [{ type: "string", content: "a" }], + openMark: "^", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "b" }], + openMark: "_", + closeMark: "", + }, + ], + }, + ]); + + nodes = strToNodes("\\xxx^{a_b}"); + attachMacroArgsInArray(nodes, { xxx: { signature: "e{^_}" } }); + expect(nodes).toEqual([ + { + type: "macro", + content: "xxx", + args: [ + { + type: "argument", + content: [{ type: "string", content: "a_b" }], + openMark: "^", + closeMark: "", + }, + { + type: "argument", + content: [], + openMark: "", + closeMark: "", + }, + ], + }, + ]); + + // non-punctuation optional token and delimiters combined + nodes = strToNodes("\\xxx^_789_"); + attachMacroArgsInArray(nodes, { xxx: { signature: "t^ r__" } }); + expect(nodes).toEqual([ + { + type: "macro", + content: "xxx", + args: [ + { + type: "argument", + content: [{ type: "string", content: "^" }], + openMark: "", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "789" }], + openMark: "_", + closeMark: "_", + }, + ], + }, + ]); }); }); diff --git a/packages/unified-latex-util-arguments/tests/attach-arguments.test.ts b/packages/unified-latex-util-arguments/tests/attach-arguments.test.ts index 60d46cbd..dfe85172 100644 --- a/packages/unified-latex-util-arguments/tests/attach-arguments.test.ts +++ b/packages/unified-latex-util-arguments/tests/attach-arguments.test.ts @@ -416,6 +416,41 @@ describe("unified-latex-util-arguments", () => { ]); }); + it("Can attach arguments with complex signature", () => { + let nodes = strToNodes(`\\xxx_a{bc}`); + attachMacroArgs(nodes, { + xxx: { + signature: "e{_} m r<>", + }, + }); + expect(nodes).toEqual([ + { + type: "macro", + content: "xxx", + args: [ + { + type: "argument", + content: [{ type: "string", content: "a" }], + openMark: "_", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "bc" }], + openMark: "{", + closeMark: "}", + }, + { + type: "argument", + content: [{ type: "string", content: "d" }], + openMark: "<", + closeMark: ">", + }, + ], + }, + ]); + }); + it("Custom argument parser", () => { /** * Unconditionally take the first node as an argument. diff --git a/packages/unified-latex-util-arguments/tests/get-args-content.test.ts b/packages/unified-latex-util-arguments/tests/get-args-content.test.ts index ca5d41a3..2a6de7d8 100644 --- a/packages/unified-latex-util-arguments/tests/get-args-content.test.ts +++ b/packages/unified-latex-util-arguments/tests/get-args-content.test.ts @@ -13,7 +13,6 @@ console.log = (...args) => { }; describe("unified-latex-util-arguments", () => { - it("can get args content", () => { // Recursively apply substitutions in groups let nodes = strToNodes("\\xxx b"); diff --git a/packages/unified-latex-util-arguments/tests/gobble-arguments.test.ts b/packages/unified-latex-util-arguments/tests/gobble-arguments.test.ts index be8ecea9..6fe09513 100644 --- a/packages/unified-latex-util-arguments/tests/gobble-arguments.test.ts +++ b/packages/unified-latex-util-arguments/tests/gobble-arguments.test.ts @@ -75,6 +75,115 @@ describe("unified-latex-util-arguments", () => { ]); }); + it("can gobble arguments that represents mutiple embellishments", () => { + let argspec = parseArgspec("e{_ad}"); + value = "_{1234}abcde"; + file = processLatexToAstViaUnified().processSync({ value }); + let nodes = trimRenderInfo((file.result as any).content) as Ast.Node[]; + expect(gobbleArguments(nodes, argspec)).toEqual({ + args: [ + { + type: "argument", + content: [{ type: "string", content: "1234" }], + openMark: "_", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "b" }], + openMark: "a", + closeMark: "", + }, + { + type: "argument", + content: [], + openMark: "", + closeMark: "", + }, + ], + nodesRemoved: 4, + }); + expect(nodes).toEqual([{ type: "string", content: "cde" }]); + + // Order of embellishments shouldn't matter + argspec = parseArgspec("e{_ad}"); + value = "_{1234}daac"; + file = processLatexToAstViaUnified().processSync({ value }); + nodes = trimRenderInfo((file.result as any).content) as Ast.Node[]; + expect(gobbleArguments(nodes, argspec)).toEqual({ + args: [ + { + type: "argument", + content: [{ type: "string", content: "1234" }], + openMark: "_", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "c" }], + openMark: "a", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "a" }], + openMark: "d", + closeMark: "", + }, + ], + nodesRemoved: 6, + }); + expect(nodes).toEqual([]); + + // Whitespaces between embellishment arguments should be ignored. + argspec = parseArgspec("e{^_}"); + value = "^1 _2"; + file = processLatexToAstViaUnified().processSync({ value }); + nodes = trimRenderInfo((file.result as any).content) as Ast.Node[]; + expect(gobbleArguments(nodes, argspec)).toEqual({ + args: [ + { + type: "argument", + content: [{ type: "string", content: "1" }], + openMark: "^", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "2" }], + openMark: "_", + closeMark: "", + }, + ], + nodesRemoved: 5, + }); + expect(nodes).toEqual([]); + + // Embellishment tokens enclosed in braces + argspec = parseArgspec("e{{^}{_}}"); + value = "^a_b"; + file = processLatexToAstViaUnified().processSync({ value }); + nodes = trimRenderInfo((file.result as any).content) as Ast.Node[]; + expect(gobbleArguments(nodes, argspec)).toEqual({ + args: [ + { + type: "argument", + content: [{ type: "string", content: "a" }], + openMark: "^", + closeMark: "", + }, + { + type: "argument", + content: [{ type: "string", content: "b" }], + openMark: "_", + closeMark: "", + }, + ], + nodesRemoved: 4, + }); + expect(nodes).toEqual([]); + }); + it("can gobble arguments with custom argument parser", () => { /** * Unconditionally take the first node as an argument. diff --git a/packages/unified-latex-util-arguments/tests/gobble-single-argument.test.ts b/packages/unified-latex-util-arguments/tests/gobble-single-argument.test.ts index 509dc0b6..7c2f7e22 100644 --- a/packages/unified-latex-util-arguments/tests/gobble-single-argument.test.ts +++ b/packages/unified-latex-util-arguments/tests/gobble-single-argument.test.ts @@ -424,6 +424,20 @@ describe("unified-latex-util-arguments", () => { argument: null, nodesRemoved: 0, }); + + // non-punctuation optional token + ast = [{ type: "whitespace" }, { type: "string", content: "_abc" }]; + expect( + gobbleSingleArgument([...ast], parseArgspec("t_")[0]) + ).toMatchObject({ + argument: { + type: "argument", + content: [{ type: "string", content: "_" }], + openMark: "", + closeMark: "", + }, + nodesRemoved: 2, + }); }); it("gobbleSingleArgument gobbles optional group (i.e., optional argument in '{...}' braces)", () => { let ast: Ast.Node[]; @@ -563,7 +577,7 @@ describe("unified-latex-util-arguments", () => { }); expect(nodes).toEqual([{ content: "x", type: "string" }]); }); - it.skip("can gobble an 'until' that requires splitting a string", () => { + it("can gobble an 'until' that requires splitting a string", () => { let argspec = parseArgspec("ux")[0]; value = "(val)mxyx"; file = processLatexToAstViaUnified().processSync({ value }); @@ -580,8 +594,162 @@ describe("unified-latex-util-arguments", () => { openMark: "", closeMark: "x", }, - nodesRemoved: 3, + nodesRemoved: 5, }); expect(nodes).toEqual([{ content: "yx", type: "string" }]); }); + it("gobbleSingleArgument gobbles non-punctuation delimited arguments", () => { + let ast: Ast.Node[] = [ + { type: "whitespace" }, + { type: "string", content: "_a__" }, // additional delimiter should be ignored + { type: "group", content: [{ type: "string", content: "b" }] }, + { type: "string", content: "!" }, + ]; + expect(gobbleSingleArgument(ast, parseArgspec("r__")[0])).toMatchObject( + { + argument: { + type: "argument", + content: [{ type: "string", content: "a" }], + openMark: "_", + closeMark: "_", + }, + nodesRemoved: 4, + } + ); + + ast = [ + { type: "whitespace" }, + { type: "string", content: "^ab" }, + { type: "string", content: "!" }, + { type: "string", content: "b" }, + { type: "whitespace" }, + { type: "string", content: "d" }, + { type: "string", content: "y_" }, + ]; + expect( + gobbleSingleArgument(ast, parseArgspec("R^_{default}")[0]) + ).toMatchObject({ + argument: { + type: "argument", + content: [ + { type: "string", content: "ab" }, + { type: "string", content: "!" }, + { type: "string", content: "b" }, + { type: "whitespace" }, + { type: "string", content: "d" }, + { type: "string", content: "y" }, + ], + openMark: "^", + closeMark: "_", + }, + nodesRemoved: 9, + }); + + // Optional arguments are optional + ast = [ + { type: "whitespace" }, + { type: "string", content: "ThisPreventsMatchingOptionalArg_" }, + { type: "whitespace" }, + { type: "string", content: "1" }, + { type: "string", content: "-" }, + ]; + expect(gobbleSingleArgument(ast, parseArgspec("d_-")[0])).toMatchObject( + { + argument: null, + nodesRemoved: 0, + } + ); + + // missing closing brace + ast = [ + { type: "whitespace" }, + { type: "string", content: "^ab" }, + { type: "string", content: "!" }, + { type: "string", content: "b" }, + { type: "string", content: "c" }, + { type: "string", content: "d" }, + { type: "string", content: "y" }, + ]; + expect(gobbleSingleArgument(ast, parseArgspec("r^_")[0])).toMatchObject( + { + argument: null, + nodesRemoved: 0, + } + ); + + // closing delimiter in the middle + ast = [{ type: "string", content: "_a^_^" }]; + expect(gobbleSingleArgument(ast, parseArgspec("r__")[0])).toMatchObject( + { + argument: { + type: "argument", + content: [{ type: "string", content: "a^" }], + openMark: "_", + closeMark: "_", + }, + nodesRemoved: 3, + } + ); + }); + it("can gobble embellishments", () => { + let ast: Ast.Node[] = [{ type: "string", content: "xxx" }]; + expect(gobbleSingleArgument(ast, parseArgspec("e{}")[0])).toMatchObject( + { + argument: null, + nodesRemoved: 0, + matchNum: 0, + } + ); + + ast = [ + { type: "whitespace" }, + { type: "string", content: "_1234" }, + { type: "string", content: "!" }, + ]; + expect( + gobbleSingleArgument(ast, parseArgspec("e{_}")[0]) + ).toMatchObject({ + argument: { + type: "argument", + content: [{ type: "string", content: "1" }], + openMark: "_", + closeMark: "", + }, + nodesRemoved: 3, + matchNum: 1, + }); + + ast = [ + { type: "string", content: "_" }, + { type: "group", content: [{ type: "string", content: "1234" }] }, + { type: "string", content: "abcde" }, + ]; + expect( + gobbleSingleArgument(ast, parseArgspec("e{a_}")[0]) + ).toMatchObject({ + argument: { + type: "argument", + content: [{ type: "string", content: "1234" }], + openMark: "_", + closeMark: "", + }, + nodesRemoved: 2, + matchNum: 2, + }); + }); + it("can gobble embellishments whose token is in a group one level deep", () => { + let ast: Ast.Node[] = [{ type: "string", content: "^a_b" }]; + expect( + gobbleSingleArgument(ast, parseArgspec("e{{^}{_}}")[0]) + ).toMatchObject({ + argument: { + type: "argument", + content: [{ type: "string", content: "a" }], + openMark: "^", + closeMark: "", + }, + nodesRemoved: 2, + matchNum: 1, + }); + }); }); diff --git a/packages/unified-latex-util-catcode/libs/regions.ts b/packages/unified-latex-util-catcode/libs/regions.ts index 0af38050..06add5f6 100644 --- a/packages/unified-latex-util-catcode/libs/regions.ts +++ b/packages/unified-latex-util-catcode/libs/regions.ts @@ -76,7 +76,7 @@ export function refineRegions(regions: Region[]): { * within the bounds of `array`. */ export function splitByRegions< - T extends unknown, + T, RegionRecord extends Record >(array: T[], regionsRecord: RegionRecord) { const ret: [keyof RegionRecord | null, T[]][] = []; diff --git a/packages/unified-latex-util-comments/tests/delete-comments.test.ts b/packages/unified-latex-util-comments/tests/delete-comments.test.ts index 895ebe00..1f892874 100644 --- a/packages/unified-latex-util-comments/tests/delete-comments.test.ts +++ b/packages/unified-latex-util-comments/tests/delete-comments.test.ts @@ -12,7 +12,6 @@ console.log = (...args) => { }; describe("unified-latex-util-comments", () => { - it("can delete comments", () => { let nodes = strToNodes("a%\nb%xx\nc"); deleteComments(nodes); diff --git a/packages/unified-latex-util-macros/libs/newcommand.ts b/packages/unified-latex-util-macros/libs/newcommand.ts index b7182c8b..adb5f493 100644 --- a/packages/unified-latex-util-macros/libs/newcommand.ts +++ b/packages/unified-latex-util-macros/libs/newcommand.ts @@ -41,7 +41,7 @@ const NEWCOMMAND_ARGUMENTS_BEAMER = [ "body", ] as const; type NewcommandNamedArgs = Record< - typeof NEWCOMMAND_ARGUMENTS_REG[number], + (typeof NEWCOMMAND_ARGUMENTS_REG)[number], Ast.Node[] | null >; diff --git a/packages/unified-latex-util-packages/tests/list-packages.test.ts b/packages/unified-latex-util-packages/tests/list-packages.test.ts index 19a78c08..4777904b 100644 --- a/packages/unified-latex-util-packages/tests/list-packages.test.ts +++ b/packages/unified-latex-util-packages/tests/list-packages.test.ts @@ -15,29 +15,29 @@ console.log = (...args) => { describe("unified-latex-packages", () => { let value: string | undefined; let file: VFile | undefined; - let ast: Ast.Ast | undefined - it("can list \\usepackage packages", ()=> { - value = "\\usepackage[baz]{foo}\\usepackage{bar-nun}" - file = processLatexToAstViaUnified().processSync({value}) - ast = file.result as Ast.Ast - - let packages = listPackages(ast).map(node => node.content) - expect(packages).toEqual(["foo", "bar-nun"]) - }) - it("can list \\RequirePackage packages", ()=> { - value = "\\RequirePackage{foo}\\RequirePackage{bar}" - file = processLatexToAstViaUnified().processSync({value}) - ast = file.result as Ast.Ast - - let packages = listPackages(ast).map(node => node.content) - expect(packages).toEqual(["foo", "bar"]) - }) - it("can list packages separated by commas", ()=> { - value = "\\usepackage{foo, bar%\n,baz}" - file = processLatexToAstViaUnified().processSync({value}) - ast = file.result as Ast.Ast - - let packages = listPackages(ast).map(node => node.content) - expect(packages).toEqual(["foo", "bar", "baz"]) - }) -}) \ No newline at end of file + let ast: Ast.Ast | undefined; + it("can list \\usepackage packages", () => { + value = "\\usepackage[baz]{foo}\\usepackage{bar-nun}"; + file = processLatexToAstViaUnified().processSync({ value }); + ast = file.result as Ast.Ast; + + let packages = listPackages(ast).map((node) => node.content); + expect(packages).toEqual(["foo", "bar-nun"]); + }); + it("can list \\RequirePackage packages", () => { + value = "\\RequirePackage{foo}\\RequirePackage{bar}"; + file = processLatexToAstViaUnified().processSync({ value }); + ast = file.result as Ast.Ast; + + let packages = listPackages(ast).map((node) => node.content); + expect(packages).toEqual(["foo", "bar"]); + }); + it("can list packages separated by commas", () => { + value = "\\usepackage{foo, bar%\n,baz}"; + file = processLatexToAstViaUnified().processSync({ value }); + ast = file.result as Ast.Ast; + + let packages = listPackages(ast).map((node) => node.content); + expect(packages).toEqual(["foo", "bar", "baz"]); + }); +}); diff --git a/packages/unified-latex-util-parse/libs/process-macros-and-environments.ts b/packages/unified-latex-util-parse/libs/process-macros-and-environments.ts index 567f9d6b..6823e80a 100644 --- a/packages/unified-latex-util-parse/libs/process-macros-and-environments.ts +++ b/packages/unified-latex-util-parse/libs/process-macros-and-environments.ts @@ -1,12 +1,13 @@ import * as Ast from "@unified-latex/unified-latex-types"; -import { EnvInfoRecord, MacroInfoRecord } from "@unified-latex/unified-latex-types"; +import { + EnvInfoRecord, + MacroInfoRecord, +} from "@unified-latex/unified-latex-types"; import { Plugin } from "unified"; import { visit } from "@unified-latex/unified-latex-util-visit"; import { match } from "@unified-latex/unified-latex-util-match"; import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; -import { - unifiedLatexReparseMathConstructPlugin, -} from "./reparse-math"; +import { unifiedLatexReparseMathConstructPlugin } from "./reparse-math"; import { attachMacroArgsInArray } from "@unified-latex/unified-latex-util-arguments"; import { processEnvironment } from "@unified-latex/unified-latex-util-environments"; diff --git a/packages/unified-latex-util-parse/tests/parse-basic.test.ts b/packages/unified-latex-util-parse/tests/parse-basic.test.ts index 8f12e6e6..fd1a2c72 100644 --- a/packages/unified-latex-util-parse/tests/parse-basic.test.ts +++ b/packages/unified-latex-util-parse/tests/parse-basic.test.ts @@ -229,9 +229,7 @@ describe("unified-latex-util-parse", () => { ], }); expect( - trimRenderInfo( - parse("\\mintinline[options]{language}#some_code$#") - ) + trimRenderInfo(parse("\\mintinline[options]{language}#some_code$#")) ).toEqual({ type: "root", content: [ diff --git a/packages/unified-latex-util-parse/tests/parse-override-macros.test.ts b/packages/unified-latex-util-parse/tests/parse-override-macros.test.ts index 943defb6..6ffc7d51 100644 --- a/packages/unified-latex-util-parse/tests/parse-override-macros.test.ts +++ b/packages/unified-latex-util-parse/tests/parse-override-macros.test.ts @@ -28,10 +28,9 @@ describe("unified-latex-util-parse", () => { }); // Default parsing - parser = unified().use( - unifiedLatexFromString, - { macros: { mathbb: { signature: "m m" } } }, - ); + parser = unified().use(unifiedLatexFromString, { + macros: { mathbb: { signature: "m m" } }, + }); expect(trimRenderInfo(parser.parse(`\\mathbb X Y`))).toEqual({ type: "root", content: [m("mathbb", args(["X", "Y"]))], diff --git a/packages/unified-latex-util-pegjs/index.ts b/packages/unified-latex-util-pegjs/index.ts index 11c339b6..879345c5 100644 --- a/packages/unified-latex-util-pegjs/index.ts +++ b/packages/unified-latex-util-pegjs/index.ts @@ -9,7 +9,7 @@ export * from "./libs/pegjs-parsers.js"; * Pegjs grammars to help parse strings into a `unified-latex` Abstract Syntax Tree (AST). Note, * because of the dynamic nature of LaTeX, to get a full AST with arguments attached to macros, etc., * the tree is parsed multiple times. - * + * * Also included are functions to decorate a `Ast.Node[]` array so that Pegjs can process it as if it were * a string. This allows for complex second-pass parsing. * diff --git a/packages/unified-latex-util-pegjs/libs/pegjs-parsers.ts b/packages/unified-latex-util-pegjs/libs/pegjs-parsers.ts index 644149a1..b4784529 100644 --- a/packages/unified-latex-util-pegjs/libs/pegjs-parsers.ts +++ b/packages/unified-latex-util-pegjs/libs/pegjs-parsers.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ // This file needs to be here because typescript does not know how to use babel's transpiler // to directly load Pegjs grammars. // @ts-nocheck diff --git a/packages/unified-latex-util-print-raw/libs/print-raw.ts b/packages/unified-latex-util-print-raw/libs/print-raw.ts index b78a8e79..950d2945 100644 --- a/packages/unified-latex-util-print-raw/libs/print-raw.ts +++ b/packages/unified-latex-util-print-raw/libs/print-raw.ts @@ -29,9 +29,9 @@ function _printRaw(node: Printable | Printable[]): PrintToken[] { case "argument": return [node.openMark, ..._printRaw(node.content), node.closeMark]; case "comment": - var suffix = node.suffixParbreak ? "" : linebreak; + let suffix = node.suffixParbreak ? "" : linebreak; // A comment is responsible for printing its own leading whitespace - var leadingWhitespace = ""; + let leadingWhitespace = ""; if (node.sameline && node.leadingWhitespace) { leadingWhitespace = " "; } @@ -47,9 +47,9 @@ function _printRaw(node: Printable | Printable[]): PrintToken[] { case "environment": case "mathenv": case "verbatim": - var env = _printRaw(node.env); - var envStart: PrintToken[] = [ESCAPE + "begin{", ...env, "}"]; - var envEnd: PrintToken[] = [ESCAPE + "end{", ...env, "}"]; + let env = _printRaw(node.env); + let envStart: PrintToken[] = [ESCAPE + "begin{", ...env, "}"]; + let envEnd: PrintToken[] = [ESCAPE + "end{", ...env, "}"]; argsString = (node as any).args == null ? [] : _printRaw((node as any).args); return [ diff --git a/packages/unified-latex-util-replace/libs/replace-node.ts b/packages/unified-latex-util-replace/libs/replace-node.ts index 51e8ed18..a2ec0cbc 100644 --- a/packages/unified-latex-util-replace/libs/replace-node.ts +++ b/packages/unified-latex-util-replace/libs/replace-node.ts @@ -1,8 +1,5 @@ import * as Ast from "@unified-latex/unified-latex-types"; -import { - visit, - VisitInfo, -} from "@unified-latex/unified-latex-util-visit"; +import { visit, VisitInfo } from "@unified-latex/unified-latex-util-visit"; /** * Recursively replace nodes in `ast`. The `visitor` function is called on each node. If diff --git a/packages/unified-latex-util-replace/tests/significant-node.test.ts b/packages/unified-latex-util-replace/tests/significant-node.test.ts index 55c548d8..704e537b 100644 --- a/packages/unified-latex-util-replace/tests/significant-node.test.ts +++ b/packages/unified-latex-util-replace/tests/significant-node.test.ts @@ -22,16 +22,16 @@ describe("unified-latex-replace", () => { nodes = strToNodes("a b c%foo"); expect(lastSignificantNodeIndex(nodes)).toEqual(4); - + nodes = strToNodes("a b c %foo"); expect(lastSignificantNodeIndex(nodes)).toEqual(4); - + nodes = strToNodes("a b c %foo\n%bar"); expect(lastSignificantNodeIndex(nodes)).toEqual(4); - + nodes = strToNodes("a b c%foo\n\n%bar"); expect(lastSignificantNodeIndex(nodes)).toEqual(6); - + nodes = strToNodes("a b c%foo\n\n%bar"); expect(lastSignificantNodeIndex(nodes, true)).toEqual(4); }); @@ -47,7 +47,7 @@ describe("unified-latex-replace", () => { nodes = strToNodes("%foo\n\na b c"); expect(firstSignificantNodeIndex(nodes)).toEqual(1); - + nodes = strToNodes("%foo\n\na b c"); expect(firstSignificantNodeIndex(nodes, true)).toEqual(2); }); diff --git a/packages/unified-latex-util-replace/tests/unified-latex-replace-streaming-command.test.ts b/packages/unified-latex-util-replace/tests/unified-latex-replace-streaming-command.test.ts index 383ef33c..827205d8 100644 --- a/packages/unified-latex-util-replace/tests/unified-latex-replace-streaming-command.test.ts +++ b/packages/unified-latex-util-replace/tests/unified-latex-replace-streaming-command.test.ts @@ -33,7 +33,7 @@ describe("unified-latex-util-replace:unified-latex-replace-streaming-command", ( file = process("x \\foo y\n\nz"); expect(file.value).toEqual("x \\FOO{y}\n\n\\FOO{z}"); - + file = process("x \\foo y \\bar yy\n\nz"); expect(file.value).toEqual("x \\FOO{y \\BAR{yy}}\n\n\\FOO{\\BAR{z}}"); }); diff --git a/packages/unified-latex-util-split/index.ts b/packages/unified-latex-util-split/index.ts index 6d81743d..8e1e3a7d 100644 --- a/packages/unified-latex-util-split/index.ts +++ b/packages/unified-latex-util-split/index.ts @@ -13,4 +13,4 @@ export * from "./libs/array-join"; * * If you want break apart or join an array of nodes based on a condition. For example, * this is used to split on `&` characters in the `align` environment. - */ \ No newline at end of file + */ diff --git a/packages/unified-latex-util-visit/libs/visit.ts b/packages/unified-latex-util-visit/libs/visit.ts index abc01746..f5c219fc 100644 --- a/packages/unified-latex-util-visit/libs/visit.ts +++ b/packages/unified-latex-util-visit/libs/visit.ts @@ -21,11 +21,12 @@ type GetGuard = T extends (x: any, ...y: any[]) => x is infer R ? R : never; * Gets the type that a type-guard function is guarding. If * the guard type cannot be determined, the input type is returned. */ -type GuardTypeOf boolean> = GetGuard extends never - ? T extends (x: infer A) => any - ? A - : never - : GetGuard; +type GuardTypeOf boolean> = + GetGuard extends never + ? T extends (x: infer A) => any + ? A + : never + : GetGuard; /** * Extracts the guard type from the `test` function provided in a