Escape unindented indented pseudo setext headers (#19350)
Co-authored-by: fisker <lionkay@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
6 files changed
+144
-11
No files matched your search
@@ -0,0 +1,31 @@
|
||||
#### Escape unindented indented pseudo setext headers (#19350 by @tats-u)
|
||||
|
||||
Indented lines composed of only `=` or `-` were previously unindented without being escaped, which caused them to be mistakenly parsed as setext headers. With this change, Prettier now escapes such lines to prevent the misinterpretation:
|
||||
|
||||
[Check parse results on CommonMark playground](https://spec.commonmark.org/dingus/?text=%3C!--%20Input%20--%3E%0ANot%20a%20header%0A%20%20%20%20%3D%3D%3D%0ANot%20a%20header%0A%20%20%20%20---%0ANot%20a%20list%20item%20or%20a%20header%0A%20%20%20%20-%0A%0A------%0A%0A%3C!--%20Prettier%20stable%20--%3E%0ANot%20a%20header%0A%3D%3D%3D%0ANot%20a%20header%0A---%0ANot%20a%20list%20item%20or%20a%20header%20-%0A%0A------%0A%0A%3C!--%20Prettier%20main%20--%3E%0ANot%20a%20header%0A%5C%3D%3D%3D%0ANot%20a%20header%0A%5C---%0ANot%20a%20list%20item%20or%20a%20header%0A%5C-)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```markdown
|
||||
<!-- Input -->
|
||||
Not a header
|
||||
===
|
||||
Not a header
|
||||
---
|
||||
Not a list item or a header
|
||||
-
|
||||
|
||||
<!-- Prettier stable -->
|
||||
Not a header
|
||||
===
|
||||
Not a header
|
||||
---
|
||||
Not a list item or a header -
|
||||
|
||||
<!-- Prettier main -->
|
||||
Not a header
|
||||
\===
|
||||
Not a header
|
||||
\---
|
||||
Not a list item or a header
|
||||
\-
|
||||
```
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
getFencedCodeBlockValue,
|
||||
getNthListSiblingIndex,
|
||||
isAutolink,
|
||||
isNewLine,
|
||||
isPrettierIgnore,
|
||||
splitText,
|
||||
} from "../utilities.js";
|
||||
@@ -65,6 +66,19 @@ function hasFakeWhitespaceAfterNextToken(path) {
|
||||
return afterNext?.type === "whitespace" && afterNext.value === "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AstPath} path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isNextTokenFakeSetextH2Line(path) {
|
||||
if (!isNewLine(path.node) || path.next?.value !== "-") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const afterNext = path.siblings[path.index + 2];
|
||||
return !afterNext || isNewLine(afterNext);
|
||||
}
|
||||
|
||||
function printMdast(path, options, print) {
|
||||
const { node } = path;
|
||||
|
||||
@@ -116,7 +130,9 @@ function printMdast(path, options, print) {
|
||||
case "sentence":
|
||||
return printSentence(path, print);
|
||||
case "word":
|
||||
return options.parser !== "mdx" ? printWord(path) : printWordLegacy(path);
|
||||
return options.parser !== "mdx"
|
||||
? printWord(path, options)
|
||||
: printWordLegacy(path);
|
||||
case "whitespace": {
|
||||
const { next } = path;
|
||||
|
||||
@@ -125,7 +141,9 @@ function printMdast(path, options, print) {
|
||||
next &&
|
||||
/^>|^(?:[*+-]|#{1,6}|\d+[).])$/.test(next.value) &&
|
||||
// Avoid https://github.com/prettier/prettier/issues/18861
|
||||
!hasFakeWhitespaceAfterNextToken(path)
|
||||
!hasFakeWhitespaceAfterNextToken(path) &&
|
||||
// Next fake setext h2 `-` is going to be escaped, so no need to join adjacent words
|
||||
!(options.proseWrap === "preserve" && isNextTokenFakeSetextH2Line(path))
|
||||
? "never"
|
||||
: options.proseWrap;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { PUNCTUATION_REGEXP } from "../constants.evaluate.js";
|
||||
import { isAutolink } from "../utilities.js";
|
||||
import { isAutolink, isNewLine } from "../utilities.js";
|
||||
|
||||
const fakeSetextHeaderRegex = /^(?:=+|-+)$/;
|
||||
|
||||
/**
|
||||
* @import AstPath from "../../common/ast-path.js"
|
||||
@@ -8,25 +10,36 @@ import { isAutolink } from "../utilities.js";
|
||||
|
||||
/**
|
||||
* @param {AstPath} path
|
||||
* @param {*} options
|
||||
* @return {Doc}
|
||||
*/
|
||||
function printWord(path) {
|
||||
function printWord(path, options) {
|
||||
const { node } = path;
|
||||
const emphasisOrStrong = path.findAncestor(
|
||||
(p) => p.type === "emphasis" || p.type === "strong",
|
||||
);
|
||||
if (!emphasisOrStrong) {
|
||||
return node.value;
|
||||
}
|
||||
const { previous, next, grandparent } = path;
|
||||
let text = node.value;
|
||||
if (!emphasisOrStrong) {
|
||||
if (
|
||||
options.proseWrap === "preserve" &&
|
||||
path.parent.type === "sentence" &&
|
||||
fakeSetextHeaderRegex.test(text) &&
|
||||
isNewLine(path.previous) &&
|
||||
(path.isLast || isNewLine(path.next))
|
||||
) {
|
||||
// escape indented pseudo setext header, e.g. `Previous line↵␣␣␣␣===`
|
||||
return `\\${text}`;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// escape leading `*` or `_` if it's the first character in an emphasis/strong
|
||||
if (
|
||||
path.isFirst &&
|
||||
(text.startsWith("*") || text.startsWith("_")) &&
|
||||
path.callParent(() => path.isFirst) &&
|
||||
grandparent === emphasisOrStrong
|
||||
path.grandparent === emphasisOrStrong
|
||||
) {
|
||||
text = `\\${text}`;
|
||||
}
|
||||
@@ -44,9 +57,9 @@ function printWord(path) {
|
||||
}
|
||||
if (
|
||||
canOpenOrCloseStrongOrEmphasis(
|
||||
preceding.at(-1) || previous?.value.at(-1),
|
||||
preceding.at(-1) || path.previous?.value.at(-1),
|
||||
delimiterRun,
|
||||
following[0] || next?.value[0],
|
||||
following[0] || path.next?.value[0],
|
||||
)
|
||||
) {
|
||||
return `${preceding}\\${delimiterRun}${following}`;
|
||||
|
||||
@@ -321,6 +321,8 @@ function isSetextHeading(node) {
|
||||
return start.line !== end.line;
|
||||
}
|
||||
|
||||
const isNewLine = (node) => node?.type === "whitespace" && node.value === "\n";
|
||||
|
||||
export {
|
||||
getFencedCodeBlockValue,
|
||||
getNthListSiblingIndex,
|
||||
@@ -330,6 +332,7 @@ export {
|
||||
INLINE_NODE_TYPES,
|
||||
INLINE_NODE_WRAPPER_TYPES,
|
||||
isAutolink,
|
||||
isNewLine,
|
||||
isPrettierIgnore,
|
||||
isSetextHeading,
|
||||
KIND_CJ_LETTER,
|
||||
|
||||
@@ -124,6 +124,55 @@ Bar
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`not-header.md format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["markdown"]
|
||||
printWidth: 80 (default) |
|
||||
=====================================input======================================
|
||||
\\===
|
||||
===
|
||||
\\===
|
||||
---
|
||||
\\---
|
||||
-
|
||||
\\-
|
||||
=
|
||||
\\=
|
||||
\\\\---
|
||||
\\\\===
|
||||
\\-
|
||||
\\=
|
||||
\\\\-
|
||||
\\\\=
|
||||
-foo ---**bar**===あ===
|
||||
|
||||
\\-
|
||||
-
|
||||
|
||||
=====================================output=====================================
|
||||
\\===
|
||||
\\===
|
||||
\\===
|
||||
\\---
|
||||
\\---
|
||||
\\-
|
||||
\\-
|
||||
\\=
|
||||
\\=
|
||||
\\\\---
|
||||
\\\\===
|
||||
\\-
|
||||
\\=
|
||||
\\\\-
|
||||
\\\\=
|
||||
-foo ---**bar**===あ===
|
||||
|
||||
\\-
|
||||
\\-
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`setext.md format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["markdown"]
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
\===
|
||||
===
|
||||
\===
|
||||
---
|
||||
\---
|
||||
-
|
||||
\-
|
||||
=
|
||||
\=
|
||||
\\---
|
||||
\\===
|
||||
\-
|
||||
\=
|
||||
\\-
|
||||
\\=
|
||||
-foo ---**bar**===あ===
|
||||
|
||||
\-
|
||||
-
|
||||
Reference in new issue
Block a user