{"version":3,"file":"index.cjs","names":[],"sources":["../../../rules/unified-latex-lint-argument-color-commands/index.ts"],"sourcesContent":["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 { hasParbreak } from \"../../utils/has-parbreak\";\nimport { trimEnd, trimStart } from \"@unified-latex/unified-latex-util-trim\";\nimport { colorToTextcolorMacro } from \"@unified-latex/unified-latex-ctan/package/xcolor\";\nimport {\n    firstSignificantNode,\n    replaceNodeDuringVisit,\n    replaceStreamingCommand,\n} from \"@unified-latex/unified-latex-util-replace\";\n\nconst REPLACEMENTS: Record<\n    string,\n    (content: Ast.Node | Ast.Node[], originalMacro: Ast.Macro) => Ast.Macro\n> = {\n    color: colorToTextcolorMacro,\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) && !hasParbreak(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 fond color commands with arguments (e.g. \\`\\\\textcolor{red}{foo bar}\\`) over in-stream color commands\n(e.g. \\`{\\\\color{red} 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 unifiedLatexLintArgumentColorCommands = lintRule<\n    Ast.Root,\n    PluginOptions\n>(\n    { origin: \"unified-latex-lint:argument-color-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(\"...\"), node)\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](\n                                content,\n                                command\n                            );\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 (hasParbreak(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(\"...\"), node)\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](\n                                content,\n                                command\n                            );\n                        }\n                    );\n                }\n            },\n            { includeArrays: true, test: Array.isArray }\n        );\n    }\n);\n"],"mappings":";;;;;;;;;;;AAeA,IAAM,eAGF,EACA,OAAO,iDAAA,uBACV;AAED,IAAM,gBAAgB,wCAAA,MAAM,mBAAmB,aAAa;;;;AAK5D,SAAS,qCACL,OACkB;AAClB,KAAI,CAAC,wCAAA,MAAM,MAAM,MAAM,CACnB,QAAO;AAIX,QAAO,eAAA,GAAA,0CAAA,sBAD+C,MAAM,QAAQ,CACrC,IAAI,CAAC,qBAAA,YAAY,MAAM,QAAQ;;AAclE,IAAa,cAAc;;;;;;;iCAOM,OAAO,KAAK,aAAa,CACrD,KAAK,OAAA,GAAA,4CAAA,WAAA,GAAA,qCAAA,GAAiB,EAAE,CAAC,CAAC,CAC1B,KAAK,MAAM,CAAC;;AAGjB,IAAa,wCAAwC,YAAA,SAIjD,EAAE,QAAQ,8CAA8C,GACvD,MAAM,MAAM,YAAY;CACrB,MAAM,8BAAc,IAAI,KAAK;AAK7B,EAAA,GAAA,wCAAA,OACI,OACC,OAAO,SAAS;EACb,MAAM,QAAQ,MAAM;AACpB,OAAK,MAAM,QAAQ,MACf,KAAI,cAAc,KAAK,IAAI,CAAC,YAAY,IAAI,KAAK,EAAE;AAC/C,eAAY,IAAI,KAAK;GACrB,MAAM,YAAY,KAAK;AACvB,QAAK,QACD,aAAA,GAAA,4CAAA,UAAqB,MAAM,CAAC,WAAA,GAAA,4CAAA,UACxB,aAAa,YAAA,GAAA,qCAAA,GAAa,MAAM,EAAE,KAAK,CAC1C,CAAC,IACF,KACH;AACD;;AAIR,MAAI,SAAS,KAAK;GACd,IAAI,SAAA,GAAA,0CAAA,yBACA,OACA,gBACC,SAAS,YAAY;AAClB,WAAO,aAAa,QAAQ,SACxB,SACA,QACH;KAER;AAGD,OAAI,CAAC,KAAK,mBAAmB,KAAK,SAAS,KACvC;GAMJ,MAAM,YAAY,KAAK,gBAAgB,KAAK,QAAQ;GACpD,MAAM,YAAY,KAAK,gBAAgB,KAAK,QAAQ;AACpD,OACI,wCAAA,MAAM,eAAe,UAAU,IAC/B,wCAAA,MAAM,eAAe,MAAM,GAAG,CAE9B,EAAA,GAAA,uCAAA,WAAU,MAAM;AAEpB,OACI,wCAAA,MAAM,eAAe,UAAU,IAC/B,wCAAA,MAAM,eAAe,MAAM,MAAM,SAAS,GAAG,CAE7C,EAAA,GAAA,uCAAA,SAAQ,MAAM;AAElB,IAAA,GAAA,0CAAA,wBAAuB,OAAO,KAAK;;IAG3C,EAAE,MAAM,sCAAsC,CACjD;AAED,EAAA,GAAA,wCAAA,OACI,OACC,UAAU;AACP,MAAI,qBAAA,YAAY,MAAM,CAClB;EAGJ,IAAI,wBAAwB;AAC5B,OAAK,MAAM,QAAQ,MACf,KAAI,cAAc,KAAK,IAAI,CAAC,YAAY,IAAI,KAAK,EAAE;AAC/C,eAAY,IAAI,KAAK;AACrB,2BAAwB;GACxB,MAAM,YAAY,KAAK;AACvB,QAAK,QACD,aAAA,GAAA,4CAAA,UAAqB,MAAM,CAAC,WAAA,GAAA,4CAAA,UACxB,aAAa,YAAA,GAAA,qCAAA,GAAa,MAAM,EAAE,KAAK,CAC1C,CAAC,IACF,KACH;;AAIT,MAAI,yBAAyB,SAAS,IAElC,EAAA,GAAA,0CAAA,yBACI,OACA,gBACC,SAAS,YAAY;AAClB,UAAO,aAAa,QAAQ,SACxB,SACA,QACH;IAER;IAGT;EAAE,eAAe;EAAM,MAAM,MAAM;EAAS,CAC/C;EAER"}