mirror of
https://github.com/LukeHagar/prettier-plugin-openapi.git
synced 2025-12-06 04:21:03 +00:00
chore: update dependencies and full integrate markdown formatting with official prettier implementations
This commit is contained in:
110
bun.lock
110
bun.lock
@@ -4,20 +4,20 @@
|
||||
"": {
|
||||
"name": "prettier-plugin-openapi",
|
||||
"dependencies": {
|
||||
"@types/js-yaml": "^4.0.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"js-yaml": "^4.1.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.2.4",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.6",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^20.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"rollup": "^4.52.2",
|
||||
"@biomejs/biome": "^2.3.2",
|
||||
"@rollup/plugin-alias": "^6.0.0",
|
||||
"@rollup/plugin-commonjs": "^29.0.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@types/bun": "^1.3.1",
|
||||
"@types/node": "^24.9.2",
|
||||
"prettier": "^3.6.2",
|
||||
"rollup": "^4.52.5",
|
||||
"rollup-plugin-typescript": "^1.0.1",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript": "^5.9.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prettier": "^3.0.0",
|
||||
@@ -25,93 +25,93 @@
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@biomejs/biome": ["@biomejs/biome@2.2.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.4", "@biomejs/cli-darwin-x64": "2.2.4", "@biomejs/cli-linux-arm64": "2.2.4", "@biomejs/cli-linux-arm64-musl": "2.2.4", "@biomejs/cli-linux-x64": "2.2.4", "@biomejs/cli-linux-x64-musl": "2.2.4", "@biomejs/cli-win32-arm64": "2.2.4", "@biomejs/cli-win32-x64": "2.2.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg=="],
|
||||
"@biomejs/biome": ["@biomejs/biome@2.3.2", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.2", "@biomejs/cli-darwin-x64": "2.3.2", "@biomejs/cli-linux-arm64": "2.3.2", "@biomejs/cli-linux-arm64-musl": "2.3.2", "@biomejs/cli-linux-x64": "2.3.2", "@biomejs/cli-linux-x64-musl": "2.3.2", "@biomejs/cli-win32-arm64": "2.3.2", "@biomejs/cli-win32-x64": "2.3.2" }, "bin": { "biome": "bin/biome" } }, "sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg=="],
|
||||
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA=="],
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew=="],
|
||||
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg=="],
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw=="],
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ=="],
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw=="],
|
||||
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ=="],
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA=="],
|
||||
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg=="],
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA=="],
|
||||
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ=="],
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg=="],
|
||||
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg=="],
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@rollup/plugin-alias": ["@rollup/plugin-alias@5.1.1", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ=="],
|
||||
"@rollup/plugin-alias": ["@rollup/plugin-alias@6.0.0", "", { "peerDependencies": { "rollup": ">=4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g=="],
|
||||
|
||||
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.6", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw=="],
|
||||
"@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@29.0.0", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ=="],
|
||||
|
||||
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.1", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA=="],
|
||||
"@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.2", "", { "os": "android", "cpu": "arm" }, "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ=="],
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw=="],
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.5", "", { "os": "android", "cpu": "arm64" }, "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA=="],
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ=="],
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw=="],
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng=="],
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.2", "", { "os": "linux", "cpu": "arm" }, "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA=="],
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w=="],
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg=="],
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA=="],
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.2", "", { "os": "linux", "cpu": "none" }, "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw=="],
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A=="],
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.2", "", { "os": "linux", "cpu": "none" }, "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w=="],
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.2", "", { "os": "linux", "cpu": "none" }, "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q=="],
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.5", "", { "os": "linux", "cpu": "none" }, "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ=="],
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.2", "", { "os": "linux", "cpu": "x64" }, "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA=="],
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.2", "", { "os": "linux", "cpu": "x64" }, "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ=="],
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.5", "", { "os": "linux", "cpu": "x64" }, "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.2", "", { "os": "none", "cpu": "arm64" }, "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q=="],
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.5", "", { "os": "none", "cpu": "arm64" }, "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA=="],
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A=="],
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA=="],
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw=="],
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
|
||||
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
|
||||
|
||||
"@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],
|
||||
"@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
||||
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||
|
||||
"@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
|
||||
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||
|
||||
"commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
@@ -145,9 +145,9 @@
|
||||
|
||||
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||
|
||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"rollup": ["rollup@4.52.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.2", "@rollup/rollup-android-arm64": "4.52.2", "@rollup/rollup-darwin-arm64": "4.52.2", "@rollup/rollup-darwin-x64": "4.52.2", "@rollup/rollup-freebsd-arm64": "4.52.2", "@rollup/rollup-freebsd-x64": "4.52.2", "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", "@rollup/rollup-linux-arm-musleabihf": "4.52.2", "@rollup/rollup-linux-arm64-gnu": "4.52.2", "@rollup/rollup-linux-arm64-musl": "4.52.2", "@rollup/rollup-linux-loong64-gnu": "4.52.2", "@rollup/rollup-linux-ppc64-gnu": "4.52.2", "@rollup/rollup-linux-riscv64-gnu": "4.52.2", "@rollup/rollup-linux-riscv64-musl": "4.52.2", "@rollup/rollup-linux-s390x-gnu": "4.52.2", "@rollup/rollup-linux-x64-gnu": "4.52.2", "@rollup/rollup-linux-x64-musl": "4.52.2", "@rollup/rollup-openharmony-arm64": "4.52.2", "@rollup/rollup-win32-arm64-msvc": "4.52.2", "@rollup/rollup-win32-ia32-msvc": "4.52.2", "@rollup/rollup-win32-x64-gnu": "4.52.2", "@rollup/rollup-win32-x64-msvc": "4.52.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA=="],
|
||||
"rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="],
|
||||
|
||||
"rollup-plugin-typescript": ["rollup-plugin-typescript@1.0.1", "", { "dependencies": { "resolve": "^1.10.0", "rollup-pluginutils": "^2.5.0" }, "peerDependencies": { "tslib": "*", "typescript": ">=2.1.0" } }, "sha512-rwJDNn9jv/NsKZuyBb/h0jsclP4CJ58qbvZt2Q9zDIGILF2LtdtvCqMOL+Gq9IVq5MTrTlHZNrn8h7VjQgd8tw=="],
|
||||
|
||||
@@ -157,14 +157,10 @@
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"bun-types/@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="],
|
||||
|
||||
"bun-types/@types/node/undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
||||
}
|
||||
}
|
||||
|
||||
20
package.json
20
package.json
@@ -71,19 +71,19 @@
|
||||
"prettier": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.2.4",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.6",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^20.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"rollup": "^4.52.2",
|
||||
"@biomejs/biome": "^2.3.2",
|
||||
"@rollup/plugin-alias": "^6.0.0",
|
||||
"@rollup/plugin-commonjs": "^29.0.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@types/bun": "^1.3.1",
|
||||
"@types/node": "^24.9.2",
|
||||
"prettier": "^3.6.2",
|
||||
"rollup": "^4.52.5",
|
||||
"rollup-plugin-typescript": "^1.0.1",
|
||||
"typescript": "^5.0.0"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/js-yaml": "^4.0.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"js-yaml": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
1303
src/index.ts
1303
src/index.ts
File diff suppressed because it is too large
Load Diff
54
src/prettier-markdown/README.md
Normal file
54
src/prettier-markdown/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Prettier Markdown Integration
|
||||
|
||||
This directory contains Prettier's markdown language implementation, adapted for use within this plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
### Core Files (from Prettier)
|
||||
These files are copied from Prettier's `src/language-markdown` directory:
|
||||
- `parser-markdown.js` - Markdown parser
|
||||
- `printer-markdown.js` - Markdown printer
|
||||
- `clean.js` - AST cleaning utilities
|
||||
- `utils.js` - Utility functions
|
||||
- `constants.evaluate.js` - Constants
|
||||
- `print/` - Printing utilities
|
||||
- `unified-plugins/` - Unified/Remark plugins
|
||||
- Other supporting files
|
||||
|
||||
### Adapter Files (created for this plugin)
|
||||
These files adapt Prettier's internal dependencies:
|
||||
- `adapter-document-builders.js` - Adapts Prettier's document builders
|
||||
- `adapter-document-utils.js` - Adapts Prettier's document utilities
|
||||
- `adapter-document-constants.js` - Adapts Prettier's document constants
|
||||
- `adapter-prettier-utils.js` - Adapts Prettier's utility functions
|
||||
- `adapter-pragma.js` - Adapts pragma handling (simplified)
|
||||
|
||||
### Integration Files
|
||||
- `format-markdown.ts` - Type-safe wrapper for formatting markdown
|
||||
- `options.js` - Markdown formatting options (adapted from Prettier)
|
||||
|
||||
## Updating from Prettier
|
||||
|
||||
When Prettier updates its markdown implementation:
|
||||
|
||||
1. **Copy updated files** from `prettier/src/language-markdown/` to this directory
|
||||
2. **Update adapter files** if Prettier's internal structure changed:
|
||||
- Check if document builders path changed → update `adapter-document-builders.js`
|
||||
- Check if document utils path changed → update `adapter-document-utils.js`
|
||||
- Check if utility functions changed → update `adapter-prettier-utils.js`
|
||||
3. **Update imports** in copied files to use adapter files:
|
||||
- `../document/builders.js` → `./adapter-document-builders.js`
|
||||
- `../document/utils.js` → `./adapter-document-utils.js`
|
||||
- `../document/constants.js` → `./adapter-document-constants.js`
|
||||
- `../utils/*` → `./adapter-prettier-utils.js`
|
||||
- `../common/common-options.evaluate.js` → update `options.js`
|
||||
- `../main/front-matter/index.js` → update `adapter-pragma.js` or `clean.js`
|
||||
- `../utils/pragma/pragma.evaluate.js` → update `adapter-pragma.js`
|
||||
4. **Test** that markdown formatting still works correctly
|
||||
|
||||
## Dependencies
|
||||
|
||||
The adapter files attempt to access Prettier's internal APIs at runtime. If Prettier's internal structure changes significantly, you may need to update the adapter files to match.
|
||||
|
||||
The interfaces in the adapter files are designed to be similar to Prettier's actual structure, making updates easier.
|
||||
|
||||
55
src/prettier-markdown/adapter-document-builders.js
Normal file
55
src/prettier-markdown/adapter-document-builders.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Adapter for Prettier's document builders
|
||||
*
|
||||
* This file attempts to import Prettier's document builders from internal APIs.
|
||||
* Update this file when Prettier's internal structure changes.
|
||||
*/
|
||||
|
||||
let builders: any = null;
|
||||
|
||||
try {
|
||||
const prettier = require("prettier");
|
||||
|
||||
// Try multiple paths to access document builders
|
||||
if (prettier.__internal?.document?.builders) {
|
||||
builders = prettier.__internal.document.builders;
|
||||
} else {
|
||||
// Try to require directly (may work in plugin context)
|
||||
try {
|
||||
builders = require("prettier/internal/document/builders");
|
||||
} catch {
|
||||
// Fallback: try alternative paths
|
||||
try {
|
||||
const doc = require("prettier/doc");
|
||||
if (doc) {
|
||||
builders = doc;
|
||||
}
|
||||
} catch {
|
||||
// Not accessible
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Builders not accessible
|
||||
}
|
||||
|
||||
// Export what we found, or throw if not available
|
||||
if (!builders) {
|
||||
throw new Error(
|
||||
"Prettier document builders not accessible. " +
|
||||
"Markdown formatting requires Prettier's internal document builders."
|
||||
);
|
||||
}
|
||||
|
||||
export const {
|
||||
align,
|
||||
fill,
|
||||
group,
|
||||
hardline,
|
||||
indent,
|
||||
line,
|
||||
literalline,
|
||||
markAsRoot,
|
||||
softline,
|
||||
} = builders;
|
||||
|
||||
33
src/prettier-markdown/adapter-document-constants.js
Normal file
33
src/prettier-markdown/adapter-document-constants.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Adapter for Prettier's document constants
|
||||
*
|
||||
* This file attempts to import Prettier's document constants from internal APIs.
|
||||
* Update this file when Prettier's internal structure changes.
|
||||
*/
|
||||
|
||||
let constants: any = null;
|
||||
|
||||
try {
|
||||
const prettier = require("prettier");
|
||||
|
||||
if (prettier.__internal?.document?.constants) {
|
||||
constants = prettier.__internal.document.constants;
|
||||
} else {
|
||||
try {
|
||||
constants = require("prettier/internal/document/constants");
|
||||
} catch {
|
||||
// Constants may not be accessible, provide a fallback
|
||||
constants = {
|
||||
DOC_TYPE_STRING: "doc-type-string",
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Provide fallback
|
||||
constants = {
|
||||
DOC_TYPE_STRING: "doc-type-string",
|
||||
};
|
||||
}
|
||||
|
||||
export const { DOC_TYPE_STRING } = constants;
|
||||
|
||||
39
src/prettier-markdown/adapter-document-utils.js
Normal file
39
src/prettier-markdown/adapter-document-utils.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Adapter for Prettier's document utils
|
||||
*
|
||||
* This file attempts to import Prettier's document utilities from internal APIs.
|
||||
* Update this file when Prettier's internal structure changes.
|
||||
*/
|
||||
|
||||
let utils: any = null;
|
||||
|
||||
try {
|
||||
const prettier = require("prettier");
|
||||
|
||||
if (prettier.__internal?.document?.utils) {
|
||||
utils = prettier.__internal.document.utils;
|
||||
} else {
|
||||
try {
|
||||
utils = require("prettier/internal/document/utils");
|
||||
} catch {
|
||||
try {
|
||||
const docUtils = require("prettier/doc");
|
||||
utils = docUtils;
|
||||
} catch {
|
||||
// Not accessible
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Utils not accessible
|
||||
}
|
||||
|
||||
if (!utils) {
|
||||
throw new Error(
|
||||
"Prettier document utils not accessible. " +
|
||||
"Markdown formatting requires Prettier's internal document utilities."
|
||||
);
|
||||
}
|
||||
|
||||
export const { getDocType, replaceEndOfLine } = utils;
|
||||
|
||||
48
src/prettier-markdown/adapter-pragma.js
Normal file
48
src/prettier-markdown/adapter-pragma.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Adapter for Prettier's pragma utilities
|
||||
*
|
||||
* Simplified version that doesn't depend on Prettier's internal pragma system.
|
||||
* Update this file when Prettier's pragma behavior changes.
|
||||
*/
|
||||
|
||||
// Simplified pragma regexes based on Prettier's implementation
|
||||
const MARKDOWN_HAS_PRAGMA_REGEXP = /^<!--\s*@(prettier|format)\s*-->$/m;
|
||||
const MARKDOWN_HAS_IGNORE_PRAGMA_REGEXP = /^<!--\s*prettier-ignore(?:-(start|end))?\s*-->$/m;
|
||||
const FORMAT_PRAGMA_TO_INSERT = "format";
|
||||
|
||||
/**
|
||||
* Simple front matter parser (minimal implementation)
|
||||
*/
|
||||
function parseFrontMatter(text: string) {
|
||||
const yamlMatch = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
||||
if (yamlMatch) {
|
||||
return {
|
||||
content: text.slice(yamlMatch[0].length),
|
||||
frontMatter: {
|
||||
raw: yamlMatch[0],
|
||||
end: { index: yamlMatch[0].length },
|
||||
},
|
||||
};
|
||||
}
|
||||
return { content: text, frontMatter: null };
|
||||
}
|
||||
|
||||
const hasPragma = (text: string) =>
|
||||
parseFrontMatter(text).content.trimStart().match(MARKDOWN_HAS_PRAGMA_REGEXP)
|
||||
?.index === 0;
|
||||
|
||||
const hasIgnorePragma = (text: string) =>
|
||||
parseFrontMatter(text)
|
||||
.content.trimStart()
|
||||
.match(MARKDOWN_HAS_IGNORE_PRAGMA_REGEXP)?.index === 0;
|
||||
|
||||
const insertPragma = (text: string) => {
|
||||
const { frontMatter } = parseFrontMatter(text);
|
||||
const pragma = `<!-- @${FORMAT_PRAGMA_TO_INSERT} -->`;
|
||||
return frontMatter
|
||||
? `${frontMatter.raw}\n\n${pragma}\n\n${text.slice(frontMatter.end.index)}`
|
||||
: `${pragma}\n\n${text}`;
|
||||
};
|
||||
|
||||
export { hasIgnorePragma, hasPragma, insertPragma };
|
||||
|
||||
130
src/prettier-markdown/adapter-prettier-internals.js
Normal file
130
src/prettier-markdown/adapter-prettier-internals.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Adapter for Prettier's internal document builders and utilities
|
||||
*
|
||||
* This file attempts to access Prettier's internal APIs that are needed
|
||||
* by the markdown parser and printer. These adapters provide a type-safe
|
||||
* way to access Prettier internals when available.
|
||||
*
|
||||
* Note: This file can be updated when Prettier's internal structure changes.
|
||||
* The interfaces should remain similar to allow easy updates.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Attempts to import Prettier's document builders
|
||||
* @returns {Promise<typeof import('prettier/internal/document/builders')> | null>}
|
||||
*/
|
||||
export function getDocumentBuilders() {
|
||||
try {
|
||||
// Try to require Prettier's document builders
|
||||
// Prettier 3.x structure
|
||||
const prettier = require("prettier");
|
||||
|
||||
// Path 1: Check if accessible via __internal
|
||||
if (prettier.__internal?.document?.builders) {
|
||||
return prettier.__internal.document.builders;
|
||||
}
|
||||
|
||||
// Path 2: Try to require directly (may work in some contexts)
|
||||
try {
|
||||
return require("prettier/internal/document/builders");
|
||||
} catch {
|
||||
// Not accessible
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to import Prettier's document utils
|
||||
*/
|
||||
export function getDocumentUtils() {
|
||||
try {
|
||||
const prettier = require("prettier");
|
||||
|
||||
if (prettier.__internal?.document?.utils) {
|
||||
return prettier.__internal.document.utils;
|
||||
}
|
||||
|
||||
try {
|
||||
return require("prettier/internal/document/utils");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to import Prettier's document constants
|
||||
*/
|
||||
export function getDocumentConstants() {
|
||||
try {
|
||||
const prettier = require("prettier");
|
||||
|
||||
if (prettier.__internal?.document?.constants) {
|
||||
return prettier.__internal.document.constants;
|
||||
}
|
||||
|
||||
try {
|
||||
return require("prettier/internal/document/constants");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to import Prettier's utility functions
|
||||
*/
|
||||
export function getPrettierUtils() {
|
||||
try {
|
||||
const prettier = require("prettier");
|
||||
|
||||
if (prettier.__internal?.utils) {
|
||||
return prettier.__internal.utils;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get Prettier's doc-to-string printer
|
||||
* This is needed to convert Doc objects to strings
|
||||
*/
|
||||
export function getDocPrinter() {
|
||||
try {
|
||||
const prettier = require("prettier");
|
||||
|
||||
// Try multiple paths
|
||||
if (prettier.__internal?.doc?.printDocToString) {
|
||||
return prettier.__internal.doc.printDocToString;
|
||||
}
|
||||
|
||||
if (prettier.__internal?.docPrinter?.formatDoc) {
|
||||
return prettier.__internal.docPrinter.formatDoc;
|
||||
}
|
||||
|
||||
try {
|
||||
const docUtils = require("prettier/internal/doc");
|
||||
if (docUtils?.printDocToString) {
|
||||
return docUtils.printDocToString;
|
||||
}
|
||||
} catch {
|
||||
// Not accessible
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
72
src/prettier-markdown/adapter-prettier-utils.js
Normal file
72
src/prettier-markdown/adapter-prettier-utils.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Adapter for Prettier's utility functions
|
||||
*
|
||||
* This file attempts to import Prettier's utility functions from internal APIs.
|
||||
* Update this file when Prettier's internal structure changes.
|
||||
*/
|
||||
|
||||
let utils: any = null;
|
||||
|
||||
try {
|
||||
const prettier = require("prettier");
|
||||
|
||||
if (prettier.__internal?.utils) {
|
||||
utils = prettier.__internal.utils;
|
||||
} else {
|
||||
try {
|
||||
// Try alternative paths
|
||||
utils = require("prettier/internal/utils");
|
||||
} catch {
|
||||
// Utils may not be accessible
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Utils not accessible
|
||||
}
|
||||
|
||||
// Provide fallback implementations if utils aren't accessible
|
||||
if (!utils) {
|
||||
// Minimal fallback implementations
|
||||
utils = {
|
||||
getMaxContinuousCount: (str: string, char: string) => {
|
||||
let max = 0;
|
||||
let current = 0;
|
||||
for (const c of str) {
|
||||
if (c === char) {
|
||||
current++;
|
||||
max = Math.max(max, current);
|
||||
} else {
|
||||
current = 0;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
},
|
||||
getMinNotPresentContinuousCount: (str: string, char: string) => {
|
||||
let count = 1;
|
||||
while (str.includes(char.repeat(count))) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
},
|
||||
getPreferredQuote: (str: string, singleQuote: boolean) => {
|
||||
if (singleQuote) return "'";
|
||||
const hasSingle = str.includes("'");
|
||||
const hasDouble = str.includes('"');
|
||||
if (hasSingle && !hasDouble) return '"';
|
||||
return "'";
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const getMaxContinuousCount = utils.getMaxContinuousCount;
|
||||
export const getMinNotPresentContinuousCount = utils.getMinNotPresentContinuousCount;
|
||||
export const getPreferredQuote = utils.getPreferredQuote;
|
||||
|
||||
// UnexpectedNodeError may be in utils or separate
|
||||
export class UnexpectedNodeError extends Error {
|
||||
constructor(node: any, language: string) {
|
||||
super(`Unexpected node type: ${node.type} in ${language}`);
|
||||
this.name = "UnexpectedNodeError";
|
||||
}
|
||||
}
|
||||
|
||||
91
src/prettier-markdown/clean.js
Normal file
91
src/prettier-markdown/clean.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import collapseWhiteSpace from "collapse-white-space";
|
||||
import { hasPragma } from "./adapter-pragma.js";
|
||||
|
||||
// Simplified front matter check
|
||||
function isFrontMatter(node: any): boolean {
|
||||
return node?.type === "yaml" || node?.type === "toml";
|
||||
}
|
||||
|
||||
const ignoredProperties = new Set([
|
||||
"position",
|
||||
"raw", // front-matter
|
||||
]);
|
||||
function clean(original, cloned, parent) {
|
||||
// for codeblock
|
||||
if (
|
||||
original.type === "code" ||
|
||||
original.type === "yaml" ||
|
||||
original.type === "import" ||
|
||||
original.type === "export" ||
|
||||
original.type === "jsx"
|
||||
) {
|
||||
delete cloned.value;
|
||||
}
|
||||
|
||||
if (original.type === "list") {
|
||||
delete cloned.isAligned;
|
||||
}
|
||||
|
||||
if (original.type === "list" || original.type === "listItem") {
|
||||
delete cloned.spread;
|
||||
}
|
||||
|
||||
// texts can be splitted or merged
|
||||
if (original.type === "text") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (original.type === "inlineCode") {
|
||||
cloned.value = original.value.replaceAll("\n", " ");
|
||||
}
|
||||
|
||||
if (original.type === "wikiLink") {
|
||||
cloned.value = original.value.trim().replaceAll(/[\t\n]+/gu, " ");
|
||||
}
|
||||
|
||||
if (
|
||||
original.type === "definition" ||
|
||||
original.type === "linkReference" ||
|
||||
original.type === "imageReference"
|
||||
) {
|
||||
cloned.label = collapseWhiteSpace(original.label);
|
||||
}
|
||||
|
||||
if (
|
||||
(original.type === "link" || original.type === "image") &&
|
||||
original.url &&
|
||||
original.url.includes("(")
|
||||
) {
|
||||
for (const character of "<>") {
|
||||
cloned.url = original.url.replaceAll(
|
||||
character,
|
||||
encodeURIComponent(character),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(original.type === "definition" ||
|
||||
original.type === "link" ||
|
||||
original.type === "image") &&
|
||||
original.title
|
||||
) {
|
||||
cloned.title = original.title.replaceAll(/\\(?=["')])/gu, "");
|
||||
}
|
||||
|
||||
// for insert pragma
|
||||
if (
|
||||
parent?.type === "root" &&
|
||||
parent.children.length > 0 &&
|
||||
(parent.children[0] === original ||
|
||||
(isFrontMatter(parent.children[0]) && parent.children[1] === original)) &&
|
||||
original.type === "html" &&
|
||||
hasPragma(original.value)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
clean.ignoredProperties = ignoredProperties;
|
||||
|
||||
export default clean;
|
||||
86
src/prettier-markdown/constants.evaluate.js
Normal file
86
src/prettier-markdown/constants.evaluate.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import { all as getCjkCharset } from "cjk-regex";
|
||||
import { Charset } from "regexp-util";
|
||||
import unicodeRegex from "unicode-regex";
|
||||
|
||||
const cjkCharset = new Charset(
|
||||
getCjkCharset(),
|
||||
unicodeRegex({
|
||||
Script_Extensions: ["Han", "Katakana", "Hiragana", "Hangul", "Bopomofo"],
|
||||
General_Category: [
|
||||
"Other_Letter",
|
||||
"Letter_Number",
|
||||
"Other_Symbol",
|
||||
"Modifier_Letter",
|
||||
"Modifier_Symbol",
|
||||
"Nonspacing_Mark",
|
||||
],
|
||||
}),
|
||||
);
|
||||
const variationSelectorsCharset = unicodeRegex({
|
||||
Block: ["Variation_Selectors", "Variation_Selectors_Supplement"],
|
||||
});
|
||||
|
||||
const CJK_REGEXP = new RegExp(
|
||||
`(?:${cjkCharset.toString("u")})(?:${variationSelectorsCharset.toString("u")})?`,
|
||||
"u",
|
||||
);
|
||||
|
||||
const asciiPunctuationCharacters = [
|
||||
"!",
|
||||
'"',
|
||||
"#",
|
||||
"$",
|
||||
"%",
|
||||
"&",
|
||||
"'",
|
||||
"(",
|
||||
")",
|
||||
"*",
|
||||
"+",
|
||||
",",
|
||||
"-",
|
||||
".",
|
||||
"/",
|
||||
":",
|
||||
";",
|
||||
"<",
|
||||
"=",
|
||||
">",
|
||||
"?",
|
||||
"@",
|
||||
"[",
|
||||
"\\",
|
||||
"]",
|
||||
"^",
|
||||
"_",
|
||||
"`",
|
||||
"{",
|
||||
"|",
|
||||
"}",
|
||||
"~",
|
||||
];
|
||||
|
||||
// https://spec.commonmark.org/0.25/#punctuation-character
|
||||
// https://unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values
|
||||
const unicodePunctuationClasses = [
|
||||
/* Pc */ "Connector_Punctuation",
|
||||
/* Pd */ "Dash_Punctuation",
|
||||
/* Pe */ "Close_Punctuation",
|
||||
/* Pf */ "Final_Punctuation",
|
||||
/* Pi */ "Initial_Punctuation",
|
||||
/* Po */ "Other_Punctuation",
|
||||
/* Ps */ "Open_Punctuation",
|
||||
];
|
||||
|
||||
const PUNCTUATION_REGEXP = new RegExp(
|
||||
`(?:${[
|
||||
new Charset(...asciiPunctuationCharacters).toRegExp("u").source,
|
||||
...unicodePunctuationClasses.map(
|
||||
(charset) => String.raw`\p{General_Category=${charset}}`,
|
||||
"\u{ff5e}", // Used as a substitute for U+301C in Windows
|
||||
),
|
||||
].join("|")})`,
|
||||
"u",
|
||||
);
|
||||
|
||||
export { CJK_REGEXP, PUNCTUATION_REGEXP };
|
||||
87
src/prettier-markdown/embed.js
Normal file
87
src/prettier-markdown/embed.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { hardline, markAsRoot } from "../document/builders.js";
|
||||
import { replaceEndOfLine } from "../document/utils.js";
|
||||
import getMaxContinuousCount from "../utils/get-max-continuous-count.js";
|
||||
import inferParser from "../utils/infer-parser.js";
|
||||
import { getFencedCodeBlockValue } from "./utils.js";
|
||||
|
||||
function embed(path, options) {
|
||||
const { node } = path;
|
||||
|
||||
if (node.type === "code" && node.lang !== null) {
|
||||
const parser = inferParser(options, { language: node.lang });
|
||||
if (parser) {
|
||||
return async (textToDoc) => {
|
||||
const styleUnit = options.__inJsTemplate ? "~" : "`";
|
||||
const style = styleUnit.repeat(
|
||||
Math.max(3, getMaxContinuousCount(node.value, styleUnit) + 1),
|
||||
);
|
||||
const newOptions = { parser };
|
||||
|
||||
// Override the filepath option.
|
||||
// This is because whether the trailing comma of type parameters
|
||||
// should be printed depends on whether it is `*.ts` or `*.tsx`.
|
||||
// https://github.com/prettier/prettier/issues/15282
|
||||
if (node.lang === "ts" || node.lang === "typescript") {
|
||||
newOptions.filepath = "dummy.ts";
|
||||
} else if (node.lang === "tsx") {
|
||||
newOptions.filepath = "dummy.tsx";
|
||||
}
|
||||
|
||||
const doc = await textToDoc(
|
||||
getFencedCodeBlockValue(node, options.originalText),
|
||||
newOptions,
|
||||
);
|
||||
|
||||
return markAsRoot([
|
||||
style,
|
||||
node.lang,
|
||||
node.meta ? " " + node.meta : "",
|
||||
hardline,
|
||||
replaceEndOfLine(doc),
|
||||
hardline,
|
||||
style,
|
||||
]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
// MDX
|
||||
case "import":
|
||||
case "export":
|
||||
return (textToDoc) =>
|
||||
textToDoc(node.value, {
|
||||
// TODO: Rename this option since it's not used in HTML
|
||||
__onHtmlBindingRoot: (ast) => validateImportExport(ast, node.type),
|
||||
parser: "babel",
|
||||
});
|
||||
case "jsx":
|
||||
return (textToDoc) =>
|
||||
textToDoc(`<$>${node.value}</$>`, {
|
||||
parser: "__js_expression",
|
||||
rootMarker: "mdx",
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateImportExport(ast, type) {
|
||||
const {
|
||||
program: { body },
|
||||
} = ast;
|
||||
|
||||
// https://github.com/mdx-js/mdx/blob/3430138958c9c0344ecad9d59e0d6b5d72bedae3/packages/remark-mdx/extract-imports-and-exports.js#L16
|
||||
if (
|
||||
!body.every(
|
||||
(node) =>
|
||||
node.type === "ImportDeclaration" ||
|
||||
node.type === "ExportDefaultDeclaration" ||
|
||||
node.type === "ExportNamedDeclaration",
|
||||
)
|
||||
) {
|
||||
throw new Error(`Unexpected '${type}' in MDX.`);
|
||||
}
|
||||
}
|
||||
|
||||
export default embed;
|
||||
123
src/prettier-markdown/format-markdown.js
Normal file
123
src/prettier-markdown/format-markdown.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Type-safe wrapper for Prettier's markdown formatting
|
||||
*
|
||||
* This module provides a synchronous interface to Prettier's markdown
|
||||
* parser and printer, adapted to work within a Prettier plugin context.
|
||||
*/
|
||||
|
||||
const { markdown: markdownParser } = require("./parser-markdown.js");
|
||||
const printer = require("./printer-markdown.js");
|
||||
const { getDocPrinter } = require("./adapter-prettier-internals.js");
|
||||
|
||||
/**
|
||||
* Formats a markdown string using Prettier's markdown parser and printer
|
||||
*
|
||||
* @param {string} markdown - The markdown string to format
|
||||
* @param {Object} options - Formatting options
|
||||
* @param {number} [options.printWidth=80] - Maximum line width
|
||||
* @param {number} [options.tabWidth=2] - Tab width
|
||||
* @param {string} [options.proseWrap='preserve'] - Prose wrapping mode
|
||||
* @param {boolean} [options.singleQuote=false] - Use single quotes
|
||||
* @returns {string} The formatted markdown string, or the original if formatting fails
|
||||
*/
|
||||
function formatMarkdown(markdown, options = {}) {
|
||||
if (!markdown || typeof markdown !== "string") {
|
||||
return markdown;
|
||||
}
|
||||
|
||||
const trimmed = markdown.trim();
|
||||
if (trimmed.length === 0) {
|
||||
return markdown;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse markdown to AST
|
||||
const ast = markdownParser.parse(trimmed, {
|
||||
originalText: trimmed,
|
||||
filepath: "temp.md",
|
||||
printWidth: options.printWidth || 80,
|
||||
tabWidth: options.tabWidth || 2,
|
||||
proseWrap: options.proseWrap || "preserve",
|
||||
singleQuote: options.singleQuote || false,
|
||||
});
|
||||
|
||||
// Create an AstPath-like object for the printer
|
||||
const astPath = {
|
||||
getNode: () => ast,
|
||||
stack: [ast],
|
||||
node: ast,
|
||||
callParent: (fn) => fn(astPath),
|
||||
each: (fn) => {
|
||||
if (ast.children) {
|
||||
ast.children.forEach((child, index) => {
|
||||
const childPath = {
|
||||
getNode: () => child,
|
||||
stack: [...astPath.stack, child],
|
||||
node: child,
|
||||
index,
|
||||
previous: index > 0 ? ast.children[index - 1] : null,
|
||||
next: index < ast.children.length - 1 ? ast.children[index + 1] : null,
|
||||
parent: ast,
|
||||
isFirst: index === 0,
|
||||
isLast: index === ast.children.length - 1,
|
||||
callParent: (fn) => fn(childPath),
|
||||
};
|
||||
fn(childPath);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Create a print function for recursive printing
|
||||
const createPrintFn = (path) => {
|
||||
return (printPath) => {
|
||||
return printer.print(printPath, {
|
||||
printWidth: options.printWidth || 80,
|
||||
tabWidth: options.tabWidth || 2,
|
||||
proseWrap: options.proseWrap || "preserve",
|
||||
singleQuote: options.singleQuote || false,
|
||||
originalText: trimmed,
|
||||
}, createPrintFn);
|
||||
};
|
||||
};
|
||||
|
||||
// Print the AST to a Doc object
|
||||
const doc = printer.print(astPath, {
|
||||
printWidth: options.printWidth || 80,
|
||||
tabWidth: options.tabWidth || 2,
|
||||
proseWrap: options.proseWrap || "preserve",
|
||||
singleQuote: options.singleQuote || false,
|
||||
originalText: trimmed,
|
||||
}, createPrintFn);
|
||||
|
||||
// Convert Doc to string
|
||||
if (typeof doc === "string") {
|
||||
return doc.trimEnd();
|
||||
}
|
||||
|
||||
// Try to convert Doc object to string using Prettier's doc printer
|
||||
const docPrinter = getDocPrinter();
|
||||
if (docPrinter && typeof docPrinter === "function") {
|
||||
try {
|
||||
const formattedString = docPrinter(doc, {
|
||||
printWidth: options.printWidth || 80,
|
||||
tabWidth: options.tabWidth || 2,
|
||||
useTabs: false,
|
||||
});
|
||||
return typeof formattedString === "string" ? formattedString.trimEnd() : markdown;
|
||||
} catch {
|
||||
// Doc printing failed
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't convert Doc to string, return original
|
||||
return markdown;
|
||||
} catch (error) {
|
||||
// Parsing or printing failed, return original
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { formatMarkdown };
|
||||
|
||||
110
src/prettier-markdown/format-markdown.ts
Normal file
110
src/prettier-markdown/format-markdown.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Type-safe wrapper for Prettier's markdown formatting
|
||||
*
|
||||
* This module provides a synchronous interface to Prettier's markdown
|
||||
* parser and printer, adapted to work within a Prettier plugin context.
|
||||
*/
|
||||
|
||||
import type { ParserOptions } from "prettier";
|
||||
import {
|
||||
getDocPrinter,
|
||||
getDocumentBuilders,
|
||||
getDocumentConstants,
|
||||
getDocumentUtils,
|
||||
} from "./adapter-prettier-internals.js";
|
||||
import { markdown as markdownParser } from "./parser-markdown.js";
|
||||
import printer from "./printer-markdown.js";
|
||||
|
||||
interface MarkdownFormatOptions {
|
||||
printWidth?: number;
|
||||
tabWidth?: number;
|
||||
proseWrap?: "always" | "never" | "preserve";
|
||||
singleQuote?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a markdown string using Prettier's markdown parser and printer
|
||||
*
|
||||
* @param markdown - The markdown string to format
|
||||
* @param options - Formatting options
|
||||
* @returns The formatted markdown string, or the original if formatting fails
|
||||
*/
|
||||
export function formatMarkdown(markdown: string, options: MarkdownFormatOptions = {}): string {
|
||||
if (!markdown || typeof markdown !== "string") {
|
||||
return markdown;
|
||||
}
|
||||
|
||||
const trimmed = markdown.trim();
|
||||
if (trimmed.length === 0) {
|
||||
return markdown;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse markdown to AST
|
||||
const ast = markdownParser.parse(trimmed, {
|
||||
originalText: trimmed,
|
||||
filepath: "temp.md",
|
||||
} as ParserOptions);
|
||||
|
||||
// Create an AstPath-like object for the printer
|
||||
const astPath = {
|
||||
getNode: () => ast,
|
||||
stack: [ast],
|
||||
callParent: (fn: (path: any) => any) => fn(astPath),
|
||||
each: (fn: (path: any) => void) => {
|
||||
if (ast.children) {
|
||||
ast.children.forEach((child: any, index: number) => {
|
||||
const childPath = {
|
||||
getNode: () => child,
|
||||
stack: [...astPath.stack, child],
|
||||
index,
|
||||
previous: index > 0 ? ast.children[index - 1] : null,
|
||||
next: index < ast.children.length - 1 ? ast.children[index + 1] : null,
|
||||
parent: ast,
|
||||
isFirst: index === 0,
|
||||
isLast: index === ast.children.length - 1,
|
||||
};
|
||||
fn(childPath);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Create a print function for recursive printing
|
||||
const createPrintFn = (path: any): any => {
|
||||
return (printPath: any) => {
|
||||
return printer.print(printPath, options as ParserOptions, createPrintFn);
|
||||
};
|
||||
};
|
||||
|
||||
// Print the AST to a Doc object
|
||||
const doc = printer.print(astPath, options as ParserOptions, createPrintFn);
|
||||
|
||||
// Convert Doc to string
|
||||
if (typeof doc === "string") {
|
||||
return doc.trimEnd();
|
||||
}
|
||||
|
||||
// Try to convert Doc object to string using Prettier's doc printer
|
||||
const docPrinter = getDocPrinter();
|
||||
if (docPrinter && typeof docPrinter === "function") {
|
||||
try {
|
||||
const formattedString = docPrinter(doc, {
|
||||
printWidth: options.printWidth || 80,
|
||||
tabWidth: options.tabWidth || 2,
|
||||
useTabs: false,
|
||||
});
|
||||
return typeof formattedString === "string" ? formattedString.trimEnd() : markdown;
|
||||
} catch {
|
||||
// Doc printing failed
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't convert Doc to string, return original
|
||||
return markdown;
|
||||
} catch (error) {
|
||||
// Parsing or printing failed, return original
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
6
src/prettier-markdown/get-visitor-keys.js
Normal file
6
src/prettier-markdown/get-visitor-keys.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import createGetVisitorKeys from "../utils/create-get-visitor-keys.js";
|
||||
import visitorKeys from "./visitor-keys.js";
|
||||
|
||||
const getVisitorKeys = createGetVisitorKeys(visitorKeys);
|
||||
|
||||
export default getVisitorKeys;
|
||||
8
src/prettier-markdown/index.js
Normal file
8
src/prettier-markdown/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import printer from "./printer-markdown.js";
|
||||
|
||||
export const printers = {
|
||||
mdast: printer,
|
||||
};
|
||||
export { default as languages } from "./languages.evaluate.js";
|
||||
export { default as options } from "./options.js";
|
||||
export * as parsers from "./parser-markdown.js";
|
||||
20
src/prettier-markdown/languages.evaluate.js
Normal file
20
src/prettier-markdown/languages.evaluate.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as linguistLanguages from "linguist-languages";
|
||||
import createLanguage from "../utils/create-language.js";
|
||||
|
||||
const languages = [
|
||||
createLanguage(linguistLanguages.Markdown, (data) => ({
|
||||
parsers: ["markdown"],
|
||||
vscodeLanguageIds: ["markdown"],
|
||||
filenames: [...data.filenames, "README"],
|
||||
extensions: data.extensions.filter((extension) => extension !== ".mdx"),
|
||||
})),
|
||||
createLanguage(linguistLanguages.Markdown, () => ({
|
||||
name: "MDX",
|
||||
parsers: ["mdx"],
|
||||
vscodeLanguageIds: ["mdx"],
|
||||
filenames: [],
|
||||
extensions: [".mdx"],
|
||||
})),
|
||||
];
|
||||
|
||||
export default languages;
|
||||
4
src/prettier-markdown/loc.js
Normal file
4
src/prettier-markdown/loc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const locStart = (node) => node.position.start.offset;
|
||||
const locEnd = (node) => node.position.end.offset;
|
||||
|
||||
export { locEnd, locStart };
|
||||
83
src/prettier-markdown/mdx.js
Normal file
83
src/prettier-markdown/mdx.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* modified from https://github.com/mdx-js/mdx/blob/c91b00c673bcf3e7c28b861fd692b69016026c45/packages/remark-mdx/index.js
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017-2018 Compositor and Zeit, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const IMPORT_REGEX = /^import\s/u;
|
||||
const EXPORT_REGEX = /^export\s/u;
|
||||
const BLOCKS_REGEX = String.raw`[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*|`;
|
||||
const COMMENT_REGEX = /<!---->|<!---?[^>-](?:-?[^-])*-->/u;
|
||||
const ES_COMMENT_REGEX = /^\{\s*\/\*(.*)\*\/\s*\}/u;
|
||||
const EMPTY_NEWLINE = "\n\n";
|
||||
|
||||
const isImport = (text) => IMPORT_REGEX.test(text);
|
||||
const isExport = (text) => EXPORT_REGEX.test(text);
|
||||
const isImportOrExport = (text) => isImport(text) || isExport(text);
|
||||
|
||||
const tokenizeEsSyntax = (eat, value) => {
|
||||
const index = value.indexOf(EMPTY_NEWLINE);
|
||||
const subvalue = index === -1 ? value : value.slice(0, index);
|
||||
|
||||
if (isImportOrExport(subvalue)) {
|
||||
return eat(subvalue)({
|
||||
type: isExport(subvalue) ? "export" : "import",
|
||||
value: subvalue,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
tokenizeEsSyntax.notInBlock = true;
|
||||
|
||||
tokenizeEsSyntax.locator = (value /* , fromIndex*/) =>
|
||||
isImportOrExport(value) ? -1 : 1;
|
||||
|
||||
const tokenizeEsComment = (eat, value) => {
|
||||
const match = ES_COMMENT_REGEX.exec(value);
|
||||
|
||||
if (match) {
|
||||
return eat(match[0])({
|
||||
type: "esComment",
|
||||
value: match[1].trim(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
tokenizeEsComment.locator = (value, fromIndex) => value.indexOf("{", fromIndex);
|
||||
|
||||
/** @import {Plugin, Settings} from "unified" */
|
||||
|
||||
/**
|
||||
* @type {Plugin<[], Settings>}
|
||||
*/
|
||||
const esSyntax = function () {
|
||||
const { Parser } = this;
|
||||
const { blockTokenizers, blockMethods, inlineTokenizers, inlineMethods } =
|
||||
Parser.prototype;
|
||||
|
||||
blockTokenizers.esSyntax = tokenizeEsSyntax;
|
||||
inlineTokenizers.esComment = tokenizeEsComment;
|
||||
|
||||
blockMethods.splice(blockMethods.indexOf("paragraph"), 0, "esSyntax");
|
||||
inlineMethods.splice(inlineMethods.indexOf("text"), 0, "esComment");
|
||||
};
|
||||
|
||||
export { BLOCKS_REGEX, COMMENT_REGEX, esSyntax };
|
||||
20
src/prettier-markdown/options.js
Normal file
20
src/prettier-markdown/options.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// Options for markdown formatting
|
||||
// These match Prettier's default markdown options
|
||||
// Update this file if Prettier's markdown options change
|
||||
const options = {
|
||||
proseWrap: {
|
||||
type: "choice",
|
||||
default: "preserve",
|
||||
choices: [
|
||||
{ value: "always", description: "Wrap prose if it exceeds the print width" },
|
||||
{ value: "never", description: "Don't wrap prose" },
|
||||
{ value: "preserve", description: "Preserve the original wrapping" },
|
||||
],
|
||||
},
|
||||
singleQuote: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default options;
|
||||
57
src/prettier-markdown/parser-markdown.js
Normal file
57
src/prettier-markdown/parser-markdown.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import footnotes from "remark-footnotes";
|
||||
import remarkMath from "remark-math";
|
||||
import remarkParse from "remark-parse";
|
||||
import unified from "unified";
|
||||
import { locEnd, locStart } from "./loc.js";
|
||||
import { BLOCKS_REGEX, esSyntax } from "./mdx.js";
|
||||
import { hasIgnorePragma, hasPragma } from "./pragma.js";
|
||||
import frontMatter from "./unified-plugins/front-matter.js";
|
||||
import htmlToJsx from "./unified-plugins/html-to-jsx.js";
|
||||
import liquid from "./unified-plugins/liquid.js";
|
||||
import wikiLink from "./unified-plugins/wiki-link.js";
|
||||
|
||||
/**
|
||||
* based on [MDAST](https://github.com/syntax-tree/mdast) with following modifications:
|
||||
*
|
||||
* 1. restore unescaped character (Text)
|
||||
* 2. merge continuous Texts
|
||||
* 3. replace whitespaces in InlineCode#value with one whitespace
|
||||
* reference: http://spec.commonmark.org/0.25/#example-605
|
||||
* 4. split Text into Sentence
|
||||
*
|
||||
* interface Word { value: string }
|
||||
* interface Whitespace { value: string }
|
||||
* interface Sentence { children: Array<Word | Whitespace> }
|
||||
* interface InlineCode { children: Array<Sentence> }
|
||||
*/
|
||||
function createParse({ isMDX }) {
|
||||
return (text) => {
|
||||
const processor = unified()
|
||||
.use(remarkParse, {
|
||||
commonmark: true,
|
||||
...(isMDX && { blocks: [BLOCKS_REGEX] }),
|
||||
})
|
||||
.use(footnotes)
|
||||
.use(frontMatter)
|
||||
.use(remarkMath)
|
||||
.use(isMDX ? esSyntax : noop)
|
||||
.use(liquid)
|
||||
.use(isMDX ? htmlToJsx : noop)
|
||||
.use(wikiLink);
|
||||
return processor.run(processor.parse(text));
|
||||
};
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
const baseParser = {
|
||||
astFormat: "mdast",
|
||||
hasPragma,
|
||||
hasIgnorePragma,
|
||||
locStart,
|
||||
locEnd,
|
||||
};
|
||||
|
||||
export const markdown = { ...baseParser, parse: createParse({ isMDX: false }) };
|
||||
export const mdx = { ...baseParser, parse: createParse({ isMDX: true }) };
|
||||
export { markdown as remark };
|
||||
25
src/prettier-markdown/pragma.js
Normal file
25
src/prettier-markdown/pragma.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { parseFrontMatter } from "../main/front-matter/index.js";
|
||||
import {
|
||||
FORMAT_PRAGMA_TO_INSERT,
|
||||
MARKDOWN_HAS_IGNORE_PRAGMA_REGEXP,
|
||||
MARKDOWN_HAS_PRAGMA_REGEXP,
|
||||
} from "../utils/pragma/pragma.evaluate.js";
|
||||
|
||||
const hasPragma = (text) =>
|
||||
parseFrontMatter(text).content.trimStart().match(MARKDOWN_HAS_PRAGMA_REGEXP)
|
||||
?.index === 0;
|
||||
|
||||
const hasIgnorePragma = (text) =>
|
||||
parseFrontMatter(text)
|
||||
.content.trimStart()
|
||||
.match(MARKDOWN_HAS_IGNORE_PRAGMA_REGEXP)?.index === 0;
|
||||
|
||||
const insertPragma = (text) => {
|
||||
const { frontMatter } = parseFrontMatter(text);
|
||||
const pragma = `<!-- @${FORMAT_PRAGMA_TO_INSERT} -->`;
|
||||
return frontMatter
|
||||
? `${frontMatter.raw}\n\n${pragma}\n\n${text.slice(frontMatter.end.index)}`
|
||||
: `${pragma}\n\n${text}`;
|
||||
};
|
||||
|
||||
export { hasIgnorePragma, hasPragma, insertPragma };
|
||||
55
src/prettier-markdown/print-paragraph.js
Normal file
55
src/prettier-markdown/print-paragraph.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { fill } from "../document/builders.js";
|
||||
import { DOC_TYPE_ARRAY, DOC_TYPE_FILL } from "../document/constants.js";
|
||||
import { getDocType } from "../document/utils.js";
|
||||
|
||||
/**
|
||||
* @import AstPath from "../common/ast-path.js"
|
||||
* @import {Doc} from "../document/builders.js"
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {AstPath} path
|
||||
* @param {*} options
|
||||
* @param {*} print
|
||||
* @returns {Doc}
|
||||
*/
|
||||
function printParagraph(path, options, print) {
|
||||
const parts = path.map(print, "children");
|
||||
return flattenFill(parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Doc[]} docs
|
||||
* @returns {Doc}
|
||||
*/
|
||||
function flattenFill(docs) {
|
||||
/*
|
||||
* We assume parts always meet following conditions:
|
||||
* - parts.length is odd
|
||||
* - odd elements are line-like doc that comes from odd element off inner fill
|
||||
*/
|
||||
/** @type {Doc[]} */
|
||||
const parts = [""];
|
||||
|
||||
(function rec(/** @type {*} */ docArray) {
|
||||
for (const doc of docArray) {
|
||||
const docType = getDocType(doc);
|
||||
if (docType === DOC_TYPE_ARRAY) {
|
||||
rec(doc);
|
||||
continue;
|
||||
}
|
||||
|
||||
let head = doc;
|
||||
let rest = [];
|
||||
if (docType === DOC_TYPE_FILL) {
|
||||
[head, ...rest] = doc.parts;
|
||||
}
|
||||
|
||||
parts.push([parts.pop(), head], ...rest);
|
||||
}
|
||||
})(docs);
|
||||
|
||||
return fill(parts);
|
||||
}
|
||||
|
||||
export { printParagraph };
|
||||
256
src/prettier-markdown/print-preprocess.js
Normal file
256
src/prettier-markdown/print-preprocess.js
Normal file
@@ -0,0 +1,256 @@
|
||||
import htmlWhitespaceUtils from "../utils/html-whitespace-utils.js";
|
||||
import { getOrderedListItemInfo, mapAst, splitText } from "./utils.js";
|
||||
|
||||
// 0x0 ~ 0x10ffff
|
||||
const isSingleCharRegex = /^\\?.$/su;
|
||||
const isNewLineBlockquoteRegex = /^\n *>[ >]*$/u;
|
||||
|
||||
function preprocess(ast, options) {
|
||||
ast = restoreUnescapedCharacter(ast, options);
|
||||
ast = mergeContinuousTexts(ast);
|
||||
ast = transformIndentedCodeblockAndMarkItsParentList(ast, options);
|
||||
ast = markAlignedList(ast, options);
|
||||
ast = splitTextIntoSentences(ast);
|
||||
return ast;
|
||||
}
|
||||
|
||||
function restoreUnescapedCharacter(ast, options) {
|
||||
return mapAst(ast, (node) => {
|
||||
if (node.type !== "text") {
|
||||
return node;
|
||||
}
|
||||
|
||||
const { value } = node;
|
||||
|
||||
if (
|
||||
value === "*" ||
|
||||
value === "_" || // handle these cases in printer
|
||||
!isSingleCharRegex.test(value) ||
|
||||
node.position.end.offset - node.position.start.offset === value.length
|
||||
) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const text = options.originalText.slice(
|
||||
node.position.start.offset,
|
||||
node.position.end.offset,
|
||||
);
|
||||
|
||||
if (isNewLineBlockquoteRegex.test(text)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
return { ...node, value: text };
|
||||
});
|
||||
}
|
||||
|
||||
function mergeChildren(ast, shouldMerge, mergeNode) {
|
||||
return mapAst(ast, (node) => {
|
||||
if (!node.children) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const children = [];
|
||||
let lastChild;
|
||||
let changed;
|
||||
for (let child of node.children) {
|
||||
if (lastChild && shouldMerge(lastChild, child)) {
|
||||
child = mergeNode(lastChild, child);
|
||||
// Replace the previous node
|
||||
children.splice(-1, 1, child);
|
||||
changed ||= true;
|
||||
} else {
|
||||
children.push(child);
|
||||
}
|
||||
|
||||
lastChild = child;
|
||||
}
|
||||
|
||||
return changed ? { ...node, children } : node;
|
||||
});
|
||||
}
|
||||
|
||||
function mergeContinuousTexts(ast) {
|
||||
return mergeChildren(
|
||||
ast,
|
||||
(prevNode, node) => prevNode.type === "text" && node.type === "text",
|
||||
(prevNode, node) => ({
|
||||
type: "text",
|
||||
value: prevNode.value + node.value,
|
||||
position: {
|
||||
start: prevNode.position.start,
|
||||
end: node.position.end,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function splitTextIntoSentences(ast) {
|
||||
return mapAst(ast, (node, index, [parentNode]) => {
|
||||
if (node.type !== "text") {
|
||||
return node;
|
||||
}
|
||||
|
||||
let { value } = node;
|
||||
|
||||
if (parentNode.type === "paragraph") {
|
||||
// CommonMark doesn't remove trailing/leading \f, but it should be
|
||||
// removed in the HTML rendering process
|
||||
if (index === 0) {
|
||||
value = htmlWhitespaceUtils.trimStart(value);
|
||||
}
|
||||
if (index === parentNode.children.length - 1) {
|
||||
value = htmlWhitespaceUtils.trimEnd(value);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: "sentence",
|
||||
position: node.position,
|
||||
children: splitText(value),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function transformIndentedCodeblockAndMarkItsParentList(ast, options) {
|
||||
return mapAst(ast, (node, index, parentStack) => {
|
||||
if (node.type === "code") {
|
||||
// the first char may point to `\n`, e.g. `\n\t\tbar`, just ignore it
|
||||
const isIndented = /^\n?(?: {4,}|\t)/u.test(
|
||||
options.originalText.slice(
|
||||
node.position.start.offset,
|
||||
node.position.end.offset,
|
||||
),
|
||||
);
|
||||
|
||||
node.isIndented = isIndented;
|
||||
|
||||
if (isIndented) {
|
||||
for (let i = 0; i < parentStack.length; i++) {
|
||||
const parent = parentStack[i];
|
||||
|
||||
// no need to check checked items
|
||||
if (parent.hasIndentedCodeblock) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (parent.type === "list") {
|
||||
parent.hasIndentedCodeblock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
function markAlignedList(ast, options) {
|
||||
return mapAst(ast, (node, index, parentStack) => {
|
||||
if (node.type === "list" && node.children.length > 0) {
|
||||
// if one of its parents is not aligned, it's not possible to be aligned in sub-lists
|
||||
for (let i = 0; i < parentStack.length; i++) {
|
||||
const parent = parentStack[i];
|
||||
if (parent.type === "list" && !parent.isAligned) {
|
||||
node.isAligned = false;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
node.isAligned = isAligned(node);
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
|
||||
function getListItemStart(listItem) {
|
||||
return listItem.children.length === 0
|
||||
? -1
|
||||
: listItem.children[0].position.start.column - 1;
|
||||
}
|
||||
|
||||
function isAligned(list) {
|
||||
if (!list.ordered) {
|
||||
/**
|
||||
* - 123
|
||||
* - 123
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
const [firstItem, secondItem] = list.children;
|
||||
|
||||
const firstInfo = getOrderedListItemInfo(firstItem, options);
|
||||
|
||||
if (firstInfo.leadingSpaces.length > 1) {
|
||||
/**
|
||||
* 1. 123
|
||||
*
|
||||
* 1. 123
|
||||
* 1. 123
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
const firstStart = getListItemStart(firstItem);
|
||||
|
||||
if (firstStart === -1) {
|
||||
/**
|
||||
* 1.
|
||||
*
|
||||
* 1.
|
||||
* 1.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
if (list.children.length === 1) {
|
||||
/**
|
||||
* aligned:
|
||||
*
|
||||
* 11. 123
|
||||
*
|
||||
* not aligned:
|
||||
*
|
||||
* 1. 123
|
||||
*/
|
||||
return firstStart % options.tabWidth === 0;
|
||||
}
|
||||
|
||||
const secondStart = getListItemStart(secondItem);
|
||||
|
||||
if (firstStart !== secondStart) {
|
||||
/**
|
||||
* 11. 123
|
||||
* 1. 123
|
||||
*
|
||||
* 1. 123
|
||||
* 11. 123
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
if (firstStart % options.tabWidth === 0) {
|
||||
/**
|
||||
* 11. 123
|
||||
* 12. 123
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* aligned:
|
||||
*
|
||||
* 11. 123
|
||||
* 1. 123
|
||||
*
|
||||
* not aligned:
|
||||
*
|
||||
* 1. 123
|
||||
* 2. 123
|
||||
*/
|
||||
const secondInfo = getOrderedListItemInfo(secondItem, options);
|
||||
return secondInfo.leadingSpaces.length > 1;
|
||||
}
|
||||
}
|
||||
|
||||
export default preprocess;
|
||||
37
src/prettier-markdown/print-sentence.js
Normal file
37
src/prettier-markdown/print-sentence.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @import AstPath from "../common/ast-path.js"
|
||||
* @import {Doc} from "../document/builders.js"
|
||||
*/
|
||||
|
||||
import { fill } from "../document/builders.js";
|
||||
import { DOC_TYPE_STRING } from "../document/constants.js";
|
||||
import { getDocType } from "../document/utils.js";
|
||||
|
||||
/**
|
||||
* @param {AstPath} path
|
||||
* @param {*} print
|
||||
* @returns {Doc}
|
||||
*/
|
||||
function printSentence(path, print) {
|
||||
/** @type {Doc[]} */
|
||||
const parts = [""];
|
||||
|
||||
path.each(() => {
|
||||
const { node } = path;
|
||||
const doc = print();
|
||||
switch (node.type) {
|
||||
case "whitespace":
|
||||
if (getDocType(doc) !== DOC_TYPE_STRING) {
|
||||
parts.push(doc, "");
|
||||
break;
|
||||
}
|
||||
// fallthrough
|
||||
default:
|
||||
parts.push([parts.pop(), doc]);
|
||||
}
|
||||
}, "children");
|
||||
|
||||
return fill(parts);
|
||||
}
|
||||
|
||||
export { printSentence };
|
||||
266
src/prettier-markdown/print-whitespace.js
Normal file
266
src/prettier-markdown/print-whitespace.js
Normal file
@@ -0,0 +1,266 @@
|
||||
import { hardline, line, softline } from "../document/builders.js";
|
||||
import {
|
||||
KIND_CJ_LETTER,
|
||||
KIND_CJK_PUNCTUATION,
|
||||
KIND_K_LETTER,
|
||||
KIND_NON_CJK,
|
||||
} from "./utils.js";
|
||||
|
||||
/**
|
||||
* @import {WordNode, WhitespaceValue, WordKind} from "./utils.js"
|
||||
* @import AstPath from "../common/ast-path.js"
|
||||
* @typedef {"always" | "never" | "preserve"} ProseWrap
|
||||
* @typedef {{ next?: WordNode | null, previous?: WordNode | null }}
|
||||
* AdjacentNodes Nodes adjacent to a `whitespace` node. Are always of type
|
||||
* `word`.
|
||||
*/
|
||||
|
||||
const SINGLE_LINE_NODE_TYPES = new Set([
|
||||
"heading",
|
||||
"tableCell",
|
||||
"link",
|
||||
"wikiLink",
|
||||
]);
|
||||
|
||||
/**
|
||||
* A line break between a character from this set and CJ can be converted to a
|
||||
* space. Includes only ASCII punctuation marks for now.
|
||||
*/
|
||||
const lineBreakBetweenTheseAndCJConvertsToSpace = new Set(
|
||||
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
|
||||
);
|
||||
|
||||
/**
|
||||
* Determine the preferred style of spacing between Chinese or Japanese and non-CJK
|
||||
* characters in the parent `sentence` node.
|
||||
*
|
||||
* @param {AstPath} path
|
||||
* @returns {boolean} `true` if Space tends to be inserted between CJ and
|
||||
* non-CJK, `false` otherwise.
|
||||
*/
|
||||
function isInSentenceWithCJSpaces({ parent: sentenceNode }) {
|
||||
if (sentenceNode.usesCJSpaces === undefined) {
|
||||
const stats = { " ": 0, "": 0 };
|
||||
const { children } = sentenceNode;
|
||||
|
||||
for (let i = 1; i < children.length - 1; ++i) {
|
||||
const node = children[i];
|
||||
if (
|
||||
node.type === "whitespace" &&
|
||||
(node.value === " " || node.value === "")
|
||||
) {
|
||||
const previousKind = children[i - 1].kind;
|
||||
const nextKind = children[i + 1].kind;
|
||||
if (
|
||||
(previousKind === KIND_CJ_LETTER && nextKind === KIND_NON_CJK) ||
|
||||
(previousKind === KIND_NON_CJK && nextKind === KIND_CJ_LETTER)
|
||||
) {
|
||||
++stats[node.value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inject a property to cache the result.
|
||||
sentenceNode.usesCJSpaces = stats[" "] > stats[""];
|
||||
}
|
||||
|
||||
return sentenceNode.usesCJSpaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given `"\n"` node can be converted to a space.
|
||||
*
|
||||
* For example, if you would like to squash English text
|
||||
*
|
||||
* "You might want\nto use Prettier."
|
||||
*
|
||||
* into a single line, you would replace `"\n"` with `" "`:
|
||||
*
|
||||
* "You might want to use Prettier."
|
||||
*
|
||||
* However, Chinese and Japanese don't use U+0020 Space to divide words, so line
|
||||
* breaks shouldn't be replaced with spaces for those languages.
|
||||
*
|
||||
* PRs are welcome to support line breaking rules for other languages.
|
||||
*
|
||||
* @param {AstPath} path
|
||||
* @param {boolean} isLink
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function lineBreakCanBeConvertedToSpace(path, isLink) {
|
||||
if (isLink) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @type {AdjacentNodes} */
|
||||
const { previous, next } = path;
|
||||
|
||||
// e.g. " \nletter"
|
||||
if (!previous || !next) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const previousKind = previous.kind;
|
||||
const nextKind = next.kind;
|
||||
|
||||
if (
|
||||
// "\n" between non-CJK or Korean characters always can be converted to a
|
||||
// space. Korean Hangul simulates Latin words. See
|
||||
// https://github.com/prettier/prettier/issues/6516
|
||||
(isNonCJKOrKoreanLetter(previousKind) &&
|
||||
isNonCJKOrKoreanLetter(nextKind)) ||
|
||||
// Han & Hangul: same way preferred
|
||||
(previousKind === KIND_K_LETTER && nextKind === KIND_CJ_LETTER) ||
|
||||
(nextKind === KIND_K_LETTER && previousKind === KIND_CJ_LETTER)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not convert \n to a space:
|
||||
if (
|
||||
// around CJK punctuation
|
||||
previousKind === KIND_CJK_PUNCTUATION ||
|
||||
nextKind === KIND_CJK_PUNCTUATION ||
|
||||
// between CJ
|
||||
(previousKind === KIND_CJ_LETTER && nextKind === KIND_CJ_LETTER)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The rest of this function deals only with line breaks between CJ and
|
||||
// non-CJK characters.
|
||||
|
||||
// Convert a line break between CJ and certain non-letter characters (e.g.
|
||||
// ASCII punctuation) to a space.
|
||||
//
|
||||
// E.g. :::\n句子句子句子\n::: → ::: 句子句子句子 :::
|
||||
//
|
||||
// Note: line breaks like "(\n句子句子\n)" or "句子\n." are suppressed in
|
||||
// `isBreakable(...)`.
|
||||
if (
|
||||
lineBreakBetweenTheseAndCJConvertsToSpace.has(next.value[0]) ||
|
||||
lineBreakBetweenTheseAndCJConvertsToSpace.has(previous.value.at(-1))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Converting a line break between CJ and non-ASCII punctuation to a space is
|
||||
// undesired in many cases. PRs are welcome to fine-tune this logic.
|
||||
//
|
||||
// Examples where \n must not be converted to a space:
|
||||
//
|
||||
// 1. "〜" (U+301C, belongs to Pd) in
|
||||
//
|
||||
// "ア〜\nエの中から1つ選べ。"
|
||||
//
|
||||
// 2. "…" (U+2026, belongs to Po) in
|
||||
//
|
||||
// "これはひどい……\nなんと汚いコミットログなんだ……"
|
||||
if (previous.hasTrailingPunctuation || next.hasLeadingPunctuation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the sentence uses the style with spaces between CJ and non-CJK, "\n" can
|
||||
// be converted to a space.
|
||||
return isInSentenceWithCJSpaces(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WordKind | undefined} kind
|
||||
* @returns {boolean} `true` if `kind` is Korean letter or non-CJK
|
||||
*/
|
||||
function isNonCJKOrKoreanLetter(kind) {
|
||||
return kind === KIND_NON_CJK || kind === KIND_K_LETTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether whitespace can be printed as a line break.
|
||||
*
|
||||
* @param {AstPath} path
|
||||
* @param {WhitespaceValue} value
|
||||
* @param {ProseWrap} proseWrap
|
||||
* @param {boolean} isLink
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isBreakable(path, value, proseWrap, isLink) {
|
||||
if (
|
||||
proseWrap !== "always" ||
|
||||
path.hasAncestor((node) => SINGLE_LINE_NODE_TYPES.has(node.type))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isLink) {
|
||||
return value !== "";
|
||||
}
|
||||
|
||||
/** @type {AdjacentNodes} */
|
||||
const { previous, next } = path;
|
||||
|
||||
// [1]: We will make a breaking change to the rule to convert spaces between
|
||||
// a Chinese or Japanese character and another character in the future.
|
||||
// Such a space must have been always interchangeable with a line break.
|
||||
// https://wpt.fyi/results/css/css-text/line-breaking?label=master&label=experimental&aligned&q=segment-break-transformation-rules-
|
||||
// [2]: we should not break lines even between Chinese/Japanese characters because Chrome & Safari replaces "\n" between such characters with " " now.
|
||||
// [3]: Hangul (Korean) must simulate Latin words; see https://github.com/prettier/prettier/issues/6516
|
||||
// [printable][""][Hangul] & vice versa => Don't break
|
||||
// [printable][\n][Hangul] will be interchangeable to [printable][" "][Hangul] in the future
|
||||
// (will be compatible with Firefox's behavior)
|
||||
|
||||
if (!previous || !next) {
|
||||
// empty side is Latin ASCII symbol (e.g. *, [, ], or `)
|
||||
// value is " " or "\n" (not "")
|
||||
// [1] & [2]? No, it's the only exception because " " & "\n" have been always interchangeable only here
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value === "") {
|
||||
// [1] & [2] & [3]
|
||||
// At least either of previous or next is non-Latin (=CJK)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
// See the same product terms as the following in lineBreakCanBeConvertedToSpace
|
||||
// The behavior is consistent between browsers and Prettier in that line breaks between Korean and Chinese/Japanese letters are equivalent to spaces.
|
||||
// Currently, [CJK punctuation][\n][Hangul] is interchangeable to [CJK punctuation][""][Hangul],
|
||||
// but this is not compatible with Firefox's behavior.
|
||||
// Will be changed to [CJK punctuation][" "][Hangul] in the future
|
||||
(previous.kind === KIND_K_LETTER && next.kind === KIND_CJ_LETTER) ||
|
||||
(next.kind === KIND_K_LETTER && previous.kind === KIND_CJ_LETTER)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// [1] & [2]
|
||||
if (previous.isCJ || next.isCJ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AstPath} path
|
||||
* @param {WhitespaceValue} value
|
||||
* @param {ProseWrap} proseWrap
|
||||
* @param {boolean} [isLink] Special mode of (un)wrapping that preserves the
|
||||
* normalized form of link labels. https://spec.commonmark.org/0.30/#matches
|
||||
*/
|
||||
function printWhitespace(path, value, proseWrap, isLink) {
|
||||
if (proseWrap === "preserve" && value === "\n") {
|
||||
return hardline;
|
||||
}
|
||||
|
||||
const canBeSpace =
|
||||
value === " " ||
|
||||
(value === "\n" && lineBreakCanBeConvertedToSpace(path, isLink));
|
||||
|
||||
if (isBreakable(path, value, proseWrap, isLink)) {
|
||||
return canBeSpace ? line : softline;
|
||||
}
|
||||
|
||||
return canBeSpace ? " " : "";
|
||||
}
|
||||
|
||||
export { printWhitespace };
|
||||
81
src/prettier-markdown/print/table.js
Normal file
81
src/prettier-markdown/print/table.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
breakParent,
|
||||
group,
|
||||
hardlineWithoutBreakParent,
|
||||
ifBreak,
|
||||
join,
|
||||
} from "../../document/builders.js";
|
||||
import { printDocToString } from "../../document/printer.js";
|
||||
import getStringWidth from "../../utils/get-string-width.js";
|
||||
|
||||
function printTable(path, options, print) {
|
||||
const { node } = path;
|
||||
|
||||
const columnMaxWidths = [];
|
||||
// { [rowIndex: number]: { [columnIndex: number]: {text: string, width: number} } }
|
||||
const contents = path.map(
|
||||
() =>
|
||||
path.map(({ index: columnIndex }) => {
|
||||
const text = printDocToString(print(), options).formatted;
|
||||
const width = getStringWidth(text);
|
||||
columnMaxWidths[columnIndex] = Math.max(
|
||||
columnMaxWidths[columnIndex] ?? 3, // minimum width = 3 (---, :--, :-:, --:)
|
||||
width,
|
||||
);
|
||||
return { text, width };
|
||||
}, "children"),
|
||||
"children",
|
||||
);
|
||||
|
||||
const alignedTable = printTableContents(/* isCompact */ false);
|
||||
if (options.proseWrap !== "never") {
|
||||
return [breakParent, alignedTable];
|
||||
}
|
||||
|
||||
// Only if the --prose-wrap never is set and it exceeds the print width.
|
||||
const compactTable = printTableContents(/* isCompact */ true);
|
||||
return [breakParent, group(ifBreak(compactTable, alignedTable))];
|
||||
|
||||
function printTableContents(isCompact) {
|
||||
return join(
|
||||
hardlineWithoutBreakParent,
|
||||
[
|
||||
printRow(contents[0], isCompact),
|
||||
printAlign(isCompact),
|
||||
...contents
|
||||
.slice(1)
|
||||
.map((rowContents) => printRow(rowContents, isCompact)),
|
||||
].map((columns) => `| ${columns.join(" | ")} |`),
|
||||
);
|
||||
}
|
||||
|
||||
function printAlign(isCompact) {
|
||||
return columnMaxWidths.map((width, index) => {
|
||||
const align = node.align[index];
|
||||
const first = align === "center" || align === "left" ? ":" : "-";
|
||||
const last = align === "center" || align === "right" ? ":" : "-";
|
||||
const middle = isCompact ? "-" : "-".repeat(width - 2);
|
||||
return `${first}${middle}${last}`;
|
||||
});
|
||||
}
|
||||
|
||||
function printRow(columns, isCompact) {
|
||||
return columns.map(({ text, width }, columnIndex) => {
|
||||
if (isCompact) {
|
||||
return text;
|
||||
}
|
||||
const spaces = columnMaxWidths[columnIndex] - width;
|
||||
const align = node.align[columnIndex];
|
||||
let before = 0;
|
||||
if (align === "right") {
|
||||
before = spaces;
|
||||
} else if (align === "center") {
|
||||
before = Math.floor(spaces / 2);
|
||||
}
|
||||
const after = spaces - before;
|
||||
return `${" ".repeat(before)}${text}${" ".repeat(after)}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { printTable };
|
||||
810
src/prettier-markdown/printer-markdown.js
Normal file
810
src/prettier-markdown/printer-markdown.js
Normal file
@@ -0,0 +1,810 @@
|
||||
import collapseWhiteSpace from "collapse-white-space";
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import {
|
||||
align,
|
||||
fill,
|
||||
group,
|
||||
hardline,
|
||||
indent,
|
||||
line,
|
||||
literalline,
|
||||
markAsRoot,
|
||||
softline,
|
||||
} from "./adapter-document-builders.js";
|
||||
import { DOC_TYPE_STRING } from "./adapter-document-constants.js";
|
||||
import { getDocType, replaceEndOfLine } from "./adapter-document-utils.js";
|
||||
import {
|
||||
getMaxContinuousCount,
|
||||
getMinNotPresentContinuousCount,
|
||||
getPreferredQuote,
|
||||
UnexpectedNodeError,
|
||||
} from "./adapter-prettier-utils.js";
|
||||
import clean from "./clean.js";
|
||||
import { PUNCTUATION_REGEXP } from "./constants.evaluate.js";
|
||||
import embed from "./embed.js";
|
||||
import getVisitorKeys from "./get-visitor-keys.js";
|
||||
import { locEnd, locStart } from "./loc.js";
|
||||
import { insertPragma } from "./pragma.js";
|
||||
import { printTable } from "./print/table.js";
|
||||
import { printParagraph } from "./print-paragraph.js";
|
||||
import preprocess from "./print-preprocess.js";
|
||||
import { printSentence } from "./print-sentence.js";
|
||||
import { printWhitespace } from "./print-whitespace.js";
|
||||
import {
|
||||
getFencedCodeBlockValue,
|
||||
hasGitDiffFriendlyOrderedList,
|
||||
INLINE_NODE_TYPES,
|
||||
INLINE_NODE_WRAPPER_TYPES,
|
||||
isAutolink,
|
||||
splitText,
|
||||
} from "./utils.js";
|
||||
|
||||
/**
|
||||
* @import {Doc} from "../document/builders.js"
|
||||
*/
|
||||
|
||||
const SIBLING_NODE_TYPES = new Set(["listItem", "definition"]);
|
||||
|
||||
function prevOrNextWord(path) {
|
||||
const { previous, next } = path;
|
||||
const hasPrevOrNextWord =
|
||||
(previous?.type === "sentence" &&
|
||||
previous.children.at(-1)?.type === "word" &&
|
||||
!previous.children.at(-1).hasTrailingPunctuation) ||
|
||||
(next?.type === "sentence" &&
|
||||
next.children[0]?.type === "word" &&
|
||||
!next.children[0].hasLeadingPunctuation);
|
||||
return hasPrevOrNextWord;
|
||||
}
|
||||
|
||||
function genericPrint(path, options, print) {
|
||||
const { node } = path;
|
||||
|
||||
if (shouldRemainTheSameContent(path)) {
|
||||
/*
|
||||
* We assume parts always meet following conditions:
|
||||
* - parts.length is odd
|
||||
* - odd (0-indexed) elements are line-like doc
|
||||
*/
|
||||
/** @type {Doc[]} */
|
||||
const parts = [""];
|
||||
const textsNodes = splitText(
|
||||
options.originalText.slice(
|
||||
node.position.start.offset,
|
||||
node.position.end.offset,
|
||||
),
|
||||
);
|
||||
for (const node of textsNodes) {
|
||||
if (node.type === "word") {
|
||||
parts.push([parts.pop(), node.value]);
|
||||
continue;
|
||||
}
|
||||
const doc = printWhitespace(path, node.value, options.proseWrap, true);
|
||||
if (getDocType(doc) === DOC_TYPE_STRING) {
|
||||
parts.push([parts.pop(), doc]);
|
||||
continue;
|
||||
}
|
||||
// In this path, doc is line. To meet the condition, we need additional element "".
|
||||
parts.push(doc, "");
|
||||
}
|
||||
return fill(parts);
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case "root":
|
||||
/* c8 ignore next 3 */
|
||||
if (node.children.length === 0) {
|
||||
return "";
|
||||
}
|
||||
return [printRoot(path, options, print), hardline];
|
||||
case "paragraph":
|
||||
return printParagraph(path, options, print);
|
||||
case "sentence":
|
||||
return printSentence(path, print);
|
||||
case "word": {
|
||||
let escapedValue = node.value
|
||||
.replaceAll("*", String.raw`\*`) // escape all `*`
|
||||
.replaceAll(
|
||||
new RegExp(
|
||||
[
|
||||
`(^|${PUNCTUATION_REGEXP.source})(_+)`,
|
||||
`(_+)(${PUNCTUATION_REGEXP.source}|$)`,
|
||||
].join("|"),
|
||||
"gu",
|
||||
),
|
||||
(_, text1, underscore1, underscore2, text2) =>
|
||||
(underscore1
|
||||
? `${text1}${underscore1}`
|
||||
: `${underscore2}${text2}`
|
||||
).replaceAll("_", String.raw`\_`),
|
||||
); // escape all `_` except concating with non-punctuation, e.g. `1_2_3` is not considered emphasis
|
||||
|
||||
const isFirstSentence = (node, name, index) =>
|
||||
node.type === "sentence" && index === 0;
|
||||
const isLastChildAutolink = (node, name, index) =>
|
||||
isAutolink(node.children[index - 1]);
|
||||
|
||||
if (
|
||||
escapedValue !== node.value &&
|
||||
(path.match(undefined, isFirstSentence, isLastChildAutolink) ||
|
||||
path.match(
|
||||
undefined,
|
||||
isFirstSentence,
|
||||
(node, name, index) => node.type === "emphasis" && index === 0,
|
||||
isLastChildAutolink,
|
||||
))
|
||||
) {
|
||||
// backslash is parsed as part of autolinks, so we need to remove it
|
||||
escapedValue = escapedValue.replace(/^(\\?[*_])+/u, (prefix) =>
|
||||
prefix.replaceAll("\\", ""),
|
||||
);
|
||||
}
|
||||
|
||||
return escapedValue;
|
||||
}
|
||||
case "whitespace": {
|
||||
const { next } = path;
|
||||
|
||||
const proseWrap =
|
||||
// leading char that may cause different syntax
|
||||
next && /^>|^(?:[*+-]|#{1,6}|\d+[).])$/u.test(next.value)
|
||||
? "never"
|
||||
: options.proseWrap;
|
||||
|
||||
return printWhitespace(path, node.value, proseWrap);
|
||||
}
|
||||
case "emphasis": {
|
||||
let style;
|
||||
if (isAutolink(node.children[0])) {
|
||||
style = options.originalText[node.position.start.offset];
|
||||
} else {
|
||||
const hasPrevOrNextWord = prevOrNextWord(path); // `1*2*3` is considered emphasis but `1_2_3` is not
|
||||
const inStrongAndHasPrevOrNextWord = // `1***2***3` is considered strong emphasis but `1**_2_**3` is not
|
||||
path.callParent(
|
||||
({ node }) => node.type === "strong" && prevOrNextWord(path),
|
||||
);
|
||||
style =
|
||||
hasPrevOrNextWord ||
|
||||
inStrongAndHasPrevOrNextWord ||
|
||||
path.hasAncestor((node) => node.type === "emphasis")
|
||||
? "*"
|
||||
: "_";
|
||||
}
|
||||
return [style, printChildren(path, options, print), style];
|
||||
}
|
||||
case "strong":
|
||||
return ["**", printChildren(path, options, print), "**"];
|
||||
case "delete":
|
||||
return ["~~", printChildren(path, options, print), "~~"];
|
||||
case "inlineCode": {
|
||||
const code =
|
||||
options.proseWrap === "preserve"
|
||||
? node.value
|
||||
: node.value.replaceAll("\n", " ");
|
||||
const backtickCount = getMinNotPresentContinuousCount(code, "`");
|
||||
const backtickString = "`".repeat(backtickCount);
|
||||
const padding =
|
||||
code.startsWith("`") ||
|
||||
code.endsWith("`") ||
|
||||
(/^[\n ]/u.test(code) && /[\n ]$/u.test(code) && /[^\n ]/u.test(code))
|
||||
? " "
|
||||
: "";
|
||||
return [backtickString, padding, code, padding, backtickString];
|
||||
}
|
||||
case "wikiLink": {
|
||||
let contents = "";
|
||||
if (options.proseWrap === "preserve") {
|
||||
contents = node.value;
|
||||
} else {
|
||||
contents = node.value.replaceAll(/[\t\n]+/gu, " ");
|
||||
}
|
||||
|
||||
return ["[[", contents, "]]"];
|
||||
}
|
||||
case "link":
|
||||
switch (options.originalText[node.position.start.offset]) {
|
||||
case "<": {
|
||||
const mailto = "mailto:";
|
||||
const url =
|
||||
// <hello@example.com> is parsed as { url: "mailto:hello@example.com" }
|
||||
node.url.startsWith(mailto) &&
|
||||
options.originalText.slice(
|
||||
node.position.start.offset + 1,
|
||||
node.position.start.offset + 1 + mailto.length,
|
||||
) !== mailto
|
||||
? node.url.slice(mailto.length)
|
||||
: node.url;
|
||||
return ["<", url, ">"];
|
||||
}
|
||||
case "[":
|
||||
return [
|
||||
"[",
|
||||
printChildren(path, options, print),
|
||||
"](",
|
||||
printUrl(node.url, ")"),
|
||||
printTitle(node.title, options),
|
||||
")",
|
||||
];
|
||||
default:
|
||||
return options.originalText.slice(
|
||||
node.position.start.offset,
|
||||
node.position.end.offset,
|
||||
);
|
||||
}
|
||||
case "image":
|
||||
return [
|
||||
""),
|
||||
printTitle(node.title, options),
|
||||
")",
|
||||
];
|
||||
case "blockquote":
|
||||
return ["> ", align("> ", printChildren(path, options, print))];
|
||||
case "heading":
|
||||
return [
|
||||
"#".repeat(node.depth) + " ",
|
||||
printChildren(path, options, print),
|
||||
];
|
||||
case "code": {
|
||||
if (node.isIndented) {
|
||||
// indented code block
|
||||
const alignment = " ".repeat(4);
|
||||
return align(alignment, [
|
||||
alignment,
|
||||
replaceEndOfLine(node.value, hardline),
|
||||
]);
|
||||
}
|
||||
|
||||
// fenced code block
|
||||
const styleUnit = options.__inJsTemplate ? "~" : "`";
|
||||
const style = styleUnit.repeat(
|
||||
Math.max(3, getMaxContinuousCount(node.value, styleUnit) + 1),
|
||||
);
|
||||
return [
|
||||
style,
|
||||
node.lang || "",
|
||||
node.meta ? " " + node.meta : "",
|
||||
hardline,
|
||||
replaceEndOfLine(
|
||||
getFencedCodeBlockValue(node, options.originalText),
|
||||
hardline,
|
||||
),
|
||||
hardline,
|
||||
style,
|
||||
];
|
||||
}
|
||||
case "html": {
|
||||
const { parent, isLast } = path;
|
||||
const value =
|
||||
parent.type === "root" && isLast ? node.value.trimEnd() : node.value;
|
||||
const isHtmlComment = /^<!--.*-->$/su.test(value);
|
||||
|
||||
return replaceEndOfLine(
|
||||
value,
|
||||
// @ts-expect-error
|
||||
isHtmlComment ? hardline : markAsRoot(literalline),
|
||||
);
|
||||
}
|
||||
case "list": {
|
||||
const nthSiblingIndex = getNthListSiblingIndex(node, path.parent);
|
||||
|
||||
const isGitDiffFriendlyOrderedList = hasGitDiffFriendlyOrderedList(
|
||||
node,
|
||||
options,
|
||||
);
|
||||
|
||||
return printChildren(path, options, print, {
|
||||
processor() {
|
||||
const prefix = getPrefix();
|
||||
const { node: childNode } = path;
|
||||
|
||||
if (
|
||||
childNode.children.length === 2 &&
|
||||
childNode.children[1].type === "html" &&
|
||||
childNode.children[0].position.start.column !==
|
||||
childNode.children[1].position.start.column
|
||||
) {
|
||||
return [prefix, printListItem(path, options, print, prefix)];
|
||||
}
|
||||
|
||||
return [
|
||||
prefix,
|
||||
align(
|
||||
" ".repeat(prefix.length),
|
||||
printListItem(path, options, print, prefix),
|
||||
),
|
||||
];
|
||||
|
||||
function getPrefix() {
|
||||
const rawPrefix = node.ordered
|
||||
? (path.isFirst
|
||||
? node.start
|
||||
: isGitDiffFriendlyOrderedList
|
||||
? 1
|
||||
: node.start + path.index) +
|
||||
(nthSiblingIndex % 2 === 0 ? ". " : ") ")
|
||||
: nthSiblingIndex % 2 === 0
|
||||
? "- "
|
||||
: "* ";
|
||||
|
||||
return (node.isAligned ||
|
||||
/* workaround for https://github.com/remarkjs/remark/issues/315 */ node.hasIndentedCodeblock) &&
|
||||
node.ordered
|
||||
? alignListPrefix(rawPrefix, options)
|
||||
: rawPrefix;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
case "thematicBreak": {
|
||||
const { ancestors } = path;
|
||||
const counter = ancestors.findIndex((node) => node.type === "list");
|
||||
if (counter === -1) {
|
||||
return "---";
|
||||
}
|
||||
const nthSiblingIndex = getNthListSiblingIndex(
|
||||
ancestors[counter],
|
||||
ancestors[counter + 1],
|
||||
);
|
||||
return nthSiblingIndex % 2 === 0 ? "***" : "---";
|
||||
}
|
||||
case "linkReference":
|
||||
return [
|
||||
"[",
|
||||
printChildren(path, options, print),
|
||||
"]",
|
||||
node.referenceType === "full"
|
||||
? printLinkReference(node)
|
||||
: node.referenceType === "collapsed"
|
||||
? "[]"
|
||||
: "",
|
||||
];
|
||||
case "imageReference":
|
||||
switch (node.referenceType) {
|
||||
case "full":
|
||||
return ["![", node.alt || "", "]", printLinkReference(node)];
|
||||
default:
|
||||
return [
|
||||
"![",
|
||||
node.alt,
|
||||
"]",
|
||||
node.referenceType === "collapsed" ? "[]" : "",
|
||||
];
|
||||
}
|
||||
case "definition": {
|
||||
const lineOrSpace = options.proseWrap === "always" ? line : " ";
|
||||
return group([
|
||||
printLinkReference(node),
|
||||
":",
|
||||
indent([
|
||||
lineOrSpace,
|
||||
printUrl(node.url),
|
||||
node.title === null
|
||||
? ""
|
||||
: [lineOrSpace, printTitle(node.title, options, false)],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
// `footnote` requires `.use(footnotes, {inlineNotes: true})`, we are not using this option
|
||||
// https://github.com/remarkjs/remark-footnotes#optionsinlinenotes
|
||||
/* c8 ignore next 2 */
|
||||
case "footnote":
|
||||
return ["[^", printChildren(path, options, print), "]"];
|
||||
case "footnoteReference":
|
||||
return printFootnoteReference(node);
|
||||
case "footnoteDefinition": {
|
||||
const shouldInlineFootnote =
|
||||
node.children.length === 1 &&
|
||||
node.children[0].type === "paragraph" &&
|
||||
(options.proseWrap === "never" ||
|
||||
(options.proseWrap === "preserve" &&
|
||||
node.children[0].position.start.line ===
|
||||
node.children[0].position.end.line));
|
||||
return [
|
||||
printFootnoteReference(node),
|
||||
": ",
|
||||
shouldInlineFootnote
|
||||
? printChildren(path, options, print)
|
||||
: group([
|
||||
align(
|
||||
" ".repeat(4),
|
||||
printChildren(path, options, print, {
|
||||
processor: ({ isFirst }) =>
|
||||
isFirst ? group([softline, print()]) : print(),
|
||||
}),
|
||||
),
|
||||
]),
|
||||
];
|
||||
}
|
||||
case "table":
|
||||
return printTable(path, options, print);
|
||||
case "tableCell":
|
||||
return printChildren(path, options, print);
|
||||
case "break":
|
||||
return /\s/u.test(options.originalText[node.position.start.offset])
|
||||
? [" ", markAsRoot(literalline)]
|
||||
: ["\\", hardline];
|
||||
case "liquidNode":
|
||||
return replaceEndOfLine(node.value, hardline);
|
||||
// MDX
|
||||
// fallback to the original text if multiparser failed
|
||||
// or `embeddedLanguageFormatting: "off"`
|
||||
case "import":
|
||||
case "export":
|
||||
case "jsx":
|
||||
return node.value.trimEnd();
|
||||
case "esComment":
|
||||
return ["{/* ", node.value, " */}"];
|
||||
case "math":
|
||||
return [
|
||||
"$$",
|
||||
hardline,
|
||||
node.value ? [replaceEndOfLine(node.value, hardline), hardline] : "",
|
||||
"$$",
|
||||
];
|
||||
case "inlineMath":
|
||||
// remark-math trims content but we don't want to remove whitespaces
|
||||
// since it's very possible that it's recognized as math accidentally
|
||||
return options.originalText.slice(locStart(node), locEnd(node));
|
||||
|
||||
case "frontMatter": // Handled in core
|
||||
case "tableRow": // handled in "table"
|
||||
case "listItem": // handled in "list"
|
||||
case "text": // handled in other types
|
||||
default:
|
||||
/* c8 ignore next */
|
||||
throw new UnexpectedNodeError(node, "Markdown");
|
||||
}
|
||||
}
|
||||
|
||||
function printListItem(path, options, print, listPrefix) {
|
||||
const { node } = path;
|
||||
const prefix = node.checked === null ? "" : node.checked ? "[x] " : "[ ] ";
|
||||
return [
|
||||
prefix,
|
||||
printChildren(path, options, print, {
|
||||
processor({ node, isFirst }) {
|
||||
if (isFirst && node.type !== "list") {
|
||||
return align(" ".repeat(prefix.length), print());
|
||||
}
|
||||
|
||||
const alignment = " ".repeat(
|
||||
clamp(options.tabWidth - listPrefix.length, 0, 3), // 4+ will cause indented code block
|
||||
);
|
||||
return [alignment, align(alignment, print())];
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
function alignListPrefix(prefix, options) {
|
||||
const additionalSpaces = getAdditionalSpaces();
|
||||
return (
|
||||
prefix +
|
||||
" ".repeat(
|
||||
additionalSpaces >= 4 ? 0 : additionalSpaces, // 4+ will cause indented code block
|
||||
)
|
||||
);
|
||||
|
||||
function getAdditionalSpaces() {
|
||||
const restSpaces = prefix.length % options.tabWidth;
|
||||
return restSpaces === 0 ? 0 : options.tabWidth - restSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
function getNthListSiblingIndex(node, parentNode) {
|
||||
return getNthSiblingIndex(
|
||||
node,
|
||||
parentNode,
|
||||
(siblingNode) => siblingNode.ordered === node.ordered,
|
||||
);
|
||||
}
|
||||
|
||||
function getNthSiblingIndex(node, parentNode, condition) {
|
||||
let index = -1;
|
||||
|
||||
for (const childNode of parentNode.children) {
|
||||
if (childNode.type === node.type && condition(childNode)) {
|
||||
index++;
|
||||
} else {
|
||||
index = -1;
|
||||
}
|
||||
|
||||
if (childNode === node) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printRoot(path, options, print) {
|
||||
/** @typedef {{ index: number, offset: number }} IgnorePosition */
|
||||
/** @type {Array<{start: IgnorePosition, end: IgnorePosition}>} */
|
||||
const ignoreRanges = [];
|
||||
|
||||
/** @type {IgnorePosition | null} */
|
||||
let ignoreStart = null;
|
||||
|
||||
const { children } = path.node;
|
||||
for (const [index, childNode] of children.entries()) {
|
||||
switch (isPrettierIgnore(childNode)) {
|
||||
case "start":
|
||||
if (ignoreStart === null) {
|
||||
ignoreStart = { index, offset: childNode.position.end.offset };
|
||||
}
|
||||
break;
|
||||
case "end":
|
||||
if (ignoreStart !== null) {
|
||||
ignoreRanges.push({
|
||||
start: ignoreStart,
|
||||
end: { index, offset: childNode.position.start.offset },
|
||||
});
|
||||
ignoreStart = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return printChildren(path, options, print, {
|
||||
processor({ index }) {
|
||||
if (ignoreRanges.length > 0) {
|
||||
const ignoreRange = ignoreRanges[0];
|
||||
|
||||
if (index === ignoreRange.start.index) {
|
||||
return [
|
||||
printIgnoreComment(children[ignoreRange.start.index]),
|
||||
options.originalText.slice(
|
||||
ignoreRange.start.offset,
|
||||
ignoreRange.end.offset,
|
||||
),
|
||||
printIgnoreComment(children[ignoreRange.end.index]),
|
||||
];
|
||||
}
|
||||
|
||||
if (ignoreRange.start.index < index && index < ignoreRange.end.index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index === ignoreRange.end.index) {
|
||||
ignoreRanges.shift();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return print();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function printChildren(path, options, print, events = {}) {
|
||||
const { processor = print } = events;
|
||||
|
||||
const parts = [];
|
||||
|
||||
path.each(() => {
|
||||
const result = processor(path);
|
||||
if (result !== false) {
|
||||
if (parts.length > 0 && shouldPrePrintHardline(path)) {
|
||||
parts.push(hardline);
|
||||
|
||||
if (
|
||||
shouldPrePrintDoubleHardline(path, options) ||
|
||||
shouldPrePrintTripleHardline(path)
|
||||
) {
|
||||
parts.push(hardline);
|
||||
}
|
||||
|
||||
if (shouldPrePrintTripleHardline(path)) {
|
||||
parts.push(hardline);
|
||||
}
|
||||
}
|
||||
|
||||
parts.push(result);
|
||||
}
|
||||
}, "children");
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
function printIgnoreComment(node) {
|
||||
if (node.type === "html") {
|
||||
return node.value;
|
||||
}
|
||||
|
||||
if (
|
||||
node.type === "paragraph" &&
|
||||
Array.isArray(node.children) &&
|
||||
node.children.length === 1 &&
|
||||
node.children[0].type === "esComment"
|
||||
) {
|
||||
return ["{/* ", node.children[0].value, " */}"];
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {false | 'next' | 'start' | 'end'} */
|
||||
function isPrettierIgnore(node) {
|
||||
let match;
|
||||
|
||||
if (node.type === "html") {
|
||||
match = node.value.match(
|
||||
/^<!--\s*prettier-ignore(?:-(start|end))?\s*-->$/u,
|
||||
);
|
||||
} else {
|
||||
let comment;
|
||||
|
||||
if (node.type === "esComment") {
|
||||
comment = node;
|
||||
} else if (
|
||||
node.type === "paragraph" &&
|
||||
node.children.length === 1 &&
|
||||
node.children[0].type === "esComment"
|
||||
) {
|
||||
comment = node.children[0];
|
||||
}
|
||||
|
||||
if (comment) {
|
||||
match = comment.value.match(/^prettier-ignore(?:-(start|end))?$/u);
|
||||
}
|
||||
}
|
||||
|
||||
return match ? match[1] || "next" : false;
|
||||
}
|
||||
|
||||
function shouldPrePrintHardline({ node, parent }) {
|
||||
const isInlineNode = INLINE_NODE_TYPES.has(node.type);
|
||||
|
||||
const isInlineHTML =
|
||||
node.type === "html" && INLINE_NODE_WRAPPER_TYPES.has(parent.type);
|
||||
|
||||
return !isInlineNode && !isInlineHTML;
|
||||
}
|
||||
|
||||
function isLooseListItem(node, options) {
|
||||
return (
|
||||
node.type === "listItem" &&
|
||||
(node.spread ||
|
||||
// Check if `listItem` ends with `\n`
|
||||
// since it can't be empty, so we only need check the last character
|
||||
options.originalText.charAt(node.position.end.offset - 1) === "\n")
|
||||
);
|
||||
}
|
||||
|
||||
function shouldPrePrintDoubleHardline({ node, previous, parent }, options) {
|
||||
if (
|
||||
isLooseListItem(previous, options) ||
|
||||
(node.type === "list" &&
|
||||
parent.type === "listItem" &&
|
||||
previous.type === "code")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isSequence = previous.type === node.type;
|
||||
const isSiblingNode = isSequence && SIBLING_NODE_TYPES.has(node.type);
|
||||
const isInTightListItem =
|
||||
parent.type === "listItem" &&
|
||||
(node.type === "list" || !isLooseListItem(parent, options));
|
||||
const isPrevNodePrettierIgnore = isPrettierIgnore(previous) === "next";
|
||||
const isBlockHtmlWithoutBlankLineBetweenPrevHtml =
|
||||
node.type === "html" &&
|
||||
previous.type === "html" &&
|
||||
previous.position.end.line + 1 === node.position.start.line;
|
||||
const isHtmlDirectAfterListItem =
|
||||
node.type === "html" &&
|
||||
parent.type === "listItem" &&
|
||||
previous.type === "paragraph" &&
|
||||
previous.position.end.line + 1 === node.position.start.line;
|
||||
|
||||
return !(
|
||||
isSiblingNode ||
|
||||
isInTightListItem ||
|
||||
isPrevNodePrettierIgnore ||
|
||||
isBlockHtmlWithoutBlankLineBetweenPrevHtml ||
|
||||
isHtmlDirectAfterListItem
|
||||
);
|
||||
}
|
||||
|
||||
function shouldPrePrintTripleHardline({ node, previous }) {
|
||||
const isPrevNodeList = previous.type === "list";
|
||||
const isIndentedCode = node.type === "code" && node.isIndented;
|
||||
|
||||
return isPrevNodeList && isIndentedCode;
|
||||
}
|
||||
|
||||
function shouldRemainTheSameContent(path) {
|
||||
const node = path.findAncestor(
|
||||
(node) => node.type === "linkReference" || node.type === "imageReference",
|
||||
);
|
||||
return (
|
||||
node && (node.type !== "linkReference" || node.referenceType !== "full")
|
||||
);
|
||||
}
|
||||
|
||||
const encodeUrl = (url, characters) => {
|
||||
for (const character of characters) {
|
||||
url = url.replaceAll(character, encodeURIComponent(character));
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {string[] | string} [dangerousCharOrChars]
|
||||
* @returns {string}
|
||||
*/
|
||||
function printUrl(url, dangerousCharOrChars = []) {
|
||||
const dangerousChars = [
|
||||
" ",
|
||||
...(Array.isArray(dangerousCharOrChars)
|
||||
? dangerousCharOrChars
|
||||
: [dangerousCharOrChars]),
|
||||
];
|
||||
|
||||
return new RegExp(
|
||||
dangerousChars.map((x) => escapeStringRegexp(x)).join("|"),
|
||||
"u",
|
||||
).test(url)
|
||||
? `<${encodeUrl(url, "<>")}>`
|
||||
: url;
|
||||
}
|
||||
|
||||
function printTitle(title, options, printSpace = true) {
|
||||
if (!title) {
|
||||
return "";
|
||||
}
|
||||
if (printSpace) {
|
||||
return " " + printTitle(title, options, false);
|
||||
}
|
||||
|
||||
// title is escaped after `remark-parse` v7
|
||||
title = title.replaceAll(/\\(?=["')])/gu, "");
|
||||
|
||||
if (title.includes('"') && title.includes("'") && !title.includes(")")) {
|
||||
return `(${title})`; // avoid escaped quotes
|
||||
}
|
||||
const quote = getPreferredQuote(title, options.singleQuote);
|
||||
title = title.replaceAll("\\", "\\\\");
|
||||
title = title.replaceAll(quote, `\\${quote}`);
|
||||
return `${quote}${title}${quote}`;
|
||||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.max(min, Math.min(value, max));
|
||||
}
|
||||
|
||||
function hasPrettierIgnore(path) {
|
||||
return path.index > 0 && isPrettierIgnore(path.previous) === "next";
|
||||
}
|
||||
|
||||
// `remark-parse` lowercase the `label` as `identifier`, we don't want do that
|
||||
// https://github.com/remarkjs/remark/blob/daddcb463af2d5b2115496c395d0571c0ff87d15/packages/remark-parse/lib/tokenize/reference.js
|
||||
function printLinkReference(node) {
|
||||
return `[${collapseWhiteSpace(node.label)}]`;
|
||||
}
|
||||
|
||||
function printFootnoteReference(node) {
|
||||
return `[^${node.label}]`;
|
||||
}
|
||||
|
||||
const printer = {
|
||||
features: {
|
||||
experimental_frontMatterSupport: {
|
||||
massageAstNode: true,
|
||||
embed: true,
|
||||
print: true,
|
||||
},
|
||||
},
|
||||
preprocess,
|
||||
print: genericPrint,
|
||||
embed,
|
||||
massageAstNode: clean,
|
||||
hasPrettierIgnore,
|
||||
insertPragma,
|
||||
getVisitorKeys,
|
||||
};
|
||||
|
||||
export default printer;
|
||||
23
src/prettier-markdown/unified-plugins/front-matter.js
Normal file
23
src/prettier-markdown/unified-plugins/front-matter.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { parseFrontMatter } from "../../main/front-matter/index.js";
|
||||
|
||||
/** @import {Plugin, Settings} from "unified" */
|
||||
|
||||
/**
|
||||
* @type {Plugin<[], Settings>}
|
||||
*/
|
||||
const frontMatter = function () {
|
||||
const proto = this.Parser.prototype;
|
||||
proto.blockMethods = ["frontMatter", ...proto.blockMethods];
|
||||
proto.blockTokenizers.frontMatter = tokenizer;
|
||||
|
||||
function tokenizer(eat, value) {
|
||||
const { frontMatter } = parseFrontMatter(value);
|
||||
|
||||
if (frontMatter) {
|
||||
return eat(frontMatter.raw)({ ...frontMatter, type: "frontMatter" });
|
||||
}
|
||||
}
|
||||
tokenizer.onlyAtStart = true;
|
||||
};
|
||||
|
||||
export default frontMatter;
|
||||
19
src/prettier-markdown/unified-plugins/html-to-jsx.js
Normal file
19
src/prettier-markdown/unified-plugins/html-to-jsx.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { COMMENT_REGEX } from "../mdx.js";
|
||||
import { INLINE_NODE_WRAPPER_TYPES, mapAst } from "../utils.js";
|
||||
|
||||
function htmlToJsx() {
|
||||
return (ast) =>
|
||||
mapAst(ast, (node, _index, [parent]) => {
|
||||
if (
|
||||
node.type !== "html" ||
|
||||
// Keep HTML-style comments (legacy MDX)
|
||||
COMMENT_REGEX.test(node.value) ||
|
||||
INLINE_NODE_WRAPPER_TYPES.has(parent.type)
|
||||
) {
|
||||
return node;
|
||||
}
|
||||
return { ...node, type: "jsx" };
|
||||
});
|
||||
}
|
||||
|
||||
export default htmlToJsx;
|
||||
27
src/prettier-markdown/unified-plugins/liquid.js
Normal file
27
src/prettier-markdown/unified-plugins/liquid.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/** @import {Plugin, Settings} from "unified" */
|
||||
|
||||
/**
|
||||
* @type {Plugin<[], Settings>}
|
||||
*/
|
||||
const liquid = function () {
|
||||
const proto = this.Parser.prototype;
|
||||
const methods = proto.inlineMethods;
|
||||
methods.splice(methods.indexOf("text"), 0, "liquid");
|
||||
proto.inlineTokenizers.liquid = tokenizer;
|
||||
|
||||
function tokenizer(eat, value) {
|
||||
const match = value.match(/^(\{%.*?%\}|\{\{.*?\}\})/su);
|
||||
|
||||
if (match) {
|
||||
return eat(match[0])({
|
||||
type: "liquidNode",
|
||||
value: match[0],
|
||||
});
|
||||
}
|
||||
}
|
||||
tokenizer.locator = function (value, fromIndex) {
|
||||
return value.indexOf("{", fromIndex);
|
||||
};
|
||||
};
|
||||
|
||||
export default liquid;
|
||||
32
src/prettier-markdown/unified-plugins/wiki-link.js
Normal file
32
src/prettier-markdown/unified-plugins/wiki-link.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/** @import {Plugin, Settings} from "unified" */
|
||||
|
||||
/**
|
||||
* @type {Plugin<[], Settings>}
|
||||
*/
|
||||
const wikiLink = function () {
|
||||
const entityType = "wikiLink";
|
||||
const wikiLinkRegex = /^\[\[(?<linkContents>.+?)\]\]/su;
|
||||
const proto = this.Parser.prototype;
|
||||
const methods = proto.inlineMethods;
|
||||
methods.splice(methods.indexOf("link"), 0, entityType);
|
||||
proto.inlineTokenizers.wikiLink = tokenizer;
|
||||
|
||||
function tokenizer(eat, value) {
|
||||
const match = wikiLinkRegex.exec(value);
|
||||
|
||||
if (match) {
|
||||
const linkContents = match.groups.linkContents.trim();
|
||||
|
||||
return eat(match[0])({
|
||||
type: entityType,
|
||||
value: linkContents,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tokenizer.locator = function (value, fromIndex) {
|
||||
return value.indexOf("[", fromIndex);
|
||||
};
|
||||
};
|
||||
|
||||
export default wikiLink;
|
||||
268
src/prettier-markdown/utils.js
Normal file
268
src/prettier-markdown/utils.js
Normal file
@@ -0,0 +1,268 @@
|
||||
import * as assert from "#universal/assert";
|
||||
import { CJK_REGEXP, PUNCTUATION_REGEXP } from "./constants.evaluate.js";
|
||||
import { locEnd, locStart } from "./loc.js";
|
||||
|
||||
const INLINE_NODE_TYPES = new Set([
|
||||
"liquidNode",
|
||||
"inlineCode",
|
||||
"emphasis",
|
||||
"esComment",
|
||||
"strong",
|
||||
"delete",
|
||||
"wikiLink",
|
||||
"link",
|
||||
"linkReference",
|
||||
"image",
|
||||
"imageReference",
|
||||
"footnote",
|
||||
"footnoteReference",
|
||||
"sentence",
|
||||
"whitespace",
|
||||
"word",
|
||||
"break",
|
||||
"inlineMath",
|
||||
]);
|
||||
|
||||
const INLINE_NODE_WRAPPER_TYPES = new Set([
|
||||
...INLINE_NODE_TYPES,
|
||||
"tableCell",
|
||||
"paragraph",
|
||||
"heading",
|
||||
]);
|
||||
|
||||
const KIND_NON_CJK = "non-cjk";
|
||||
const KIND_CJ_LETTER = "cj-letter";
|
||||
const KIND_K_LETTER = "k-letter";
|
||||
const KIND_CJK_PUNCTUATION = "cjk-punctuation";
|
||||
|
||||
const K_REGEXP = /\p{Script_Extensions=Hangul}/u;
|
||||
|
||||
/**
|
||||
* @typedef {" " | "\n" | ""} WhitespaceValue
|
||||
* @typedef { KIND_NON_CJK | KIND_CJ_LETTER | KIND_K_LETTER | KIND_CJK_PUNCTUATION } WordKind
|
||||
* @typedef {{
|
||||
* type: "whitespace",
|
||||
* value: WhitespaceValue,
|
||||
* kind?: never
|
||||
* }} WhitespaceNode
|
||||
* @typedef {{
|
||||
* type: "word",
|
||||
* value: string,
|
||||
* kind: WordKind,
|
||||
* isCJ: boolean,
|
||||
* hasLeadingPunctuation: boolean,
|
||||
* hasTrailingPunctuation: boolean,
|
||||
* }} WordNode
|
||||
* Node for a single CJK character or a sequence of non-CJK characters
|
||||
* @typedef {WhitespaceNode | WordNode} TextNode
|
||||
*/
|
||||
|
||||
/**
|
||||
* split text into whitespaces and words
|
||||
* @param {string} text
|
||||
*/
|
||||
function splitText(text) {
|
||||
/** @type {Array<TextNode>} */
|
||||
const nodes = [];
|
||||
|
||||
const tokens = text.split(/([\t\n ]+)/u);
|
||||
for (const [index, token] of tokens.entries()) {
|
||||
// whitespace
|
||||
if (index % 2 === 1) {
|
||||
nodes.push({
|
||||
type: "whitespace",
|
||||
value: /\n/u.test(token) ? "\n" : " ",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// word separated by whitespace
|
||||
|
||||
if ((index === 0 || index === tokens.length - 1) && token === "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const innerTokens = token.split(new RegExp(`(${CJK_REGEXP.source})`, "u"));
|
||||
for (const [innerIndex, innerToken] of innerTokens.entries()) {
|
||||
if (
|
||||
(innerIndex === 0 || innerIndex === innerTokens.length - 1) &&
|
||||
innerToken === ""
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// non-CJK word
|
||||
if (innerIndex % 2 === 0) {
|
||||
if (innerToken !== "") {
|
||||
appendNode({
|
||||
type: "word",
|
||||
value: innerToken,
|
||||
kind: KIND_NON_CJK,
|
||||
isCJ: false,
|
||||
hasLeadingPunctuation: PUNCTUATION_REGEXP.test(innerToken[0]),
|
||||
hasTrailingPunctuation: PUNCTUATION_REGEXP.test(innerToken.at(-1)),
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// CJK character
|
||||
|
||||
// punctuation for CJ(K)
|
||||
// Korean doesn't use them in horizontal writing usually
|
||||
if (PUNCTUATION_REGEXP.test(innerToken)) {
|
||||
appendNode({
|
||||
type: "word",
|
||||
value: innerToken,
|
||||
kind: KIND_CJK_PUNCTUATION,
|
||||
isCJ: true,
|
||||
hasLeadingPunctuation: true,
|
||||
hasTrailingPunctuation: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Korean uses space to divide words, but Chinese & Japanese do not
|
||||
// This is why Korean should be treated like non-CJK
|
||||
if (K_REGEXP.test(innerToken)) {
|
||||
appendNode({
|
||||
type: "word",
|
||||
value: innerToken,
|
||||
kind: KIND_K_LETTER,
|
||||
isCJ: false,
|
||||
hasLeadingPunctuation: false,
|
||||
hasTrailingPunctuation: false,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
appendNode({
|
||||
type: "word",
|
||||
value: innerToken,
|
||||
kind: KIND_CJ_LETTER,
|
||||
isCJ: true,
|
||||
hasLeadingPunctuation: false,
|
||||
hasTrailingPunctuation: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check for `canBeConvertedToSpace` in ./print-whitespace.js etc.
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
for (let i = 1; i < nodes.length; i++) {
|
||||
assert.ok(
|
||||
!(nodes[i - 1].type === "whitespace" && nodes[i].type === "whitespace"),
|
||||
"splitText should not create consecutive whitespace nodes",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
|
||||
function appendNode(node) {
|
||||
const lastNode = nodes.at(-1);
|
||||
if (
|
||||
lastNode?.type === "word" &&
|
||||
!isBetween(KIND_NON_CJK, KIND_CJK_PUNCTUATION) &&
|
||||
// disallow leading/trailing full-width whitespace
|
||||
![lastNode.value, node.value].some((value) => /\u3000/u.test(value))
|
||||
) {
|
||||
nodes.push({ type: "whitespace", value: "" });
|
||||
}
|
||||
nodes.push(node);
|
||||
|
||||
function isBetween(kind1, kind2) {
|
||||
return (
|
||||
(lastNode.kind === kind1 && node.kind === kind2) ||
|
||||
(lastNode.kind === kind2 && node.kind === kind1)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getOrderedListItemInfo(orderListItem, options) {
|
||||
const text = options.originalText.slice(
|
||||
orderListItem.position.start.offset,
|
||||
orderListItem.position.end.offset,
|
||||
);
|
||||
|
||||
const { numberText, leadingSpaces } = text.match(
|
||||
/^\s*(?<numberText>\d+)(\.|\))(?<leadingSpaces>\s*)/u,
|
||||
).groups;
|
||||
|
||||
return { number: Number(numberText), leadingSpaces };
|
||||
}
|
||||
|
||||
function hasGitDiffFriendlyOrderedList(node, options) {
|
||||
if (!node.ordered || node.children.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const secondNumber = getOrderedListItemInfo(node.children[1], options).number;
|
||||
|
||||
if (secondNumber !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const firstNumber = getOrderedListItemInfo(node.children[0], options).number;
|
||||
|
||||
if (firstNumber !== 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
node.children.length > 2 &&
|
||||
getOrderedListItemInfo(node.children[2], options).number === 1
|
||||
);
|
||||
}
|
||||
|
||||
// The final new line should not include in value
|
||||
// https://github.com/remarkjs/remark/issues/512
|
||||
function getFencedCodeBlockValue(node, originalText) {
|
||||
const { value } = node;
|
||||
if (
|
||||
node.position.end.offset === originalText.length &&
|
||||
value.endsWith("\n") &&
|
||||
// Code block has no end mark
|
||||
originalText.endsWith("\n")
|
||||
) {
|
||||
return value.slice(0, -1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function mapAst(ast, handler) {
|
||||
return (function preorder(node, index, parentStack) {
|
||||
const newNode = { ...handler(node, index, parentStack) };
|
||||
if (newNode.children) {
|
||||
newNode.children = newNode.children.map((child, index) =>
|
||||
preorder(child, index, [newNode, ...parentStack]),
|
||||
);
|
||||
}
|
||||
|
||||
return newNode;
|
||||
})(ast, null, []);
|
||||
}
|
||||
|
||||
function isAutolink(node) {
|
||||
if (node?.type !== "link" || node.children.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const [child] = node.children;
|
||||
return locStart(node) === locStart(child) && locEnd(node) === locEnd(child);
|
||||
}
|
||||
|
||||
export {
|
||||
getFencedCodeBlockValue,
|
||||
getOrderedListItemInfo,
|
||||
hasGitDiffFriendlyOrderedList,
|
||||
INLINE_NODE_TYPES,
|
||||
INLINE_NODE_WRAPPER_TYPES,
|
||||
isAutolink,
|
||||
KIND_CJ_LETTER,
|
||||
KIND_CJK_PUNCTUATION,
|
||||
KIND_K_LETTER,
|
||||
KIND_NON_CJK,
|
||||
mapAst,
|
||||
splitText,
|
||||
};
|
||||
41
src/prettier-markdown/visitor-keys.js
Normal file
41
src/prettier-markdown/visitor-keys.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const visitorKeys = {
|
||||
root: ["children"],
|
||||
paragraph: ["children"],
|
||||
sentence: ["children"],
|
||||
word: [],
|
||||
whitespace: [],
|
||||
emphasis: ["children"],
|
||||
strong: ["children"],
|
||||
delete: ["children"],
|
||||
inlineCode: [],
|
||||
wikiLink: [],
|
||||
link: ["children"],
|
||||
image: [],
|
||||
blockquote: ["children"],
|
||||
heading: ["children"],
|
||||
code: [],
|
||||
html: [],
|
||||
list: ["children"],
|
||||
thematicBreak: [],
|
||||
linkReference: ["children"],
|
||||
imageReference: [],
|
||||
definition: [],
|
||||
footnote: ["children"],
|
||||
footnoteReference: [],
|
||||
footnoteDefinition: ["children"],
|
||||
table: ["children"],
|
||||
tableCell: ["children"],
|
||||
break: [],
|
||||
liquidNode: [],
|
||||
import: [],
|
||||
export: [],
|
||||
esComment: [],
|
||||
jsx: [],
|
||||
math: [],
|
||||
inlineMath: [],
|
||||
tableRow: ["children"],
|
||||
listItem: ["children"],
|
||||
text: [],
|
||||
};
|
||||
|
||||
export default visitorKeys;
|
||||
176
test/markdown-formatting.test.ts
Normal file
176
test/markdown-formatting.test.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { printers } from "../src/index.js";
|
||||
|
||||
describe("Markdown Formatting in Descriptions", () => {
|
||||
const printer = printers?.["openapi-ast"];
|
||||
|
||||
describe("Basic markdown formatting", () => {
|
||||
it("should format description fields with markdown", () => {
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: "yaml",
|
||||
content: {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: "Test API",
|
||||
version: "1.0.0",
|
||||
description: "This is a test description\n\n\nWith multiple spaces ",
|
||||
},
|
||||
paths: {
|
||||
"/test": {
|
||||
get: {
|
||||
summary: "Get endpoint ",
|
||||
description: "Endpoint description\n\n\nwith extra spaces",
|
||||
operationId: "getTest",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success response",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
originalText: "",
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Result is undefined");
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
// Check that multiple spaces are normalized in the original content
|
||||
// Note: YAML may format this differently, but the content should be processed
|
||||
// The description field should exist and be formatted
|
||||
expect(resultString).toContain("description:");
|
||||
|
||||
// Check that multiple blank lines are normalized
|
||||
expect(resultString).not.toMatch(/\n{4,}/);
|
||||
});
|
||||
|
||||
it("should preserve code blocks in descriptions", () => {
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: "yaml",
|
||||
content: {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: "Test API",
|
||||
version: "1.0.0",
|
||||
description:
|
||||
"Here is some code:\n\n const x = 1;\n const y = 2;\n\nAnd more text.",
|
||||
},
|
||||
},
|
||||
originalText: "",
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Result is undefined");
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
// Code blocks (4+ spaces) should be preserved
|
||||
expect(resultString).toContain(" const x = 1;");
|
||||
expect(resultString).toContain(" const y = 2;");
|
||||
});
|
||||
|
||||
it("should format markdown in nested objects", () => {
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: "yaml",
|
||||
content: {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: "Test API",
|
||||
version: "1.0.0",
|
||||
},
|
||||
paths: {
|
||||
"/test": {
|
||||
get: {
|
||||
operationId: "test",
|
||||
parameters: [
|
||||
{
|
||||
name: "filter",
|
||||
in: "query",
|
||||
description: "Filter parameter with spaces",
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success response",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
originalText: "",
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Result is undefined");
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
// Both parameter and response descriptions should be formatted
|
||||
expect(resultString).toContain("description:");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Summary field formatting", () => {
|
||||
it("should format summary fields", () => {
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: "yaml",
|
||||
content: {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: "Test API",
|
||||
version: "1.0.0",
|
||||
summary: "API summary with spaces",
|
||||
},
|
||||
paths: {
|
||||
"/test": {
|
||||
get: {
|
||||
summary: "Get endpoint summary",
|
||||
operationId: "test",
|
||||
responses: { "200": { description: "OK" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
originalText: "",
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Result is undefined");
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
// Summary fields should be processed
|
||||
expect(resultString).toContain("summary:");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user