Support formatting for Angular ICU expression (#15777)
Co-authored-by: fisker <lionkay@gmail.com>
This commit is contained in:
1 parent
23b3ca2f52
commit
153ffd5104
21 files changed
+370
-9
No files matched your search
@@ -0,0 +1,19 @@
|
||||
#### Support formatting for Angular ICU expression (#15777 by @sosukesuzuki)
|
||||
|
||||
Support two kinds of Angular ICU expressions: [`plural`](https://angular.dev/guide/i18n/translation-files#translate-plurals) and [`select`](https://angular.dev/guide/i18n/translation-files#translate-alternate-expressions).
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
<span i18n>
|
||||
Updated:
|
||||
{minutes, plural,
|
||||
=0 {just now}
|
||||
=1 {one minute ago}
|
||||
other {{{minutes}} minutes ago}
|
||||
}
|
||||
</span>
|
||||
|
||||
<span i18n>
|
||||
The author is {gender, select, male {male} female {female} other {other}}
|
||||
</span>
|
||||
```
|
||||
+1
-1
@@ -43,7 +43,7 @@
|
||||
"acorn": "8.11.2",
|
||||
"acorn-jsx": "5.3.2",
|
||||
"angular-estree-parser": "9.0.0",
|
||||
"angular-html-parser": "5.0.2",
|
||||
"angular-html-parser": "5.2.0",
|
||||
"camelcase": "8.0.0",
|
||||
"chalk": "5.3.0",
|
||||
"ci-info": "4.0.0",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const NODES_KEYS = {
|
||||
attrs: true,
|
||||
children: true,
|
||||
cases: true, // plural and select
|
||||
expression: true, // for expansionCase
|
||||
};
|
||||
|
||||
const NON_ENUMERABLE_PROPERTIES = new Set(["parent"]);
|
||||
@@ -136,12 +138,12 @@ class Node {
|
||||
|
||||
get prev() {
|
||||
// @ts-expect-error
|
||||
return this.parent?.children[this.parent.children.indexOf(this) - 1];
|
||||
return this.parent?.children?.[this.parent.children.indexOf(this) - 1];
|
||||
}
|
||||
|
||||
get next() {
|
||||
// @ts-expect-error
|
||||
return this.parent?.children[this.parent.children.indexOf(this) + 1];
|
||||
return this.parent?.children?.[this.parent.children.indexOf(this) + 1];
|
||||
}
|
||||
|
||||
// for element and attribute
|
||||
|
||||
@@ -10,6 +10,9 @@ const ignoredProperties = new Set([
|
||||
"tagDefinition",
|
||||
"tokens",
|
||||
"valueTokens",
|
||||
"switchValueSourceSpan",
|
||||
"expSourceSpan",
|
||||
"valueSourceSpan",
|
||||
]);
|
||||
|
||||
const embeddedAngularControlFlowBlocks = new Set([
|
||||
@@ -47,6 +50,10 @@ function clean(ast, newNode) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ast.type === "angularIcuExpression") {
|
||||
newNode.switchValue = ast.switchValue.trim();
|
||||
}
|
||||
}
|
||||
|
||||
clean.ignoredProperties = ignoredProperties;
|
||||
|
||||
@@ -67,6 +67,16 @@ function normalizeAngularControlFlowBlock(node) {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeAngularIcuExpression(node) {
|
||||
if (node.type === "plural" || node.type === "select") {
|
||||
node.clause = node.type;
|
||||
node.type = "angularIcuExpression";
|
||||
}
|
||||
if (node.type === "expansionCase") {
|
||||
node.type = "angularIcuCase";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {ParseOptions} parseOptions
|
||||
@@ -261,6 +271,16 @@ function ngHtmlParser(input, parseOptions, options) {
|
||||
|
||||
visitAll(
|
||||
new (class extends RecursiveVisitor {
|
||||
// Angular does not visit to the children of expansionCase
|
||||
// https://github.com/angular/angular/blob/e3a6bf9b6c3bef03df9bfc8f05b817bc875cbad6/packages/compiler/src/ml_parser/ast.ts#L161
|
||||
visitExpansionCase(ast, context) {
|
||||
if (name === "angular") {
|
||||
// @ts-expect-error
|
||||
this.visitChildren(context, (visit) => {
|
||||
visit(ast.expression);
|
||||
});
|
||||
}
|
||||
}
|
||||
visit(node) {
|
||||
restoreNameAndValue(node);
|
||||
addTagDefinition(node);
|
||||
@@ -374,6 +394,7 @@ function parse(
|
||||
}
|
||||
|
||||
normalizeAngularControlFlowBlock(node);
|
||||
normalizeAngularIcuExpression(node);
|
||||
});
|
||||
|
||||
return ast;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
softline,
|
||||
line,
|
||||
indent,
|
||||
join,
|
||||
group,
|
||||
} from "../../document/builders.js";
|
||||
import htmlWhitespaceUtils from "../../utils/html-whitespace-utils.js";
|
||||
import { printClosingTagEnd, printOpeningTagStart } from "./tag.js";
|
||||
|
||||
/*
|
||||
<span i18n>
|
||||
Updated {minutes, plural,
|
||||
=0 {just now}
|
||||
=1 {one minute ago}
|
||||
other {{{minutes}} minutes ago}
|
||||
}
|
||||
</span>
|
||||
*/
|
||||
|
||||
function printAngularIcuExpression(path, options, print) {
|
||||
const { node } = path;
|
||||
return [
|
||||
printOpeningTagStart(node, options),
|
||||
group([
|
||||
node.switchValue.trim(),
|
||||
", ",
|
||||
node.clause,
|
||||
node.cases.length > 0
|
||||
? [",", indent([line, join(line, path.map(print, "cases"))])]
|
||||
: "",
|
||||
softline,
|
||||
]),
|
||||
printClosingTagEnd(node, options),
|
||||
];
|
||||
}
|
||||
|
||||
function printAngularIcuCase(path, options, print) {
|
||||
const { node } = path;
|
||||
return [
|
||||
node.value,
|
||||
" {",
|
||||
group([
|
||||
indent([
|
||||
softline,
|
||||
path.map(({ node }) => {
|
||||
// FIXME: We need a fundamental solution.
|
||||
// Like this: https://github.com/prettier/prettier/blob/c1ebae2e8303841e59bc02e9e719a93e964ab0e9/src/language-html/print-preprocess.js#L254
|
||||
if (node.type === "text" && !htmlWhitespaceUtils.trim(node.value)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return print();
|
||||
}, "expression"),
|
||||
]),
|
||||
softline,
|
||||
]),
|
||||
"}",
|
||||
];
|
||||
}
|
||||
|
||||
export { printAngularIcuExpression, printAngularIcuCase };
|
||||
@@ -60,7 +60,8 @@ function printElement(path, options, print) {
|
||||
*/
|
||||
const shouldHugContent =
|
||||
node.children.length === 1 &&
|
||||
node.firstChild.type === "interpolation" &&
|
||||
(node.firstChild.type === "interpolation" ||
|
||||
node.firstChild.type === "angularIcuExpression") &&
|
||||
node.firstChild.isLeadingSpaceSensitive &&
|
||||
!node.firstChild.hasLeadingSpaces &&
|
||||
node.lastChild.isTrailingSpaceSensitive &&
|
||||
|
||||
@@ -97,6 +97,8 @@ function printClosingTagEndMarker(node, options) {
|
||||
return "]><!-->";
|
||||
case "interpolation":
|
||||
return "}}";
|
||||
case "angularIcuExpression":
|
||||
return "}";
|
||||
case "element":
|
||||
if (node.isSelfClosing) {
|
||||
return "/>";
|
||||
@@ -345,6 +347,8 @@ function printOpeningTagStartMarker(node) {
|
||||
return "{{";
|
||||
case "docType":
|
||||
return node.value === "html" ? "<!doctype" : "<!DOCTYPE";
|
||||
case "angularIcuExpression":
|
||||
return "{";
|
||||
case "element":
|
||||
if (node.condition) {
|
||||
return `<!--[if ${node.condition}]><!--><${node.rawName}`;
|
||||
|
||||
@@ -26,6 +26,10 @@ import {
|
||||
printAngularControlFlowBlockParameters,
|
||||
} from "./print/angular-control-flow-block.js";
|
||||
import getVisitorKeys from "./get-visitor-keys.js";
|
||||
import {
|
||||
printAngularIcuExpression,
|
||||
printAngularIcuCase,
|
||||
} from "./print/angular-icu-expression.js";
|
||||
|
||||
function genericPrint(path, options, print) {
|
||||
const { node } = path;
|
||||
@@ -49,6 +53,11 @@ function genericPrint(path, options, print) {
|
||||
case "angularControlFlowBlockParameter":
|
||||
return htmlWhitespaceUtils.trim(node.expression);
|
||||
|
||||
case "angularIcuExpression":
|
||||
return printAngularIcuExpression(path, options, print);
|
||||
case "angularIcuCase":
|
||||
return printAngularIcuCase(path, options, print);
|
||||
|
||||
case "ieConditionalStartComment":
|
||||
case "ieConditionalEndComment":
|
||||
return [printOpeningTagStart(node), printClosingTagEnd(node)];
|
||||
|
||||
@@ -14,6 +14,8 @@ const visitorKeys = {
|
||||
angularControlFlowBlock: ["children", "parameters"],
|
||||
angularControlFlowBlockParameters: ["children"],
|
||||
angularControlFlowBlockParameter: [],
|
||||
angularIcuExpression: ["cases"],
|
||||
angularIcuCase: ["expression"],
|
||||
};
|
||||
|
||||
export default visitorKeys;
|
||||
@@ -0,0 +1,198 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`basic-plural-01.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["angular"]
|
||||
printWidth: 80
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
<span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
|
||||
|
||||
=====================================output=====================================
|
||||
<span i18n
|
||||
>Updated
|
||||
{minutes, plural,
|
||||
=0 {just now}
|
||||
=1 {one minute ago}
|
||||
other {{{minutes}} minutes ago}
|
||||
|
||||
}</span>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`basic-plural-02.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["angular"]
|
||||
printWidth: 80
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
{ count, plural,
|
||||
=0 {{ sex, select,
|
||||
other {<p>deeply nested</p>}
|
||||
}}
|
||||
=other {a lot}
|
||||
}
|
||||
|
||||
{ count123, plural,
|
||||
=0 {{ sex, select,
|
||||
other {<p>deeply nested</p>}
|
||||
}}
|
||||
=other {a lot}
|
||||
}
|
||||
|
||||
=====================================output=====================================
|
||||
{count, plural, =0 {{sex, select, other {<p>deeply nested</p>}}} =other {a lot}}
|
||||
|
||||
{count123, plural,
|
||||
=0 {{sex, select, other {<p>deeply nested</p>}}}
|
||||
=other {a lot}
|
||||
}
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`basic-plural-03.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["angular"]
|
||||
printWidth: 80
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
<foo>
|
||||
<bar>{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>">beaucoup</pc>} }</bar>
|
||||
<baz>{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>">many</pc>} }</baz>
|
||||
</foo>
|
||||
|
||||
=====================================output=====================================
|
||||
<foo>
|
||||
<bar>{VAR_PLURAL, plural,
|
||||
=0 {zero}
|
||||
=1 {un}
|
||||
=2 {deux}
|
||||
other {
|
||||
<pc
|
||||
id="0"
|
||||
equivStart="START_BOLD_TEXT"
|
||||
equivEnd="CLOSE_BOLD_TEXT"
|
||||
type="fmt"
|
||||
dispStart="<b>"
|
||||
dispEnd="</b>"
|
||||
>beaucoup</pc
|
||||
>
|
||||
}
|
||||
}</bar>
|
||||
<baz>{VAR_PLURAL, plural,
|
||||
=0 {zero}
|
||||
=1 {one}
|
||||
=2 {two}
|
||||
other {
|
||||
<pc
|
||||
id="0"
|
||||
equivStart="START_BOLD_TEXT"
|
||||
equivEnd="CLOSE_BOLD_TEXT"
|
||||
type="fmt"
|
||||
dispStart="<b>"
|
||||
dispEnd="</b>"
|
||||
>many</pc
|
||||
>
|
||||
}
|
||||
}</baz>
|
||||
</foo>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`basic-plural-04.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["angular"]
|
||||
printWidth: 80
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
<foo>
|
||||
<bar>{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph id="0" equiv="INTERPOLATION" disp="{{response.getItemsList().length}}"/> results} }</bar>
|
||||
</foo>
|
||||
|
||||
=====================================output=====================================
|
||||
<foo>
|
||||
<bar>{VAR_PLURAL, plural,
|
||||
=0 {Found no results}
|
||||
=1 {Found one result}
|
||||
other {
|
||||
Found <ph
|
||||
id="0"
|
||||
equiv="INTERPOLATION"
|
||||
disp="{{ response.getItemsList().length }}"
|
||||
/> results
|
||||
}
|
||||
}</bar>
|
||||
</foo>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`basic-select-01.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["angular"]
|
||||
printWidth: 80
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
|
||||
|
||||
=====================================output=====================================
|
||||
<span i18n
|
||||
>The author is {gender, select, male {male} female {female} other {other}
|
||||
}</span>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`basic-select-02.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["angular"]
|
||||
printWidth: 80
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
<ng-template>{gender, select, male {male} female {female} other {other}}</ng-template>
|
||||
<ng-container>{age, select, 10 {ten} 20 {twenty} other {other}}</ng-container>
|
||||
|
||||
=====================================output=====================================
|
||||
<ng-template>{gender, select,
|
||||
male {male}
|
||||
female {female}
|
||||
other {other}
|
||||
}</ng-template>
|
||||
<ng-container>{age, select, 10 {ten} 20 {twenty} other {other}}</ng-container>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
exports[`combination-01.html format 1`] = `
|
||||
====================================options=====================================
|
||||
parsers: ["angular"]
|
||||
printWidth: 80
|
||||
| printWidth
|
||||
=====================================input======================================
|
||||
<span i18n>Updated: {minutes, plural,
|
||||
=0 {just now}
|
||||
=1 {one minute ago}
|
||||
other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}}
|
||||
</span>
|
||||
|
||||
=====================================output=====================================
|
||||
<span i18n
|
||||
>Updated:
|
||||
{minutes, plural,
|
||||
=0 {just now}
|
||||
=1 {one minute ago}
|
||||
other {
|
||||
{{minutes}} minutes ago by {gender, select,
|
||||
male {male}
|
||||
female {female}
|
||||
other {other}
|
||||
}
|
||||
}
|
||||
}
|
||||
</span>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
<span i18n>Updated {minutes, plural, =0 {just now} =1 {one minute ago} other {{{minutes}} minutes ago}}</span>
|
||||
@@ -0,0 +1,13 @@
|
||||
{ count, plural,
|
||||
=0 {{ sex, select,
|
||||
other {<p>deeply nested</p>}
|
||||
}}
|
||||
=other {a lot}
|
||||
}
|
||||
|
||||
{ count123, plural,
|
||||
=0 {{ sex, select,
|
||||
other {<p>deeply nested</p>}
|
||||
}}
|
||||
=other {a lot}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<foo>
|
||||
<bar>{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>">beaucoup</pc>} }</bar>
|
||||
<baz>{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<pc id="0" equivStart="START_BOLD_TEXT" equivEnd="CLOSE_BOLD_TEXT" type="fmt" dispStart="<b>" dispEnd="</b>">many</pc>} }</baz>
|
||||
</foo>
|
||||
@@ -0,0 +1,3 @@
|
||||
<foo>
|
||||
<bar>{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph id="0" equiv="INTERPOLATION" disp="{{response.getItemsList().length}}"/> results} }</bar>
|
||||
</foo>
|
||||
@@ -0,0 +1 @@
|
||||
<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
|
||||
@@ -0,0 +1,2 @@
|
||||
<ng-template>{gender, select, male {male} female {female} other {other}}</ng-template>
|
||||
<ng-container>{age, select, 10 {ten} 20 {twenty} other {other}}</ng-container>
|
||||
@@ -0,0 +1,5 @@
|
||||
<span i18n>Updated: {minutes, plural,
|
||||
=0 {just now}
|
||||
=1 {one minute ago}
|
||||
other {{{minutes}} minutes ago by {gender, select, male {male} female {female} other {other}}}}
|
||||
</span>
|
||||
@@ -0,0 +1 @@
|
||||
run_spec(import.meta, ["angular"]);
|
||||
@@ -1394,6 +1394,12 @@ exports[`visitor keys html 1`] = `
|
||||
"angularControlFlowBlockParameters": [
|
||||
"children",
|
||||
],
|
||||
"angularIcuCase": [
|
||||
"expression",
|
||||
],
|
||||
"angularIcuExpression": [
|
||||
"cases",
|
||||
],
|
||||
"attribute": [],
|
||||
"cdata": [],
|
||||
"comment": [],
|
||||
|
||||
@@ -2150,12 +2150,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"angular-html-parser@npm:5.0.2":
|
||||
version: 5.0.2
|
||||
resolution: "angular-html-parser@npm:5.0.2"
|
||||
"angular-html-parser@npm:5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "angular-html-parser@npm:5.2.0"
|
||||
dependencies:
|
||||
tslib: ^2.6.2
|
||||
checksum: 86f8fc123e696b10878759c516d1876e4eff3d7cccf06cd22c2f572f44950e721480b44e2367802005f2638f3a84c7d7b826cd1fa0751387787f255097a5b401
|
||||
checksum: 12253b32a064f945e4abb5b107287a9de3576b5404161374bd9a237381895de40b88a9b96e830a4a996724865f7ef298cd046869a56cd1acc714794870401e7f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7305,7 +7305,7 @@ __metadata:
|
||||
acorn: 8.11.2
|
||||
acorn-jsx: 5.3.2
|
||||
angular-estree-parser: 9.0.0
|
||||
angular-html-parser: 5.0.2
|
||||
angular-html-parser: 5.2.0
|
||||
benchmark: 2.1.4
|
||||
browserslist-to-esbuild: 1.2.0
|
||||
c8: 8.0.1
|
||||
|
||||
Reference in new issue
Block a user