Skip to content

Commit

Permalink
fix(vue): tweak semicolon for single expression in event bindings (#5519
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ikatyang committed Nov 29, 2018
1 parent 0af81c7 commit 0534735
Show file tree
Hide file tree
Showing 11 changed files with 987 additions and 71 deletions.
4 changes: 4 additions & 0 deletions src/common/internal-plugins.js
Expand Up @@ -33,6 +33,10 @@ module.exports = [
return eval("require")("../language-js/parser-babylon").parsers
.__vue_expression;
},
get __vue_event_binding() {
return eval("require")("../language-js/parser-babylon").parsers
.__vue_event_binding;
},
// JS - Flow
get flow() {
return eval("require")("../language-js/parser-flow").parsers.flow;
Expand Down
20 changes: 10 additions & 10 deletions src/language-html/printer-html.js
Expand Up @@ -41,7 +41,11 @@ const {
const preprocess = require("./preprocess");
const assert = require("assert");
const { insertPragma } = require("./pragma");
const { printVueFor, printVueSlotScope } = require("./syntax-vue");
const {
printVueFor,
printVueSlotScope,
isVueEventBindingExpression
} = require("./syntax-vue");
const { printImgSrcset } = require("./syntax-attribute");

function concat(parts) {
Expand Down Expand Up @@ -942,17 +946,13 @@ function printEmbeddedAttributeValue(node, originalTextToDoc, options) {
const jsExpressionBindingPatterns = ["^v-"];

if (isKeyMatched(vueEventBindingPatterns)) {
// copied from https://github.com/vuejs/vue/blob/v2.5.17/src/compiler/codegen/events.js#L3-L4
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;

const value = getValue()
// https://github.com/vuejs/vue/blob/v2.5.17/src/compiler/helpers.js#L104
.trim();
const value = getValue();
return printMaybeHug(
simplePathRE.test(value) || fnExpRE.test(value)
isVueEventBindingExpression(value)
? textToDoc(value, { parser: "__js_expression" })
: stripTrailingHardline(textToDoc(value, { parser: "babylon" }))
: stripTrailingHardline(
textToDoc(value, { parser: "__vue_event_binding" })
)
);
}

Expand Down
14 changes: 14 additions & 0 deletions src/language-html/syntax-vue.js
Expand Up @@ -66,7 +66,21 @@ function printVueSlotScope(value, textToDoc) {
});
}

function isVueEventBindingExpression(eventBindingValue) {
// https://github.com/vuejs/vue/blob/v2.5.17/src/compiler/codegen/events.js#L3-L4
// arrow function or anonymous function
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
// simple member expression chain (a, a.b, a['b'], a["b"], a[0], a[b])
const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;

// https://github.com/vuejs/vue/blob/v2.5.17/src/compiler/helpers.js#L104
const value = eventBindingValue.trim();

return fnExpRE.test(value) || simplePathRE.test(value);
}

module.exports = {
isVueEventBindingExpression,
printVueFor,
printVueSlotScope
};
19 changes: 19 additions & 0 deletions src/language-js/html-binding.js
Expand Up @@ -45,6 +45,25 @@ function printHtmlBinding(path, options, print) {
}
}

// based on https://github.com/prettier/prettier/blob/master/src/language-html/syntax-vue.js isVueEventBindingExpression()
function isVueEventBindingExpression(node) {
switch (node.type) {
case "MemberExpression":
switch (node.property.type) {
case "Identifier":
case "NumericLiteral":
case "StringLiteral":
return isVueEventBindingExpression(node.object);
}
return false;
case "Identifier":
return true;
default:
return false;
}
}

module.exports = {
isVueEventBindingExpression,
printHtmlBinding
};
86 changes: 48 additions & 38 deletions src/language-js/parser-babylon.js
Expand Up @@ -41,44 +41,49 @@ function babylonOptions(extraOptions, extraPlugins) {
);
}

function parse(text, parsers, opts) {
// Inline the require to avoid loading all the JS if we don't use it
const babylon = require("@babel/parser");
function createParse(parseMethod) {
return (text, parsers, opts) => {
// Inline the require to avoid loading all the JS if we don't use it
const babylon = require("@babel/parser");

const combinations = [
babylonOptions({ strictMode: true }, ["decorators-legacy"]),
babylonOptions({ strictMode: false }, ["decorators-legacy"]),
babylonOptions({ strictMode: true }, [
["decorators", { decoratorsBeforeExport: false }]
]),
babylonOptions({ strictMode: false }, [
["decorators", { decoratorsBeforeExport: false }]
])
];
const combinations = [
babylonOptions({ strictMode: true }, ["decorators-legacy"]),
babylonOptions({ strictMode: false }, ["decorators-legacy"]),
babylonOptions({ strictMode: true }, [
["decorators", { decoratorsBeforeExport: false }]
]),
babylonOptions({ strictMode: false }, [
["decorators", { decoratorsBeforeExport: false }]
])
];

const parseMethod =
!opts || opts.parser === "babylon" ? "parse" : "parseExpression";

let ast;
try {
ast = tryCombinations(babylon[parseMethod].bind(null, text), combinations);
} catch (error) {
throw createError(
// babel error prints (l:c) with cols that are zero indexed
// so we need our custom error
error.message.replace(/ \(.*\)/, ""),
{
start: {
line: error.loc.line,
column: error.loc.column + 1
let ast;
try {
ast = tryCombinations(
babylon[parseMethod].bind(null, text),
combinations
);
} catch (error) {
throw createError(
// babel error prints (l:c) with cols that are zero indexed
// so we need our custom error
error.message.replace(/ \(.*\)/, ""),
{
start: {
line: error.loc.line,
column: error.loc.column + 1
}
}
}
);
}
delete ast.tokens;
return postprocess(ast, Object.assign({}, opts, { originalText: text }));
);
}
delete ast.tokens;
return postprocess(ast, Object.assign({}, opts, { originalText: text }));
};
}

const parse = createParse("parse");
const parseExpression = createParse("parseExpression");

function tryCombinations(fn, combinations) {
let error;
for (let i = 0; i < combinations.length; i++) {
Expand All @@ -94,7 +99,7 @@ function tryCombinations(fn, combinations) {
}

function parseJson(text, parsers, opts) {
const ast = parse(text, parsers, Object.assign({}, opts, { parser: "json" }));
const ast = parseExpression(text, parsers, opts);

ast.comments.forEach(assertJsonNode);
assertJsonNode(ast);
Expand Down Expand Up @@ -164,17 +169,20 @@ const babylon = Object.assign(
{ parse, astFormat: "estree", hasPragma },
locFns
);
const babylonExpression = Object.assign({}, babylon, {
parse: parseExpression
});

// Export as a plugin so we can reuse the same bundle for UMD loading
module.exports = {
parsers: {
babylon,
json: Object.assign({}, babylon, {
json: Object.assign({}, babylonExpression, {
hasPragma() {
return true;
}
}),
json5: babylon,
json5: babylonExpression,
"json-stringify": Object.assign(
{
parse: parseJson,
Expand All @@ -183,8 +191,10 @@ module.exports = {
locFns
),
/** @internal */
__js_expression: babylon,
__js_expression: babylonExpression,
/** for vue filter */
__vue_expression: babylon
__vue_expression: babylonExpression,
/** for vue event binding to handle semicolon */
__vue_event_binding: babylon
}
};
20 changes: 19 additions & 1 deletion src/language-js/printer-estree.js
Expand Up @@ -34,7 +34,10 @@ const clean = require("./clean");
const insertPragma = require("./pragma").insertPragma;
const handleComments = require("./comments");
const pathNeedsParens = require("./needs-parens");
const { printHtmlBinding } = require("./html-binding");
const {
printHtmlBinding,
isVueEventBindingExpression
} = require("./html-binding");
const preprocess = require("./preprocess");
const {
hasNode,
Expand Down Expand Up @@ -494,6 +497,21 @@ function printPathNoParens(path, options, print, args) {
if (n.directive) {
return concat([nodeStr(n.expression, options, true), semi]);
}

if (options.parser === "__vue_event_binding") {
const parent = path.getParentNode();
if (
parent.type === "Program" &&
parent.body.length === 1 &&
parent.body[0] === n
) {
return concat([
path.call(print, "expression"),
isVueEventBindingExpression(n.expression) ? ";" : ""
]);
}
}

// Do not append semicolon after the only JSX element in a program
return concat([
path.call(print, "expression"),
Expand Down

0 comments on commit 0534735

Please sign in to comment.