Split up comment printing (#3575)

* Split up comment printing

* Refactor plugin handling

* Allow multiparser to use options normalization

* Rename to canAttachComment

* Add inline comment

* Format code

* Use prettier.__debug to get AST
master
Lucas Azzola 2017-12-27 00:04:09 +11:00 committed by GitHub
parent 358984ef0c
commit ee148bfded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 182 additions and 121 deletions

View File

@ -1,17 +1,22 @@
"use strict";
const comments = require("./src/main/comments");
const docblock = require("jest-docblock");
const version = require("./package.json").version;
const printAstToDoc = require("./src/main/ast-to-doc");
const util = require("./src/common/util");
const getSupportInfo = require("./src/common/support").getSupportInfo;
const comments = require("./src/main/comments");
const printAstToDoc = require("./src/main/ast-to-doc");
const normalizeOptions = require("./src/main/options").normalize;
const parser = require("./src/main/parser");
const config = require("./src/config/resolve-config");
const doc = require("./src/doc");
const printDocToString = doc.printer.printDocToString;
const printDocToDebug = doc.debug.printDocToDebug;
const normalizeOptions = require("./src/common/options").normalize;
const parser = require("./src/main/parser");
const config = require("./src/config/resolve-config");
const getSupportInfo = require("./src/common/support").getSupportInfo;
const docblock = require("jest-docblock");
function guessLineEnding(text) {
const index = text.indexOf("\n");
@ -101,7 +106,7 @@ function formatWithCursor(text, opts, addAlignmentSize) {
let cursorOffset;
if (opts.cursorOffset >= 0) {
const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset);
const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset, opts);
const cursorNode = cursorNodeAndParents.node;
if (cursorNode) {
cursorOffset = opts.cursorOffset - util.locStart(cursorNode);
@ -179,16 +184,21 @@ function findSiblingAncestors(startNodeAndParents, endNodeAndParents) {
};
}
function findNodeAtOffset(node, offset, predicate, parentNodes) {
function findNodeAtOffset(node, offset, options, predicate, parentNodes) {
predicate = predicate || (() => true);
parentNodes = parentNodes || [];
const start = util.locStart(node);
const end = util.locEnd(node);
if (start <= offset && offset <= end) {
for (const childNode of comments.getSortedChildNodes(node)) {
for (const childNode of comments.getSortedChildNodes(
node,
undefined /* text */,
options
)) {
const childResult = findNodeAtOffset(
childNode,
offset,
options,
predicate,
[node].concat(parentNodes)
);
@ -301,11 +311,17 @@ function calculateRange(text, opts, ast) {
}
}
const startNodeAndParents = findNodeAtOffset(ast, startNonWhitespace, node =>
isSourceElement(opts, node)
const startNodeAndParents = findNodeAtOffset(
ast,
startNonWhitespace,
opts,
node => isSourceElement(opts, node)
);
const endNodeAndParents = findNodeAtOffset(ast, endNonWhitespace, node =>
isSourceElement(opts, node)
const endNodeAndParents = findNodeAtOffset(
ast,
endNonWhitespace,
opts,
node => isSourceElement(opts, node)
);
if (!startNodeAndParents || !endNodeAndParents) {
@ -398,6 +414,7 @@ module.exports = {
/* istanbul ignore next */
__debug: {
parse: function(text, opts) {
opts = normalizeOptions(opts);
return parser.parse(text, opts);
},
formatAST: function(ast, opts) {

View File

@ -16,7 +16,7 @@ const cleanAST = require("../common/clean-ast").cleanAST;
const resolver = require("../config/resolve-config");
const constant = require("./constant");
const validator = require("./validator");
const apiDefaultOptions = require("../common/options").defaults;
const apiDefaultOptions = require("../main/options").defaults;
const errors = require("../common/errors");
const logger = require("./logger");
const thirdParty = require("../common/third-party");

View File

@ -12,9 +12,15 @@ function loadPlugins(options) {
require("../language-markdown"),
require("../language-html"),
require("../language-vue")
];
].filter(plugin => {
return options.plugins.indexOf(plugin) < 0;
});
const externalPlugins = options.plugins.map(plugin => {
if (typeof plugin !== "string") {
return plugin;
}
const pluginPath = resolve.sync(plugin, { basedir: process.cwd() });
return eval("require")(pluginPath);
});

View File

@ -539,7 +539,24 @@ function printSequence(sequencePath, options, print) {
});
}
function canAttachComment(node) {
return node.kind && node.kind !== "Comment";
}
function printComment(commentPath) {
const comment = commentPath.getValue();
switch (comment.kind) {
case "Comment":
return "#" + comment.value.trimRight();
default:
throw new Error("Not a comment: " + JSON.stringify(comment));
}
}
module.exports = {
print: genericPrint,
hasPrettierIgnore: util.hasIgnoreComment
hasPrettierIgnore: util.hasIgnoreComment,
printComment,
canAttachComment
};

View File

@ -5167,9 +5167,77 @@ function willPrintOwnComments(path) {
);
}
function canAttachComment(node) {
return (
node.type &&
node.type !== "CommentBlock" &&
node.type !== "CommentLine" &&
node.type !== "Line" &&
node.type !== "Block" &&
node.type !== "EmptyStatement" &&
node.type !== "TemplateElement" &&
node.type !== "Import" &&
!(node.callee && node.callee.type === "Import")
);
}
function printComment(commentPath, options) {
const comment = commentPath.getValue();
switch (comment.type) {
case "CommentBlock":
case "Block": {
if (isJsDocComment(comment)) {
return printJsDocComment(comment);
}
const isInsideFlowComment =
options.originalText.substr(util.locEnd(comment) - 3, 3) === "*-/";
return "/*" + comment.value + (isInsideFlowComment ? "*-/" : "*/");
}
case "CommentLine":
case "Line":
// Print shebangs with the proper comment characters
if (options.originalText.slice(util.locStart(comment)).startsWith("#!")) {
return "#!" + comment.value.trimRight();
}
return "//" + comment.value.trimRight();
default:
throw new Error("Not a comment: " + JSON.stringify(comment));
}
}
function isJsDocComment(comment) {
const lines = comment.value.split("\n");
return (
lines.length > 1 &&
lines.slice(0, lines.length - 1).every(line => line.trim()[0] === "*")
);
}
function printJsDocComment(comment) {
const lines = comment.value.split("\n");
return concat([
"/*",
join(
hardline,
lines.map(
(line, index) =>
(index > 0 ? " " : "") +
(index < lines.length - 1 ? line.trim() : line.trimLeft())
)
),
"*/"
]);
}
module.exports = {
print: genericPrint,
embed,
hasPrettierIgnore,
willPrintOwnComments
willPrintOwnComments,
canAttachComment,
printComment
};

View File

@ -12,12 +12,11 @@ const concat = docBuilders.concat;
const hardline = docBuilders.hardline;
const addAlignmentToDoc = docBuilders.addAlignmentToDoc;
const docUtils = doc.utils;
const getPrinter = require("./get-printer");
function printAstToDoc(ast, options, addAlignmentSize) {
addAlignmentSize = addAlignmentSize || 0;
const printer = getPrinter(options);
const printer = options.printer;
const cache = new Map();
function printGenerically(path, args) {
@ -32,11 +31,11 @@ function printAstToDoc(ast, options, addAlignmentSize) {
// UnionTypeAnnotation has to align the child without the comments
let res;
if (printer.willPrintOwnComments && printer.willPrintOwnComments(path)) {
res = genericPrint(path, options, printer, printGenerically, args);
res = genericPrint(path, options, printGenerically, args);
} else {
res = comments.printComments(
path,
p => genericPrint(p, options, printer, printGenerically, args),
p => genericPrint(p, options, printGenerically, args),
options,
args && args.needsSemi
);
@ -68,10 +67,11 @@ function printAstToDoc(ast, options, addAlignmentSize) {
return doc;
}
function genericPrint(path, options, printer, printPath, args) {
function genericPrint(path, options, printPath, args) {
assert.ok(path instanceof FastPath);
const node = path.getValue();
const printer = options.printer;
// Escape hatch
if (printer.hasPrettierIgnore && printer.hasPrettierIgnore(path)) {
@ -81,7 +81,7 @@ function genericPrint(path, options, printer, printPath, args) {
if (node) {
try {
// Potentially switch to a different parser
const sub = multiparser.printSubtree(printer, path, printPath, options);
const sub = multiparser.printSubtree(path, printPath, options);
if (sub) {
return sub;
}

View File

@ -18,25 +18,14 @@ const getNextNonSpaceNonCommentCharacter =
const getNextNonSpaceNonCommentCharacterIndex =
util.getNextNonSpaceNonCommentCharacterIndex;
function getSortedChildNodes(node, text, resultArray) {
function getSortedChildNodes(node, text, options, resultArray) {
if (!node) {
return;
}
const printer = options.printer;
if (resultArray) {
if (
node &&
((node.type &&
node.type !== "CommentBlock" &&
node.type !== "CommentLine" &&
node.type !== "Line" &&
node.type !== "Block" &&
node.type !== "EmptyStatement" &&
node.type !== "TemplateElement" &&
node.type !== "Import" &&
!(node.callee && node.callee.type === "Import")) ||
(node.kind && node.kind !== "Comment"))
) {
if (node && printer.canAttachComment && printer.canAttachComment(node)) {
// This reverse insertion sort almost always takes constant
// time because we almost always (maybe always?) append the
// nodes in order anyway.
@ -74,7 +63,7 @@ function getSortedChildNodes(node, text, resultArray) {
}
for (let i = 0, nameCount = names.length; i < nameCount; ++i) {
getSortedChildNodes(node[names[i]], text, resultArray);
getSortedChildNodes(node[names[i]], text, options, resultArray);
}
return resultArray;
@ -83,8 +72,8 @@ function getSortedChildNodes(node, text, resultArray) {
// As efficiently as possible, decorate the comment object with
// .precedingNode, .enclosingNode, and/or .followingNode properties, at
// least one of which is guaranteed to be defined.
function decorateComment(node, comment, text) {
const childNodes = getSortedChildNodes(node, text);
function decorateComment(node, comment, text, options) {
const childNodes = getSortedChildNodes(node, text, options);
let precedingNode;
let followingNode;
// Time to dust off the old binary search robes and wizard hat.
@ -101,7 +90,7 @@ function decorateComment(node, comment, text) {
// The comment is completely contained by this child node.
comment.enclosingNode = child;
decorateComment(child, comment, text);
decorateComment(child, comment, text, options);
return; // Abandon the binary search at this level.
}
@ -174,7 +163,7 @@ function attach(comments, ast, text, options) {
return;
}
decorateComment(ast, comment, text);
decorateComment(ast, comment, text, options);
const precedingNode = comment.precedingNode;
const enclosingNode = comment.enclosingNode;
@ -923,56 +912,7 @@ function handleVariableDeclaratorComments(
function printComment(commentPath, options) {
const comment = commentPath.getValue();
comment.printed = true;
switch (comment.type || comment.kind) {
case "Comment":
return "#" + comment.value.trimRight();
case "CommentBlock":
case "Block": {
if (isJsDocComment(comment)) {
return printJsDocComment(comment);
}
const isInsideFlowComment =
options.originalText.substr(util.locEnd(comment) - 3, 3) === "*-/";
return "/*" + comment.value + (isInsideFlowComment ? "*-/" : "*/");
}
case "CommentLine":
case "Line":
// Print shebangs with the proper comment characters
if (options.originalText.slice(util.locStart(comment)).startsWith("#!")) {
return "#!" + comment.value.trimRight();
}
return "//" + comment.value.trimRight();
default:
throw new Error("Not a comment: " + JSON.stringify(comment));
}
}
function isJsDocComment(comment) {
const lines = comment.value.split("\n");
return (
lines.length > 1 &&
lines.slice(0, lines.length - 1).every(line => line.trim()[0] === "*")
);
}
function printJsDocComment(comment) {
const lines = comment.value.split("\n");
return concat([
"/*",
join(
hardline,
lines.map(
(line, index) =>
(index > 0 ? " " : "") +
(index < lines.length - 1 ? line.trim() : line.trimLeft())
)
),
"*/"
]);
return options.printer.printComment(commentPath, options);
}
function findExpressionIndexForComment(quasis, comment) {

View File

@ -1,13 +1,14 @@
"use strict";
const loadPlugins = require("../common/load-plugins");
const parser = require("./parser");
function getPrinter(options) {
const plugins = loadPlugins(options);
const parsers = parser.getParsers(plugins, options);
const astFormat = parser.resolveParser(parsers, options).astFormat;
const printerPlugin = plugins.find(plugin => plugin.printers[astFormat]);
const astFormat = options.astFormat;
if (!astFormat) {
throw new Error("getPrinter() requires astFormat to be set");
}
const printerPlugin = options.plugins.find(
plugin => plugin.printers[astFormat]
);
if (!printerPlugin) {
throw new Error(
`Couldn't find printer plugin for AST format "${astFormat}"`

View File

@ -1,10 +1,11 @@
"use strict";
const normalize = require("./options").normalize;
const comments = require("./comments");
function printSubtree(printer, path, print, options) {
if (printer.embed) {
return printer.embed(
function printSubtree(path, print, options) {
if (options.printer.embed) {
return options.printer.embed(
path,
print,
(text, partialNextOptions) =>
@ -15,13 +16,12 @@ function printSubtree(printer, path, print, options) {
}
function textToDoc(text, partialNextOptions, parentOptions) {
const nextOptions = Object.assign({}, parentOptions, partialNextOptions, {
parentParser: parentOptions.parser,
originalText: text
});
if (nextOptions.parser === "json") {
nextOptions.trailingComma = "none";
}
const nextOptions = normalize(
Object.assign({}, parentOptions, partialNextOptions, {
parentParser: parentOptions.parser,
originalText: text
})
);
const ast = require("./parser").parse(text, nextOptions);
const astComments = ast.comments;

View File

@ -4,7 +4,10 @@ const path = require("path");
const validate = require("jest-validate").validate;
const deprecatedConfig = require("./deprecated");
const getSupportInfo = require("./support").getSupportInfo;
const getSupportInfo = require("../common/support").getSupportInfo;
const loadPlugins = require("../common/load-plugins");
const resolveParser = require("./parser").resolveParser;
const getPrinter = require("./get-printer");
const defaults = {
cursorOffset: -1,
@ -18,12 +21,16 @@ const defaults = {
bracketSpacing: true,
jsxBracketSameLine: false,
parser: "babylon",
parentParser: "",
insertPragma: false,
requirePragma: false,
semi: true,
proseWrap: "preserve",
arrowParens: "avoid",
plugins: []
plugins: [],
astFormat: "estree",
printer: {},
__inJsTemplate: false
};
const exampleConfig = Object.assign({}, defaults, {
@ -39,6 +46,7 @@ function normalize(options) {
if (
filepath &&
!normalized.parentParser &&
(!normalized.parser || normalized.parser === defaults.parser)
) {
const extension = path.extname(filepath);
@ -114,6 +122,10 @@ function normalize(options) {
delete normalized.useFlowParser;
}
normalized.plugins = loadPlugins(normalized);
normalized.astFormat = resolveParser(normalized).astFormat;
normalized.printer = getPrinter(normalized);
Object.keys(defaults).forEach(k => {
if (normalized[k] == null) {
normalized[k] = defaults[k];

View File

@ -2,16 +2,17 @@
const path = require("path");
const ConfigError = require("../common/errors").ConfigError;
const loadPlugins = require("../common/load-plugins");
function getParsers(plugins) {
return plugins.reduce(
function getParsers(options) {
return options.plugins.reduce(
(parsers, plugin) => Object.assign({}, parsers, plugin.parsers),
{}
);
}
function resolveParser(parsers, opts) {
function resolveParser(opts, parsers) {
parsers = parsers || getParsers(opts);
if (typeof opts.parser === "function") {
// Custom parser API always works with JavaScript.
return {
@ -39,7 +40,7 @@ function resolveParser(parsers, opts) {
}
function parse(text, opts) {
const parsers = getParsers(loadPlugins(opts), opts);
const parsers = getParsers(opts);
// Copy the "parse" function from parser to a new object whose values are
// functions. Use defineProperty()/getOwnPropertyDescriptor() such that we
@ -54,7 +55,7 @@ function parse(text, opts) {
{}
);
const parser = resolveParser(parsers, opts);
const parser = resolveParser(opts, parsers);
try {
return parser.parse(text, parsersForCustomParserApi, opts);
@ -75,4 +76,4 @@ function parse(text, opts) {
}
}
module.exports = { getParsers, parse, resolveParser };
module.exports = { parse, resolveParser };

View File

@ -3,7 +3,6 @@
const fs = require("fs");
const extname = require("path").extname;
const prettier = require("./require_prettier");
const parser = require("../src/main/parser");
const massageAST = require("../src/common/clean-ast.js").massageAST;
const AST_COMPARE = process.env["AST_COMPARE"];
@ -116,7 +115,7 @@ function stripLocation(ast) {
}
function parse(string, opts) {
return stripLocation(parser.parse(string, opts));
return stripLocation(prettier.__debug.parse(string, opts));
}
function prettyprint(src, filename, options) {