Support Angular @let declaration syntax (#16474)

gmail.com>
This commit is contained in:
SUZUKI Sosuke authored and GitHub committed 2024-07-13 18:24:57 +09:00
1 parent bfcfdccdf8
commit 5ca2fc9be8
16 files changed
+508 -1

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
#### Support `@let` declaration syntax (#16474 by @sosukesuzuki)
Adds support for Angular v18 `@let` declaration syntax.
Please see the following code example. The `@let` declaration allows you to define local variables within the template:
<!-- prettier-ignore -->
```html
@let name = 'Frodo';
<h1>Dashboard for {{name}}</h1>
Hello, {{name}}
```
For more details, please refer to the excellent blog post by the Angular Team: [Introducing @let in Angular](https://blog.angular.dev/introducing-let-in-angular-686f9f383f0f).
We also appreciate the Angular Team for kindly answering our questions to implement this feature.
+4
View File
@@ -61,6 +61,10 @@ function clean(original, cloned) {
if (original.type === "angularIcuExpression") {
cloned.switchValue = original.switchValue.trim();
}
if (original.type === "angularLetDeclarationInitializer") {
delete cloned.value;
}
}
clean.ignoredProperties = ignoredProperties;
+8
View File
@@ -8,6 +8,7 @@ import {
import printFrontMatter from "../utils/front-matter/print.js";
import printAngularControlFlowBlockParameters from "./embed/angular-control-flow-block-parameters.js";
import printAttribute from "./embed/attribute.js";
import { formatAttributeValue } from "./embed/utils.js";
import getNodeContent from "./get-node-content.js";
import {
needsToBorrowPrevClosingTagEndMarker,
@@ -148,6 +149,13 @@ function embed(path, options) {
}
return printAngularControlFlowBlockParameters;
case "angularLetDeclarationInitializer":
return (textToDoc) =>
formatAttributeValue(node.value, textToDoc, {
parser: "__ng_binding",
__isInHtmlAttribute: false,
});
}
}
+20
View File
@@ -68,6 +68,24 @@ function normalizeAngularControlFlowBlock(node) {
};
}
function normalizeAngularLetDeclaration(node) {
if (node.type !== "letDeclaration") {
return;
}
// Similar to `VariableDeclarator` in estree
node.type = "angularLetDeclaration";
node.id = node.name;
node.init = {
type: "angularLetDeclarationInitializer",
sourceSpan: new ParseSourceSpan(node.valueSpan.start, node.valueSpan.end),
value: node.value,
};
delete node.name;
delete node.value;
}
function normalizeAngularIcuExpression(node) {
if (node.type === "plural" || node.type === "select") {
node.clause = node.type;
@@ -103,6 +121,7 @@ function ngHtmlParser(input, parseOptions, options) {
shouldParseAsRawText(...args) ? TagContentType.RAW_TEXT : undefined
: undefined,
tokenizeAngularBlocks: name === "angular" ? true : undefined,
tokenizeAngularLetDeclaration: name === "angular" ? true : undefined,
});
if (name === "vue") {
@@ -395,6 +414,7 @@ function parse(
}
normalizeAngularControlFlowBlock(node);
normalizeAngularLetDeclaration(node);
normalizeAngularIcuExpression(node);
});
+13 -1
View File
@@ -2,7 +2,7 @@
* @typedef {import("../document/builders.js").Doc} Doc
*/
import { fill, group, hardline } from "../document/builders.js";
import { fill, group, hardline, indent, line } from "../document/builders.js";
import { cleanDoc, replaceEndOfLine } from "../document/utils.js";
import getPreferredQuote from "../utils/get-preferred-quote.js";
import htmlWhitespaceUtils from "../utils/html-whitespace-utils.js";
@@ -53,6 +53,18 @@ function genericPrint(path, options, print) {
case "angularControlFlowBlockParameter":
return htmlWhitespaceUtils.trim(node.expression);
case "angularLetDeclaration":
// print like "break-after-operator" layout assignment in estree printer
return group([
"@let ",
group([node.id, " =", group(indent([line, print("init")]))]),
// semicolon is required
";",
]);
case "angularLetDeclarationInitializer":
// basically printed via embedded formatting
return node.value;
case "angularIcuExpression":
return printAngularIcuExpression(path, options, print);
case "angularIcuCase":
+2
View File
@@ -14,6 +14,8 @@ const visitorKeys = {
angularControlFlowBlock: ["children", "parameters"],
angularControlFlowBlockParameters: ["children"],
angularControlFlowBlockParameter: [],
angularLetDeclaration: ["init"],
angularLetDeclarationInitializer: [],
angularIcuExpression: ["cases"],
angularIcuCase: ["expression"],
};
@@ -0,0 +1,280 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`basic-01.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@let name = 'Frodo';
<h1>Dashboard for {{name}}</h1>
Hello, {{name}}
=====================================output=====================================
@let name = "Frodo";
<h1>Dashboard for {{ name }}</h1>
Hello, {{ name }}
================================================================================
`;
exports[`basic-02.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
<input #name>
@let greeting = 'Hello ' + name.value;
@let user = user$ | async;
=====================================output=====================================
<input #name />
@let greeting = "Hello " + name.value;
@let user = user$ | async;
================================================================================
`;
exports[`basic-03.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@let topLevel = value;
@if (condition) {
@let nested = value;
}
<div *ngIf="condition">
@let nestedNgIf = value;
</div>
{{topLevel}}
{{nested}}
{{nestedNgIf}}
=====================================output=====================================
@let topLevel = value;
@if (condition) {
@let nested = value;
}
<div *ngIf="condition">
@let nestedNgIf = value;
</div>
{{ topLevel }}
{{ nested }}
{{ nestedNgIf }}
================================================================================
`;
exports[`complex.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@let foo = a | b : c:d :e;
@let foo = a | pipe;
@let foo = 0 - 1;
@let foo = - 1;
@let foo = a ? 1 : 2;
@let foo = a ( 1 ) ( 2 );
@let foo = a [ b ];
@let foo = [ 1 ];
@let foo = { 'a' : 1 };
@let foo = { a : 1 };
@let foo = true;
@let foo = undefined;
@let foo = null;
@let foo = ( 1 );
@let foo = 1;
@let foo = 'hello';
@let foo = a ( 1 , 2 );
@let foo = a . b ( 1 , 2 );
@let foo = x !;
@let foo = ! x;
@let foo = ( ( a ) );
@let foo = a;
@let foo = a; // hello
@let foo = a . b;
@let foo = a ?. b ( );
@let foo = a ?. b;
@let foo = a; // hello
@let foo = "SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection;
@let foo = copyTypes[options.copyType];
@let foo = listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3;
@let foo = a;
@let foo = b;
@let foo = aNormalValue | aPipe;
@let foo = aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis;
@let foo = 'delete' | translate: {what: ('entities' | translate: {count: array.length})};
@let foo = {a:1+{} };
@let foo = {a:a==={} };
@let foo = {a:!{} };
@let foo = {a:a?b:{} };
=====================================output=====================================
@let foo = a | b: c : d : e;
@let foo = a | pipe;
@let foo = 0 - 1;
@let foo = -1;
@let foo = a ? 1 : 2;
@let foo = a(1)(2);
@let foo = a[b];
@let foo = [1];
@let foo = { a: 1 };
@let foo = { a: 1 };
@let foo = true;
@let foo = undefined;
@let foo = null;
@let foo = 1;
@let foo = 1;
@let foo = "hello";
@let foo = a(1, 2);
@let foo = a.b(1, 2);
@let foo = x!;
@let foo = !x;
@let foo = a;
@let foo = a;
@let foo = a; // hello
@let foo = a.b;
@let foo = a?.b();
@let foo = a?.b;
@let foo = a; // hello
@let foo =
"SearchSelection.transmissionLayoutRadioButton"
| localize: localizationSection;
@let foo = copyTypes[options.copyType];
@let foo =
listRow.NextScheduledSendStatus == 1 ||
listRow.NextScheduledSendStatus == 2 ||
listRow.NextScheduledSendStatus == 3;
@let foo = a;
@let foo = b;
@let foo = aNormalValue | aPipe;
@let foo = aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis;
@let foo =
"delete"
| translate: { what: ("entities" | translate: { count: array.length }) };
@let foo = { a: 1 + {} };
@let foo = { a: a === {} };
@let foo = { a: !{} };
@let foo = { a: a ? b : {} };
================================================================================
`;
exports[`valid-from-tokenizer-test.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
<!-- import from https://github.com/angular/angular/commit/62c5b66b4119b7e1dfaf1e28c100e5f8431cb05b -->
@let foo = 123 + 456;
@let foo = 123 + 456;
@let foo=123 + 456;
@let foo =123 + 456;
@let foo= 123 + 456;
@let
foo = 123;
@let
foo = 123;
@let
foo = 123;
@let foo
= 123;
@let foo
= 123;
@let foo
= 123;
@let
foo
= 123;
@let foo =
123 +
456 +
789
;
@block {@let foo = 123 + 456;}
@let foo = 'a; b';
@let foo = "';'";
@let foo = '\\';\\'' + "\\",";
@let foo = fn(a, b) + fn2(c, d, e);
@let foo = [1, 2, 3];
@let foo = [0, [foo[1]], 3];
@let foo = {a: 1, b: {c: something + 2}};
@let foo = {};
@let foo = {foo: ";"};
@let foo = fn({a: 1, b: [otherFn([{c: ";"}], 321, {d: [',']})]});
=====================================output=====================================
<!-- import from https://github.com/angular/angular/commit/62c5b66b4119b7e1dfaf1e28c100e5f8431cb05b -->
@let foo = 123 + 456;
@let foo = 123 + 456;
@let foo = 123 + 456;
@let foo = 123 + 456;
@let foo = 123 + 456;
@let foo = 123;
@let foo = 123;
@let foo = 123;
@let foo = 123;
@let foo = 123;
@let foo = 123;
@let foo = 123;
@let foo = 123 + 456 + 789;
@block {
@let foo = 123 + 456;
}
@let foo = "a; b";
@let foo = "';'";
@let foo = "';'" + '",';
@let foo = fn(a, b) + fn2(c, d, e);
@let foo = [1, 2, 3];
@let foo = [0, [foo[1]], 3];
@let foo = { a: 1, b: { c: something + 2 } };
@let foo = {};
@let foo = { foo: ";" };
@let foo = fn({ a: 1, b: [otherFn([{ c: ";" }], 321, { d: [","] })] });
================================================================================
`;
@@ -0,0 +1,3 @@
@let name = 'Frodo';
<h1>Dashboard for {{name}}</h1>
Hello, {{name}}
@@ -0,0 +1,5 @@
<input #name>
@let greeting = 'Hello ' + name.value;
@let user = user$ | async;
@@ -0,0 +1,15 @@
@let topLevel = value;
@if (condition) {
@let nested = value;
}
<div *ngIf="condition">
@let nestedNgIf = value;
</div>
{{topLevel}}
{{nested}}
{{nestedNgIf}}
@@ -0,0 +1,39 @@
@let foo = a | b : c:d :e;
@let foo = a | pipe;
@let foo = 0 - 1;
@let foo = - 1;
@let foo = a ? 1 : 2;
@let foo = a ( 1 ) ( 2 );
@let foo = a [ b ];
@let foo = [ 1 ];
@let foo = { 'a' : 1 };
@let foo = { a : 1 };
@let foo = true;
@let foo = undefined;
@let foo = null;
@let foo = ( 1 );
@let foo = 1;
@let foo = 'hello';
@let foo = a ( 1 , 2 );
@let foo = a . b ( 1 , 2 );
@let foo = x !;
@let foo = ! x;
@let foo = ( ( a ) );
@let foo = a;
@let foo = a; // hello
@let foo = a . b;
@let foo = a ?. b ( );
@let foo = a ?. b;
@let foo = a; // hello
@let foo = "SearchSelection.transmissionLayoutRadioButton" | localize:localizationSection;
@let foo = copyTypes[options.copyType];
@let foo = listRow.NextScheduledSendStatus == 1 || listRow.NextScheduledSendStatus == 2 || listRow.NextScheduledSendStatus == 3;
@let foo = a;
@let foo = b;
@let foo = aNormalValue | aPipe;
@let foo = aReallyReallySuperLongValue | andASuperLongPipeJustToBreakThis;
@let foo = 'delete' | translate: {what: ('entities' | translate: {count: array.length})};
@let foo = {a:1+{} };
@let foo = {a:a==={} };
@let foo = {a:!{} };
@let foo = {a:a?b:{} };
@@ -0,0 +1 @@
runFormatTest(import.meta, ["angular"]);
@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`snippet: #0 - {"embeddedLanguageFormatting":"off"} format 1`] = `
====================================options=====================================
embeddedLanguageFormatting: "off"
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@let foo = 'Hello' + ', World';
=====================================output=====================================
@let foo = 'Hello' + ', World';
================================================================================
`;
exports[`snippet: #0 - {"semi":false} format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
semi: false
| printWidth
=====================================input======================================
@let foo = 'Hello' + ', World';
=====================================output=====================================
@let foo = "Hello" + ", World";
================================================================================
`;
@@ -0,0 +1,19 @@
const snippets = ["@let foo = 'Hello' + ', World'; "];
runFormatTest(
{
importMeta: import.meta,
snippets,
},
["angular"],
{ embeddedLanguageFormatting: "off" },
);
runFormatTest(
{
importMeta: import.meta,
snippets,
},
["angular"],
{ semi: false },
);
@@ -0,0 +1,49 @@
<!-- import from https://github.com/angular/angular/commit/62c5b66b4119b7e1dfaf1e28c100e5f8431cb05b -->
@let foo = 123 + 456;
@let foo = 123 + 456;
@let foo=123 + 456;
@let foo =123 + 456;
@let foo= 123 + 456;
@let
foo = 123;
@let
foo = 123;
@let
foo = 123;
@let foo
= 123;
@let foo
= 123;
@let foo
= 123;
@let
foo
= 123;
@let foo =
123 +
456 +
789
;
@block {@let foo = 123 + 456;}
@let foo = 'a; b';
@let foo = "';'";
@let foo = '\';\'' + "\",";
@let foo = fn(a, b) + fn2(c, d, e);
@let foo = [1, 2, 3];
@let foo = [0, [foo[1]], 3];
@let foo = {a: 1, b: {c: something + 2}};
@let foo = {};
@let foo = {foo: ";"};
@let foo = fn({a: 1, b: [otherFn([{c: ";"}], 321, {d: [',']})]});
@@ -1427,6 +1427,10 @@ exports[`visitor keys html 1`] = `
"angularIcuExpression": [
"cases",
],
"angularLetDeclaration": [
"init",
],
"angularLetDeclarationInitializer": [],
"attribute": [],
"cdata": [],
"comment": [],