Skip to content
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

Merged
merged 90 commits into from
Oct 11, 2017
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
d25469f
feat(markdown): inital implementation
ikatyang Sep 21, 2017
50aeda5
feat(markdown): support strong
ikatyang Sep 21, 2017
63de920
fix: add missing default value
ikatyang Sep 21, 2017
41d0f43
feat(markdown): support inlineCode
ikatyang Sep 21, 2017
f1cce5b
feat: support delete
ikatyang Sep 21, 2017
a59dbcc
feat: support link
ikatyang Sep 21, 2017
1ae442a
feat: support image
ikatyang Sep 21, 2017
c8308ee
feat: support blockquote
ikatyang Sep 21, 2017
1e41713
feat: support heading
ikatyang Sep 22, 2017
703db97
feat: support code
ikatyang Sep 22, 2017
956eca7
feat: support yaml
ikatyang Sep 22, 2017
3085364
feat: support html
ikatyang Sep 22, 2017
57d549b
feat: support list
ikatyang Sep 22, 2017
0b42021
feat: support thematicBreak
ikatyang Sep 22, 2017
9fb9f71
feat: support table
ikatyang Sep 22, 2017
9b85280
feat: support linkReference
ikatyang Sep 22, 2017
032a1c0
feat: support imageReference
ikatyang Sep 22, 2017
fba4662
feat: support definition
ikatyang Sep 22, 2017
a982f35
feat: support footnote
ikatyang Sep 22, 2017
219356e
feat: support footnoteReference
ikatyang Sep 22, 2017
cecd425
feat: support footnoteDefinition
ikatyang Sep 22, 2017
bbd7837
test(cli): update snapshots
ikatyang Sep 22, 2017
efa96e3
refactor: extract SINGLE_LINE_NODE_TYPES
ikatyang Sep 25, 2017
709093f
refactor: printChildren
ikatyang Sep 25, 2017
f764d72
fix: correct newlines
ikatyang Sep 26, 2017
f31025a
test: add trailing newline
ikatyang Sep 26, 2017
9a99ac7
fix: blockquote formatting
ikatyang Sep 27, 2017
5eead94
fix: node types
ikatyang Sep 27, 2017
a5c64cb
fix: break line correctly
ikatyang Sep 28, 2017
7c15481
fix: remove unnecessary properties to make AST_COMPARE happy
ikatyang Sep 28, 2017
2608853
fix: escape `|` in tableCell content
ikatyang Sep 28, 2017
e9d0ed9
fix: unexpected line break
ikatyang Sep 28, 2017
9609465
fix: ast difference from loose list
ikatyang Sep 28, 2017
3cb24e7
fix: html break lines
ikatyang Sep 29, 2017
232dc7f
Merge branch 'master' into feat/markdown
ikatyang Sep 30, 2017
ec302e6
refactor: fix linting
ikatyang Sep 30, 2017
6b1ed27
fix: normalize ast
ikatyang Sep 30, 2017
78327ad
fix: escape specific chars
ikatyang Sep 30, 2017
19c2ae7
test: add more tests
ikatyang Sep 30, 2017
1271883
fix: build markdown parser
ikatyang Sep 30, 2017
e9c68b1
chore: remove unnecessary *.log
ikatyang Oct 1, 2017
dc12b70
fix: escape html entity
ikatyang Oct 1, 2017
01ce9ff
feat: support prettier-ignore
ikatyang Oct 1, 2017
0b442c1
fix: line break for non-loose listItem
ikatyang Oct 1, 2017
066599b
feat: support formatting `code` based on `lang`
ikatyang Oct 1, 2017
e1ca36d
fix: add `jsx` and `tsx`
ikatyang Oct 1, 2017
505fe5a
fix: use multiparser
ikatyang Oct 1, 2017
0e8ecdf
refactor: fix linting
ikatyang Oct 1, 2017
f485b2a
test: update test case 😉
ikatyang Oct 1, 2017
acea7f7
feat: switch to `_` style emphasis
ikatyang Oct 1, 2017
ee546a5
fix: sequence list should use different prefix
ikatyang Oct 1, 2017
461c97c
test: add tests
ikatyang Oct 1, 2017
0c4b3a7
fix: do not print additional new line after `prettier-ignore`
ikatyang Oct 1, 2017
a1f9761
fix(list): enforce `1.` to avoid unnecessary git diff
ikatyang Oct 1, 2017
4f17533
feat: enable `commonmark` option
ikatyang Oct 1, 2017
d0630ad
fix: respect autolink-style link
ikatyang Oct 1, 2017
4143c10
feat: support md`...` and markdown`...`
ikatyang Oct 2, 2017
654d772
Merge branch 'master' into feat/markdown
ikatyang Oct 2, 2017
1f4872d
docs: replace ands with commas
ikatyang Oct 3, 2017
5268a35
fix: respect indented code block
ikatyang Oct 3, 2017
f69dad1
fix: respect html entity
ikatyang Oct 3, 2017
4306f43
docs: add docs for modified MDAST
ikatyang Oct 3, 2017
e57e7d6
fix: inlineCode is breakline-able
ikatyang Oct 3, 2017
05629fe
feat: support backtick in inlineCode
ikatyang Oct 3, 2017
2653cda
feat: support a-lot-of-backtick in fenced code block
ikatyang Oct 3, 2017
1e4f3ae
feat: use `~~~`-style code block in js template
ikatyang Oct 4, 2017
ed0e8c1
fix: respect escaped chars
ikatyang Oct 5, 2017
8a0a90f
test: add test cases
ikatyang Oct 5, 2017
aa5e560
fix: use `- - -`-style thematicBreak to avoid conflict with yaml
ikatyang Oct 5, 2017
7704bb7
fix: remain the same content for linkReference identifier
ikatyang Oct 6, 2017
f670f1f
refactor: fix typo
ikatyang Oct 6, 2017
506f8bc
fix: wrap `definition`'s url if there's whitespace
ikatyang Oct 6, 2017
f1dcfbe
fix: remove unnecessary whitespace at the end of paragraph
ikatyang Oct 6, 2017
7c95809
fix: fix: remove unnecessary whitespace at the start of paragraph
ikatyang Oct 6, 2017
e4a52ac
fix: setence children length is possible 0
ikatyang Oct 6, 2017
b8e43cf
fix: support continuous ordered list
ikatyang Oct 6, 2017
65b8e9a
fix: do not print addtional hardline after loose list
ikatyang Oct 6, 2017
76cc30a
fix: use double-backtick style for single-backtick value in inlineCode
ikatyang Oct 6, 2017
a6a1f05
fix: support nested emphasis
ikatyang Oct 6, 2017
9e3d01a
fix: support space-url in link/image
ikatyang Oct 6, 2017
90d73b8
fix: escape `)` in link/image url
ikatyang Oct 6, 2017
0acc85d
fix: support single-quote in link/image/definition title
ikatyang Oct 6, 2017
2a54bcd
fix: respect alt in image/imageReference
ikatyang Oct 6, 2017
52ca697
fix: use `*`-style thematicBreak in list
ikatyang Oct 6, 2017
a29a73c
fix: loose/tight list linebreaks
ikatyang Oct 6, 2017
1f7a8d1
fix: print third linebreak before indented code block with a tight li…
ikatyang Oct 6, 2017
53d6c95
test: move bug cases
ikatyang Oct 6, 2017
ea98870
fix: remove unnecessary linebreaks
ikatyang Oct 6, 2017
e196a12
Merge branch 'master' into feat/markdown
ikatyang Oct 6, 2017
d82febe
refactor: fix typo
ikatyang Oct 7, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@
"postcss-scss": "1.0.0",
"postcss-selector-parser": "2.2.3",
"postcss-values-parser": "git://github.com/lydell/postcss-values-parser.git#af2c80b2bb558a6e7d61540d97f068f9fa162b38",
"remark-frontmatter": "1.1.0",
"remark-parse": "4.0.0",
"strip-bom": "3.0.0",
"typescript": "2.5.1",
"typescript-eslint-parser": "git://github.com/eslint/typescript-eslint-parser.git#5576fb48c89165967557e867b9462d43431dcb10"
"typescript-eslint-parser": "git://github.com/eslint/typescript-eslint-parser.git#5576fb48c89165967557e867b9462d43431dcb10",
"unified": "6.1.5",
"unist-util-map": "1.0.3"
},
"devDependencies": {
"babel-cli": "6.24.1",
Expand Down
10 changes: 9 additions & 1 deletion src/cli-constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,15 @@ const detailedOptions = normalizeDetailedOptions({
category: CATEGORY_FORMAT,
forwardToApi: true,
exception: value => typeof value === "string", // Allow path to a parser module.
choices: ["flow", "babylon", "typescript", "postcss", "json", "graphql"],
choices: [
"flow",
"babylon",
"typescript",
"postcss",
"json",
"graphql",
"markdown"
],
description: "Which parser to use.",
getter: (value, argv) => (argv["flow-parser"] ? "flow" : value)
},
Expand Down
2 changes: 2 additions & 0 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ function normalize(options) {
normalized.parser = "graphql";
} else if (/\.json$/.test(filepath)) {
normalized.parser = "json";
} else if (/\.(md|markdown)$/.test(filepath)) {
normalized.parser = "markdown";
}

if (normalized.parser === "json") {
Expand Down
36 changes: 36 additions & 0 deletions src/parser-markdown.js
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;
3 changes: 3 additions & 0 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const parsers = {
},
get json() {
return eval("require")("./parser-babylon");
},
get markdown() {
return eval("require")("./parser-markdown");
}
};

Expand Down
292 changes: 292 additions & 0 deletions src/printer-markdown.js
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}. ` : "- ";
Copy link
Contributor

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)

Copy link
Member Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so

1. one
3. two
2. three

is changed to

1. one
2. two
3. three

?

In that case I guess it's fine 🙂

Copy link
Member Author

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.

Copy link
Member

@azz azz Oct 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

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(
Copy link

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you open a new issue so we can track this better?

Copy link

Choose a reason for hiding this comment

The 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;
2 changes: 2 additions & 0 deletions src/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ function getPrintFunction(options) {
return require("./printer-htmlparser2");
case "postcss":
return require("./printer-postcss");
case "markdown":
return require("./printer-markdown");
default:
return genericPrintNoParens;
}
Expand Down