diff --git a/package-lock.json b/package-lock.json index 086660c0..467cea37 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1731,6 +1731,15 @@ "@types/node": "*" } }, + "node_modules/@types/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/cssesc/-/cssesc-3.0.0.tgz", @@ -13860,6 +13869,8 @@ "unified-latex": "unified-latex-cli.mjs" }, "devDependencies": { + "@types/cross-spawn": "^6.0.6", + "cross-spawn": "^7.0.3", "source-map-support": "^0.5.21" } }, diff --git a/packages/unified-latex-cli/package.json b/packages/unified-latex-cli/package.json index 5daac9ec..48991339 100644 --- a/packages/unified-latex-cli/package.json +++ b/packages/unified-latex-cli/package.json @@ -77,6 +77,8 @@ "homepage": "https://github.com/siefkenj/unified-latex#readme", "private": true, "devDependencies": { + "@types/cross-spawn": "^6.0.6", + "cross-spawn": "^7.0.3", "source-map-support": "^0.5.21" } } diff --git a/packages/unified-latex-cli/tests/cli.test.ts b/packages/unified-latex-cli/tests/cli.test.ts index f495fe4a..61011117 100644 --- a/packages/unified-latex-cli/tests/cli.test.ts +++ b/packages/unified-latex-cli/tests/cli.test.ts @@ -4,6 +4,7 @@ import "../../test-common"; import { exec as _exec } from "node:child_process"; import * as fsLegacy from "node:fs"; import * as path from "node:path"; +import spawn from "cross-spawn"; const exec = util.promisify(_exec); @@ -18,87 +19,138 @@ console.log = (...args) => { const exePath = path.resolve(__dirname, "../dist/unified-latex-cli.mjs"); const examplesPath = path.resolve(__dirname, "examples"); -describe("unified-latex-cli", () => { - let stdout: string, stderr: string; - it("executable exists", async () => { - expect(fsLegacy.existsSync(exePath)).toBeTruthy(); - }); - it("can execute without error", async () => { - let { stdout, stderr } = await exec(`node ${exePath} -h`); - expect(stdout).toBeTruthy(); - }); - it("can format document", async () => { - let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/needs-fixing.tex` - ); - expect(stdout).toMatchSnapshot(); - }); - it("can expand macro", async () => { - { +describe( + "unified-latex-cli", + () => { + let stdout: string, stderr: string; + it("executable exists", async () => { + expect(fsLegacy.existsSync(exePath)).toBeTruthy(); + }); + it("can execute without error", async () => { + let { stdout, stderr } = await exec(`node ${exePath} -h`); + expect(stdout).toBeTruthy(); + }); + it("can format document", async () => { let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/needs-expanding.tex -e '\\newcommand{foo}[1]{FOO(#1)}' -e '{name: "bar", body: "baz"}'` + `node ${exePath} ${examplesPath}/needs-fixing.tex` ); expect(stdout).toMatchSnapshot(); - } - { - // Make sure we don't lose spaces in math mode + }); + it("can expand macro", async () => { + { + let stdout = await executeCommand(`node`, [ + exePath, + `${examplesPath}/needs-expanding.tex`, + `-e`, + "\\newcommand{foo}[1]{FOO(#1)}", + `-e`, + '{name: "bar", body: "baz"}', + ]); + expect(stdout).toMatchSnapshot(); + } + { + // Make sure we don't lose spaces in math mode + let stdout = await executeCommand(`node`, [ + exePath, + `${examplesPath}/needs-expanding.tex`, + `-e`, + "\\newcommand{foo}[1]{$\\x #1$}", + `-e`, + '{name: "bar", body: "baz"}', + ]); + expect(stdout).toMatchSnapshot(); + } + }); + it("can expand macros defined in document", async () => { let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/needs-expanding.tex -e '\\newcommand{foo}[1]{$\\x #1$}' -e '{name: "bar", body: "baz"}'` + `node ${exePath} ${examplesPath}/has-definition.tex --stats-json` ); - expect(stdout).toMatchSnapshot(); - } - }); - it("can expand macros defined in document", async () => { - let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/has-definition.tex --stats-json` - ); - const { newcommands } = JSON.parse(stdout) as { - newcommands: { name: string }[]; - }; - const newcommandNames = newcommands.map((c) => c.name); - expect(newcommandNames).toEqual(["foo", "baz"]); + const { newcommands } = JSON.parse(stdout) as { + newcommands: { name: string }[]; + }; + const newcommandNames = newcommands.map((c) => c.name); + expect(newcommandNames).toEqual(["foo", "baz"]); - { - let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/has-definition.tex --expand-document-macro foo --expand-document-macro baz` - ); - expect(stdout).toMatchSnapshot(); - } - }); - it("can override default macros", async () => { - { - let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/has-existing-definition.tex` - ); - expect(stdout).toMatchSnapshot(); - } - { - let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/has-existing-definition.tex -e '\\newcommand{mathbb}{\\mathbb}'` - ); - expect(stdout).toMatchSnapshot(); - } - { - let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/has-existing-definition.tex -e '\\newcommand{mathbb}[2]{\\mathbb{#1}{#2}}'` - ); - expect(stdout).toMatchSnapshot(); - } - }); - it("can convert to html", async () => { - { - let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/simple.tex --html` - ); - expect(stdout).toMatchSnapshot(); - } - }); - it("can convert to markdown", async () => { - { - let { stdout, stderr } = await exec( - `node ${exePath} ${examplesPath}/simple.tex --markdown` - ); - expect(stdout).toMatchSnapshot(); - } + { + let { stdout, stderr } = await exec( + `node ${exePath} ${examplesPath}/has-definition.tex --expand-document-macro foo --expand-document-macro baz` + ); + expect(stdout).toMatchSnapshot(); + } + }); + it("can override default macros", async () => { + { + let stdout = await executeCommand(`node`, [ + exePath, + `${examplesPath}/has-existing-definition.tex`, + ]); + expect(stdout).toMatchSnapshot(); + } + { + let stdout = await executeCommand(`node`, [ + exePath, + `${examplesPath}/has-existing-definition.tex`, + `-e`, + "\\newcommand{mathbb}{\\mathbb}", + ]); + expect(stdout).toMatchSnapshot(); + } + { + let stdout = await executeCommand(`node`, [ + exePath, + `${examplesPath}/has-existing-definition.tex`, + `-e`, + "\\newcommand{mathbb}[2]{\\mathbb{#1}{#2}}", + ]); + expect(stdout).toMatchSnapshot(); + } + }); + it("can convert to html", async () => { + { + let { stdout, stderr } = await exec( + `node ${exePath} ${examplesPath}/simple.tex --html` + ); + expect(stdout).toMatchSnapshot(); + } + }); + it("can convert to markdown", async () => { + { + let { stdout, stderr } = await exec( + `node ${exePath} ${examplesPath}/simple.tex --markdown` + ); + expect(stdout).toMatchSnapshot(); + } + }); + }, + { + timeout: 60 * 1000, + } +); + +/** + * Run commands with arguments using "cross-spawn", which correctly escapes arguments + * so that end results are the same across different shells. + */ +async function executeCommand(executablePath: string, args: string[]) { + return new Promise((resolve, reject) => { + const childProcess = spawn(executablePath, args, { stdio: "pipe" }); + + let stdoutData = ""; + + childProcess.stdout!.on("data", (data) => { + stdoutData += data.toString(); + }); + + childProcess.on("error", (err) => { + reject(err); + }); + + childProcess.on("close", (code) => { + if (code === 0) { + resolve(stdoutData); + } else { + reject(new Error(`Child process exited with code ${code}`)); + } + }); }); -}); +} diff --git a/packages/unified-latex-to-hast/index.ts b/packages/unified-latex-to-hast/index.ts index b1c8bcac..1b19df06 100644 --- a/packages/unified-latex-to-hast/index.ts +++ b/packages/unified-latex-to-hast/index.ts @@ -31,7 +31,7 @@ export * from "./libs/convert-to-html"; * import { printRaw } from "@unified-latex/unified-latex-util-print-raw"; * import { unifiedLatexToHast } from "@unified-latex/unified-latex-to-hast"; * import { unifiedLatexFromString } from "@unified-latex/unified-latex-util-parse"; - * import { getArgsContent } from "@unified-latex/unified-latex-util-arguments"; + * import { getArgsContent } from "@unified-latex/unified-latex-util-arguments"; * * const convert = (value) => * unified() 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 daea542c..a51b33e8 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 @@ -178,5 +178,5 @@ export const environmentReplacements: Record< content: env.content, attributes: { className: "environment quote" }, }); - } + }, }; diff --git a/packages/unified-latex-to-mdast/tests/convert-to-markdown.test.ts b/packages/unified-latex-to-mdast/tests/convert-to-markdown.test.ts index 06579e79..904d78c5 100644 --- a/packages/unified-latex-to-mdast/tests/convert-to-markdown.test.ts +++ b/packages/unified-latex-to-mdast/tests/convert-to-markdown.test.ts @@ -44,24 +44,20 @@ describe("unified-latex-to-mdast:convert-to-markdown", () => { `### My Section\n\nHi there\n\nAnd here $x^{2}$ math\n` ); }); - + it("display math converts to $$...$$", () => { let ast = unified() .use(unifiedLatexFromString) .parse("my\\[math\\]yay!"); markdown = convertToMarkdown(ast); - expect(markdown).toEqual( - `my\n\n\`\`\`math\nmath\n\`\`\`\n\nyay!\n` - ); + expect(markdown).toEqual(`my\n\n\`\`\`math\nmath\n\`\`\`\n\nyay!\n`); }); - + it("math isn't mangled when it is rendered", () => { let ast = unified() .use(unifiedLatexFromString) .parse("$\\sum_x^{y} x+Y$"); markdown = convertToMarkdown(ast); - expect(markdown).toEqual( - `$\\sum_{x}^{y}x+Y$\n` - ); + expect(markdown).toEqual(`$\\sum_{x}^{y}x+Y$\n`); }); }); diff --git a/packages/unified-latex-util-catcode/libs/regions.ts b/packages/unified-latex-util-catcode/libs/regions.ts index 06add5f6..dd4cc18a 100644 --- a/packages/unified-latex-util-catcode/libs/regions.ts +++ b/packages/unified-latex-util-catcode/libs/regions.ts @@ -77,7 +77,7 @@ export function refineRegions(regions: Region[]): { */ export function splitByRegions< T, - RegionRecord extends Record + RegionRecord extends Record, >(array: T[], regionsRecord: RegionRecord) { const ret: [keyof RegionRecord | null, T[]][] = []; diff --git a/packages/unified-latex-util-ligatures/libs/ligature-lookup.ts b/packages/unified-latex-util-ligatures/libs/ligature-lookup.ts index b0ee93b6..5ac2a1b0 100644 --- a/packages/unified-latex-util-ligatures/libs/ligature-lookup.ts +++ b/packages/unified-latex-util-ligatures/libs/ligature-lookup.ts @@ -35,7 +35,7 @@ const SUBSTITUTION_MAP: Map = new Map([ ["- - -", makeString("—")], ["- -", makeString("–")], ["` `", makeString("“")], - ["\"", makeString("”")], + ['"', makeString("”")], ["' '", makeString("”")], ["`", makeString("‘")], ["'", makeString("’")], diff --git a/packages/unified-latex-util-visit/libs/visit.ts b/packages/unified-latex-util-visit/libs/visit.ts index f5c219fc..fdc82959 100644 --- a/packages/unified-latex-util-visit/libs/visit.ts +++ b/packages/unified-latex-util-visit/libs/visit.ts @@ -34,7 +34,7 @@ type GuardTypeOf boolean> = */ type GuardFromOptions< Opts extends VisitOptions, - PossibleTypes = Ast.Ast + PossibleTypes = Ast.Ast, > = Opts extends { test: infer R; } diff --git a/scripts/package-consistency.ts b/scripts/package-consistency.ts index 136096c1..fc21bcce 100644 --- a/scripts/package-consistency.ts +++ b/scripts/package-consistency.ts @@ -20,7 +20,7 @@ async function getImportsInDir(dirname): Promise { for (const line of contents.split("\n")) { // Lines that start with ` * ` are comments; we don't track them. if (line.match(/^\s*\*\s/)) { - continue + continue; } for (const m of line.matchAll(/from "([^"]+)"/g)) { const imp = m[1];