-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support markdown #2943
feat: support markdown #2943
Changes from 15 commits
d25469f
50aeda5
63de920
41d0f43
f1cce5b
a59dbcc
1ae442a
c8308ee
1e41713
703db97
956eca7
3085364
57d549b
0b42021
9fb9f71
9b85280
032a1c0
fba4662
a982f35
219356e
cecd425
bbd7837
efa96e3
709093f
f764d72
f31025a
9a99ac7
5eead94
a5c64cb
7c15481
2608853
e9d0ed9
9609465
3cb24e7
232dc7f
ec302e6
6b1ed27
78327ad
19c2ae7
1271883
e9c68b1
dc12b70
01ce9ff
0b442c1
066599b
e1ca36d
505fe5a
0e8ecdf
f485b2a
acea7f7
ee546a5
461c97c
0c4b3a7
a1f9761
4f17533
d0630ad
4143c10
654d772
1f4872d
5268a35
f69dad1
4306f43
e57e7d6
05629fe
2653cda
1e4f3ae
ed0e8c1
8a0a90f
aa5e560
7704bb7
f670f1f
506f8bc
f1dcfbe
7c95809
e4a52ac
b8e43cf
65b8e9a
76cc30a
a6a1f05
9e3d01a
90d73b8
0acc85d
2a54bcd
52ca697
a29a73c
1f7a8d1
53d6c95
ea98870
e196a12
d82febe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"use strict"; | ||
|
||
const map = require("unist-util-map"); | ||
const remarkFrontmatter = require("remark-frontmatter"); | ||
const remarkParse = require("remark-parse"); | ||
const unified = require("unified"); | ||
|
||
function splitText() { | ||
return ast => | ||
map(ast, node => { | ||
return node.type !== "text" | ||
? node | ||
: Object.assign({}, node, { | ||
type: "sentence", | ||
children: node.value | ||
.split(/(\s+)/g) | ||
.map( | ||
(text, index) => | ||
index % 2 === 0 | ||
? { type: "word", value: text } | ||
: { type: "whitespace", value: " " } | ||
) | ||
.filter(node => node.value.length) // remove empty word | ||
}); | ||
}); | ||
} | ||
|
||
function parse(text /*, parsers, opts*/) { | ||
const processor = unified() | ||
.use(remarkParse, { position: false }) | ||
.use(remarkFrontmatter, ["yaml"]) | ||
.use(splitText); | ||
return processor.runSync(processor.parse(text)); | ||
} | ||
|
||
module.exports = parse; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,292 @@ | ||
"use strict"; | ||
|
||
const util = require("./util"); | ||
const docBuilders = require("./doc-builders"); | ||
const concat = docBuilders.concat; | ||
const join = docBuilders.join; | ||
const line = docBuilders.line; | ||
const hardline = docBuilders.hardline; | ||
const softline = docBuilders.softline; | ||
const group = docBuilders.group; | ||
const fill = docBuilders.fill; | ||
const indent = docBuilders.indent; | ||
const ifBreak = docBuilders.ifBreak; | ||
const align = docBuilders.align; | ||
const docPrinter = require("./doc-printer"); | ||
const printDocToString = docPrinter.printDocToString; | ||
|
||
function genericPrint(path, options, print) { | ||
const node = path.getValue(); | ||
|
||
switch (node.type) { | ||
case "root": | ||
return concat([printChildren(path, options, print), hardline]); | ||
case "paragraph": | ||
return concat([printChildren(path, options, print)]); | ||
case "sentence": | ||
return concat([ | ||
printBlockquotePrefix(path), | ||
printChildren( | ||
path, | ||
options, | ||
print, | ||
(parts, childPath, index) => { | ||
const childNode = childPath.getValue(); | ||
if ( | ||
!( | ||
childNode.type === "whitespace" && | ||
index === node.children.length - 1 | ||
) | ||
) { | ||
parts.push(childPath.call(print)); | ||
} | ||
}, | ||
fill | ||
) | ||
]); | ||
case "word": | ||
return node.value; | ||
case "whitespace": | ||
return concat([ | ||
// parent types that disallow multi-line content | ||
hasParentType(path, ["heading", "table"]) ? " " : line, | ||
ifBreak(printBlockquotePrefix(path)) | ||
]); | ||
case "emphasis": | ||
return concat(["*", printChildren(path, options, print), "*"]); | ||
case "strong": | ||
return concat(["**", printChildren(path, options, print), "**"]); | ||
case "delete": | ||
return concat(["~~", printChildren(path, options, print), "~~"]); | ||
case "inlineCode": | ||
return concat(["`", node.value, "`"]); | ||
case "link": | ||
return concat([ | ||
"[", | ||
printChildren(path, options, print), | ||
"](", | ||
node.url, | ||
node.title ? ` "${node.title}"` : "", | ||
")" | ||
]); | ||
case "image": | ||
return concat([ | ||
"![", | ||
node.alt || "", | ||
"](", | ||
node.url, | ||
node.title ? ` "${node.title}"` : "", | ||
")" | ||
]); | ||
case "blockquote": | ||
return printChildren(path, options, print); | ||
case "heading": | ||
return concat([ | ||
"#".repeat(node.depth) + " ", | ||
printChildren(path, options, print), | ||
hardline, | ||
hardline | ||
]); | ||
case "code": | ||
return concat([ | ||
"```", | ||
node.lang || "", | ||
hardline, | ||
node.value, | ||
hardline, | ||
"```", | ||
hardline | ||
]); | ||
case "yaml": | ||
return concat(["---", hardline, node.value, hardline, "---", hardline]); | ||
case "html": | ||
return node.value; | ||
case "list": | ||
return concat([ | ||
printChildren(path, options, print, (parts, childPath, index) => { | ||
const prefix = node.ordered ? `${node.start + index}. ` : "- "; | ||
parts.push( | ||
prefix, | ||
align(prefix.length, childPath.call(print)), | ||
hardline | ||
); | ||
}), | ||
hardline | ||
]); | ||
case "listItem": { | ||
const prefix = | ||
node.checked === null ? "" : node.checked ? "[x] " : "[ ] "; | ||
return concat([ | ||
prefix, | ||
align( | ||
prefix.length, | ||
printChildren(path, options, print, (parts, childPath, index) => { | ||
const childNode = childPath.getValue(); | ||
parts.push(childPath.call(print)); | ||
if ( | ||
index !== node.children.length - 1 && | ||
childNode.type === "paragraph" | ||
) { | ||
parts.push(hardline); | ||
} | ||
}) | ||
) | ||
]); | ||
} | ||
case "thematicBreak": | ||
return concat(["---", hardline]); | ||
case "table": | ||
return printTable(path, options, print); | ||
case "tableCell": | ||
return printChildren(path, options, print); | ||
case "tableRow": // handled in "table" | ||
default: | ||
throw new Error(`Unknown markdown type ${JSON.stringify(node.type)}`); | ||
} | ||
} | ||
|
||
function hasParentType(path, typeOrTypes) { | ||
const types = [].concat(typeOrTypes); | ||
|
||
let counter = 0; | ||
let parentNode; | ||
|
||
while ((parentNode = path.getParentNode(counter++))) { | ||
if (types.indexOf(parentNode.type) !== -1) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
function printTable(path, options, print) { | ||
const node = path.getValue(); | ||
const contents = []; // { [rowIndex: number]: { [columnIndex: number]: string } } | ||
|
||
path.map(rowPath => { | ||
const rowContents = []; | ||
|
||
rowPath.map(cellPath => { | ||
rowContents.push( | ||
printDocToString(cellPath.call(print), options).formatted | ||
); | ||
}, "children"); | ||
|
||
contents.push(rowContents); | ||
}, "children"); | ||
|
||
const columnMaxWidths = contents.reduce( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should also limit the max length of the columns. For example, All-Contributors tables contain HTML code and table dash columns processed by Prettier get very very long. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you open a new issue so we can track this better? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done #4195 |
||
(currentWidths, rowContents) => | ||
currentWidths.map((width, columnIndex) => | ||
Math.max(width, rowContents[columnIndex].length) | ||
), | ||
contents[0].map(() => 3) // minimum width = 3 (---, :--, :-:, --:) | ||
); | ||
|
||
return concat([ | ||
concat(contents.slice(0, 1).map(printRow)), | ||
printSeparator(), | ||
concat(contents.slice(1).map(printRow)) | ||
]); | ||
|
||
function printSeparator() { | ||
return concat([ | ||
"| ", | ||
join( | ||
" | ", | ||
columnMaxWidths.map((width, index) => { | ||
switch (node.align[index]) { | ||
case "left": | ||
return ":" + "-".repeat(width - 1); | ||
case "right": | ||
return "-".repeat(width - 1) + ":"; | ||
case "center": | ||
return ":" + "-".repeat(width - 2) + ":"; | ||
default: | ||
return "-".repeat(width); | ||
} | ||
}) | ||
), | ||
" |", | ||
hardline | ||
]); | ||
} | ||
|
||
function printRow(rowContents) { | ||
return concat([ | ||
"| ", | ||
join( | ||
" | ", | ||
rowContents.map((rowContent, columnIndex) => { | ||
switch (node.align[columnIndex]) { | ||
case "right": | ||
return alignRight(rowContent, columnMaxWidths[columnIndex]); | ||
case "center": | ||
return alignCenter(rowContent, columnMaxWidths[columnIndex]); | ||
default: | ||
return alignLeft(rowContent, columnMaxWidths[columnIndex]); | ||
} | ||
}) | ||
), | ||
" |", | ||
hardline | ||
]); | ||
} | ||
|
||
function alignLeft(text, width) { | ||
return concat([text, " ".repeat(width - text.length)]); | ||
} | ||
|
||
function alignRight(text, width) { | ||
return concat([" ".repeat(width - text.length), text]); | ||
} | ||
|
||
function alignCenter(text, width) { | ||
const spaces = width - text.length; | ||
const left = Math.floor(spaces / 2); | ||
const right = spaces - left; | ||
return concat([" ".repeat(left), text, " ".repeat(right)]); | ||
} | ||
} | ||
|
||
function printBlockquotePrefix(path) { | ||
let blockquoteLevel = 0; | ||
let counter = 0; | ||
let parentNode; | ||
|
||
while ((parentNode = path.getParentNode(counter++))) { | ||
if (parentNode.type === "blockquote") { | ||
blockquoteLevel++; | ||
} | ||
} | ||
|
||
return blockquoteLevel ? ">".repeat(blockquoteLevel) + " " : ""; | ||
} | ||
|
||
function printChildren(path, options, print, iterator, command) { | ||
command = command || concat; | ||
|
||
const node = path.getValue(); | ||
|
||
const callback = | ||
typeof iterator === "function" | ||
? iterator | ||
: (parts, childPath, index) => { | ||
parts.push(childPath.call(print)); | ||
if (index !== node.children.length - 1) { | ||
parts.push(iterator || ""); | ||
} | ||
}; | ||
|
||
const parts = []; | ||
|
||
path.map((childPath, index) => { | ||
// TODO: prettier-ignore | ||
callback(parts, childPath, index); | ||
}, "children"); | ||
|
||
return command(parts); | ||
} | ||
|
||
module.exports = genericPrint; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why include the index? One of the things I like about markdown is that I can move around the entries in an ordered list without having to renumber them
(loving this PR btw, prettier keeps on making new stuff pretty :D)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I think it looks better with the index and the index will be renumbered by Prettier, you don't have to edit it manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so
is changed to
?
In that case I guess it's fine 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, only the initial number matters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome!