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:
31 files changed
+1085
-5
No files matched your search
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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 };
|
||||
@@ -128,6 +128,7 @@ function needsToBorrowPrevClosingTagEndMarker(node) {
|
||||
return (
|
||||
node.prev &&
|
||||
node.prev.type !== "docType" &&
|
||||
node.type !== "block" &&
|
||||
!isTextLikeNode(node.prev) &&
|
||||
node.isLeadingSpaceSensitive &&
|
||||
!node.hasLeadingSpaces
|
||||
|
||||
@@ -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)];
|
||||
|
||||
@@ -11,6 +11,8 @@ const visitorKeys = {
|
||||
comment: [],
|
||||
attribute: [],
|
||||
cdata: [],
|
||||
block: ["children", "parameters"],
|
||||
blockParameter: [],
|
||||
};
|
||||
|
||||
export default visitorKeys;
|
||||
Vendored
+5
-5
@@ -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>@if and @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>@if and @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 />}
|
||||
+18
@@ -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>
|
||||
@@ -0,0 +1,36 @@
|
||||
<h3>@if and @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": [],
|
||||
|
||||
Reference in new issue
Block a user