{"version":3,"file":"unified-latex-lint-argument-font-shaping-commands-BWptQrWy.cjs","names":[],"sources":["../utils/macro-factory.ts","../rules/unified-latex-lint-argument-font-shaping-commands/index.ts"],"sourcesContent":["import * as Ast from \"@unified-latex/unified-latex-types\";\n\n/**\n * Factory function that returns a wrapper which wraps the passed in `content`\n * as an arg to a macro named `macroName`.\n *\n * E.g.\n * ```\n * f = singleArgumentMacroFactory(\"foo\");\n *\n * // Gives \"\\\\foo{bar}\"\n * printRaw(f(\"bar\"));\n * ```\n */\nexport function singleArgMacroFactory(\n    macroName: string\n): (content: Ast.Node | Ast.Node[]) => Ast.Macro {\n    return (content: Ast.Node | Ast.Node[]) => {\n        if (!Array.isArray(content)) {\n            content = [content];\n        }\n        return {\n            type: \"macro\",\n            content: macroName,\n            args: [\n                {\n                    type: \"argument\",\n                    openMark: \"{\",\n                    closeMark: \"}\",\n                    content,\n                },\n            ],\n            _renderInfo: { inParMode: true },\n        };\n    };\n}\n","import { lintRule } from \"unified-lint-rule\";\nimport { m, s } from \"@unified-latex/unified-latex-builder\";\nimport { printRaw } from \"@unified-latex/unified-latex-util-print-raw\";\nimport * as Ast from \"@unified-latex/unified-latex-types\";\nimport { match } from \"@unified-latex/unified-latex-util-match\";\nimport { visit } from \"@unified-latex/unified-latex-util-visit\";\nimport { singleArgMacroFactory } from \"../../utils/macro-factory\";\nimport {\n    firstSignificantNode,\n    replaceNodeDuringVisit,\n} from \"@unified-latex/unified-latex-util-replace\";\nimport { replaceStreamingCommand } from \"@unified-latex/unified-latex-util-replace\";\nimport { hasBreakingNode } from \"../../utils/has-parbreak\";\nimport { trimEnd, trimStart } from \"@unified-latex/unified-latex-util-trim\";\n\nconst REPLACEMENTS: Record<\n    string,\n    (content: Ast.Node | Ast.Node[]) => Ast.Macro\n> = {\n    bfseries: singleArgMacroFactory(\"textbf\"),\n    itshape: singleArgMacroFactory(\"textit\"),\n    rmfamily: singleArgMacroFactory(\"textrm\"),\n    scshape: singleArgMacroFactory(\"textsc\"),\n    sffamily: singleArgMacroFactory(\"textsf\"),\n    slshape: singleArgMacroFactory(\"textsl\"),\n    ttfamily: singleArgMacroFactory(\"texttt\"),\n    em: singleArgMacroFactory(\"emph\"),\n};\n\nconst isReplaceable = match.createMacroMatcher(REPLACEMENTS);\n\n/**\n * Returns true if the `group` is a group that starts with one of the `REPLACEMENT` macros.\n */\nfunction groupStartsWithMacroAndHasNoParbreak(\n    group: Ast.Ast\n): group is Ast.Group {\n    if (!match.group(group)) {\n        return false;\n    }\n    // Find the first non-whitespace non-comment node\n    let firstNode: Ast.Node | null = firstSignificantNode(group.content);\n    return isReplaceable(firstNode) && !hasBreakingNode(group.content);\n}\n\ntype PluginOptions =\n    | {\n          /**\n           * Whether or not to fix the lint\n           *\n           * @type {boolean}\n           */\n          fix?: boolean;\n      }\n    | undefined;\n\nexport const DESCRIPTION = `## Lint Rule\n\nPrefer using text shaping commands with arguments (e.g. \\`\\\\textbf{foo bar}\\`) over in-stream text shaping commands\n(e.g. \\`{\\\\bfseries foo bar}\\`) if the style does not apply for multiple paragraphs.\nThis rule is useful when parsing LaTeX into other tree structures (e.g., when converting from LaTeX to HTML). \n\n\nThis rule flags any usage of \\`${Object.keys(REPLACEMENTS)\n    .map((r) => printRaw(m(r)))\n    .join(\"` `\")}\\`\n`;\n\nexport const unifiedLatexLintArgumentFontShapingCommands = lintRule<\n    Ast.Root,\n    PluginOptions\n>(\n    { origin: \"unified-latex-lint:argument-font-shaping-commands\" },\n    (tree, file, options) => {\n        const lintedNodes = new Set();\n\n        // We do two passes. First we deal with all the groups like `{\\bfseries xxx}`\n        // and then we replace all remaining streaming commands that appear in arrays.\n\n        visit(\n            tree,\n            (group, info) => {\n                const nodes = group.content;\n                for (const node of nodes) {\n                    if (isReplaceable(node) && !lintedNodes.has(node)) {\n                        lintedNodes.add(node);\n                        const macroName = node.content;\n                        file.message(\n                            `Replace \"${printRaw(group)}\" with \"${printRaw(\n                                REPLACEMENTS[macroName](s(\"...\"))\n                            )}\"`,\n                            node\n                        );\n                        break;\n                    }\n                }\n\n                if (options?.fix) {\n                    let fixed = replaceStreamingCommand(\n                        group,\n                        isReplaceable,\n                        (content, command) => {\n                            return REPLACEMENTS[command.content](content);\n                        }\n                    );\n\n                    // We cannot replace the node unless we can access the containing array.\n                    if (!info.containingArray || info.index == null) {\n                        return;\n                    }\n\n                    // `fixed` may consist of only whitespace. If this is the case,\n                    // surrounding whitespace must trimmed before\n                    // inserting the group's contents.\n                    const prevToken = info.containingArray[info.index - 1];\n                    const nextToken = info.containingArray[info.index + 1];\n                    if (\n                        match.whitespaceLike(prevToken) &&\n                        match.whitespaceLike(fixed[0])\n                    ) {\n                        trimStart(fixed);\n                    }\n                    if (\n                        match.whitespaceLike(nextToken) &&\n                        match.whitespaceLike(fixed[fixed.length - 1])\n                    ) {\n                        trimEnd(fixed);\n                    }\n                    replaceNodeDuringVisit(fixed, info);\n                }\n            },\n            { test: groupStartsWithMacroAndHasNoParbreak }\n        );\n\n        visit(\n            tree,\n            (nodes) => {\n                if (hasBreakingNode(nodes)) {\n                    return;\n                }\n\n                let hasReplaceableContent = false;\n                for (const node of nodes) {\n                    if (isReplaceable(node) && !lintedNodes.has(node)) {\n                        lintedNodes.add(node);\n                        hasReplaceableContent = true;\n                        const macroName = node.content;\n                        file.message(\n                            `Replace \"${printRaw(nodes)}\" with \"${printRaw(\n                                REPLACEMENTS[macroName](s(\"...\"))\n                            )}\"`,\n                            node\n                        );\n                    }\n                }\n\n                if (hasReplaceableContent && options?.fix) {\n                    // In an array replacements happen in-place\n                    replaceStreamingCommand(\n                        nodes,\n                        isReplaceable,\n                        (content, command) => {\n                            return REPLACEMENTS[command.content](content);\n                        }\n                    );\n                }\n            },\n            { includeArrays: true, test: Array.isArray }\n        );\n    }\n);\n"],"mappings":"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"}