+ attr.value &&
+ isStringLiteral(attr.value) &&
+ attr.value.value.includes("\n")
+ );
+
return group(
concat([
"<",
@@ -1792,7 +1917,8 @@ function printPathNoParens(path, options, print, args) {
n.selfClosing ? line : bracketSameLine ? ">" : softline
]),
n.selfClosing ? "/>" : bracketSameLine ? "" : ">"
- ])
+ ]),
+ { shouldBreak }
);
}
case "JSXClosingElement":
@@ -1803,7 +1929,7 @@ function printPathNoParens(path, options, print, args) {
case "TSJsxClosingFragment": {
const hasComment = n.comments && n.comments.length;
const hasOwnLineComment =
- hasComment && !n.comments.every(util.isBlockComment);
+ hasComment && !n.comments.every(privateUtil.isBlockComment);
const isOpeningFragment =
n.type === "JSXOpeningFragment" || n.type === "TSJsxOpeningFragment";
return concat([
@@ -1825,7 +1951,7 @@ function printPathNoParens(path, options, print, args) {
throw new Error("JSXTest should be handled by JSXElement");
case "JSXEmptyExpression": {
const requiresHardline =
- n.comments && !n.comments.every(util.isBlockComment);
+ n.comments && !n.comments.every(privateUtil.isBlockComment);
return concat([
comments.printDanglingComments(
@@ -1938,7 +2064,7 @@ function printPathNoParens(path, options, print, args) {
// expression inside at the beginning of ${ instead of the beginning
// of the `.
const tabWidth = options.tabWidth;
- const indentSize = util.getIndentSize(
+ const indentSize = privateUtil.getIndentSize(
childPath.getValue().value.raw,
tabWidth
);
@@ -2108,7 +2234,7 @@ function printPathNoParens(path, options, print, args) {
(parent.type === "ObjectTypeProperty" &&
!getFlowVariance(parent) &&
!parent.optional &&
- util.locStart(parent) === util.locStart(n)) ||
+ options.locStart(parent) === options.locStart(n)) ||
parent.type === "ObjectTypeCallProperty" ||
(parentParentParent && parentParentParent.type === "DeclareFunction")
);
@@ -2128,7 +2254,7 @@ function printPathNoParens(path, options, print, args) {
parent.type === "TSTypeAnnotation") &&
parentParent.type === "ArrowFunctionExpression";
- if (isObjectTypePropertyAFunction(parent)) {
+ if (isObjectTypePropertyAFunction(parent, options)) {
isArrowFunctionTypeAnnotation = true;
needsColon = true;
}
@@ -2259,7 +2385,7 @@ function printPathNoParens(path, options, print, args) {
!(
(parent.type === "TypeAlias" ||
parent.type === "VariableDeclarator") &&
- hasLeadingOwnLineComment(options.originalText, n)
+ hasLeadingOwnLineComment(options.originalText, n, options)
);
// {
@@ -2302,7 +2428,7 @@ function printPathNoParens(path, options, print, args) {
(greatGreatGrandParent.type === "TSUnionType" ||
greatGreatGrandParent.type === "TSIntersectionType");
} else {
- hasParens = path.needsParens(options);
+ hasParens = pathNeedsParens(path, options);
}
if (hasParens) {
@@ -2349,7 +2475,7 @@ function printPathNoParens(path, options, print, args) {
variance || "",
printPropertyKey(path, options, print),
printOptionalToken(path),
- isFunctionNotation(n) ? "" : ": ",
+ isFunctionNotation(n, options) ? "" : ": ",
path.call(print, "value")
]);
}
@@ -2365,9 +2491,9 @@ function printPathNoParens(path, options, print, args) {
assert.strictEqual(typeof n.value, "number");
if (n.extra != null) {
- return util.printNumber(n.extra.raw);
+ return privateUtil.printNumber(n.extra.raw);
}
- return util.printNumber(n.raw);
+ return privateUtil.printNumber(n.raw);
case "StringTypeAnnotation":
return "string";
@@ -2629,7 +2755,7 @@ function printPathNoParens(path, options, print, args) {
return concat(parts);
}
case "TSTypeOperator":
- return concat(["keyof ", path.call(print, "typeAnnotation")]);
+ return concat([n.operator, " ", path.call(print, "typeAnnotation")]);
case "TSMappedType":
return group(
concat([
@@ -2638,13 +2764,21 @@ function printPathNoParens(path, options, print, args) {
concat([
options.bracketSpacing ? line : softline,
n.readonlyToken
- ? concat([path.call(print, "readonlyToken"), " "])
+ ? concat([
+ getTypeScriptMappedTypeModifier(
+ n.readonlyToken,
+ "readonly"
+ ),
+ " "
+ ])
: "",
printTypeScriptModifiers(path, options, print),
"[",
path.call(print, "typeParameter"),
"]",
- n.questionToken ? "?" : "",
+ n.questionToken
+ ? getTypeScriptMappedTypeModifier(n.questionToken, "?")
+ : "",
": ",
path.call(print, "typeAnnotation")
])
@@ -2777,7 +2911,10 @@ function printPathNoParens(path, options, print, args) {
n.id.type === "Identifier" &&
n.id.name === "global" &&
!/namespace|module/.test(
- options.originalText.slice(util.locStart(n), util.locStart(n.id))
+ options.originalText.slice(
+ options.locStart(n),
+ options.locStart(n.id)
+ )
);
if (!isGlobalDeclaration) {
@@ -2820,6 +2957,26 @@ function printPathNoParens(path, options, print, args) {
case "PrivateName":
return concat(["#", path.call(print, "id")]);
+ case "TSConditionalType":
+ return formatTernaryOperator(path, options, print, {
+ beforeParts: () => [
+ path.call(print, "checkType"),
+ " ",
+ "extends",
+ " ",
+ path.call(print, "extendsType")
+ ],
+ shouldCheckJsx: false,
+ operatorName: "TSConditionalType",
+ consequentNode: "trueType",
+ alternateNode: "falseType",
+ testNode: "checkType",
+ breakNested: false
+ });
+
+ case "TSInferType":
+ return concat(["infer", " ", path.call(print, "typeParameter")]);
+
default:
/* istanbul ignore next */
throw new Error("unknown type: " + JSON.stringify(n.type));
@@ -2880,7 +3037,10 @@ function printStatementSequence(path, options, print) {
}
}
- if (util.isNextLineEmpty(text, stmt) && !isLastStatement(stmtPath)) {
+ if (
+ sharedUtil.isNextLineEmpty(text, stmt, options) &&
+ !isLastStatement(stmtPath)
+ ) {
parts.push(hardline);
}
@@ -2986,8 +3146,8 @@ function couldGroupArg(arg) {
}
function shouldGroupLastArg(args) {
- const lastArg = util.getLast(args);
- const penultimateArg = util.getPenultimate(args);
+ const lastArg = privateUtil.getLast(args);
+ const penultimateArg = privateUtil.getPenultimate(args);
return (
!hasLeadingComment(lastArg) &&
!hasTrailingComment(lastArg) &&
@@ -3034,7 +3194,7 @@ function printArgumentsList(path, options, print) {
if (index === lastArgIndex) {
// do nothing
- } else if (util.isNextLineEmpty(options.originalText, arg)) {
+ } else if (sharedUtil.isNextLineEmpty(options.originalText, arg, options)) {
if (index === 0) {
hasEmptyLineFollowingFirstArg = true;
}
@@ -3048,9 +3208,6 @@ function printArgumentsList(path, options, print) {
return concat(parts);
}, "arguments");
- // This is just an optimization; I think we could return the
- // conditional group for all function calls, but it's more expensive
- // so only do it for specific forms.
const shouldGroupFirst = shouldGroupFirstArg(args);
const shouldGroupLast = shouldGroupLastArg(args);
if (shouldGroupFirst || shouldGroupLast) {
@@ -3109,7 +3266,7 @@ function printArgumentsList(path, options, print) {
: concat([
"(",
concat(printedArguments.slice(0, -1)),
- group(util.getLast(printedExpanded), {
+ group(privateUtil.getLast(printedExpanded), {
shouldBreak: true
}),
")"
@@ -3149,15 +3306,23 @@ function printTypeAnnotation(path, options, print) {
}
const parentNode = path.getParentNode();
+ const isDefinite =
+ node.definite ||
+ (parentNode &&
+ parentNode.type === "VariableDeclarator" &&
+ parentNode.definite);
+
const isFunctionDeclarationIdentifier =
parentNode.type === "DeclareFunction" && parentNode.id === node;
- if (isFlowAnnotationComment(options.originalText, node.typeAnnotation)) {
+ if (
+ isFlowAnnotationComment(options.originalText, node.typeAnnotation, options)
+ ) {
return concat([" /*: ", path.call(print, "typeAnnotation"), " */"]);
}
return concat([
- isFunctionDeclarationIdentifier ? "" : ": ",
+ isFunctionDeclarationIdentifier ? "" : isDefinite ? "!: " : ": ",
path.call(print, "typeAnnotation")
]);
}
@@ -3196,16 +3361,17 @@ function printFunctionParams(path, print, options, expandArg, printTypeParams) {
options,
/* sameIndent */ true,
comment =>
- util.getNextNonSpaceNonCommentCharacter(
+ privateUtil.getNextNonSpaceNonCommentCharacter(
options.originalText,
- comment
+ comment,
+ options.locEnd
) === ")"
),
")"
]);
}
- const lastParam = util.getLast(fun[paramsField]);
+ const lastParam = privateUtil.getLast(fun[paramsField]);
// If the parent is a call with the first/last argument expansion and this is the
// params of the first/last argument, we dont want the arguments to break and instead
@@ -3264,8 +3430,8 @@ function printFunctionParams(path, print, options, expandArg, printTypeParams) {
];
const isFlowShorthandWithOneArg =
- (isObjectTypePropertyAFunction(parent) ||
- isTypeAnnotationAFunction(parent) ||
+ (isObjectTypePropertyAFunction(parent, options) ||
+ isTypeAnnotationAFunction(parent, options) ||
parent.type === "TypeAlias" ||
parent.type === "UnionTypeAnnotation" ||
parent.type === "TSUnionType" ||
@@ -3285,6 +3451,9 @@ function printFunctionParams(path, print, options, expandArg, printTypeParams) {
!fun.rest;
if (isFlowShorthandWithOneArg) {
+ if (options.arrowParens === "always") {
+ return concat(["(", concat(printed), ")"]);
+ }
return concat(printed);
}
@@ -3411,7 +3580,7 @@ function printReturnType(path, print, options) {
if (
n.returnType &&
- isFlowAnnotationComment(options.originalText, n.returnType)
+ isFlowAnnotationComment(options.originalText, n.returnType, options)
) {
return concat([" /*: ", returnType, " */"]);
}
@@ -3437,7 +3606,9 @@ function printExportDeclaration(path, options, print) {
const semi = options.semi ? ";" : "";
const parts = ["export "];
- if (decl["default"] || decl.type === "ExportDefaultDeclaration") {
+ const isDefault = decl["default"] || decl.type === "ExportDefaultDeclaration";
+
+ if (isDefault) {
parts.push("default ");
}
@@ -3445,14 +3616,20 @@ function printExportDeclaration(path, options, print) {
comments.printDanglingComments(path, options, /* sameIndent */ true)
);
+ if (needsHardlineAfterDanglingComment(decl)) {
+ parts.push(hardline);
+ }
+
if (decl.declaration) {
parts.push(path.call(print, "declaration"));
if (
- decl.type === "ExportDefaultDeclaration" &&
+ isDefault &&
(decl.declaration.type !== "ClassDeclaration" &&
decl.declaration.type !== "FunctionDeclaration" &&
- decl.declaration.type !== "TSAbstractClassDeclaration")
+ decl.declaration.type !== "TSAbstractClassDeclaration" &&
+ decl.declaration.type !== "DeclareClass" &&
+ decl.declaration.type !== "DeclareFunction")
) {
parts.push(semi);
}
@@ -3517,7 +3694,7 @@ function printExportDeclaration(path, options, print) {
}
function printFlowDeclaration(path, parts) {
- const parentExportDecl = util.getParentExportDeclaration(path);
+ const parentExportDecl = privateUtil.getParentExportDeclaration(path);
if (parentExportDecl) {
assert.strictEqual(parentExportDecl.type, "DeclareExportDeclaration");
@@ -3632,24 +3809,41 @@ function printClass(path, options, print) {
const partsGroup = [];
if (n.superClass) {
- if (hasLeadingOwnLineComment(options.originalText, n.superClass)) {
- parts.push(hardline);
- } else {
- parts.push(" ");
- }
-
const printed = concat([
"extends ",
path.call(print, "superClass"),
path.call(print, "superTypeParameters")
]);
- parts.push(
- path.call(
- superClass =>
- comments.printComments(superClass, () => printed, options),
- "superClass"
- )
- );
+ // Keep old behaviour of extends in same line
+ // If there is only on extends and there are not comments
+ if (
+ (!n.implements || n.implements.length === 0) &&
+ (!n.superClass.comments || n.superClass.comments.length === 0)
+ ) {
+ parts.push(
+ concat([
+ " ",
+ path.call(
+ superClass =>
+ comments.printComments(superClass, () => printed, options),
+ "superClass"
+ )
+ ])
+ );
+ } else {
+ partsGroup.push(
+ group(
+ concat([
+ line,
+ path.call(
+ superClass =>
+ comments.printComments(superClass, () => printed, options),
+ "superClass"
+ )
+ ])
+ )
+ );
+ }
} else if (n.extends && n.extends.length > 0) {
parts.push(" extends ", join(", ", path.map(print, "extends")));
}
@@ -3657,8 +3851,15 @@ function printClass(path, options, print) {
if (n["implements"] && n["implements"].length > 0) {
partsGroup.push(
line,
- "implements ",
- group(indent(join(concat([",", line]), path.map(print, "implements"))))
+ "implements",
+ group(
+ indent(
+ concat([
+ line,
+ join(concat([",", line]), path.map(print, "implements"))
+ ])
+ )
+ )
);
}
@@ -3677,7 +3878,7 @@ function printClass(path, options, print) {
if (
n.body &&
n.body.comments &&
- hasLeadingOwnLineComment(options.originalText, n.body)
+ hasLeadingOwnLineComment(options.originalText, n.body, options)
) {
parts.push(hardline);
} else {
@@ -3749,19 +3950,24 @@ function printMemberChain(path, options, print) {
// the first group whether it is in parentheses or not
function shouldInsertEmptyLineAfter(node) {
const originalText = options.originalText;
- const nextCharIndex = util.getNextNonSpaceNonCommentCharacterIndex(
+ const nextCharIndex = sharedUtil.getNextNonSpaceNonCommentCharacterIndex(
originalText,
- node
+ node,
+ options
);
const nextChar = originalText.charAt(nextCharIndex);
// if it is cut off by a parenthesis, we only account for one typed empty
// line after that parenthesis
if (nextChar == ")") {
- return util.isNextLineEmptyAfterIndex(originalText, nextCharIndex + 1);
+ return sharedUtil.isNextLineEmptyAfterIndex(
+ originalText,
+ nextCharIndex + 1,
+ options
+ );
}
- return util.isNextLineEmpty(originalText, node);
+ return sharedUtil.isNextLineEmpty(originalText, node, options);
}
function rec(path) {
@@ -3800,6 +4006,12 @@ function printMemberChain(path, options, print) {
)
});
path.call(object => rec(object), "object");
+ } else if (node.type === "TSNonNullExpression") {
+ printedNodes.unshift({
+ node: node,
+ printed: comments.printComments(path, () => "!", options)
+ });
+ path.call(expression => rec(expression), "expression");
} else {
printedNodes.unshift({
node: node,
@@ -3850,6 +4062,7 @@ function printMemberChain(path, options, print) {
let i = 1;
for (; i < printedNodes.length; ++i) {
if (
+ printedNodes[i].node.type === "TSNonNullExpression" ||
printedNodes[i].node.type === "CallExpression" ||
(printedNodes[i].node.type === "MemberExpression" &&
printedNodes[i].node.computed &&
@@ -3978,7 +4191,7 @@ function printMemberChain(path, options, print) {
// Find out the last node in the first group and check if it has an
// empty line after
- const lastNodeBeforeIndent = util.getLast(
+ const lastNodeBeforeIndent = privateUtil.getLast(
shouldMerge ? groups.slice(1, 2)[0] : groups[0]
).node;
const shouldHaveEmptyLineBeforeIndent =
@@ -4203,7 +4416,7 @@ function printJSXChildren(path, options, print, jsxWhitespace) {
let endWhitespace;
// Ends with whitespace
- if (util.getLast(words) === "") {
+ if (privateUtil.getLast(words) === "") {
words.pop();
endWhitespace = words.pop();
}
@@ -4391,7 +4604,8 @@ function printJSXElement(path, options, print) {
// Trim trailing lines (or empty strings)
while (
children.length &&
- (isLineNext(util.getLast(children)) || isEmpty(util.getLast(children)))
+ (isLineNext(privateUtil.getLast(children)) ||
+ isEmpty(privateUtil.getLast(children)))
) {
children.pop();
}
@@ -4474,6 +4688,7 @@ function maybeWrapJSXElementInParens(path, elem) {
const NO_WRAP_PARENTS = {
ArrayExpression: true,
+ JSXAttribute: true,
JSXElement: true,
JSXExpressionContainer: true,
JSXFragment: true,
@@ -4562,7 +4777,7 @@ function printBinaryishExpressions(
// precedence level and should be treated as a separate group, so
// print them normally. (This doesn't hold for the `**` operator,
// which is unique in that it is right-associative.)
- if (util.shouldFlatten(node.operator, node.left.operator)) {
+ if (privateUtil.shouldFlatten(node.operator, node.left.operator)) {
// Flatten them out by recursively calling this function.
parts = parts.concat(
path.call(
@@ -4619,7 +4834,7 @@ function printBinaryishExpressions(
}
function printAssignmentRight(rightNode, printedRight, canBreak, options) {
- if (hasLeadingOwnLineComment(options.originalText, rightNode)) {
+ if (hasLeadingOwnLineComment(options.originalText, rightNode, options)) {
return indent(concat([hardline, printedRight]));
}
@@ -4678,7 +4893,7 @@ function nodeStr(node, options, isFlowOrTypeScriptDirectiveLiteral) {
const raw = rawText(node);
const isDirectiveLiteral =
isFlowOrTypeScriptDirectiveLiteral || node.type === "DirectiveLiteral";
- return util.printString(raw, options, isDirectiveLiteral);
+ return privateUtil.printString(raw, options, isDirectiveLiteral);
}
function printRegex(node) {
@@ -4709,15 +4924,16 @@ function hasTrailingComment(node) {
return node.comments && node.comments.some(comment => comment.trailing);
}
-function hasLeadingOwnLineComment(text, node) {
+function hasLeadingOwnLineComment(text, node, options) {
if (isJSXNode(node)) {
- return util.hasNodeIgnoreComment(node);
+ return privateUtil.hasNodeIgnoreComment(node);
}
const res =
node.comments &&
node.comments.some(
- comment => comment.leading && util.hasNewline(text, util.locEnd(comment))
+ comment =>
+ comment.leading && privateUtil.hasNewline(text, options.locEnd(comment))
);
return res;
}
@@ -4737,9 +4953,9 @@ function hasNakedLeftSide(node) {
);
}
-function isFlowAnnotationComment(text, typeAnnotation) {
- const start = util.locStart(typeAnnotation);
- const end = util.skipWhitespace(text, util.locEnd(typeAnnotation));
+function isFlowAnnotationComment(text, typeAnnotation, options) {
+ const start = options.locStart(typeAnnotation);
+ const end = privateUtil.skipWhitespace(text, options.locEnd(typeAnnotation));
return text.substr(start, 2) === "/*" && text.substr(end, 2) === "*/";
}
@@ -4790,7 +5006,7 @@ function exprNeedsASIProtection(path, options) {
const node = path.getValue();
const maybeASIProblem =
- path.needsParens(options) ||
+ pathNeedsParens(path, options) ||
node.type === "ParenthesizedExpression" ||
node.type === "TypeCastExpression" ||
(node.type === "ArrowFunctionExpression" &&
@@ -4902,7 +5118,7 @@ function classChildNeedsASIProtection(node) {
// (the leftmost leaf node) and, if it (or its parents) has any
// leadingComments, returns true (so it can be wrapped in parens).
function returnArgumentHasLeadingComment(options, argument) {
- if (hasLeadingOwnLineComment(options.originalText, argument)) {
+ if (hasLeadingOwnLineComment(options.originalText, argument, options)) {
return true;
}
@@ -4912,7 +5128,7 @@ function returnArgumentHasLeadingComment(options, argument) {
while ((newLeftMost = getLeftSide(leftMost))) {
leftMost = newLeftMost;
- if (hasLeadingOwnLineComment(options.originalText, leftMost)) {
+ if (hasLeadingOwnLineComment(options.originalText, leftMost, options)) {
return true;
}
}
@@ -4934,38 +5150,38 @@ function isMemberExpressionChain(node) {
// Hack to differentiate between the following two which have the same ast
// type T = { method: () => void };
// type T = { method(): void };
-function isObjectTypePropertyAFunction(node) {
+function isObjectTypePropertyAFunction(node, options) {
return (
node.type === "ObjectTypeProperty" &&
node.value.type === "FunctionTypeAnnotation" &&
!node.static &&
- !isFunctionNotation(node)
+ !isFunctionNotation(node, options)
);
}
// TODO: This is a bad hack and we need a better way to distinguish between
// arrow functions and otherwise
-function isFunctionNotation(node) {
- return isGetterOrSetter(node) || sameLocStart(node, node.value);
+function isFunctionNotation(node, options) {
+ return isGetterOrSetter(node) || sameLocStart(node, node.value, options);
}
function isGetterOrSetter(node) {
return node.kind === "get" || node.kind === "set";
}
-function sameLocStart(nodeA, nodeB) {
- return util.locStart(nodeA) === util.locStart(nodeB);
+function sameLocStart(nodeA, nodeB, options) {
+ return options.locStart(nodeA) === options.locStart(nodeB);
}
// Hack to differentiate between the following two which have the same ast
// declare function f(a): void;
// var f: (a) => void;
-function isTypeAnnotationAFunction(node) {
+function isTypeAnnotationAFunction(node, options) {
return (
(node.type === "TypeAnnotation" || node.type === "TSTypeAnnotation") &&
node.typeAnnotation.type === "FunctionTypeAnnotation" &&
!node.static &&
- !sameLocStart(node, node.typeAnnotation)
+ !sameLocStart(node, node.typeAnnotation, options)
);
}
@@ -4975,7 +5191,7 @@ function isNodeStartingWithDeclare(node, options) {
}
return (
options.originalText
- .slice(0, util.locStart(node))
+ .slice(0, options.locStart(node))
.match(/declare[ \t]*$/) ||
options.originalText
.slice(node.range[0], node.range[1])
@@ -5045,12 +5261,12 @@ function templateLiteralHasNewLines(template) {
return template.quasis.some(quasi => quasi.value.raw.includes("\n"));
}
-function isTemplateOnItsOwnLine(n, text) {
+function isTemplateOnItsOwnLine(n, text, options) {
return (
((n.type === "TemplateLiteral" && templateLiteralHasNewLines(n)) ||
(n.type === "TaggedTemplateExpression" &&
templateLiteralHasNewLines(n.quasi))) &&
- !util.hasNewline(text, util.locStart(n), { backwards: true })
+ !privateUtil.hasNewline(text, options.locStart(n), { backwards: true })
);
}
@@ -5065,7 +5281,11 @@ function printArrayItems(path, options, printPath, print) {
separatorParts = [",", line];
if (
childPath.getValue() &&
- util.isNextLineEmpty(options.originalText, childPath.getValue())
+ sharedUtil.isNextLineEmpty(
+ options.originalText,
+ childPath.getValue(),
+ options
+ )
) {
separatorParts.push(softline);
}
@@ -5081,6 +5301,18 @@ function hasDanglingComments(node) {
);
}
+function needsHardlineAfterDanglingComment(node) {
+ if (!node.comments) {
+ return false;
+ }
+ const lastDanglingComment = privateUtil.getLast(
+ node.comments.filter(comment => !comment.leading && !comment.trailing)
+ );
+ return (
+ lastDanglingComment && !privateUtil.isBlockComment(lastDanglingComment)
+ );
+}
+
function isLiteral(node) {
return (
node.type === "BooleanLiteral" ||
@@ -5115,24 +5347,76 @@ function isObjectType(n) {
}
// eg; `describe("some string", (done) => {})`
-function isTestCall(n) {
- const unitTestRe = /^(f|x)?(it|describe|test)$/;
+function isTestCall(n, parent) {
+ const unitTestRe = /^(skip|(f|x)?(it|describe|test))$/;
+
+ if (n.arguments.length === 1) {
+ if (
+ n.callee.type === "Identifier" &&
+ n.callee.name === "async" &&
+ parent &&
+ parent.type === "CallExpression" &&
+ isTestCall(parent)
+ ) {
+ return isFunctionOrArrowExpression(n.arguments[0].type);
+ }
+
+ if (isUnitTestSetUp(n)) {
+ return (
+ isFunctionOrArrowExpression(n.arguments[0].type) ||
+ isIdentiferAsync(n.arguments[0])
+ );
+ }
+ } else if (n.arguments.length === 2) {
+ if (
+ ((n.callee.type === "Identifier" && unitTestRe.test(n.callee.name)) ||
+ isSkipOrOnlyBlock(n)) &&
+ (isTemplateLiteral(n.arguments[0]) || isStringLiteral(n.arguments[0]))
+ ) {
+ return (
+ (isFunctionOrArrowExpression(n.arguments[1].type) &&
+ n.arguments[1].params.length <= 1) ||
+ isIdentiferAsync(n.arguments[1])
+ );
+ }
+ }
+ return false;
+}
+
+function isSkipOrOnlyBlock(node) {
+ const unitTestRe = /^(skip|(f|x)?(it|describe|test))$/;
return (
- ((n.callee.type === "Identifier" && unitTestRe.test(n.callee.name)) ||
- (n.callee.type === "MemberExpression" &&
- n.callee.object.type === "Identifier" &&
- n.callee.property.type === "Identifier" &&
- unitTestRe.test(n.callee.object.name) &&
- (n.callee.property.name === "only" ||
- n.callee.property.name === "skip"))) &&
- n.arguments.length === 2 &&
- (n.arguments[0].type === "StringLiteral" ||
- n.arguments[0].type === "TemplateLiteral" ||
- (n.arguments[0].type === "Literal" &&
- typeof n.arguments[0].value === "string")) &&
- (n.arguments[1].type === "FunctionExpression" ||
- n.arguments[1].type === "ArrowFunctionExpression") &&
- n.arguments[1].params.length <= 1
+ node.callee.type === "MemberExpression" &&
+ node.callee.object.type === "Identifier" &&
+ node.callee.property.type === "Identifier" &&
+ unitTestRe.test(node.callee.object.name) &&
+ (node.callee.property.name === "only" ||
+ node.callee.property.name === "skip")
+ );
+}
+
+function isTemplateLiteral(node) {
+ return node.type === "TemplateLiteral";
+}
+
+function isIdentiferAsync(node) {
+ return (
+ node.type === "CallExpression" &&
+ node.callee.type === "Identifier" &&
+ node.callee.name === "async"
+ );
+}
+
+function isFunctionOrArrowExpression(type) {
+ return type === "FunctionExpression" || type === "ArrowFunctionExpression";
+}
+
+function isUnitTestSetUp(n) {
+ const unitTestSetUpRe = /^(before|after)(Each|All)$/;
+ return (
+ n.callee.type === "Identifier" &&
+ unitTestSetUpRe.test(n.callee.name) &&
+ n.arguments.length === 1
);
}
@@ -5166,7 +5450,7 @@ function willPrintOwnComments(path) {
((parent.type === "ClassDeclaration" ||
parent.type === "ClassExpression") &&
parent.superClass === node)))) &&
- !util.hasIgnoreComment(path)
+ !privateUtil.hasIgnoreComment(path)
);
}
@@ -5195,14 +5479,16 @@ function printComment(commentPath, options) {
}
const isInsideFlowComment =
- options.originalText.substr(util.locEnd(comment) - 3, 3) === "*-/";
+ options.originalText.substr(options.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("#!")) {
+ if (
+ options.originalText.slice(options.locStart(comment)).startsWith("#!")
+ ) {
return "#!" + comment.value.trimRight();
}
return "//" + comment.value.trimRight();
@@ -5237,12 +5523,17 @@ function printJsDocComment(comment) {
}
module.exports = {
- options: printerOptions,
print: genericPrint,
embed,
+ insertPragma,
massageAstNode: clean,
hasPrettierIgnore,
willPrintOwnComments,
canAttachComment,
- printComment
+ printComment,
+ handleComments: {
+ ownLine: handleComments.handleOwnLineComment,
+ endOfLine: handleComments.handleEndOfLineComment,
+ remaining: handleComments.handleRemainingComment
+ }
};
diff --git a/src/language-markdown/embed.js b/src/language-markdown/embed.js
index 85fdc360..ee8f25e9 100644
--- a/src/language-markdown/embed.js
+++ b/src/language-markdown/embed.js
@@ -5,33 +5,42 @@ const support = require("../common/support");
const doc = require("../doc");
const docBuilders = doc.builders;
const hardline = docBuilders.hardline;
+const literalline = docBuilders.literalline;
const concat = docBuilders.concat;
+const markAsRoot = docBuilders.markAsRoot;
function embed(path, print, textToDoc, options) {
const node = path.getValue();
if (node.type === "code") {
- const parser = getParserName(node.lang);
+ // only look for the first string so as to support [markdown-preview-enhanced](https://shd101wyy.github.io/markdown-preview-enhanced/#/code-chunk)
+ const lang = node.lang.split(/\s/, 1)[0];
+ const parser = getParserName(lang);
if (parser) {
const styleUnit = options.__inJsTemplate ? "~" : "`";
const style = styleUnit.repeat(
Math.max(3, util.getMaxContinuousCount(node.value, styleUnit) + 1)
);
const doc = textToDoc(node.value, { parser });
- return concat([
- style,
- node.lang,
- hardline,
- replaceNewlinesWithHardlines(doc),
- style
- ]);
+ return markAsRoot(
+ concat([
+ style,
+ node.lang,
+ hardline,
+ replaceNewlinesWithLiterallines(doc),
+ style
+ ])
+ );
}
}
return null;
function getParserName(lang) {
- const supportInfo = support.getSupportInfo(undefined, options);
+ const supportInfo = support.getSupportInfo(null, {
+ plugins: options.plugins,
+ pluginsLoaded: true
+ });
const language = supportInfo.languages.find(
language =>
language.name.toLowerCase() === lang ||
@@ -45,7 +54,7 @@ function embed(path, print, textToDoc, options) {
return null;
}
- function replaceNewlinesWithHardlines(doc) {
+ function replaceNewlinesWithLiterallines(doc) {
return util.mapDoc(
doc,
currentDoc =>
@@ -53,7 +62,7 @@ function embed(path, print, textToDoc, options) {
? concat(
currentDoc
.split(/(\n)/g)
- .map((v, i) => (i % 2 === 0 ? v : hardline))
+ .map((v, i) => (i % 2 === 0 ? v : literalline))
)
: currentDoc
);
diff --git a/src/language-markdown/index.js b/src/language-markdown/index.js
index d287c46b..2d537302 100644
--- a/src/language-markdown/index.js
+++ b/src/language-markdown/index.js
@@ -1,6 +1,8 @@
"use strict";
const printer = require("./printer-markdown");
+const options = require("./options");
+const pragma = require("./pragma");
// Based on:
// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
@@ -37,7 +39,10 @@ const remark = {
get parse() {
return eval("require")("./parser-markdown");
},
- astFormat: "mdast"
+ astFormat: "mdast",
+ hasPragma: pragma.hasPragma,
+ locStart: node => node.position.start.offset,
+ locEnd: node => node.position.end.offset
};
const parsers = {
@@ -52,6 +57,7 @@ const printers = {
module.exports = {
languages,
+ options,
parsers,
printers
};
diff --git a/src/language-markdown/pragma.js b/src/language-markdown/pragma.js
new file mode 100644
index 00000000..e8862e66
--- /dev/null
+++ b/src/language-markdown/pragma.js
@@ -0,0 +1,38 @@
+"use strict";
+
+const pragmas = ["format", "prettier"];
+
+function startWithPragma(text) {
+ const pragma = `@(${pragmas.join("|")})`;
+ const regex = new RegExp(
+ [
+ ``,
+ ``
+ ].join("|"),
+ "m"
+ );
+ const matched = text.match(regex);
+ return matched && matched.index === 0;
+}
+
+function extract(text) {
+ // yaml (---) and toml (+++)
+ const matched = text.match(
+ /^((---|\+\+\+)(?:\n[\s\S]*?\n|\n)\2(?:\n|$))?([\s\S]*)/
+ );
+ const frontMatter = matched[1];
+ const mainContent = matched[3];
+ return { frontMatter, mainContent };
+}
+
+module.exports = {
+ startWithPragma,
+ hasPragma: text => startWithPragma(extract(text).mainContent.trimLeft()),
+ insertPragma: text => {
+ const extracted = extract(text);
+ const pragma = ``;
+ return extracted.frontMatter
+ ? `${extracted.frontMatter}\n\n${pragma}\n\n${extracted.mainContent}`
+ : `${pragma}\n\n${extracted.mainContent}`;
+ }
+};
diff --git a/src/language-markdown/printer-markdown.js b/src/language-markdown/printer-markdown.js
index 497eb349..6bb4a75f 100644
--- a/src/language-markdown/printer-markdown.js
+++ b/src/language-markdown/printer-markdown.js
@@ -1,7 +1,8 @@
"use strict";
-const util = require("../common/util");
+const privateUtil = require("../common/util");
const embed = require("./embed");
+const pragma = require("./pragma");
const doc = require("../doc");
const docBuilders = doc.builders;
const concat = docBuilders.concat;
@@ -11,15 +12,10 @@ const hardline = docBuilders.hardline;
const softline = docBuilders.softline;
const fill = docBuilders.fill;
const align = docBuilders.align;
+const group = docBuilders.group;
const printDocToString = doc.printer.printDocToString;
-const printerOptions = require("./options");
-const SINGLE_LINE_NODE_TYPES = [
- "heading",
- "tableCell",
- "footnoteDefinition",
- "link"
-];
+const SINGLE_LINE_NODE_TYPES = ["heading", "tableCell", "link"];
const SIBLING_NODE_TYPES = ["listItem", "definition", "footnoteDefinition"];
@@ -51,7 +47,7 @@ function genericPrint(path, options, print) {
if (shouldRemainTheSameContent(path)) {
return concat(
- util
+ privateUtil
.splitText(
options.originalText.slice(
node.position.start.offset,
@@ -69,10 +65,7 @@ function genericPrint(path, options, print) {
switch (node.type) {
case "root":
- return concat([
- normalizeDoc(printChildren(path, options, print)),
- hardline
- ]);
+ return concat([normalizeDoc(printRoot(path, options, print)), hardline]);
case "paragraph":
return printChildren(path, options, print, {
postprocessor: fill
@@ -85,8 +78,8 @@ function genericPrint(path, options, print) {
.replace(
new RegExp(
[
- `(^|[${util.punctuationCharRange}])(_+)`,
- `(_+)([${util.punctuationCharRange}]|$)`
+ `(^|[${privateUtil.punctuationCharRange}])(_+)`,
+ `(_+)([${privateUtil.punctuationCharRange}]|$)`
].join("|"),
"g"
),
@@ -118,8 +111,8 @@ function genericPrint(path, options, print) {
(prevNode &&
prevNode.type === "sentence" &&
prevNode.children.length > 0 &&
- util.getLast(prevNode.children).type === "word" &&
- !util.getLast(prevNode.children).hasTrailingPunctuation) ||
+ privateUtil.getLast(prevNode.children).type === "word" &&
+ !privateUtil.getLast(prevNode.children).hasTrailingPunctuation) ||
(nextNode &&
nextNode.type === "sentence" &&
nextNode.children.length > 0 &&
@@ -134,7 +127,7 @@ function genericPrint(path, options, print) {
case "delete":
return concat(["~~", printChildren(path, options, print), "~~"]);
case "inlineCode": {
- const backtickCount = util.getMaxContinuousCount(node.value, "`");
+ const backtickCount = privateUtil.getMaxContinuousCount(node.value, "`");
const style = backtickCount === 1 ? "``" : "`";
const gap = backtickCount ? " " : "";
return concat([style, gap, node.value, gap, style]);
@@ -195,7 +188,10 @@ function genericPrint(path, options, print) {
// fenced code block
const styleUnit = options.__inJsTemplate ? "~" : "`";
const style = styleUnit.repeat(
- Math.max(3, util.getMaxContinuousCount(node.value, styleUnit) + 1)
+ Math.max(
+ 3,
+ privateUtil.getMaxContinuousCount(node.value, styleUnit) + 1
+ )
);
return concat([
style,
@@ -207,15 +203,20 @@ function genericPrint(path, options, print) {
]);
}
case "yaml":
- return concat(["---", hardline, node.value, hardline, "---"]);
- case "toml":
- return concat(["+++", hardline, node.value, hardline, "+++"]);
+ case "toml": {
+ const style = node.type === "yaml" ? "---" : "+++";
+ return node.value
+ ? concat([style, hardline, node.value, hardline, style])
+ : concat([style, hardline, style]);
+ }
case "html": {
const parentNode = path.getParentNode();
- return parentNode.type === "root" &&
- util.getLast(parentNode.children) === node
- ? node.value.trimRight()
- : node.value;
+ return replaceNewlinesWithHardlines(
+ parentNode.type === "root" &&
+ privateUtil.getLast(parentNode.children) === node
+ ? node.value.trimRight()
+ : node.value
+ );
}
case "list": {
const nthSiblingIndex = getNthListSiblingIndex(
@@ -235,32 +236,34 @@ function genericPrint(path, options, print) {
return printChildren(path, options, print, {
processor: (childPath, index) => {
- const prefix = node.ordered
- ? (index === 0
- ? node.start
- : isGitDiffFriendlyOrderedList ? 1 : node.start + index) +
- (nthSiblingIndex % 2 === 0 ? ". " : ") ")
- : nthSiblingIndex % 2 === 0 ? "* " : "- ";
+ const prefix = getPrefix();
return concat([
prefix,
- align(" ".repeat(prefix.length), childPath.call(print))
+ align(
+ " ".repeat(prefix.length),
+ printListItem(childPath, options, print, prefix)
+ )
]);
+
+ function getPrefix() {
+ const rawPrefix = node.ordered
+ ? (index === 0
+ ? node.start
+ : isGitDiffFriendlyOrderedList ? 1 : node.start + index) +
+ (nthSiblingIndex % 2 === 0 ? ". " : ") ")
+ : nthSiblingIndex % 2 === 0 ? "* " : "- ";
+
+ // do not print trailing spaces for empty list item since it might be treated as `break` node
+ // by [doc-printer](https://github.com/prettier/prettier/blob/1.10.2/src/doc/doc-printer.js#L395-L405),
+ // we don't want to preserve unnecessary trailing spaces.
+ const listItem = childPath.getValue();
+ return listItem.children.length
+ ? alignListPrefix(rawPrefix, options)
+ : rawPrefix;
+ }
}
});
}
- case "listItem": {
- const prefix =
- node.checked === null ? "" : node.checked ? "[x] " : "[ ] ";
- return concat([
- prefix,
- printChildren(path, options, print, {
- processor: (childPath, index) =>
- index === 0 && childPath.getValue().type !== "list"
- ? align(" ".repeat(prefix.length), childPath.call(print))
- : childPath.call(print)
- })
- ]);
- }
case "thematicBreak": {
const counter = getAncestorCounter(path, "list");
if (counter === -1) {
@@ -284,7 +287,7 @@ function genericPrint(path, options, print) {
case "imageReference":
switch (node.referenceType) {
case "full":
- return concat(["![", node.alt, "][", node.identifier, "]"]);
+ return concat(["![", node.alt || "", "][", node.identifier, "]"]);
default:
return concat([
"![",
@@ -305,13 +308,28 @@ function genericPrint(path, options, print) {
return concat(["[^", printChildren(path, options, print), "]"]);
case "footnoteReference":
return concat(["[^", node.identifier, "]"]);
- case "footnoteDefinition":
+ case "footnoteDefinition": {
+ const nextNode = path.getParentNode().children[path.getName() + 1];
return concat([
"[^",
node.identifier,
"]: ",
- printChildren(path, options, print)
+ group(
+ concat([
+ align(
+ " ".repeat(options.tabWidth),
+ printChildren(path, options, print, {
+ processor: (childPath, index) =>
+ index === 0
+ ? group(concat([softline, softline, childPath.call(print)]))
+ : childPath.call(print)
+ })
+ ),
+ nextNode && nextNode.type === "footnoteDefinition" ? softline : ""
+ ])
+ )
]);
+ }
case "table":
return printTable(path, options, print);
case "tableCell":
@@ -324,11 +342,47 @@ function genericPrint(path, options, print) {
hardline
]);
case "tableRow": // handled in "table"
+ case "listItem": // handled in "list"
default:
throw new Error(`Unknown markdown type ${JSON.stringify(node.type)}`);
}
}
+function printListItem(path, options, print, listPrefix) {
+ const node = path.getValue();
+ const prefix = node.checked === null ? "" : node.checked ? "[x] " : "[ ] ";
+ return concat([
+ prefix,
+ printChildren(path, options, print, {
+ processor: (childPath, index) => {
+ if (index === 0 && childPath.getValue().type !== "list") {
+ return align(" ".repeat(prefix.length), childPath.call(print));
+ }
+
+ const alignment = " ".repeat(
+ clamp(options.tabWidth - listPrefix.length, 0, 3) // 4+ will cause indented code block
+ );
+ return concat([alignment, align(alignment, childPath.call(print))]);
+ }
+ })
+ ]);
+}
+
+function alignListPrefix(prefix, options) {
+ const additionalSpaces = getAdditionalSpaces();
+ return (
+ prefix +
+ " ".repeat(
+ additionalSpaces >= 4 ? 0 : additionalSpaces // 4+ will cause indented code block
+ )
+ );
+
+ function getAdditionalSpaces() {
+ const restSpaces = prefix.length % options.tabWidth;
+ return restSpaces === 0 ? 0 : options.tabWidth - restSpaces;
+ }
+}
+
function getNthListSiblingIndex(node, parentNode) {
return getNthSiblingIndex(
node,
@@ -337,6 +391,10 @@ function getNthListSiblingIndex(node, parentNode) {
);
}
+function replaceNewlinesWithHardlines(str) {
+ return join(hardline, str.split("\n"));
+}
+
function getNthSiblingIndex(node, parentNode, condition) {
condition = condition || (() => true);
@@ -407,7 +465,7 @@ function printTable(path, options, print) {
const columnMaxWidths = contents.reduce(
(currentWidths, rowContents) =>
currentWidths.map((width, columnIndex) =>
- Math.max(width, util.getStringWidth(rowContents[columnIndex]))
+ Math.max(width, privateUtil.getStringWidth(rowContents[columnIndex]))
),
contents[0].map(() => 3) // minimum width = 3 (---, :--, :-:, --:)
);
@@ -461,21 +519,83 @@ function printTable(path, options, print) {
}
function alignLeft(text, width) {
- return concat([text, " ".repeat(width - util.getStringWidth(text))]);
+ return concat([text, " ".repeat(width - privateUtil.getStringWidth(text))]);
}
function alignRight(text, width) {
- return concat([" ".repeat(width - util.getStringWidth(text)), text]);
+ return concat([" ".repeat(width - privateUtil.getStringWidth(text)), text]);
}
function alignCenter(text, width) {
- const spaces = width - util.getStringWidth(text);
+ const spaces = width - privateUtil.getStringWidth(text);
const left = Math.floor(spaces / 2);
const right = spaces - left;
return concat([" ".repeat(left), text, " ".repeat(right)]);
}
}
+function printRoot(path, options, print) {
+ /** @typedef {{ index: number, offset: number }} IgnorePosition */
+ /** @type {Array<{start: IgnorePosition, end: IgnorePosition}>} */
+ const ignoreRanges = [];
+
+ /** @type {IgnorePosition | null} */
+ let ignoreStart = null;
+
+ const children = path.getValue().children;
+ children.forEach((childNode, index) => {
+ switch (isPrettierIgnore(childNode)) {
+ case "start":
+ if (ignoreStart === null) {
+ ignoreStart = { index, offset: childNode.position.end.offset };
+ }
+ break;
+ case "end":
+ if (ignoreStart !== null) {
+ ignoreRanges.push({
+ start: ignoreStart,
+ end: { index, offset: childNode.position.start.offset }
+ });
+ ignoreStart = null;
+ }
+ break;
+ default:
+ // do nothing
+ break;
+ }
+ });
+
+ return printChildren(path, options, print, {
+ processor: (childPath, index) => {
+ if (ignoreRanges.length !== 0) {
+ const ignoreRange = ignoreRanges[0];
+
+ if (index === ignoreRange.start.index) {
+ return concat([
+ children[ignoreRange.start.index].value,
+ options.originalText.slice(
+ ignoreRange.start.offset,
+ ignoreRange.end.offset
+ ),
+ children[ignoreRange.end.index].value
+ ]);
+ }
+
+ if (ignoreRange.start.index < index && index < ignoreRange.end.index) {
+ return false;
+ }
+
+ if (index === ignoreRange.end.index) {
+ ignoreRanges.shift();
+ return false;
+ }
+ }
+
+ return childPath.call(print);
+ }
+ });
+}
+
function printChildren(path, options, print, events) {
events = events || {};
@@ -485,28 +605,15 @@ function printChildren(path, options, print, events) {
const node = path.getValue();
const parts = [];
- let counter = 0;
let lastChildNode;
- let prettierIgnore = false;
path.map((childPath, index) => {
const childNode = childPath.getValue();
- const result = prettierIgnore
- ? options.originalText.slice(
- childNode.position.start.offset,
- childNode.position.end.offset
- )
- : processor(childPath, index);
-
- prettierIgnore = false;
-
+ const result = processor(childPath, index);
if (result !== false) {
- prettierIgnore = isPrettierIgnore(childNode);
-
const data = {
parts,
- index: counter++,
prevNode: lastChildNode,
parentNode: node,
options
@@ -536,10 +643,15 @@ function printChildren(path, options, print, events) {
return postprocessor(parts);
}
+/** @return {false | 'next' | 'start' | 'end'} */
function isPrettierIgnore(node) {
- return (
- node.type === "html" && /^$/.test(node.value)
+ if (node.type !== "html") {
+ return false;
+ }
+ const match = node.value.match(
+ /^$/
);
+ return match === null ? false : match[1] ? match[1] : "next";
}
function shouldNotPrePrintHardline(node, data) {
@@ -564,7 +676,7 @@ function shouldPrePrintDoubleHardline(node, data) {
const isPrevNodeLooseListItem =
data.prevNode && data.prevNode.type === "listItem" && data.prevNode.loose;
- const isPrevNodePrettierIgnore = isPrettierIgnore(data.prevNode);
+ const isPrevNodePrettierIgnore = isPrettierIgnore(data.prevNode) === "next";
return (
isPrevNodeLooseListItem ||
@@ -595,7 +707,7 @@ function shouldRemainTheSameContent(path) {
}
function normalizeDoc(doc) {
- return util.mapDoc(doc, currentDoc => {
+ return privateUtil.mapDoc(doc, currentDoc => {
if (!currentDoc.parts) {
return currentDoc;
}
@@ -647,7 +759,7 @@ function printTitle(title, options) {
function normalizeParts(parts) {
return parts.reduce((current, part) => {
- const lastPart = util.getLast(current);
+ const lastPart = privateUtil.getLast(current);
if (typeof lastPart === "string" && typeof part === "string") {
current.splice(-1, 1, lastPart + part);
@@ -659,21 +771,49 @@ function normalizeParts(parts) {
}, []);
}
-function clean(ast, newObj) {
- // for markdown codeblock
+function clamp(value, min, max) {
+ return value < min ? min : value > max ? max : value;
+}
+
+function clean(ast, newObj, parent) {
+ // for codeblock
if (ast.type === "code") {
delete newObj.value;
}
- // for markdown whitespace: "\n" and " " are considered the same
+ // for whitespace: "\n" and " " are considered the same
if (ast.type === "whitespace" && ast.value === "\n") {
newObj.value = " ";
}
+ // for insert pragma
+ if (
+ parent &&
+ parent.type === "root" &&
+ (parent.children[0] === ast ||
+ ((parent.children[0].type === "yaml" ||
+ parent.children[0].type === "toml") &&
+ parent.children[1] === ast)) &&
+ ast.type === "html" &&
+ pragma.startWithPragma(ast.value)
+ ) {
+ return null;
+ }
+}
+
+function hasPrettierIgnore(path) {
+ const index = +path.getName();
+
+ if (index === 0) {
+ return false;
+ }
+
+ const prevNode = path.getParentNode().children[index - 1];
+ return isPrettierIgnore(prevNode) === "next";
}
module.exports = {
- options: printerOptions,
print: genericPrint,
embed,
massageAstNode: clean,
- hasPrettierIgnore: util.hasIgnoreComment
+ hasPrettierIgnore,
+ insertPragma: pragma.insertPragma
};
diff --git a/src/language-vue/embed.js b/src/language-vue/embed.js
index 805b338e..bcb37bee 100644
--- a/src/language-vue/embed.js
+++ b/src/language-vue/embed.js
@@ -7,7 +7,7 @@ const hardline = docBuilders.hardline;
function embed(path, print, textToDoc, options) {
const node = path.getValue();
const parent = path.getParentNode();
- if (!parent || parent.tag !== "root") {
+ if (!parent || parent.tag !== "root" || node.unary) {
return null;
}
@@ -15,7 +15,7 @@ function embed(path, print, textToDoc, options) {
if (node.tag === "style") {
const langAttr = node.attrs.find(attr => attr.name === "lang");
- if (!langAttr) {
+ if (!langAttr || langAttr.value === "postcss") {
parser = "css";
} else if (langAttr.value === "scss") {
parser = "scss";
@@ -28,7 +28,7 @@ function embed(path, print, textToDoc, options) {
const langAttr = node.attrs.find(attr => attr.name === "lang");
if (!langAttr) {
parser = "babylon";
- } else if (langAttr.value === "ts") {
+ } else if (langAttr.value === "ts" || langAttr.value === "tsx") {
parser = "typescript";
}
}
diff --git a/src/language-vue/parser-vue.js b/src/language-vue/parser-vue.js
index b1f07a0a..482eca34 100644
--- a/src/language-vue/parser-vue.js
+++ b/src/language-vue/parser-vue.js
@@ -388,12 +388,16 @@ function parse(text /*, parsers, opts*/) {
attrs,
unary,
start,
- contentStart: end,
children: []
};
obj.children.push(newObj);
- objStack.push(newObj);
- obj = newObj;
+ if (unary) {
+ newObj.end = end;
+ } else {
+ newObj.contentStart = end;
+ objStack.push(newObj);
+ obj = newObj;
+ }
},
end: function(tag, start, end) {
objStack.pop();
diff --git a/src/language-vue/printer-vue.js b/src/language-vue/printer-vue.js
index ca2075ef..c095b1b7 100644
--- a/src/language-vue/printer-vue.js
+++ b/src/language-vue/printer-vue.js
@@ -3,6 +3,7 @@
const embed = require("./embed");
const docBuilders = require("../doc").builders;
const concat = docBuilders.concat;
+const hardline = docBuilders.hardline;
function genericPrint(path, options, print) {
const n = path.getValue();
@@ -15,8 +16,17 @@ function genericPrint(path, options, print) {
res.push(childPath.call(print));
index = child.end;
}, "children");
+
+ // If there are no children, we just print the node from start to end.
+ // Otherwise, index should point to the end of the last child, and we
+ // need to print the closing tag.
res.push(options.originalText.slice(index, n.end));
+ // Only force a trailing newline if there were any contents.
+ if (n.tag === "root" && n.children.length) {
+ res.push(hardline);
+ }
+
return concat(res);
}
diff --git a/src/main/ast-to-doc.js b/src/main/ast-to-doc.js
index 7ebaa540..c15bd433 100644
--- a/src/main/ast-to-doc.js
+++ b/src/main/ast-to-doc.js
@@ -4,7 +4,6 @@ const assert = require("assert");
const comments = require("./comments");
const FastPath = require("../common/fast-path");
const multiparser = require("./multiparser");
-const util = require("../common/util");
const doc = require("../doc");
const docBuilders = doc.builders;
@@ -75,7 +74,10 @@ function genericPrint(path, options, printPath, args) {
// Escape hatch
if (printer.hasPrettierIgnore && printer.hasPrettierIgnore(path)) {
- return options.originalText.slice(util.locStart(node), util.locEnd(node));
+ return options.originalText.slice(
+ options.locStart(node),
+ options.locEnd(node)
+ );
}
if (node) {
diff --git a/src/main/comments.js b/src/main/comments.js
index ad2fe68d..4113a925 100644
--- a/src/main/comments.js
+++ b/src/main/comments.js
@@ -9,20 +9,21 @@ const indent = docBuilders.indent;
const lineSuffix = docBuilders.lineSuffix;
const join = docBuilders.join;
const cursor = docBuilders.cursor;
-const util = require("../common/util");
+const privateUtil = require("../common/util");
+const sharedUtil = require("../common/util-shared");
const childNodesCacheKey = Symbol("child-nodes");
-const locStart = util.locStart;
-const locEnd = util.locEnd;
-const getNextNonSpaceNonCommentCharacter =
- util.getNextNonSpaceNonCommentCharacter;
-const getNextNonSpaceNonCommentCharacterIndex =
- util.getNextNonSpaceNonCommentCharacterIndex;
+
+const addLeadingComment = sharedUtil.addLeadingComment;
+const addTrailingComment = sharedUtil.addTrailingComment;
+const addDanglingComment = sharedUtil.addDanglingComment;
function getSortedChildNodes(node, text, options, resultArray) {
if (!node) {
return;
}
const printer = options.printer;
+ const locStart = options.locStart;
+ const locEnd = options.locEnd;
if (resultArray) {
if (node && printer.canAttachComment && printer.canAttachComment(node)) {
@@ -45,13 +46,22 @@ function getSortedChildNodes(node, text, options, resultArray) {
return node[childNodesCacheKey];
}
- let names;
- if (node && typeof node === "object") {
- names = Object.keys(node).filter(
- n =>
- n !== "enclosingNode" && n !== "precedingNode" && n !== "followingNode"
- );
- } else {
+ let childNodes;
+
+ if (printer.getCommentChildNodes) {
+ childNodes = printer.getCommentChildNodes(node);
+ } else if (node && typeof node === "object") {
+ childNodes = Object.keys(node)
+ .filter(
+ n =>
+ n !== "enclosingNode" &&
+ n !== "precedingNode" &&
+ n !== "followingNode"
+ )
+ .map(n => node[n]);
+ }
+
+ if (!childNodes) {
return;
}
@@ -62,9 +72,9 @@ function getSortedChildNodes(node, text, options, resultArray) {
});
}
- for (let i = 0, nameCount = names.length; i < nameCount; ++i) {
- getSortedChildNodes(node[names[i]], text, options, resultArray);
- }
+ childNodes.forEach(childNode => {
+ getSortedChildNodes(childNode, text, options, resultArray);
+ });
return resultArray;
}
@@ -73,6 +83,8 @@ function getSortedChildNodes(node, text, options, resultArray) {
// .precedingNode, .enclosingNode, and/or .followingNode properties, at
// least one of which is guaranteed to be defined.
function decorateComment(node, comment, text, options) {
+ const locStart = options.locStart;
+ const locEnd = options.locEnd;
const childNodes = getSortedChildNodes(node, text, options);
let precedingNode;
let followingNode;
@@ -125,17 +137,23 @@ function decorateComment(node, comment, text, options) {
comment.enclosingNode.type === "TemplateLiteral"
) {
const quasis = comment.enclosingNode.quasis;
- const commentIndex = findExpressionIndexForComment(quasis, comment);
+ const commentIndex = findExpressionIndexForComment(
+ quasis,
+ comment,
+ options
+ );
if (
precedingNode &&
- findExpressionIndexForComment(quasis, precedingNode) !== commentIndex
+ findExpressionIndexForComment(quasis, precedingNode, options) !==
+ commentIndex
) {
precedingNode = null;
}
if (
followingNode &&
- findExpressionIndexForComment(quasis, followingNode) !== commentIndex
+ findExpressionIndexForComment(quasis, followingNode, options) !==
+ commentIndex
) {
followingNode = null;
}
@@ -156,6 +174,8 @@ function attach(comments, ast, text, options) {
}
const tiesToBreak = [];
+ const locStart = options.locStart;
+ const locEnd = options.locEnd;
comments.forEach((comment, i) => {
if (options.parser === "json" && locStart(comment) - locStart(ast) <= 0) {
@@ -169,51 +189,26 @@ function attach(comments, ast, text, options) {
const enclosingNode = comment.enclosingNode;
const followingNode = comment.followingNode;
+ const pluginHandleOwnLineComment =
+ options.printer.handleComments && options.printer.handleComments.ownLine
+ ? options.printer.handleComments.ownLine
+ : () => false;
+ const pluginHandleEndOfLineComment =
+ options.printer.handleComments && options.printer.handleComments.endOfLine
+ ? options.printer.handleComments.endOfLine
+ : () => false;
+ const pluginHandleRemainingComment =
+ options.printer.handleComments && options.printer.handleComments.remaining
+ ? options.printer.handleComments.remaining
+ : () => false;
+
const isLastComment = comments.length - 1 === i;
- if (util.hasNewline(text, locStart(comment), { backwards: true })) {
+ if (privateUtil.hasNewline(text, locStart(comment), { backwards: true })) {
// If a comment exists on its own line, prefer a leading comment.
// We also need to check if it's the first line of the file.
if (
- handleLastFunctionArgComments(
- text,
- precedingNode,
- enclosingNode,
- followingNode,
- comment
- ) ||
- handleMemberExpressionComments(enclosingNode, followingNode, comment) ||
- handleIfStatementComments(
- text,
- precedingNode,
- enclosingNode,
- followingNode,
- comment
- ) ||
- handleTryStatementComments(enclosingNode, followingNode, comment) ||
- handleClassComments(
- enclosingNode,
- precedingNode,
- followingNode,
- comment
- ) ||
- handleImportSpecifierComments(enclosingNode, comment) ||
- handleForComments(enclosingNode, precedingNode, comment) ||
- handleUnionTypeComments(
- precedingNode,
- enclosingNode,
- followingNode,
- comment
- ) ||
- handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
- handleImportDeclarationComments(
- text,
- enclosingNode,
- precedingNode,
- comment
- ) ||
- handleAssignmentPatternComments(enclosingNode, comment) ||
- handleMethodNameComments(text, enclosingNode, precedingNode, comment)
+ pluginHandleOwnLineComment(comment, text, options, ast, isLastComment)
) {
// We're good
} else if (followingNode) {
@@ -228,43 +223,9 @@ function attach(comments, ast, text, options) {
/* istanbul ignore next */
addDanglingComment(ast, comment);
}
- } else if (util.hasNewline(text, locEnd(comment))) {
+ } else if (privateUtil.hasNewline(text, locEnd(comment))) {
if (
- handleLastFunctionArgComments(
- text,
- precedingNode,
- enclosingNode,
- followingNode,
- comment
- ) ||
- handleConditionalExpressionComments(
- enclosingNode,
- precedingNode,
- followingNode,
- comment,
- text
- ) ||
- handleImportSpecifierComments(enclosingNode, comment) ||
- handleIfStatementComments(
- text,
- precedingNode,
- enclosingNode,
- followingNode,
- comment
- ) ||
- handleClassComments(
- enclosingNode,
- precedingNode,
- followingNode,
- comment
- ) ||
- handleLabeledStatementComments(enclosingNode, comment) ||
- handleCallExpressionComments(precedingNode, enclosingNode, comment) ||
- handlePropertyComments(enclosingNode, comment) ||
- handleExportNamedDeclarationComments(enclosingNode, comment) ||
- handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
- handleTypeAliasComments(enclosingNode, followingNode, comment) ||
- handleVariableDeclaratorComments(enclosingNode, followingNode, comment)
+ pluginHandleEndOfLineComment(comment, text, options, ast, isLastComment)
) {
// We're good
} else if (precedingNode) {
@@ -282,19 +243,7 @@ function attach(comments, ast, text, options) {
}
} else {
if (
- handleIfStatementComments(
- text,
- precedingNode,
- enclosingNode,
- followingNode,
- comment
- ) ||
- handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) ||
- handleCommentInEmptyParens(text, enclosingNode, comment) ||
- handleMethodNameComments(text, enclosingNode, precedingNode, comment) ||
- handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
- handleCommentAfterArrowParams(text, enclosingNode, comment) ||
- handleFunctionNameComments(text, enclosingNode, precedingNode, comment)
+ pluginHandleRemainingComment(comment, text, options, ast, isLastComment)
) {
// We're good
} else if (precedingNode && followingNode) {
@@ -307,7 +256,7 @@ function attach(comments, ast, text, options) {
if (tieCount > 0) {
const lastTie = tiesToBreak[tieCount - 1];
if (lastTie.followingNode !== comment.followingNode) {
- breakTies(tiesToBreak, text);
+ breakTies(tiesToBreak, text, options);
}
}
tiesToBreak.push(comment);
@@ -325,7 +274,7 @@ function attach(comments, ast, text, options) {
}
});
- breakTies(tiesToBreak, text);
+ breakTies(tiesToBreak, text, options);
comments.forEach(comment => {
// These node references were useful for breaking ties, but we
@@ -337,7 +286,7 @@ function attach(comments, ast, text, options) {
});
}
-function breakTies(tiesToBreak, text) {
+function breakTies(tiesToBreak, text, options) {
const tieCount = tiesToBreak.length;
if (tieCount === 0) {
return;
@@ -345,7 +294,7 @@ function breakTies(tiesToBreak, text) {
const precedingNode = tiesToBreak[0].precedingNode;
const followingNode = tiesToBreak[0].followingNode;
- let gapEndPos = locStart(followingNode);
+ let gapEndPos = options.locStart(followingNode);
// Iterate backwards through tiesToBreak, examining the gaps
// between the tied comments. In order to qualify as leading, a
@@ -362,9 +311,9 @@ function breakTies(tiesToBreak, text) {
assert.strictEqual(comment.precedingNode, precedingNode);
assert.strictEqual(comment.followingNode, followingNode);
- const gap = text.slice(locEnd(comment), gapEndPos).trim();
+ const gap = text.slice(options.locEnd(comment), gapEndPos).trim();
if (gap === "" || /^\(+$/.test(gap)) {
- gapEndPos = locStart(comment);
+ gapEndPos = options.locStart(comment);
} else {
// The gap string contained something other than whitespace or open
// parentheses.
@@ -383,540 +332,14 @@ function breakTies(tiesToBreak, text) {
tiesToBreak.length = 0;
}
-function addCommentHelper(node, comment) {
- const comments = node.comments || (node.comments = []);
- comments.push(comment);
- comment.printed = false;
-
- // For some reason, TypeScript parses `// x` inside of JSXText as a comment
- // We already "print" it via the raw text, we don't need to re-print it as a
- // comment
- if (node.type === "JSXText") {
- comment.printed = true;
- }
-}
-
-function addLeadingComment(node, comment) {
- comment.leading = true;
- comment.trailing = false;
- addCommentHelper(node, comment);
-}
-
-function addDanglingComment(node, comment) {
- comment.leading = false;
- comment.trailing = false;
- addCommentHelper(node, comment);
-}
-
-function addTrailingComment(node, comment) {
- comment.leading = false;
- comment.trailing = true;
- addCommentHelper(node, comment);
-}
-
-function addBlockStatementFirstComment(node, comment) {
- const body = node.body.filter(n => n.type !== "EmptyStatement");
- if (body.length === 0) {
- addDanglingComment(node, comment);
- } else {
- addLeadingComment(body[0], comment);
- }
-}
-
-function addBlockOrNotComment(node, comment) {
- if (node.type === "BlockStatement") {
- addBlockStatementFirstComment(node, comment);
- } else {
- addLeadingComment(node, comment);
- }
-}
-
-// There are often comments before the else clause of if statements like
-//
-// if (1) { ... }
-// // comment
-// else { ... }
-//
-// They are being attached as leading comments of the BlockExpression which
-// is not well printed. What we want is to instead move the comment inside
-// of the block and make it leadingComment of the first element of the block
-// or dangling comment of the block if there is nothing inside
-//
-// if (1) { ... }
-// else {
-// // comment
-// ...
-// }
-function handleIfStatementComments(
- text,
- precedingNode,
- enclosingNode,
- followingNode,
- comment
-) {
- if (
- !enclosingNode ||
- enclosingNode.type !== "IfStatement" ||
- !followingNode
- ) {
- return false;
- }
-
- // We unfortunately have no way using the AST or location of nodes to know
- // if the comment is positioned before the condition parenthesis:
- // if (a /* comment */) {}
- // The only workaround I found is to look at the next character to see if
- // it is a ).
- const nextCharacter = getNextNonSpaceNonCommentCharacter(text, comment);
- if (nextCharacter === ")") {
- addTrailingComment(precedingNode, comment);
- return true;
- }
-
- if (followingNode.type === "BlockStatement") {
- addBlockStatementFirstComment(followingNode, comment);
- return true;
- }
-
- if (followingNode.type === "IfStatement") {
- addBlockOrNotComment(followingNode.consequent, comment);
- return true;
- }
-
- // For comments positioned after the condition parenthesis in an if statement
- // before the consequent with or without brackets on, such as
- // if (a) /* comment */ {} or if (a) /* comment */ true,
- // we look at the next character to see if it is a { or if the following node
- // is the consequent for the if statement
- if (nextCharacter === "{" || enclosingNode.consequent === followingNode) {
- addLeadingComment(followingNode, comment);
- return true;
- }
-
- return false;
-}
-
-// Same as IfStatement but for TryStatement
-function handleTryStatementComments(enclosingNode, followingNode, comment) {
- if (
- !enclosingNode ||
- enclosingNode.type !== "TryStatement" ||
- !followingNode
- ) {
- return false;
- }
-
- if (followingNode.type === "BlockStatement") {
- addBlockStatementFirstComment(followingNode, comment);
- return true;
- }
-
- if (followingNode.type === "TryStatement") {
- addBlockOrNotComment(followingNode.finalizer, comment);
- return true;
- }
-
- if (followingNode.type === "CatchClause") {
- addBlockOrNotComment(followingNode.body, comment);
- return true;
- }
-
- return false;
-}
-
-function handleMemberExpressionComments(enclosingNode, followingNode, comment) {
- if (
- enclosingNode &&
- enclosingNode.type === "MemberExpression" &&
- followingNode &&
- followingNode.type === "Identifier"
- ) {
- addLeadingComment(enclosingNode, comment);
- return true;
- }
-
- return false;
-}
-
-function handleConditionalExpressionComments(
- enclosingNode,
- precedingNode,
- followingNode,
- comment,
- text
-) {
- const isSameLineAsPrecedingNode =
- precedingNode &&
- !util.hasNewlineInRange(text, locEnd(precedingNode), locStart(comment));
-
- if (
- (!precedingNode || !isSameLineAsPrecedingNode) &&
- enclosingNode &&
- enclosingNode.type === "ConditionalExpression" &&
- followingNode
- ) {
- addLeadingComment(followingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) {
- if (
- enclosingNode &&
- (enclosingNode.type === "ObjectProperty" ||
- enclosingNode.type === "Property") &&
- enclosingNode.shorthand &&
- enclosingNode.key === precedingNode &&
- enclosingNode.value.type === "AssignmentPattern"
- ) {
- addTrailingComment(enclosingNode.value.left, comment);
- return true;
- }
- return false;
-}
-
-function handleClassComments(
- enclosingNode,
- precedingNode,
- followingNode,
- comment
-) {
- if (
- enclosingNode &&
- (enclosingNode.type === "ClassDeclaration" ||
- enclosingNode.type === "ClassExpression") &&
- (enclosingNode.decorators && enclosingNode.decorators.length > 0) &&
- !(followingNode && followingNode.type === "Decorator")
- ) {
- if (!enclosingNode.decorators || enclosingNode.decorators.length === 0) {
- addLeadingComment(enclosingNode, comment);
- } else {
- addTrailingComment(
- enclosingNode.decorators[enclosingNode.decorators.length - 1],
- comment
- );
- }
- return true;
- }
- return false;
-}
-
-function handleMethodNameComments(text, enclosingNode, precedingNode, comment) {
- // This is only needed for estree parsers (flow, typescript) to attach
- // after a method name:
- // obj = { fn /*comment*/() {} };
- if (
- enclosingNode &&
- precedingNode &&
- (enclosingNode.type === "Property" ||
- enclosingNode.type === "MethodDefinition") &&
- precedingNode.type === "Identifier" &&
- enclosingNode.key === precedingNode &&
- // special Property case: { key: /*comment*/(value) };
- // comment should be attached to value instead of key
- getNextNonSpaceNonCommentCharacter(text, precedingNode) !== ":"
- ) {
- addTrailingComment(precedingNode, comment);
- return true;
- }
-
- // Print comments between decorators and class methods as a trailing comment
- // on the decorator node instead of the method node
- if (
- precedingNode &&
- enclosingNode &&
- precedingNode.type === "Decorator" &&
- (enclosingNode.type === "ClassMethod" ||
- enclosingNode.type === "ClassProperty" ||
- enclosingNode.type === "TSAbstractClassProperty" ||
- enclosingNode.type === "TSAbstractMethodDefinition" ||
- enclosingNode.type === "MethodDefinition")
- ) {
- addTrailingComment(precedingNode, comment);
- return true;
- }
-
- return false;
-}
-
-function handleFunctionNameComments(
- text,
- enclosingNode,
- precedingNode,
- comment
-) {
- if (getNextNonSpaceNonCommentCharacter(text, comment) !== "(") {
- return false;
- }
-
- if (
- precedingNode &&
- enclosingNode &&
- (enclosingNode.type === "FunctionDeclaration" ||
- enclosingNode.type === "FunctionExpression" ||
- enclosingNode.type === "ClassMethod" ||
- enclosingNode.type === "MethodDefinition" ||
- enclosingNode.type === "ObjectMethod")
- ) {
- addTrailingComment(precedingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleCommentAfterArrowParams(text, enclosingNode, comment) {
- if (!(enclosingNode && enclosingNode.type === "ArrowFunctionExpression")) {
- return false;
- }
-
- const index = getNextNonSpaceNonCommentCharacterIndex(text, comment);
- if (text.substr(index, 2) === "=>") {
- addDanglingComment(enclosingNode, comment);
- return true;
- }
-
- return false;
-}
-
-function handleCommentInEmptyParens(text, enclosingNode, comment) {
- if (getNextNonSpaceNonCommentCharacter(text, comment) !== ")") {
- return false;
- }
-
- // Only add dangling comments to fix the case when no params are present,
- // i.e. a function without any argument.
- if (
- enclosingNode &&
- (((enclosingNode.type === "FunctionDeclaration" ||
- enclosingNode.type === "FunctionExpression" ||
- (enclosingNode.type === "ArrowFunctionExpression" &&
- (enclosingNode.body.type !== "CallExpression" ||
- enclosingNode.body.arguments.length === 0)) ||
- enclosingNode.type === "ClassMethod" ||
- enclosingNode.type === "ObjectMethod") &&
- enclosingNode.params.length === 0) ||
- (enclosingNode.type === "CallExpression" &&
- enclosingNode.arguments.length === 0))
- ) {
- addDanglingComment(enclosingNode, comment);
- return true;
- }
- if (
- enclosingNode &&
- (enclosingNode.type === "MethodDefinition" &&
- enclosingNode.value.params.length === 0)
- ) {
- addDanglingComment(enclosingNode.value, comment);
- return true;
- }
- return false;
-}
-
-function handleLastFunctionArgComments(
- text,
- precedingNode,
- enclosingNode,
- followingNode,
- comment
-) {
- // Type definitions functions
- if (
- precedingNode &&
- precedingNode.type === "FunctionTypeParam" &&
- enclosingNode &&
- enclosingNode.type === "FunctionTypeAnnotation" &&
- followingNode &&
- followingNode.type !== "FunctionTypeParam"
- ) {
- addTrailingComment(precedingNode, comment);
- return true;
- }
-
- // Real functions
- if (
- precedingNode &&
- (precedingNode.type === "Identifier" ||
- precedingNode.type === "AssignmentPattern") &&
- enclosingNode &&
- (enclosingNode.type === "ArrowFunctionExpression" ||
- enclosingNode.type === "FunctionExpression" ||
- enclosingNode.type === "FunctionDeclaration" ||
- enclosingNode.type === "ObjectMethod" ||
- enclosingNode.type === "ClassMethod") &&
- getNextNonSpaceNonCommentCharacter(text, comment) === ")"
- ) {
- addTrailingComment(precedingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleImportSpecifierComments(enclosingNode, comment) {
- if (enclosingNode && enclosingNode.type === "ImportSpecifier") {
- addLeadingComment(enclosingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleLabeledStatementComments(enclosingNode, comment) {
- if (enclosingNode && enclosingNode.type === "LabeledStatement") {
- addLeadingComment(enclosingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleCallExpressionComments(precedingNode, enclosingNode, comment) {
- if (
- enclosingNode &&
- enclosingNode.type === "CallExpression" &&
- precedingNode &&
- enclosingNode.callee === precedingNode &&
- enclosingNode.arguments.length > 0
- ) {
- addLeadingComment(enclosingNode.arguments[0], comment);
- return true;
- }
- return false;
-}
-
-function handleUnionTypeComments(
- precedingNode,
- enclosingNode,
- followingNode,
- comment
-) {
- if (
- enclosingNode &&
- (enclosingNode.type === "UnionTypeAnnotation" ||
- enclosingNode.type === "TSUnionType")
- ) {
- addTrailingComment(precedingNode, comment);
- return true;
- }
- return false;
-}
-
-function handlePropertyComments(enclosingNode, comment) {
- if (
- enclosingNode &&
- (enclosingNode.type === "Property" ||
- enclosingNode.type === "ObjectProperty")
- ) {
- addLeadingComment(enclosingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleExportNamedDeclarationComments(enclosingNode, comment) {
- if (enclosingNode && enclosingNode.type === "ExportNamedDeclaration") {
- addLeadingComment(enclosingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleOnlyComments(enclosingNode, ast, comment, isLastComment) {
- // With Flow the enclosingNode is undefined so use the AST instead.
- if (ast && ast.body && ast.body.length === 0) {
- if (isLastComment) {
- addDanglingComment(ast, comment);
- } else {
- addLeadingComment(ast, comment);
- }
- return true;
- } else if (
- enclosingNode &&
- enclosingNode.type === "Program" &&
- enclosingNode.body.length === 0 &&
- enclosingNode.directives &&
- enclosingNode.directives.length === 0
- ) {
- if (isLastComment) {
- addDanglingComment(enclosingNode, comment);
- } else {
- addLeadingComment(enclosingNode, comment);
- }
- return true;
- }
- return false;
-}
-
-function handleForComments(enclosingNode, precedingNode, comment) {
- if (
- enclosingNode &&
- (enclosingNode.type === "ForInStatement" ||
- enclosingNode.type === "ForOfStatement")
- ) {
- addLeadingComment(enclosingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleImportDeclarationComments(
- text,
- enclosingNode,
- precedingNode,
- comment
-) {
- if (
- precedingNode &&
- enclosingNode &&
- enclosingNode.type === "ImportDeclaration" &&
- util.hasNewline(text, util.locEnd(comment))
- ) {
- addTrailingComment(precedingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleAssignmentPatternComments(enclosingNode, comment) {
- if (enclosingNode && enclosingNode.type === "AssignmentPattern") {
- addLeadingComment(enclosingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleTypeAliasComments(enclosingNode, followingNode, comment) {
- if (enclosingNode && enclosingNode.type === "TypeAlias") {
- addLeadingComment(enclosingNode, comment);
- return true;
- }
- return false;
-}
-
-function handleVariableDeclaratorComments(
- enclosingNode,
- followingNode,
- comment
-) {
- if (
- enclosingNode &&
- enclosingNode.type === "VariableDeclarator" &&
- followingNode &&
- (followingNode.type === "ObjectExpression" ||
- followingNode.type === "ArrayExpression")
- ) {
- addLeadingComment(followingNode, comment);
- return true;
- }
- return false;
-}
-
function printComment(commentPath, options) {
const comment = commentPath.getValue();
comment.printed = true;
return options.printer.printComment(commentPath, options);
}
-function findExpressionIndexForComment(quasis, comment) {
- const startPos = locStart(comment) - 1;
+function findExpressionIndexForComment(quasis, comment, options) {
+ const startPos = options.locStart(comment) - 1;
for (let i = 1; i < quasis.length; ++i) {
if (startPos < getQuasiRange(quasis[i]).start) {
@@ -945,14 +368,16 @@ function printLeadingComment(commentPath, print, options) {
if (!contents) {
return "";
}
- const isBlock = util.isBlockComment(comment);
+ const isBlock = privateUtil.isBlockComment(comment);
// Leading block comments should see if they need to stay on the
// same line or not.
if (isBlock) {
return concat([
contents,
- util.hasNewline(options.originalText, locEnd(comment)) ? hardline : " "
+ privateUtil.hasNewline(options.originalText, options.locEnd(comment))
+ ? hardline
+ : " "
]);
}
@@ -965,10 +390,21 @@ function printTrailingComment(commentPath, print, options) {
if (!contents) {
return "";
}
- const isBlock = util.isBlockComment(comment);
+ const isBlock = privateUtil.isBlockComment(comment);
+
+ // We don't want the line to break
+ // when the parentParentNode is a ClassDeclaration/-Expression
+ // And the parentNode is in the superClass property
+ const parentNode = commentPath.getNode(1);
+ const parentParentNode = commentPath.getNode(2);
+ const isParentSuperClass =
+ parentParentNode &&
+ (parentParentNode.type === "ClassDeclaration" ||
+ parentParentNode.type === "ClassExpression") &&
+ parentParentNode.superClass === parentNode;
if (
- util.hasNewline(options.originalText, locStart(comment), {
+ privateUtil.hasNewline(options.originalText, options.locStart(comment), {
backwards: true
})
) {
@@ -984,15 +420,16 @@ function printTrailingComment(commentPath, print, options) {
// if this a comment on its own line; normal trailing comments are
// always at the end of another expression.
- const isLineBeforeEmpty = util.isPreviousLineEmpty(
+ const isLineBeforeEmpty = privateUtil.isPreviousLineEmpty(
options.originalText,
- comment
+ comment,
+ options.locStart
);
return lineSuffix(
concat([hardline, isLineBeforeEmpty ? hardline : "", contents])
);
- } else if (isBlock) {
+ } else if (isBlock || isParentSuperClass) {
// Trailing block comments never need a newline
return concat([" ", contents]);
}
@@ -1062,7 +499,12 @@ function printComments(path, print, options, needsSemi) {
leadingParts.push(contents);
const text = options.originalText;
- if (util.hasNewline(text, util.skipNewline(text, util.locEnd(comment)))) {
+ if (
+ privateUtil.hasNewline(
+ text,
+ privateUtil.skipNewline(text, options.locEnd(comment))
+ )
+ ) {
leadingParts.push(hardline);
}
} else if (trailing) {
diff --git a/src/main/get-plugin.js b/src/main/get-plugin.js
new file mode 100644
index 00000000..17e4b7bd
--- /dev/null
+++ b/src/main/get-plugin.js
@@ -0,0 +1,19 @@
+"use strict";
+
+function getPlugin(options) {
+ const astFormat = options.astFormat;
+
+ if (!astFormat) {
+ throw new Error("getPlugin() requires astFormat to be set");
+ }
+ const printerPlugin = options.plugins.find(
+ plugin => plugin.printers[astFormat]
+ );
+ if (!printerPlugin) {
+ throw new Error(`Couldn't find plugin for AST format "${astFormat}"`);
+ }
+
+ return printerPlugin;
+}
+
+module.exports = getPlugin;
diff --git a/src/main/get-printer.js b/src/main/get-printer.js
deleted file mode 100644
index cba1b100..00000000
--- a/src/main/get-printer.js
+++ /dev/null
@@ -1,21 +0,0 @@
-"use strict";
-
-function getPrinter(options) {
- 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}"`
- );
- }
-
- return printerPlugin.printers[astFormat];
-}
-
-module.exports = getPrinter;
diff --git a/src/main/multiparser.js b/src/main/multiparser.js
index 3704e658..9ddcf8e8 100644
--- a/src/main/multiparser.js
+++ b/src/main/multiparser.js
@@ -20,10 +20,14 @@ function textToDoc(text, partialNextOptions, parentOptions) {
Object.assign({}, parentOptions, partialNextOptions, {
parentParser: parentOptions.parser,
originalText: text
- })
+ }),
+ { passThrough: true, inferParser: false }
);
- const ast = require("./parser").parse(text, nextOptions);
+ const result = require("./parser").parse(text, nextOptions);
+ const ast = result.ast;
+ text = result.text;
+
const astComments = ast.comments;
delete ast.comments;
comments.attach(astComments, ast, text, nextOptions);
diff --git a/src/main/options-descriptor.js b/src/main/options-descriptor.js
new file mode 100644
index 00000000..8dc53ef7
--- /dev/null
+++ b/src/main/options-descriptor.js
@@ -0,0 +1,22 @@
+"use strict";
+
+function apiDescriptor(name, value) {
+ return arguments.length === 1
+ ? JSON.stringify(name)
+ : `\`{ ${apiDescriptor(name)}: ${JSON.stringify(value)} }\``;
+}
+
+function cliDescriptor(name, value) {
+ return value === false
+ ? `\`--no-${name}\``
+ : value === true || arguments.length === 1
+ ? `\`--${name}\``
+ : value === ""
+ ? `\`--${name}\` without an argument`
+ : `\`--${name}=${value}\``;
+}
+
+module.exports = {
+ apiDescriptor,
+ cliDescriptor
+};
diff --git a/src/main/options-normalizer.js b/src/main/options-normalizer.js
new file mode 100644
index 00000000..3457a8b1
--- /dev/null
+++ b/src/main/options-normalizer.js
@@ -0,0 +1,153 @@
+"use strict";
+
+const leven = require("leven");
+const validator = require("./options-validator");
+const descriptors = require("./options-descriptor");
+
+function normalizeOptions(options, optionInfos, opts) {
+ opts = opts || {};
+ const logger =
+ opts.logger === false
+ ? { warn() {} }
+ : opts.logger !== undefined ? opts.logger : console;
+ const descriptor = opts.descriptor || descriptors.apiDescriptor;
+ const passThrough = opts.passThrough || [];
+
+ const optionInfoMap = optionInfos.reduce(
+ (reduced, optionInfo) =>
+ Object.assign(reduced, { [optionInfo.name]: optionInfo }),
+ {}
+ );
+ const normalizedOptions = Object.keys(options).reduce((newOptions, key) => {
+ const optionInfo = optionInfoMap[key];
+
+ let optionName = key;
+ let optionValue = options[key];
+
+ if (!optionInfo) {
+ if (passThrough === true || passThrough.indexOf(optionName) !== -1) {
+ newOptions[optionName] = optionValue;
+ } else {
+ logger.warn(
+ createUnknownOptionMessage(
+ optionName,
+ optionValue,
+ optionInfos,
+ descriptor
+ )
+ );
+ }
+ return newOptions;
+ }
+
+ if (!optionInfo.deprecated) {
+ optionValue = normalizeOption(optionValue, optionInfo);
+ } else if (typeof optionInfo.redirect === "string") {
+ logger.warn(createRedirectOptionMessage(optionInfo, descriptor));
+ optionName = optionInfo.redirect;
+ } else if (optionValue) {
+ logger.warn(createRedirectOptionMessage(optionInfo, descriptor));
+ optionValue = optionInfo.redirect.value;
+ optionName = optionInfo.redirect.option;
+ }
+
+ if (optionInfo.choices) {
+ const choiceInfo = optionInfo.choices.find(
+ choice => choice.value === optionValue
+ );
+ if (choiceInfo && choiceInfo.deprecated) {
+ logger.warn(
+ createRedirectChoiceMessage(optionInfo, choiceInfo, descriptor)
+ );
+ optionValue = choiceInfo.redirect;
+ }
+ }
+
+ if (optionInfo.array && !Array.isArray(optionValue)) {
+ optionValue = [optionValue];
+ }
+
+ if (optionValue !== optionInfo.default) {
+ validator.validateOption(optionValue, optionInfoMap[optionName], {
+ descriptor
+ });
+ }
+
+ newOptions[optionName] = optionValue;
+ return newOptions;
+ }, {});
+
+ return normalizedOptions;
+}
+
+function normalizeOption(option, optionInfo) {
+ return optionInfo.type === "int" ? Number(option) : option;
+}
+
+function createUnknownOptionMessage(key, value, optionInfos, descriptor) {
+ const messages = [`Ignored unknown option ${descriptor(key, value)}.`];
+
+ const suggestedOptionInfo = optionInfos.find(
+ optionInfo => leven(optionInfo.name, key) < 3
+ );
+ if (suggestedOptionInfo) {
+ messages.push(`Did you mean ${JSON.stringify(suggestedOptionInfo.name)}?`);
+ }
+
+ return messages.join(" ");
+}
+
+function createRedirectOptionMessage(optionInfo, descriptor) {
+ return `${descriptor(
+ optionInfo.name
+ )} is deprecated. Prettier now treats it as ${
+ typeof optionInfo.redirect === "string"
+ ? descriptor(optionInfo.redirect)
+ : descriptor(optionInfo.redirect.option, optionInfo.redirect.value)
+ }.`;
+}
+
+function createRedirectChoiceMessage(optionInfo, choiceInfo, descriptor) {
+ return `${descriptor(
+ optionInfo.name,
+ choiceInfo.value
+ )} is deprecated. Prettier now treats it as ${descriptor(
+ optionInfo.name,
+ choiceInfo.redirect
+ )}.`;
+}
+
+function normalizeApiOptions(options, optionInfos, opts) {
+ return normalizeOptions(
+ options,
+ optionInfos,
+ Object.assign({ descriptor: descriptors.apiDescriptor }, opts)
+ );
+}
+
+function normalizeCliOptions(options, optionInfos, opts) {
+ const args = options["_"] || [];
+
+ const newOptions = normalizeOptions(
+ Object.keys(options).reduce(
+ (reduced, key) =>
+ Object.assign(
+ reduced,
+ key.length === 1 // omit alias
+ ? null
+ : { [key]: options[key] }
+ ),
+ {}
+ ),
+ optionInfos,
+ Object.assign({ descriptor: descriptors.cliDescriptor }, opts)
+ );
+ newOptions["_"] = args;
+
+ return newOptions;
+}
+
+module.exports = {
+ normalizeApiOptions,
+ normalizeCliOptions
+};
diff --git a/src/main/options-validator.js b/src/main/options-validator.js
new file mode 100644
index 00000000..7608c4b7
--- /dev/null
+++ b/src/main/options-validator.js
@@ -0,0 +1,81 @@
+"use strict";
+
+const descriptors = require("./options-descriptor");
+
+function validateOption(value, optionInfo, opts) {
+ opts = opts || {};
+ const descriptor = opts.descriptor || descriptors.apiDescriptor;
+
+ if (
+ typeof optionInfo.exception === "function" &&
+ optionInfo.exception(value)
+ ) {
+ return;
+ }
+
+ try {
+ validateOptionType(value, optionInfo);
+ } catch (error) {
+ throw new Error(
+ `Invalid \`${descriptor(optionInfo.name)}\` value. ${
+ error.message
+ }, but received \`${JSON.stringify(value)}\`.`
+ );
+ }
+}
+
+function validateOptionType(value, optionInfo) {
+ if (optionInfo.array) {
+ if (!Array.isArray(value)) {
+ throw new Error(`Expected an array`);
+ }
+ value.forEach(v =>
+ validateOptionType(v, Object.assign({}, optionInfo, { array: false }))
+ );
+ } else {
+ switch (optionInfo.type) {
+ case "int":
+ validateIntOption(value);
+ break;
+ case "boolean":
+ validateBooleanOption(value);
+ break;
+ case "choice":
+ validateChoiceOption(value, optionInfo.choices);
+ break;
+ }
+ }
+}
+
+function validateBooleanOption(value) {
+ if (typeof value !== "boolean") {
+ throw new Error(`Expected a boolean`);
+ }
+}
+
+function validateIntOption(value) {
+ if (
+ !(
+ typeof value === "number" &&
+ Math.floor(value) === value &&
+ value >= 0 &&
+ value !== Infinity
+ )
+ ) {
+ throw new Error(`Expected an integer`);
+ }
+}
+
+function validateChoiceOption(value, choiceInfos) {
+ if (!choiceInfos.some(choiceInfo => choiceInfo.value === value)) {
+ const choices = choiceInfos
+ .filter(choiceInfo => !choiceInfo.deprecated)
+ .map(choiceInfo => JSON.stringify(choiceInfo.value))
+ .sort();
+ const head = choices.slice(0, -2);
+ const tail = choices.slice(-2);
+ throw new Error(`Expected ${head.concat(tail.join(" or ")).join(", ")}`);
+ }
+}
+
+module.exports = { validateOption };
diff --git a/src/main/options.js b/src/main/options.js
index c52ceea5..da786dbd 100644
--- a/src/main/options.js
+++ b/src/main/options.js
@@ -1,139 +1,120 @@
"use strict";
const path = require("path");
-
-const validate = require("jest-validate").validate;
-const deprecatedConfig = require("./deprecated");
const getSupportInfo = require("../common/support").getSupportInfo;
+const normalizer = require("./options-normalizer");
const loadPlugins = require("../common/load-plugins");
const resolveParser = require("./parser").resolveParser;
-const getPrinter = require("./get-printer");
+const getPlugin = require("./get-plugin");
-const defaults = {
- cursorOffset: -1,
- rangeStart: 0,
- rangeEnd: Infinity,
- useTabs: false,
- tabWidth: 2,
- printWidth: 80,
- singleQuote: false,
- trailingComma: "none",
- bracketSpacing: true,
- jsxBracketSameLine: false,
- parser: "babylon",
- parentParser: "",
- insertPragma: false,
- requirePragma: false,
- semi: true,
- proseWrap: "preserve",
- arrowParens: "avoid",
- plugins: [],
+const hiddenDefaults = {
astFormat: "estree",
printer: {},
- __inJsTemplate: false
+ locStart: null,
+ locEnd: null
};
-const exampleConfig = Object.assign({}, defaults, {
- filepath: "path/to/Filename",
- printWidth: 80,
- originalText: "text"
-});
-
// Copy options and fill in default values.
-function normalize(options) {
- const normalized = Object.assign({}, options || {});
- const filepath = normalized.filepath;
+function normalize(options, opts) {
+ opts = opts || {};
- normalized.plugins = loadPlugins(normalized);
+ const rawOptions = Object.assign({}, options);
- if (
- filepath &&
- !normalized.parentParser &&
- (!normalized.parser || normalized.parser === defaults.parser)
- ) {
- const extension = path.extname(filepath);
- const filename = path.basename(filepath).toLowerCase();
+ const plugins = loadPlugins(rawOptions.plugins);
+ rawOptions.plugins = plugins;
- const language = getSupportInfo(null, normalized).languages.find(
- language =>
- typeof language.since === "string" &&
- (language.extensions.indexOf(extension) > -1 ||
- (language.filenames &&
- language.filenames.find(name => name.toLowerCase() === filename)))
- );
+ const supportOptions = getSupportInfo(null, {
+ plugins,
+ pluginsLoaded: true,
+ showUnreleased: true,
+ showDeprecated: true
+ }).options;
+ const defaults = supportOptions.reduce(
+ (reduced, optionInfo) =>
+ Object.assign(reduced, { [optionInfo.name]: optionInfo.default }),
+ Object.assign({}, hiddenDefaults)
+ );
- if (language) {
- normalized.parser = language.parsers[0];
+ if (opts.inferParser !== false) {
+ if (
+ rawOptions.filepath &&
+ (!rawOptions.parser || rawOptions.parser === defaults.parser)
+ ) {
+ const inferredParser = inferParser(
+ rawOptions.filepath,
+ rawOptions.plugins
+ );
+ if (inferredParser) {
+ rawOptions.parser = inferredParser;
+ }
}
}
- if (normalized.parser === "json") {
- normalized.trailingComma = "none";
- }
+ const parser = resolveParser(
+ !rawOptions.parser
+ ? rawOptions
+ : // handle deprecated parsers
+ normalizer.normalizeApiOptions(
+ rawOptions,
+ [supportOptions.find(x => x.name === "parser")],
+ { passThrough: true, logger: false }
+ )
+ );
+ rawOptions.astFormat = parser.astFormat;
+ rawOptions.locEnd = parser.locEnd;
+ rawOptions.locStart = parser.locStart;
- /* istanbul ignore if */
- if (typeof normalized.trailingComma === "boolean") {
- // Support a deprecated boolean type for the trailing comma config
- // for a few versions. This code can be removed later.
- normalized.trailingComma = "es5";
+ const plugin = getPlugin(rawOptions);
+ rawOptions.printer = plugin.printers[rawOptions.astFormat];
- // eslint-disable-next-line no-console
- console.warn(
- "Warning: `trailingComma` without any argument is deprecated. " +
- 'Specify "none", "es5", or "all".'
+ const pluginDefaults = supportOptions
+ .filter(
+ optionInfo =>
+ optionInfo.pluginDefaults && optionInfo.pluginDefaults[plugin.name]
+ )
+ .reduce(
+ (reduced, optionInfo) =>
+ Object.assign(reduced, {
+ [optionInfo.name]: optionInfo.pluginDefaults[plugin.name]
+ }),
+ {}
);
- }
- /* istanbul ignore if */
- if (typeof normalized.proseWrap === "boolean") {
- normalized.proseWrap = normalized.proseWrap ? "always" : "never";
+ const mixedDefaults = Object.assign({}, defaults, pluginDefaults);
- // eslint-disable-next-line no-console
- console.warn(
- "Warning: `proseWrap` with boolean value is deprecated. " +
- 'Use "always", "never", or "preserve" instead.'
- );
- }
-
- /* istanbul ignore if */
- if (normalized.parser === "postcss") {
- normalized.parser = "css";
-
- // eslint-disable-next-line no-console
- console.warn(
- 'Warning: `parser` with value "postcss" is deprecated. ' +
- 'Use "css", "less" or "scss" instead.'
- );
- }
-
- const parserBackup = normalized.parser;
- if (typeof normalized.parser === "function") {
- // Delete the function from the object to pass validation.
- delete normalized.parser;
- }
-
- validate(normalized, { exampleConfig, deprecatedConfig });
-
- // Restore the option back to a function;
- normalized.parser = parserBackup;
-
- // For backward compatibility. Deprecated in 0.0.10
- /* istanbul ignore if */
- if ("useFlowParser" in normalized) {
- normalized.parser = normalized.useFlowParser ? "flow" : "babylon";
- delete normalized.useFlowParser;
- }
-
- normalized.astFormat = resolveParser(normalized).astFormat;
- normalized.printer = getPrinter(normalized);
-
- Object.keys(defaults).forEach(k => {
- if (normalized[k] == null) {
- normalized[k] = defaults[k];
+ Object.keys(mixedDefaults).forEach(k => {
+ if (rawOptions[k] == null) {
+ rawOptions[k] = mixedDefaults[k];
}
});
- return normalized;
+ if (rawOptions.parser === "json") {
+ rawOptions.trailingComma = "none";
+ }
+
+ return normalizer.normalizeApiOptions(
+ rawOptions,
+ supportOptions,
+ Object.assign({ passThrough: Object.keys(hiddenDefaults) }, opts)
+ );
}
-module.exports = { normalize, defaults };
+function inferParser(filepath, plugins) {
+ const extension = path.extname(filepath);
+ const filename = path.basename(filepath).toLowerCase();
+
+ const language = getSupportInfo(null, {
+ plugins,
+ pluginsLoaded: true
+ }).languages.find(
+ language =>
+ language.since !== null &&
+ (language.extensions.indexOf(extension) > -1 ||
+ (language.filenames &&
+ language.filenames.find(name => name.toLowerCase() === filename)))
+ );
+
+ return language && language.parsers[0];
+}
+
+module.exports = { normalize, hiddenDefaults };
diff --git a/src/main/parser.js b/src/main/parser.js
index 6e04267a..8452be29 100644
--- a/src/main/parser.js
+++ b/src/main/parser.js
@@ -2,6 +2,10 @@
const path = require("path");
const ConfigError = require("../common/errors").ConfigError;
+const js = require("../language-js/index.js");
+
+const locStart = js.locStart;
+const locEnd = js.locEnd;
function getParsers(options) {
return options.plugins.reduce(
@@ -17,7 +21,9 @@ function resolveParser(opts, parsers) {
// Custom parser API always works with JavaScript.
return {
parse: opts.parser,
- astFormat: "estree"
+ astFormat: "estree",
+ locStart,
+ locEnd
};
}
@@ -28,7 +34,9 @@ function resolveParser(opts, parsers) {
try {
return {
parse: eval("require")(path.resolve(process.cwd(), opts.parser)),
- astFormat: "estree"
+ astFormat: "estree",
+ locStart,
+ locEnd
};
} catch (err) {
/* istanbul ignore next */
@@ -58,7 +66,14 @@ function parse(text, opts) {
const parser = resolveParser(opts, parsers);
try {
- return parser.parse(text, parsersForCustomParserApi, opts);
+ if (parser.preprocess) {
+ text = parser.preprocess(text, opts);
+ }
+
+ return {
+ text,
+ ast: parser.parse(text, parsersForCustomParserApi, opts)
+ };
} catch (error) {
const loc = error.loc;
diff --git a/tests/class_comment/__snapshots__/jsfmt.spec.js.snap b/tests/class_comment/__snapshots__/jsfmt.spec.js.snap
index 3216c3ee..ea348067 100644
--- a/tests/class_comment/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/class_comment/__snapshots__/jsfmt.spec.js.snap
@@ -51,8 +51,8 @@ export class SnapshotLogger {
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class A // comment 1
-// comment 2
-extends B {}
+ // comment 2
+ extends B {}
class A extends B // comment1
// comment2
@@ -66,8 +66,8 @@ class A extends B /* a */ {
class A /* a */ extends B {}
(class A // comment 1
-// comment 2
-extends B {});
+ // comment 2
+ extends B {});
(class A extends B // comment1
// comment2
@@ -91,8 +91,8 @@ class X {
TEMPLATE =
// tab index is needed so we can focus, which is needed for keyboard events
'
";
+ '
' +
+ "
";
}
export class SnapshotLogger {
diff --git a/tests/classes/__snapshots__/jsfmt.spec.js.snap b/tests/classes/__snapshots__/jsfmt.spec.js.snap
index 6c76fff6..9f419ee0 100644
--- a/tests/classes/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/classes/__snapshots__/jsfmt.spec.js.snap
@@ -50,11 +50,9 @@ export class VisTimelineComponent2
implements AfterViewInit, OnChanges, OnDestroy, AndSomethingReallyReallyLong {
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-class MyContractSelectionWidget extends React.Component<
- void,
- MyContractSelectionWidgetPropsType,
- void
-> implements SomethingLarge {
+class MyContractSelectionWidget
+ extends React.Component