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"; "use strict";
const comments = require("./src/main/comments"); const docblock = require("jest-docblock");
const version = require("./package.json").version; const version = require("./package.json").version;
const printAstToDoc = require("./src/main/ast-to-doc");
const util = require("./src/common/util"); 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 doc = require("./src/doc");
const printDocToString = doc.printer.printDocToString; const printDocToString = doc.printer.printDocToString;
const printDocToDebug = doc.debug.printDocToDebug; 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) { function guessLineEnding(text) {
const index = text.indexOf("\n"); const index = text.indexOf("\n");
@ -101,7 +106,7 @@ function formatWithCursor(text, opts, addAlignmentSize) {
let cursorOffset; let cursorOffset;
if (opts.cursorOffset >= 0) { if (opts.cursorOffset >= 0) {
const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset); const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset, opts);
const cursorNode = cursorNodeAndParents.node; const cursorNode = cursorNodeAndParents.node;
if (cursorNode) { if (cursorNode) {
cursorOffset = opts.cursorOffset - util.locStart(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); predicate = predicate || (() => true);
parentNodes = parentNodes || []; parentNodes = parentNodes || [];
const start = util.locStart(node); const start = util.locStart(node);
const end = util.locEnd(node); const end = util.locEnd(node);
if (start <= offset && offset <= end) { 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( const childResult = findNodeAtOffset(
childNode, childNode,
offset, offset,
options,
predicate, predicate,
[node].concat(parentNodes) [node].concat(parentNodes)
); );
@ -301,11 +311,17 @@ function calculateRange(text, opts, ast) {
} }
} }
const startNodeAndParents = findNodeAtOffset(ast, startNonWhitespace, node => const startNodeAndParents = findNodeAtOffset(
isSourceElement(opts, node) ast,
startNonWhitespace,
opts,
node => isSourceElement(opts, node)
); );
const endNodeAndParents = findNodeAtOffset(ast, endNonWhitespace, node => const endNodeAndParents = findNodeAtOffset(
isSourceElement(opts, node) ast,
endNonWhitespace,
opts,
node => isSourceElement(opts, node)
); );
if (!startNodeAndParents || !endNodeAndParents) { if (!startNodeAndParents || !endNodeAndParents) {
@ -398,6 +414,7 @@ module.exports = {
/* istanbul ignore next */ /* istanbul ignore next */
__debug: { __debug: {
parse: function(text, opts) { parse: function(text, opts) {
opts = normalizeOptions(opts);
return parser.parse(text, opts); return parser.parse(text, opts);
}, },
formatAST: function(ast, 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 resolver = require("../config/resolve-config");
const constant = require("./constant"); const constant = require("./constant");
const validator = require("./validator"); const validator = require("./validator");
const apiDefaultOptions = require("../common/options").defaults; const apiDefaultOptions = require("../main/options").defaults;
const errors = require("../common/errors"); const errors = require("../common/errors");
const logger = require("./logger"); const logger = require("./logger");
const thirdParty = require("../common/third-party"); const thirdParty = require("../common/third-party");

View File

@ -12,9 +12,15 @@ function loadPlugins(options) {
require("../language-markdown"), require("../language-markdown"),
require("../language-html"), require("../language-html"),
require("../language-vue") require("../language-vue")
]; ].filter(plugin => {
return options.plugins.indexOf(plugin) < 0;
});
const externalPlugins = options.plugins.map(plugin => { const externalPlugins = options.plugins.map(plugin => {
if (typeof plugin !== "string") {
return plugin;
}
const pluginPath = resolve.sync(plugin, { basedir: process.cwd() }); const pluginPath = resolve.sync(plugin, { basedir: process.cwd() });
return eval("require")(pluginPath); 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 = { module.exports = {
print: genericPrint, 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 = { module.exports = {
print: genericPrint, print: genericPrint,
embed, embed,
hasPrettierIgnore, hasPrettierIgnore,
willPrintOwnComments willPrintOwnComments,
canAttachComment,
printComment
}; };

View File

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

View File

@ -18,25 +18,14 @@ const getNextNonSpaceNonCommentCharacter =
const getNextNonSpaceNonCommentCharacterIndex = const getNextNonSpaceNonCommentCharacterIndex =
util.getNextNonSpaceNonCommentCharacterIndex; util.getNextNonSpaceNonCommentCharacterIndex;
function getSortedChildNodes(node, text, resultArray) { function getSortedChildNodes(node, text, options, resultArray) {
if (!node) { if (!node) {
return; return;
} }
const printer = options.printer;
if (resultArray) { if (resultArray) {
if ( if (node && printer.canAttachComment && printer.canAttachComment(node)) {
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"))
) {
// This reverse insertion sort almost always takes constant // This reverse insertion sort almost always takes constant
// time because we almost always (maybe always?) append the // time because we almost always (maybe always?) append the
// nodes in order anyway. // nodes in order anyway.
@ -74,7 +63,7 @@ function getSortedChildNodes(node, text, resultArray) {
} }
for (let i = 0, nameCount = names.length; i < nameCount; ++i) { 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; return resultArray;
@ -83,8 +72,8 @@ function getSortedChildNodes(node, text, resultArray) {
// As efficiently as possible, decorate the comment object with // As efficiently as possible, decorate the comment object with
// .precedingNode, .enclosingNode, and/or .followingNode properties, at // .precedingNode, .enclosingNode, and/or .followingNode properties, at
// least one of which is guaranteed to be defined. // least one of which is guaranteed to be defined.
function decorateComment(node, comment, text) { function decorateComment(node, comment, text, options) {
const childNodes = getSortedChildNodes(node, text); const childNodes = getSortedChildNodes(node, text, options);
let precedingNode; let precedingNode;
let followingNode; let followingNode;
// Time to dust off the old binary search robes and wizard hat. // 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. // The comment is completely contained by this child node.
comment.enclosingNode = child; comment.enclosingNode = child;
decorateComment(child, comment, text); decorateComment(child, comment, text, options);
return; // Abandon the binary search at this level. return; // Abandon the binary search at this level.
} }
@ -174,7 +163,7 @@ function attach(comments, ast, text, options) {
return; return;
} }
decorateComment(ast, comment, text); decorateComment(ast, comment, text, options);
const precedingNode = comment.precedingNode; const precedingNode = comment.precedingNode;
const enclosingNode = comment.enclosingNode; const enclosingNode = comment.enclosingNode;
@ -923,56 +912,7 @@ function handleVariableDeclaratorComments(
function printComment(commentPath, options) { function printComment(commentPath, options) {
const comment = commentPath.getValue(); const comment = commentPath.getValue();
comment.printed = true; comment.printed = true;
return options.printer.printComment(commentPath, options);
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())
)
),
"*/"
]);
} }
function findExpressionIndexForComment(quasis, comment) { function findExpressionIndexForComment(quasis, comment) {

View File

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

View File

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

View File

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

View File

@ -2,16 +2,17 @@
const path = require("path"); const path = require("path");
const ConfigError = require("../common/errors").ConfigError; const ConfigError = require("../common/errors").ConfigError;
const loadPlugins = require("../common/load-plugins");
function getParsers(plugins) { function getParsers(options) {
return plugins.reduce( return options.plugins.reduce(
(parsers, plugin) => Object.assign({}, parsers, plugin.parsers), (parsers, plugin) => Object.assign({}, parsers, plugin.parsers),
{} {}
); );
} }
function resolveParser(parsers, opts) { function resolveParser(opts, parsers) {
parsers = parsers || getParsers(opts);
if (typeof opts.parser === "function") { if (typeof opts.parser === "function") {
// Custom parser API always works with JavaScript. // Custom parser API always works with JavaScript.
return { return {
@ -39,7 +40,7 @@ function resolveParser(parsers, opts) {
} }
function parse(text, 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 // Copy the "parse" function from parser to a new object whose values are
// functions. Use defineProperty()/getOwnPropertyDescriptor() such that we // 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 { try {
return parser.parse(text, parsersForCustomParserApi, opts); 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 fs = require("fs");
const extname = require("path").extname; const extname = require("path").extname;
const prettier = require("./require_prettier"); const prettier = require("./require_prettier");
const parser = require("../src/main/parser");
const massageAST = require("../src/common/clean-ast.js").massageAST; const massageAST = require("../src/common/clean-ast.js").massageAST;
const AST_COMPARE = process.env["AST_COMPARE"]; const AST_COMPARE = process.env["AST_COMPARE"];
@ -116,7 +115,7 @@ function stripLocation(ast) {
} }
function parse(string, opts) { function parse(string, opts) {
return stripLocation(parser.parse(string, opts)); return stripLocation(prettier.__debug.parse(string, opts));
} }
function prettyprint(src, filename, options) { function prettyprint(src, filename, options) {