Support angular control flow (#15606)

Co-authored-by: dweizhe <d.weizhe@anyong.com.tw>
Co-authored-by: fisker <lionkay@gmail.com>
This commit is contained in:
authored and GitHub committed 2023-11-12 20:01:30 +08:00
1 parent 811eb0a085
commit 0176a33db4
31 files changed
+1085 -5

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
#### Support Angular control flow (#15606 by @DingWeizhe, @fisker)
Added support for built-in control flow in Angular 17.
For more details about control flow, you can check this article on the official blog.
https://blog.angular.io/introducing-angular-v17-4d7033312e4b
+18
View File
@@ -12,6 +12,14 @@ const ignoredProperties = new Set([
"valueTokens",
]);
const embeddedAngularControlFlowBlocks = new Set([
"if",
"else if",
"for",
"switch",
"case",
]);
function clean(ast, newNode) {
if (ast.type === "text" || ast.type === "comment") {
return null;
@@ -29,6 +37,16 @@ function clean(ast, newNode) {
if (ast.type === "docType") {
delete newNode.value;
}
if (ast.type === "block") {
for (const parameter of newNode.parameters) {
if (embeddedAngularControlFlowBlocks.has(ast.name)) {
delete parameter.expression;
} else {
parameter.expression = parameter.expression.trim();
}
}
}
}
clean.ignoredProperties = ignoredProperties;
+20
View File
@@ -23,6 +23,15 @@ import {
import isVueSfcWithTypescriptScript from "./utils/is-vue-sfc-with-typescript-script.js";
import getNodeContent from "./get-node-content.js";
import printAttribute from "./embed/attribute.js";
import printAngularControlFlowBlockParameters from "./embed/angular-control-flow-block-parameters.js";
const embeddedAngularControlFlowBlocks = new Set([
"if",
"else if",
"for",
"switch",
"case",
]);
function embed(path, options) {
const { node } = path;
@@ -135,6 +144,17 @@ function embed(path, options) {
case "front-matter":
return (textToDoc) => printFrontMatter(node, textToDoc);
// TODO: This should work on `blockParameter`, but currently we don't have a parser for it.
case "block":
if (
!embeddedAngularControlFlowBlocks.has(node.name) ||
node.parameters.length === 0
) {
return;
}
return printAngularControlFlowBlockParameters;
}
}
@@ -0,0 +1,27 @@
import { formatAttributeValue, shouldHugJsExpression } from "./utils.js";
async function printAngularControlFlowBlockParameters(
textToDoc,
print,
path,
/* options,*/
) {
const { node } = path;
const code = node.parameters.map(({ expression }) => expression).join("; ");
const doc = await formatAttributeValue(
code,
textToDoc,
{
parser: "__ng_directive",
__isInHtmlAttribute: false,
trailingComma: "none",
},
shouldHugJsExpression,
);
// We have to put the doc on the node, since `node.parameters` is not a valid "node"
node.__embed_parameters_doc = doc;
}
export default printAngularControlFlowBlockParameters;
+9
View File
@@ -38,6 +38,13 @@ import { locStart, locEnd } from "./loc.js";
* @typedef {{filepath?: string}} Options
*/
// `@else if`
function normalizeAngularControlFlowBlockName(node) {
if (node.type === "block") {
node.name = node.name.toLowerCase().replaceAll(/\s+/g, " ").trim();
}
}
/**
* @param {string} input
* @param {ParseOptions} parseOptions
@@ -62,6 +69,7 @@ function ngHtmlParser(input, parseOptions, options) {
? (...args) =>
shouldParseAsRawText(...args) ? TagContentType.RAW_TEXT : undefined
: undefined,
tokenizeAngularBlocks: name === "angular" ? true : undefined,
});
if (name === "vue") {
@@ -236,6 +244,7 @@ function ngHtmlParser(input, parseOptions, options) {
addTagDefinition(node);
normalizeName(node);
fixSourceSpan(node);
normalizeAngularControlFlowBlockName(node);
}
})(),
rootNodes,
@@ -0,0 +1,65 @@
const DEFAULT_ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS = {
isFollowingBlock: false,
followingBlocks: new Set(),
};
const ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS = new Map(
[
{
name: "if",
isFollowingBlock: false,
followingBlocks: ["else if", "else"],
},
{
name: "else if",
isFollowingBlock: true,
followingBlocks: ["else if", "else"],
},
{ name: "else", isFollowingBlock: true, followingBlocks: [] },
{ name: "switch", isFollowingBlock: false, followingBlocks: [] },
{ name: "case", isFollowingBlock: false, followingBlocks: [] },
{ name: "default", isFollowingBlock: false, followingBlocks: [] },
{ name: "for", isFollowingBlock: false, followingBlocks: ["empty"] },
{ name: "empty", isFollowingBlock: true, followingBlocks: [] },
{
name: "defer",
isFollowingBlock: false,
followingBlocks: ["placeholder", "error", "loading"],
},
{
name: "placeholder",
isFollowingBlock: true,
followingBlocks: ["placeholder", "error", "loading"],
},
{
name: "error",
isFollowingBlock: true,
followingBlocks: ["placeholder", "error", "loading"],
},
{
name: "loading",
isFollowingBlock: true,
followingBlocks: ["placeholder", "error", "loading"],
},
].map((settings) => [
settings.name,
{
DEFAULT_ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS,
...settings,
followingBlocks: settings.followingBlocks
? new Set(settings.followingBlocks)
: DEFAULT_ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS.followingBlocks,
},
]),
);
export default {
DEFAULT_ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS,
ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS,
};
+84
View File
@@ -0,0 +1,84 @@
import {
group,
hardline,
softline,
line,
indent,
join,
} from "../../document/builders.js";
import { printChildren } from "./children.js";
import settings from "./angular-control-flow-block-settings.evaluate.js";
const unClosedBlocks = new WeakSet();
function printAngularControlFlowBlock(path, options, print) {
const { node } = path;
const setting =
settings.ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS.get(node.name) ??
settings.DEFAULT_ANGULAR_CONTROL_FLOW_BLOCK_SETTINGS;
const docs = [];
const previousBlock = findPreviousBlock(path);
if (setting.isFollowingBlock && unClosedBlocks.has(previousBlock)) {
docs.push("} ");
}
docs.push("@", node.name);
if (node.parameters.length > 0) {
const parametersDoc = group(
node.__embed_parameters_doc ?? [
indent([softline, join([";", line], path.map(print, "parameters"))]),
softline,
],
);
docs.push(" (", parametersDoc, ")");
}
docs.push(" {");
const shouldPrintCloseBracket = shouldCloseBlock(
node,
setting.followingBlocks,
);
if (!shouldPrintCloseBracket) {
unClosedBlocks.add(node);
}
if (node.children.length > 0) {
node.firstChild.hasLeadingSpaces = true;
node.lastChild.hasTrailingSpaces = true;
docs.push(indent([hardline, printChildren(path, options, print)]));
if (shouldPrintCloseBracket) {
docs.push(hardline, "}");
}
} else if (shouldPrintCloseBracket) {
docs.push("}");
}
return group(docs, { shouldBreak: true });
}
function shouldCloseBlock(node, names) {
return !(
names.size > 0 &&
node.next?.type === "block" &&
names.has(node.next.name)
);
}
function findPreviousBlock(path) {
const { siblings, index } = path;
for (let i = index - 1; i >= 0; i--) {
const sibling = siblings[i];
if (sibling.type === "block") {
return sibling;
}
}
}
export { printAngularControlFlowBlock };
+1
View File
@@ -128,6 +128,7 @@ function needsToBorrowPrevClosingTagEndMarker(node) {
return (
node.prev &&
node.prev.type !== "docType" &&
node.type !== "block" &&
!isTextLikeNode(node.prev) &&
node.isLeadingSpaceSensitive &&
!node.hasLeadingSpaces
+7
View File
@@ -6,6 +6,7 @@ import { fill, group, hardline } from "../document/builders.js";
import { cleanDoc, replaceEndOfLine } from "../document/utils.js";
import UnexpectedNodeError from "../utils/unexpected-node-error.js";
import getPreferredQuote from "../utils/get-preferred-quote.js";
import htmlWhitespaceUtils from "../utils/html-whitespace-utils.js";
import clean from "./clean.js";
import { unescapeQuoteEntities, getTextValueParts } from "./utils/index.js";
import preprocess from "./print-preprocess.js";
@@ -20,6 +21,7 @@ import {
} from "./print/tag.js";
import { printElement } from "./print/element.js";
import { printChildren } from "./print/children.js";
import { printAngularControlFlowBlock } from "./print/angular-control-flow-block.js";
import getVisitorKeys from "./get-visitor-keys.js";
function genericPrint(path, options, print) {
@@ -37,6 +39,11 @@ function genericPrint(path, options, print) {
case "ieConditionalComment":
return printElement(path, options, print);
case "block":
return printAngularControlFlowBlock(path, options, print);
case "blockParameter":
return htmlWhitespaceUtils.trim(node.expression);
case "ieConditionalStartComment":
case "ieConditionalEndComment":
return [printOpeningTagStart(node), printClosingTagEnd(node)];
+2
View File
@@ -11,6 +11,8 @@ const visitorKeys = {
comment: [],
attribute: [],
cdata: [],
block: ["children", "parameters"],
blockParameter: [],
};
export default visitorKeys;
+5 -5
View File
@@ -62,7 +62,9 @@ function printAngular(path, options, print) {
const shouldNotPrintColon =
isNgForOf(path) ||
(((index === 1 &&
(node.key.name === "then" || node.key.name === "else")) ||
(node.key.name === "then" ||
node.key.name === "else" ||
node.key.name === "as")) ||
(index === 2 &&
node.key.name === "else" &&
parent.body[index - 1].type === "NGMicrosyntaxKeyedExpression" &&
@@ -88,13 +90,11 @@ function printAngular(path, options, print) {
}
}
function isNgForOf({ node, index, parent }) {
function isNgForOf({ node, index }) {
return (
node.type === "NGMicrosyntaxKeyedExpression" &&
node.key.name === "of" &&
index === 1 &&
parent.body[0].type === "NGMicrosyntaxLet" &&
parent.body[0].value === null
index === 1
);
}
@@ -0,0 +1,551 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`after-close-tag.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
<ion-list class="timeline" appScrollItem>
@for (let item of items; index as i; trackBy: trackByFn) {
<app-tweet-item></app-tweet-item>@if (
areaId &&
canTweet
) {
<ion-item class="recommend-trust-up" lines="full"></ion-item>
}
}
</ion-list>
<ng-container *ngIf="
areaId &&
canTweet
">
<ion-item class="recommend-trust-up" lines="full"></ion-item>
</ng-container>
@if (
areaId &&
canTweet
) {
<ion-item class="recommend-trust-up" lines="full"></ion-item>
}
=====================================output=====================================
<ion-list class="timeline" appScrollItem>
@for (let item of items; index as i; trackBy: trackByFn) {
<app-tweet-item></app-tweet-item>
@if (areaId && canTweet) {
<ion-item class="recommend-trust-up" lines="full"></ion-item>
}
}
</ion-list>
<ng-container *ngIf="areaId && canTweet">
<ion-item class="recommend-trust-up" lines="full"></ion-item>
</ng-container>
@if (areaId && canTweet) {
<ion-item class="recommend-trust-up" lines="full"></ion-item>
}
================================================================================
`;
exports[`defer.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@defer (when isCheckedDefer()) {
<app-c1/>
}
@placeholder {
<span>Placeholder</span>
}
@error {
<span>Error</span>
}
@loading(minimum 1s) {
<span>Loading...</span>
}
@defer (on interaction) {
<span>Clicked</span>
}
@placeholder {
<span>Placeholder (click on it!)</span>
}
@defer (on hover) {
<span>Hovered</span>
}
@placeholder {
<span>Placeholder (hover it!)</span>
}
@defer (on idle) {
<span>Browser has reached an idle state</span>
}
@placeholder {
<span>Placeholder</span>
}
@defer (on timer(5s)) {
<span>Visible after 5s</span>
}
@placeholder {
<span>Placeholder</span>
}
@defer (on viewport) {
<app-c2 text="The block entered the viewport"/>
}
@placeholder {
<span>Placeholder</span>
}
@defer (on interaction; prefetch on hover) {
<app-c3/>
}
@placeholder {
<span>Placeholder (hover it, then click on it!)</span>
}
@defer (on interaction; prefetch on hover) {
<app-c3/>
}
@placeholder {
<span>Placeholder (hover it, then click on it!)</span>
}
=====================================output=====================================
@defer (when isCheckedDefer()) {
<app-c1 />
} @placeholder {
<span>Placeholder</span>
} @error {
<span>Error</span>
} @loading (minimum 1s) {
<span>Loading...</span>
}
@defer (on interaction) {
<span>Clicked</span>
} @placeholder {
<span>Placeholder (click on it!)</span>
}
@defer (on hover) {
<span>Hovered</span>
} @placeholder {
<span>Placeholder (hover it!)</span>
}
@defer (on idle) {
<span>Browser has reached an idle state</span>
} @placeholder {
<span>Placeholder</span>
}
@defer (on timer(5s)) {
<span>Visible after 5s</span>
} @placeholder {
<span>Placeholder</span>
}
@defer (on viewport) {
<app-c2 text="The block entered the viewport" />
} @placeholder {
<span>Placeholder</span>
}
@defer (on interaction; prefetch on hover) {
<app-c3 />
} @placeholder {
<span>Placeholder (hover it, then click on it!)</span>
}
@defer (on interaction; prefetch on hover) {
<app-c3 />
} @placeholder {
<span>Placeholder (hover it, then click on it!)</span>
}
================================================================================
`;
exports[`element-tags.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@if (true) {<img />}
=====================================output=====================================
@if (true) {
<img />
}
================================================================================
`;
exports[`for.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
<ul>
@for (
let
item of items;index as
i;
trackBy: trackByFn
) {
<li><strong>{{item.name}}</strong></li>
}
@empty {
<span>The collection is empty</span>
}
</ul>
<li *ngFor="
let
item of items;index as
i;
trackBy: trackByFn
"></li>
<ul>
@for (item of collection; track item.id; let index = $index, first = $first; let last = $last, even = $even, odd = $odd; let count = $count) {
<li><strong>{{item.name}}</strong> index={{index}} first={{first}} last={{last}} even={{even}} odd={{odd}} count={{count}}</li>
}
</ul>
<ul>
@for (item of
emptyCollection; track item.id;) {
<li><strong>{{item.name}}</strong></li>
}
@empty {
<span>The collection is empty</span>
}
</ul>
<li *ngFor="
item of
emptyCollection; track item.id;
"></li>
=====================================output=====================================
<ul>
@for (let item of items; index as i; trackBy: trackByFn) {
<li>
<strong>{{ item.name }}</strong>
</li>
} @empty {
<span>The collection is empty</span>
}
</ul>
<li *ngFor="let item of items; index as i; trackBy: trackByFn"></li>
<ul>
@for (
item of collection;
track item.id;
let index = $index, first = $first;
let last = $last, even = $even, odd = $odd;
let count = $count
) {
<li>
<strong>{{ item.name }}</strong> index={{ index }} first={{
first
}}
last={{ last }} even={{ even }} odd={{ odd }} count={{ count }}
</li>
}
</ul>
<ul>
@for (item of emptyCollection; track: item.id) {
<li>
<strong>{{ item.name }}</strong>
</li>
} @empty {
<span>The collection is empty</span>
}
</ul>
<li *ngFor="item of emptyCollection; track: item.id"></li>
================================================================================
`;
exports[`if.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
<h3>&#64;if and &#64;else</h3>
<div>
<input #checkbox type="checkbox" [checked]="isChecked()" (change)="isChecked.set(checkbox.checked)" id="checkbox"/>
</div>
<div>
@if (isChecked()) {
<span>Checked</span>
}
@else {
<span>Not checked</span>
}
</div>
@if (users$ | async; as users) { {{ users.length }} }
@else if (users$ | async; as users) { {{ users.length }} }
<div *ngIf="users$ | async; as users">{{ users.length }}</div>
@else {}
@if (isDev) {}
@else if (test) {}
@if (foo( "quotes")) {}
@else if (foo( 'quotes')) {}
@if ( widthCategory !== undefined && widthCategory >
topStartToSideStartMaxSize) {
<app-component />
}
@if ( widthCategory; as item) {
}
<div *ngIf="widthCategory; as item"></div>
=====================================output=====================================
<h3>&#64;if and &#64;else</h3>
<div>
<input
#checkbox
type="checkbox"
[checked]="isChecked()"
(change)="isChecked.set(checkbox.checked)"
id="checkbox"
/>
</div>
<div>
@if (isChecked()) {
<span>Checked</span>
} @else {
<span>Not checked</span>
}
</div>
@if (users$ | async; as users) {
{{ users.length }}
} @else if (users$ | async; as users) {
{{ users.length }}
}
<div *ngIf="users$ | async; as users">{{ users.length }}</div>
@else {}
@if (isDev) {
} @else if (test) {}
@if (foo("quotes")) {
} @else if (foo("quotes")) {}
@if (
widthCategory !== undefined && widthCategory > topStartToSideStartMaxSize
) {
<app-component />
}
@if (widthCategory; as item) {}
<div *ngIf="widthCategory; as item"></div>
================================================================================
`;
exports[`mix.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@if (user.isHuman) {
<human-profile [data]="user" />
} @else if (user.isRobot) {
<!-- robot users are rare, so load their profiles lazily -->
@defer {
<robot-profile [data]="user" />
}
} @else {
<p>The profile is unknown!</p>
}
=====================================output=====================================
@if (user.isHuman) {
<human-profile [data]="user" />
} @else if (user.isRobot) {
<!-- robot users are rare, so load their profiles lazily -->
@defer {
<robot-profile [data]="user" />
}
} @else {
<p>The profile is unknown!</p>
}
================================================================================
`;
exports[`switch.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@switch (radioValue()) {
@case (1) {
<span>Case 1</span>
}
@case (2) {
<span>Case 2</span>
}
@default {
<span>Default case (Not 1 or 2)</span>
}
}
@switch(
should
.be.formatted
) {
@case(
should
.be.formatted) {}
}
=====================================output=====================================
@switch (radioValue()) {
@case (1) {
<span>Case 1</span>
}
@case (2) {
<span>Case 2</span>
}
@default {
<span>Default case (Not 1 or 2)</span>
}
}
@switch (should.be.formatted) {
@case (should.be.formatted) {}
}
================================================================================
`;
exports[`unclosed.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@empty{}
@else {}
=====================================output=====================================
@empty {}
@else {}
================================================================================
`;
exports[`unknown-block.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@unknown (foo) {
} @unknown (foo) {
}
=====================================output=====================================
@unknown (foo) {}
@unknown (foo) {}
================================================================================
`;
exports[`with-comment.html format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
<div>
<!-- comment -->
<span>Not checked</span>
</div>
<!-- comment -->
@if (isChecked()) {
<span>
Checked
<!-- comment -->
</span>
} <!-- comment -->
@else {
<span>Not checked</span>
}
<!-- comment -->
=====================================output=====================================
<div>
<!-- comment -->
<span>Not checked</span>
</div>
<!-- comment -->
@if (isChecked()) {
<span>
Checked
<!-- comment -->
</span>
}
<!-- comment -->
@else {
<span>Not checked</span>
}
<!-- comment -->
================================================================================
`;
@@ -0,0 +1,25 @@
<ion-list class="timeline" appScrollItem>
@for (let item of items; index as i; trackBy: trackByFn) {
<app-tweet-item></app-tweet-item>@if (
areaId &&
canTweet
) {
<ion-item class="recommend-trust-up" lines="full"></ion-item>
}
}
</ion-list>
<ng-container *ngIf="
areaId &&
canTweet
">
<ion-item class="recommend-trust-up" lines="full"></ion-item>
</ng-container>
@if (
areaId &&
canTweet
) {
<ion-item class="recommend-trust-up" lines="full"></ion-item>
}
@@ -0,0 +1,68 @@
@defer (when isCheckedDefer()) {
<app-c1/>
}
@placeholder {
<span>Placeholder</span>
}
@error {
<span>Error</span>
}
@loading(minimum 1s) {
<span>Loading...</span>
}
@defer (on interaction) {
<span>Clicked</span>
}
@placeholder {
<span>Placeholder (click on it!)</span>
}
@defer (on hover) {
<span>Hovered</span>
}
@placeholder {
<span>Placeholder (hover it!)</span>
}
@defer (on idle) {
<span>Browser has reached an idle state</span>
}
@placeholder {
<span>Placeholder</span>
}
@defer (on timer(5s)) {
<span>Visible after 5s</span>
}
@placeholder {
<span>Placeholder</span>
}
@defer (on viewport) {
<app-c2 text="The block entered the viewport"/>
}
@placeholder {
<span>Placeholder</span>
}
@defer (on interaction; prefetch on hover) {
<app-c3/>
}
@placeholder {
<span>Placeholder (hover it, then click on it!)</span>
}
@defer (on interaction; prefetch on hover) {
<app-c3/>
}
@placeholder {
<span>Placeholder (hover it, then click on it!)</span>
}
@@ -0,0 +1 @@
@if (true) {<img />}
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`if.html - {"embeddedLanguageFormatting":"off"} format 1`] = `
====================================options=====================================
embeddedLanguageFormatting: "off"
parsers: ["angular"]
printWidth: 80
| printWidth
=====================================input======================================
@if (should. not. format) { }
@else if (should. not. format) { }
=====================================output=====================================
@if (should. not. format) {
} @else if (should. not. format) {}
================================================================================
`;
@@ -0,0 +1,2 @@
@if (should. not. format) { }
@else if (should. not. format) { }
@@ -0,0 +1 @@
run_spec(import.meta, ["angular"], { embeddedLanguageFormatting: "off" });
@@ -0,0 +1,47 @@
<ul>
@for (
let
item of items;index as
i;
trackBy: trackByFn
) {
<li><strong>{{item.name}}</strong></li>
}
@empty {
<span>The collection is empty</span>
}
</ul>
<li *ngFor="
let
item of items;index as
i;
trackBy: trackByFn
"></li>
<ul>
@for (item of collection; track item.id; let index = $index, first = $first; let last = $last, even = $even, odd = $odd; let count = $count) {
<li><strong>{{item.name}}</strong> index={{index}} first={{first}} last={{last}} even={{even}} odd={{odd}} count={{count}}</li>
}
</ul>
<ul>
@for (item of
emptyCollection; track item.id;) {
<li><strong>{{item.name}}</strong></li>
}
@empty {
<span>The collection is empty</span>
}
</ul>
<li *ngFor="
item of
emptyCollection; track item.id;
"></li>
+36
View File
@@ -0,0 +1,36 @@
<h3>&#64;if and &#64;else</h3>
<div>
<input #checkbox type="checkbox" [checked]="isChecked()" (change)="isChecked.set(checkbox.checked)" id="checkbox"/>
</div>
<div>
@if (isChecked()) {
<span>Checked</span>
}
@else {
<span>Not checked</span>
}
</div>
@if (users$ | async; as users) { {{ users.length }} }
@else if (users$ | async; as users) { {{ users.length }} }
<div *ngIf="users$ | async; as users">{{ users.length }}</div>
@else {}
@if (isDev) {}
@else if (test) {}
@if (foo( "quotes")) {}
@else if (foo( 'quotes')) {}
@if ( widthCategory !== undefined && widthCategory >
topStartToSideStartMaxSize) {
<app-component />
}
@if ( widthCategory; as item) {
}
<div *ngIf="widthCategory; as item"></div>
@@ -0,0 +1 @@
run_spec(import.meta, ["angular"]);
@@ -0,0 +1,10 @@
@if (user.isHuman) {
<human-profile [data]="user" />
} @else if (user.isRobot) {
<!-- robot users are rare, so load their profiles lazily -->
@defer {
<robot-profile [data]="user" />
}
} @else {
<p>The profile is unknown!</p>
}
@@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`test.html - {"printWidth":20} format 1`] = `
====================================options=====================================
parsers: ["angular"]
printWidth: 20
| printWidth
=====================================input======================================
@unknown (long expression;long expression;long expression;long expression;long expression;long expression;long expression;long expression;) {
}
=====================================output=====================================
@unknown (
long expression;
long expression;
long expression;
long expression;
long expression;
long expression;
long expression;
long expression
) {}
================================================================================
`;
@@ -0,0 +1 @@
run_spec(import.meta, ["angular"], { printWidth: 20 });
@@ -0,0 +1,3 @@
@unknown (long expression;long expression;long expression;long expression;long expression;long expression;long expression;long expression;) {
}
@@ -0,0 +1,21 @@
@switch (radioValue()) {
@case (1) {
<span>Case 1</span>
}
@case (2) {
<span>Case 2</span>
}
@default {
<span>Default case (Not 1 or 2)</span>
}
}
@switch(
should
.be.formatted
) {
@case(
should
.be.formatted) {}
}
@@ -0,0 +1,3 @@
@empty{}
@else {}
@@ -0,0 +1,3 @@
@unknown (foo) {
} @unknown (foo) {
}
@@ -0,0 +1,17 @@
<div>
<!-- comment -->
<span>Not checked</span>
</div>
<!-- comment -->
@if (isChecked()) {
<span>
Checked
<!-- comment -->
</span>
} <!-- comment -->
@else {
<span>Not checked</span>
}
<!-- comment -->
@@ -47,6 +47,7 @@ test("parsers should allow omit optional arguments", async () => {
expect(typeof parsers[0][1].parse).toBe("function");
const code = {
graphql: "type A {hero: Character}",
angular: "<div></div>",
};
for (const [name, { parse }] of parsers) {
await expect(
@@ -1390,6 +1390,11 @@ exports[`visitor keys graphql 1`] = `
exports[`visitor keys html 1`] = `
{
"attribute": [],
"block": [
"children",
"parameters",
],
"blockParameter": [],
"cdata": [],
"comment": [],
"docType": [],