fix(ng,vue): add parens to avoid unexpected `}}` in interpolations (#5657)

master
Ika 2018-12-30 23:03:42 +08:00 committed by GitHub
parent 7c4cebeaa5
commit 3de36e3a2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 2331 additions and 13 deletions

View File

@ -29,17 +29,18 @@ FastPath.prototype.getValue = function getValue() {
}; };
function getNodeHelper(path, count) { function getNodeHelper(path, count) {
const s = path.stack; const stackIndex = getNodeStackIndexHelper(path.stack, count);
return stackIndex === -1 ? null : path.stack[stackIndex];
for (let i = s.length - 1; i >= 0; i -= 2) { }
const value = s[i];
function getNodeStackIndexHelper(stack, count) {
for (let i = stack.length - 1; i >= 0; i -= 2) {
const value = stack[i];
if (value && !Array.isArray(value) && --count < 0) { if (value && !Array.isArray(value) && --count < 0) {
return value; return i;
} }
} }
return -1;
return null;
} }
FastPath.prototype.getNode = function getNode(count) { FastPath.prototype.getNode = function getNode(count) {
@ -70,6 +71,14 @@ FastPath.prototype.call = function call(callback /*, name1, name2, ... */) {
return result; return result;
}; };
FastPath.prototype.callParent = function callParent(callback, count) {
const stackIndex = getNodeStackIndexHelper(this.stack, ~~count + 1);
const parentValues = this.stack.splice(stackIndex + 1);
const result = callback(this);
Array.prototype.push.apply(this.stack, parentValues);
return result;
};
// Similar to FastPath.prototype.call, except that the value obtained by // Similar to FastPath.prototype.call, except that the value obtained by
// accessing this.getValue()[name1][name2]... should be array-like. The // accessing this.getValue()[name1][name2]... should be array-like. The
// callback will be called with a reference to this path object for each // callback will be called with a reference to this path object for each

View File

@ -85,11 +85,16 @@ function embed(path, print, textToDoc, options) {
line, line,
textToDoc( textToDoc(
node.value, node.value,
options.parser === "angular" Object.assign(
? { parser: "__ng_interpolation", trailingComma: "none" } {
: options.parser === "vue" __isInHtmlInterpolation: true // to avoid unexpected `}}`
? { parser: "__vue_expression" } },
: { parser: "__js_expression" } options.parser === "angular"
? { parser: "__ng_interpolation", trailingComma: "none" }
: options.parser === "vue"
? { parser: "__vue_expression" }
: { parser: "__js_expression" }
)
) )
]) ])
), ),
@ -1043,7 +1048,10 @@ function printEmbeddedAttributeValue(node, originalTextToDoc, options) {
indent( indent(
concat([ concat([
line, line,
ngTextToDoc(part, { parser: "__ng_interpolation" }) ngTextToDoc(part, {
parser: "__ng_interpolation",
__isInHtmlInterpolation: true // to avoid unexpected `}}`
})
]) ])
), ),
line, line,

View File

@ -57,6 +57,16 @@ function needsParens(path, options) {
return false; return false;
} }
// to avoid unexpected `}}` in HTML interpolations
if (
options.__isInHtmlInterpolation &&
!options.bracketSpacing &&
endsWithRightBracket(node) &&
isFollowedByRightBracket(path)
) {
return true;
}
// Only statements don't need parentheses. // Only statements don't need parentheses.
if (isStatement(node)) { if (isStatement(node)) {
return false; return false;
@ -688,4 +698,55 @@ function isStatement(node) {
); );
} }
function endsWithRightBracket(node) {
switch (node.type) {
case "ObjectExpression":
return true;
default:
return false;
}
}
function isFollowedByRightBracket(path) {
const node = path.getValue();
const parent = path.getParentNode();
const name = path.getName();
switch (parent.type) {
case "NGPipeExpression":
if (
typeof name === "number" &&
parent.arguments[name] === node &&
parent.arguments.length - 1 === name
) {
return path.callParent(isFollowedByRightBracket);
}
break;
case "ObjectProperty":
if (name === "value") {
const parentParent = path.getParentNode(1);
return (
parentParent.properties[parentParent.properties.length - 1] === parent
);
}
break;
case "BinaryExpression":
case "LogicalExpression":
if (name === "right") {
return path.callParent(isFollowedByRightBracket);
}
break;
case "ConditionalExpression":
if (name === "alternate") {
return path.callParent(isFollowedByRightBracket);
}
break;
case "UnaryExpression":
if (parent.prefix) {
return path.callParent(isFollowedByRightBracket);
}
break;
}
return false;
}
module.exports = needsParens; module.exports = needsParens;

File diff suppressed because it is too large Load Diff

View File

@ -54,3 +54,13 @@
{{ aNormalValue | aPipe }}: {{ aNormalValue | aPipe }}:
<strong>{{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}</strong> <strong>{{ aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis }}</strong>
</span> </span>
<p>
{{
'delete'
| translate: {what: ('entities' | translate: {count: array.length})}
}}
</p>
<p>{{ {a:1+{} } }}</p>
<p>{{ {a:a==={} } }}</p>
<p>{{ {a:!{} } }}</p>
<p>{{ {a:a?b:{} } }}</p>

View File

@ -2,3 +2,4 @@ run_spec(__dirname, ["angular"]);
run_spec(__dirname, ["angular"], { trailingComma: "es5" }); run_spec(__dirname, ["angular"], { trailingComma: "es5" });
run_spec(__dirname, ["angular"], { printWidth: 1 }); run_spec(__dirname, ["angular"], { printWidth: 1 });
run_spec(__dirname, ["angular"], { htmlWhitespaceSensitivity: "ignore" }); run_spec(__dirname, ["angular"], { htmlWhitespaceSensitivity: "ignore" });
run_spec(__dirname, ["angular"], { bracketSpacing: false });