Vue: pretty-print binding syntax (#2108)
* feat(vue): pretty-print :class attributes
* feat(vue): switch to brand new custom parser API! 🎉
* refactor(vue): move AST manipulation to post-parse
master
parent
9b772cb4fd
commit
b7e1c366e0
|
@ -152,11 +152,27 @@ function propagateBreaks(doc) {
|
|||
);
|
||||
}
|
||||
|
||||
function removeLines(doc) {
|
||||
// Force this doc into flat mode by statically converting all
|
||||
// lines into spaces (or soft lines into nothing). Hard lines
|
||||
// should still output because there's too great of a chance
|
||||
// of breaking existing assumptions otherwise.
|
||||
return mapDoc(doc, d => {
|
||||
if (d.type === "line" && !d.hard) {
|
||||
return d.soft ? "" : " ";
|
||||
} else if (d.type === "if-break") {
|
||||
return d.flatContents || "";
|
||||
}
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isEmpty,
|
||||
willBreak,
|
||||
isLineNext,
|
||||
traverseDoc,
|
||||
mapDoc,
|
||||
propagateBreaks
|
||||
propagateBreaks,
|
||||
removeLines
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
const util = require("./util");
|
||||
const mapDoc = require("./doc-utils").mapDoc;
|
||||
const docUtils = require("./doc-utils");
|
||||
const docBuilders = require("./doc-builders");
|
||||
const indent = docBuilders.indent;
|
||||
const hardline = docBuilders.hardline;
|
||||
|
@ -64,9 +64,6 @@ function fromBabylonFlowOrTypeScript(path) {
|
|||
parentParent &&
|
||||
parentParent.type === "TaggedTemplateExpression" &&
|
||||
parent.quasis.length === 1 &&
|
||||
// ((parentParent.tag.type === "MemberExpression" &&
|
||||
// parentParent.tag.object.name === "Relay" &&
|
||||
// parentParent.tag.property.name === "QL") ||
|
||||
((parentParent.tag.type === "MemberExpression" &&
|
||||
parentParent.tag.object.name === "graphql" &&
|
||||
parentParent.tag.property.name === "experimental") ||
|
||||
|
@ -132,6 +129,37 @@ function fromHtmlParser2(path, options) {
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
case "attribute": {
|
||||
/*
|
||||
* Vue binding sytax: JS expressions
|
||||
* :class="{ 'some-key': value }"
|
||||
* v-bind:id="'list-' + id"
|
||||
* v-if="foo && !bar"
|
||||
* @click="someFunction()"
|
||||
*/
|
||||
if (/(^@)|(^v-)|:/.test(node.key) && !/^\w+$/.test(node.value)) {
|
||||
return {
|
||||
text: node.value,
|
||||
options: {
|
||||
parser: parseJavaScriptExpression,
|
||||
// Use singleQuote since HTML attributes use double-quotes.
|
||||
// TODO(azz): We still need to do an entity escape on the attribute.
|
||||
singleQuote: true
|
||||
},
|
||||
transformDoc: doc => {
|
||||
return concat([
|
||||
node.key,
|
||||
'="',
|
||||
util.hasNewlineInRange(node.value, 0, node.value.length)
|
||||
? doc
|
||||
: docUtils.removeLines(doc),
|
||||
'"'
|
||||
]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +186,7 @@ function replacePlaceholders(quasisDoc, expressionDocs) {
|
|||
}
|
||||
|
||||
const expressions = expressionDocs.slice();
|
||||
const newDoc = mapDoc(quasisDoc, doc => {
|
||||
const newDoc = docUtils.mapDoc(quasisDoc, doc => {
|
||||
if (!doc || !doc.parts || !doc.parts.length) {
|
||||
return doc;
|
||||
}
|
||||
|
@ -197,6 +225,16 @@ function replacePlaceholders(quasisDoc, expressionDocs) {
|
|||
return expressions.length === 0 ? newDoc : null;
|
||||
}
|
||||
|
||||
function parseJavaScriptExpression(text, parsers) {
|
||||
// Force parsing as an expression
|
||||
const ast = parsers.babylon(`(${text})`);
|
||||
// Extract expression from the declaration
|
||||
return {
|
||||
type: "File",
|
||||
program: ast.program.body[0].expression
|
||||
};
|
||||
}
|
||||
|
||||
function getText(options, node) {
|
||||
return options.originalText.slice(util.locStart(node), util.locEnd(node));
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ function parse(text) {
|
|||
treeAdapter: parse5.treeAdapters.htmlparser2,
|
||||
locationInfo: true
|
||||
});
|
||||
return ast;
|
||||
return extendAst(ast);
|
||||
} catch (error) {
|
||||
// throw createError(error.message, {
|
||||
// start: {
|
||||
|
@ -24,4 +24,27 @@ function parse(text) {
|
|||
}
|
||||
}
|
||||
|
||||
function extendAst(ast) {
|
||||
if (!ast || !ast.children) {
|
||||
return ast;
|
||||
}
|
||||
for (const child of ast.children) {
|
||||
extendAst(child);
|
||||
if (child.attribs) {
|
||||
child.attributes = convertAttribs(child.attribs);
|
||||
}
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
function convertAttribs(attribs) {
|
||||
return Object.keys(attribs).map(attributeKey => {
|
||||
return {
|
||||
type: "attribute",
|
||||
key: attributeKey,
|
||||
value: attribs[attributeKey]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parse;
|
||||
|
|
|
@ -5,7 +5,7 @@ const docBuilders = require("./doc-builders");
|
|||
const concat = docBuilders.concat;
|
||||
const join = docBuilders.join;
|
||||
const hardline = docBuilders.hardline;
|
||||
// const line = docBuilders.line;
|
||||
const line = docBuilders.line;
|
||||
const softline = docBuilders.softline;
|
||||
const group = docBuilders.group;
|
||||
const indent = docBuilders.indent;
|
||||
|
@ -41,7 +41,7 @@ function genericPrint(path, options, print) {
|
|||
|
||||
switch (n.type) {
|
||||
case "root": {
|
||||
return concat(path.map(print, "children"));
|
||||
return printChildren(path, print);
|
||||
}
|
||||
case "directive": {
|
||||
return concat(["<", n.data, ">", hardline]);
|
||||
|
@ -53,15 +53,7 @@ function genericPrint(path, options, print) {
|
|||
case "style":
|
||||
case "tag": {
|
||||
const selfClose = voidTags[n.name] ? ">" : " />";
|
||||
|
||||
const children = [];
|
||||
path.each(childPath => {
|
||||
const child = childPath.getValue();
|
||||
if (child.type !== "text") {
|
||||
children.push(softline);
|
||||
}
|
||||
children.push(childPath.call(print));
|
||||
}, "children");
|
||||
const children = printChildren(path, print);
|
||||
|
||||
const hasNewline = util.hasNewlineInRange(
|
||||
options.originalText,
|
||||
|
@ -74,39 +66,51 @@ function genericPrint(path, options, print) {
|
|||
hasNewline ? hardline : "",
|
||||
"<",
|
||||
n.name,
|
||||
printAttributes(n.attribs),
|
||||
printAttributes(path, print),
|
||||
|
||||
n.children.length ? ">" : selfClose,
|
||||
|
||||
n.name.toLowerCase() === "html"
|
||||
? concat(children)
|
||||
: indent(concat(children)),
|
||||
n.children.length ? concat([softline, "</", n.name, ">"]) : ""
|
||||
? concat([hardline, children])
|
||||
: indent(children),
|
||||
n.children.length ? concat([softline, "</", n.name, ">"]) : hardline
|
||||
])
|
||||
);
|
||||
}
|
||||
case "comment": {
|
||||
return concat(["<!-- ", n.data.trim(), " -->"]);
|
||||
}
|
||||
case "attribute": {
|
||||
if (!n.value) {
|
||||
return n.key;
|
||||
}
|
||||
return concat([n.key, '="', n.value, '"']);
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error("unknown htmlparser2 type: " + n.type);
|
||||
}
|
||||
}
|
||||
|
||||
function printAttributes(attribs) {
|
||||
const attributeKeys = Object.keys(attribs);
|
||||
function printAttributes(path, print) {
|
||||
const node = path.getValue();
|
||||
|
||||
return concat([
|
||||
attributeKeys.length ? " " : "",
|
||||
join(
|
||||
" ",
|
||||
attributeKeys.map(name => {
|
||||
if (attribs[name] === "") {
|
||||
return name;
|
||||
}
|
||||
return concat([name, '="', attribs[name], '"']);
|
||||
})
|
||||
)
|
||||
node.attributes.length ? " " : "",
|
||||
indent(join(line, path.map(print, "attributes")))
|
||||
]);
|
||||
}
|
||||
|
||||
function printChildren(path, print) {
|
||||
const children = [];
|
||||
path.each(childPath => {
|
||||
const child = childPath.getValue();
|
||||
if (child.type !== "text") {
|
||||
children.push(hardline);
|
||||
}
|
||||
children.push(childPath.call(print));
|
||||
}, "children");
|
||||
return concat(children);
|
||||
}
|
||||
|
||||
module.exports = genericPrint;
|
||||
|
|
|
@ -2959,9 +2959,9 @@ function printFunctionParams(path, print, options, expandArg, printTypeParams) {
|
|||
if (expandArg) {
|
||||
return group(
|
||||
concat([
|
||||
removeLines(typeParams),
|
||||
docUtils.removeLines(typeParams),
|
||||
"(",
|
||||
join(", ", printed.map(removeLines)),
|
||||
join(", ", printed.map(docUtils.removeLines)),
|
||||
")"
|
||||
])
|
||||
);
|
||||
|
@ -4572,21 +4572,6 @@ function isStringLiteral(node) {
|
|||
);
|
||||
}
|
||||
|
||||
function removeLines(doc) {
|
||||
// Force this doc into flat mode by statically converting all
|
||||
// lines into spaces (or soft lines into nothing). Hard lines
|
||||
// should still output because there's too great of a chance
|
||||
// of breaking existing assumptions otherwise.
|
||||
return docUtils.mapDoc(doc, d => {
|
||||
if (d.type === "line" && !d.hard) {
|
||||
return d.soft ? "" : " ";
|
||||
} else if (d.type === "if-break") {
|
||||
return d.flatContents || "";
|
||||
}
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
function isObjectType(n) {
|
||||
return n.type === "ObjectTypeAnnotation" || n.type === "TSTypeLiteral";
|
||||
}
|
||||
|
@ -4621,7 +4606,7 @@ function printAstToDoc(ast, options, addAlignmentSize) {
|
|||
// Add a hardline to make the indents take effect
|
||||
// It should be removed in index.js format()
|
||||
doc = addAlignmentToDoc(
|
||||
removeLines(concat([hardline, doc])),
|
||||
docUtils.removeLines(concat([hardline, doc])),
|
||||
addAlignmentSize,
|
||||
options.tabWidth
|
||||
);
|
||||
|
|
|
@ -16,14 +16,22 @@ exports[`hello-world.html 1`] = `
|
|||
</html>
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
|
||||
<html lang="en">
|
||||
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<meta http-equiv="X-UA-Compatible"
|
||||
content="ie=edge">
|
||||
|
||||
<title>Document</title>
|
||||
</head>
|
||||
|
||||
|
@ -39,5 +47,8 @@ exports[`html-fragment.html 1`] = `
|
|||
|
||||
<textarea>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
<a href="#">Link</a><textarea />
|
||||
|
||||
<a href="#">Link</a>
|
||||
<textarea />
|
||||
|
||||
`;
|
||||
|
|
|
@ -14,10 +14,13 @@ exports[`html-with-css-style.html 1`] = `
|
|||
</html>
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
|
||||
<html lang="en">
|
||||
|
||||
|
||||
<head>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -13,10 +13,13 @@ exports[`html-with-js-script.html 1`] = `
|
|||
</html>
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
|
||||
<html lang="en">
|
||||
|
||||
|
||||
<head>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
|
|
@ -22,10 +22,13 @@ exports[`html-with-ts-script.html 1`] = `
|
|||
</html>
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
|
||||
<html lang="en">
|
||||
|
||||
|
||||
<head>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,5 +1,55 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`template-bind.vue 1`] = `
|
||||
<template>
|
||||
<div v-bind:id=" 'list-' + id "></div>
|
||||
<div v-bind:id=" rawId | formatId "></div>
|
||||
<div v-bind:id=" ok ? 'YES' : 'NO' "></div>
|
||||
<button @click=" foo ( arg, 'string' ) "></button>
|
||||
</template>
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
<template>
|
||||
|
||||
<div v-bind:id="'list-' + id" />
|
||||
|
||||
<div v-bind:id="rawId | formatId" />
|
||||
|
||||
<div v-bind:id="ok ? 'YES' : 'NO'" />
|
||||
|
||||
<button @click="foo(arg, 'string')" />
|
||||
|
||||
</template>
|
||||
`;
|
||||
|
||||
exports[`template-class.vue 1`] = `
|
||||
<template>
|
||||
<h2
|
||||
class="title"
|
||||
:class="{ 'issue-realtime-pre-pulse': preAnimation,
|
||||
'issue-realtime-trigger-pulse': pulseAnimation}"
|
||||
v-html="titleHtml"
|
||||
>
|
||||
</h2>
|
||||
</template>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
<template>
|
||||
|
||||
|
||||
<h2 class="title"
|
||||
:class="{
|
||||
'issue-realtime-pre-pulse': preAnimation,
|
||||
'issue-realtime-trigger-pulse': pulseAnimation
|
||||
}"
|
||||
v-html="titleHtml">
|
||||
</h2>
|
||||
</template>
|
||||
`;
|
||||
|
||||
exports[`vue-component.vue 1`] = `
|
||||
<template >
|
||||
<h1 >{{greeting}} world</h1 >
|
||||
|
@ -19,9 +69,12 @@ p { font-size : 2em ; text-align : center ; }
|
|||
</style >
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
<template>
|
||||
|
||||
<h1>{{greeting}} world</h1>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
data: function() {
|
||||
|
@ -32,6 +85,7 @@ p { font-size : 2em ; text-align : center ; }
|
|||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 2em;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<div v-bind:id=" 'list-' + id "></div>
|
||||
<div v-bind:id=" rawId | formatId "></div>
|
||||
<div v-bind:id=" ok ? 'YES' : 'NO' "></div>
|
||||
<button @click=" foo ( arg, 'string' ) "></button>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<h2
|
||||
class="title"
|
||||
:class="{ 'issue-realtime-pre-pulse': preAnimation,
|
||||
'issue-realtime-trigger-pulse': pulseAnimation}"
|
||||
v-html="titleHtml"
|
||||
>
|
||||
</h2>
|
||||
</template>
|
Loading…
Reference in New Issue