From 0176a33db442e498fdb577784deaa77d7c9ae723 Mon Sep 17 00:00:00 2001 From: DingWeizhe Date: Sun, 12 Nov 2023 06:01:30 -0600 Subject: [PATCH] Support angular control flow (#15606) Co-authored-by: dweizhe Co-authored-by: fisker --- changelog_unreleased/angular/15606.md | 7 + src/language-html/clean.js | 18 + src/language-html/embed.js | 20 + .../angular-control-flow-block-parameters.js | 27 + src/language-html/parser-html.js | 9 + ...ar-control-flow-block-settings.evaluate.js | 65 +++ .../print/angular-control-flow-block.js | 84 +++ src/language-html/print/tag.js | 1 + src/language-html/printer-html.js | 7 + src/language-html/visitor-keys.js | 2 + src/language-js/print/angular.js | 10 +- .../__snapshots__/jsfmt.spec.js.snap | 551 ++++++++++++++++++ .../angular/control-flow/after-close-tag.html | 25 + tests/format/angular/control-flow/defer.html | 68 +++ .../angular/control-flow/element-tags.html | 1 + .../__snapshots__/jsfmt.spec.js.snap | 18 + .../embedded-language-formatting-off/if.html | 2 + .../jsfmt.spec.js | 1 + tests/format/angular/control-flow/for.html | 47 ++ tests/format/angular/control-flow/if.html | 36 ++ .../format/angular/control-flow/jsfmt.spec.js | 1 + tests/format/angular/control-flow/mix.html | 10 + .../__snapshots__/jsfmt.spec.js.snap | 26 + .../small-print-width/jsfmt.spec.js | 1 + .../control-flow/small-print-width/test.html | 3 + tests/format/angular/control-flow/switch.html | 21 + .../format/angular/control-flow/unclosed.html | 3 + .../angular/control-flow/unknown-block.html | 3 + .../angular/control-flow/with-comment.html | 17 + tests/integration/__tests__/parser-api.js | 1 + tests/unit/__snapshots__/visitor-keys.js.snap | 5 + 31 files changed, 1085 insertions(+), 5 deletions(-) create mode 100644 changelog_unreleased/angular/15606.md create mode 100644 src/language-html/embed/angular-control-flow-block-parameters.js create mode 100644 src/language-html/print/angular-control-flow-block-settings.evaluate.js create mode 100644 src/language-html/print/angular-control-flow-block.js create mode 100644 tests/format/angular/control-flow/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/format/angular/control-flow/after-close-tag.html create mode 100644 tests/format/angular/control-flow/defer.html create mode 100644 tests/format/angular/control-flow/element-tags.html create mode 100644 tests/format/angular/control-flow/embedded-language-formatting-off/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/format/angular/control-flow/embedded-language-formatting-off/if.html create mode 100644 tests/format/angular/control-flow/embedded-language-formatting-off/jsfmt.spec.js create mode 100644 tests/format/angular/control-flow/for.html create mode 100644 tests/format/angular/control-flow/if.html create mode 100644 tests/format/angular/control-flow/jsfmt.spec.js create mode 100644 tests/format/angular/control-flow/mix.html create mode 100644 tests/format/angular/control-flow/small-print-width/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/format/angular/control-flow/small-print-width/jsfmt.spec.js create mode 100644 tests/format/angular/control-flow/small-print-width/test.html create mode 100644 tests/format/angular/control-flow/switch.html create mode 100644 tests/format/angular/control-flow/unclosed.html create mode 100644 tests/format/angular/control-flow/unknown-block.html create mode 100644 tests/format/angular/control-flow/with-comment.html diff --git a/changelog_unreleased/angular/15606.md b/changelog_unreleased/angular/15606.md new file mode 100644 index 000000000..99469974f --- /dev/null +++ b/changelog_unreleased/angular/15606.md @@ -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 diff --git a/src/language-html/clean.js b/src/language-html/clean.js index 7734924e4..c095b4290 100644 --- a/src/language-html/clean.js +++ b/src/language-html/clean.js @@ -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; diff --git a/src/language-html/embed.js b/src/language-html/embed.js index e06426e8a..42e5d1947 100644 --- a/src/language-html/embed.js +++ b/src/language-html/embed.js @@ -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; } } diff --git a/src/language-html/embed/angular-control-flow-block-parameters.js b/src/language-html/embed/angular-control-flow-block-parameters.js new file mode 100644 index 000000000..821944648 --- /dev/null +++ b/src/language-html/embed/angular-control-flow-block-parameters.js @@ -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; diff --git a/src/language-html/parser-html.js b/src/language-html/parser-html.js index e0ccf8649..fdfb0214d 100644 --- a/src/language-html/parser-html.js +++ b/src/language-html/parser-html.js @@ -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, diff --git a/src/language-html/print/angular-control-flow-block-settings.evaluate.js b/src/language-html/print/angular-control-flow-block-settings.evaluate.js new file mode 100644 index 000000000..6cdc57906 --- /dev/null +++ b/src/language-html/print/angular-control-flow-block-settings.evaluate.js @@ -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, +}; diff --git a/src/language-html/print/angular-control-flow-block.js b/src/language-html/print/angular-control-flow-block.js new file mode 100644 index 000000000..b579143d3 --- /dev/null +++ b/src/language-html/print/angular-control-flow-block.js @@ -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 }; diff --git a/src/language-html/print/tag.js b/src/language-html/print/tag.js index 925809567..86f5a53c8 100644 --- a/src/language-html/print/tag.js +++ b/src/language-html/print/tag.js @@ -128,6 +128,7 @@ function needsToBorrowPrevClosingTagEndMarker(node) { return ( node.prev && node.prev.type !== "docType" && + node.type !== "block" && !isTextLikeNode(node.prev) && node.isLeadingSpaceSensitive && !node.hasLeadingSpaces diff --git a/src/language-html/printer-html.js b/src/language-html/printer-html.js index ec59926f7..ee2f5c8a1 100644 --- a/src/language-html/printer-html.js +++ b/src/language-html/printer-html.js @@ -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)]; diff --git a/src/language-html/visitor-keys.js b/src/language-html/visitor-keys.js index 521da08b8..9fef1bb20 100644 --- a/src/language-html/visitor-keys.js +++ b/src/language-html/visitor-keys.js @@ -11,6 +11,8 @@ const visitorKeys = { comment: [], attribute: [], cdata: [], + block: ["children", "parameters"], + blockParameter: [], }; export default visitorKeys; diff --git a/src/language-js/print/angular.js b/src/language-js/print/angular.js index 811f00ec1..df014d74f 100644 --- a/src/language-js/print/angular.js +++ b/src/language-js/print/angular.js @@ -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 ); } diff --git a/tests/format/angular/control-flow/__snapshots__/jsfmt.spec.js.snap b/tests/format/angular/control-flow/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..d36ee42e8 --- /dev/null +++ b/tests/format/angular/control-flow/__snapshots__/jsfmt.spec.js.snap @@ -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====================================== + + @for (let item of items; index as i; trackBy: trackByFn) { + @if ( + areaId && + canTweet + ) { + + } + } + + + + + + + +@if ( + areaId && + canTweet + ) { + + } + +=====================================output===================================== + + @for (let item of items; index as i; trackBy: trackByFn) { + + @if (areaId && canTweet) { + + } + } + + + + + + +@if (areaId && canTweet) { + +} + +================================================================================ +`; + +exports[`defer.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== +@defer (when isCheckedDefer()) { + +} +@placeholder { +Placeholder +} +@error { +Error +} +@loading(minimum 1s) { +Loading... +} + + +@defer (on interaction) { +Clicked +} +@placeholder { +Placeholder (click on it!) +} + + +@defer (on hover) { +Hovered +} +@placeholder { +Placeholder (hover it!) +} + + +@defer (on idle) { +Browser has reached an idle state +} +@placeholder { +Placeholder +} + + +@defer (on timer(5s)) { +Visible after 5s +} +@placeholder { +Placeholder +} + + +@defer (on viewport) { + +} +@placeholder { +Placeholder +} + + +@defer (on interaction; prefetch on hover) { + +} +@placeholder { +Placeholder (hover it, then click on it!) +} + + +@defer (on interaction; prefetch on hover) { + +} +@placeholder { +Placeholder (hover it, then click on it!) +} + +=====================================output===================================== +@defer (when isCheckedDefer()) { + +} @placeholder { + Placeholder +} @error { + Error +} @loading (minimum 1s) { + Loading... +} + +@defer (on interaction) { + Clicked +} @placeholder { + Placeholder (click on it!) +} + +@defer (on hover) { + Hovered +} @placeholder { + Placeholder (hover it!) +} + +@defer (on idle) { + Browser has reached an idle state +} @placeholder { + Placeholder +} + +@defer (on timer(5s)) { + Visible after 5s +} @placeholder { + Placeholder +} + +@defer (on viewport) { + +} @placeholder { + Placeholder +} + +@defer (on interaction; prefetch on hover) { + +} @placeholder { + Placeholder (hover it, then click on it!) +} + +@defer (on interaction; prefetch on hover) { + +} @placeholder { + Placeholder (hover it, then click on it!) +} + +================================================================================ +`; + +exports[`element-tags.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== +@if (true) {} + +=====================================output===================================== +@if (true) { + +} + +================================================================================ +`; + +exports[`for.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== +
    +@for ( + let + + item of items;index as +i; + trackBy: trackByFn +) { +
  • {{item.name}}
  • +} +@empty { +The collection is empty +} +
+ + +
  • + + + +
      +@for (item of collection; track item.id; let index = $index, first = $first; let last = $last, even = $even, odd = $odd; let count = $count) { + +
    • {{item.name}} index={{index}} first={{first}} last={{last}} even={{even}} odd={{odd}} count={{count}}
    • + +} +
    + +
      +@for (item of + emptyCollection; track item.id;) { +
    • {{item.name}}
    • +} +@empty { + The collection is empty +} +
    +
  • + +=====================================output===================================== +
      + @for (let item of items; index as i; trackBy: trackByFn) { +
    • + {{ item.name }} +
    • + } @empty { + The collection is empty + } +
    + +
  • + +
      + @for ( + item of collection; + track item.id; + let index = $index, first = $first; + let last = $last, even = $even, odd = $odd; + let count = $count + ) { +
    • + {{ item.name }} index={{ index }} first={{ + first + }} + last={{ last }} even={{ even }} odd={{ odd }} count={{ count }} +
    • + } +
    + +
      + @for (item of emptyCollection; track: item.id) { +
    • + {{ item.name }} +
    • + } @empty { + The collection is empty + } +
    +
  • + +================================================================================ +`; + +exports[`if.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== +

    @if and @else

    +
    + +
    +
    +@if (isChecked()) { +Checked +} +@else { +Not checked +} +
    + +@if (users$ | async; as users) { {{ users.length }} } +@else if (users$ | async; as users) { {{ users.length }} } + +
    {{ users.length }}
    + +@else {} + + +@if (isDev) {} +@else if (test) {} + +@if (foo( "quotes")) {} +@else if (foo( 'quotes')) {} + +@if ( widthCategory !== undefined && widthCategory > +topStartToSideStartMaxSize) { + +} + +@if ( widthCategory; as item) { +} + +
    + +=====================================output===================================== +

    @if and @else

    +
    + +
    +
    + @if (isChecked()) { + Checked + } @else { + Not checked + } +
    + +@if (users$ | async; as users) { + {{ users.length }} +} @else if (users$ | async; as users) { + {{ users.length }} +} + +
    {{ users.length }}
    + +@else {} + +@if (isDev) { +} @else if (test) {} + +@if (foo("quotes")) { +} @else if (foo("quotes")) {} + +@if ( + widthCategory !== undefined && widthCategory > topStartToSideStartMaxSize +) { + +} + +@if (widthCategory; as item) {} + +
    + +================================================================================ +`; + +exports[`mix.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== +@if (user.isHuman) { + +} @else if (user.isRobot) { + +@defer { + +} +} @else { +

    The profile is unknown!

    +} + +=====================================output===================================== +@if (user.isHuman) { + +} @else if (user.isRobot) { + + @defer { + + } +} @else { +

    The profile is unknown!

    +} + +================================================================================ +`; + +exports[`switch.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== +@switch (radioValue()) { +@case (1) { +Case 1 +} +@case (2) { +Case 2 +} +@default { +Default case (Not 1 or 2) +} +} + + +@switch( + should +.be.formatted +) { + @case( + should +.be.formatted) {} +} + +=====================================output===================================== +@switch (radioValue()) { + @case (1) { + Case 1 + } + @case (2) { + Case 2 + } + @default { + Default case (Not 1 or 2) + } +} + +@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====================================== +
    + + Not checked +
    + + + +@if (isChecked()) { + + Checked + + +} +@else { +Not checked +} + + +=====================================output===================================== +
    + + Not checked +
    + + +@if (isChecked()) { + + Checked + + +} + +@else { + Not checked +} + + +================================================================================ +`; diff --git a/tests/format/angular/control-flow/after-close-tag.html b/tests/format/angular/control-flow/after-close-tag.html new file mode 100644 index 000000000..35dad3d61 --- /dev/null +++ b/tests/format/angular/control-flow/after-close-tag.html @@ -0,0 +1,25 @@ + + @for (let item of items; index as i; trackBy: trackByFn) { + @if ( + areaId && + canTweet + ) { + + } + } + + + + + + + +@if ( + areaId && + canTweet + ) { + + } diff --git a/tests/format/angular/control-flow/defer.html b/tests/format/angular/control-flow/defer.html new file mode 100644 index 000000000..eabeceab1 --- /dev/null +++ b/tests/format/angular/control-flow/defer.html @@ -0,0 +1,68 @@ +@defer (when isCheckedDefer()) { + +} +@placeholder { +Placeholder +} +@error { +Error +} +@loading(minimum 1s) { +Loading... +} + + +@defer (on interaction) { +Clicked +} +@placeholder { +Placeholder (click on it!) +} + + +@defer (on hover) { +Hovered +} +@placeholder { +Placeholder (hover it!) +} + + +@defer (on idle) { +Browser has reached an idle state +} +@placeholder { +Placeholder +} + + +@defer (on timer(5s)) { +Visible after 5s +} +@placeholder { +Placeholder +} + + +@defer (on viewport) { + +} +@placeholder { +Placeholder +} + + +@defer (on interaction; prefetch on hover) { + +} +@placeholder { +Placeholder (hover it, then click on it!) +} + + +@defer (on interaction; prefetch on hover) { + +} +@placeholder { +Placeholder (hover it, then click on it!) +} diff --git a/tests/format/angular/control-flow/element-tags.html b/tests/format/angular/control-flow/element-tags.html new file mode 100644 index 000000000..4126bc853 --- /dev/null +++ b/tests/format/angular/control-flow/element-tags.html @@ -0,0 +1 @@ +@if (true) {} diff --git a/tests/format/angular/control-flow/embedded-language-formatting-off/__snapshots__/jsfmt.spec.js.snap b/tests/format/angular/control-flow/embedded-language-formatting-off/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..06559da98 --- /dev/null +++ b/tests/format/angular/control-flow/embedded-language-formatting-off/__snapshots__/jsfmt.spec.js.snap @@ -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) {} + +================================================================================ +`; diff --git a/tests/format/angular/control-flow/embedded-language-formatting-off/if.html b/tests/format/angular/control-flow/embedded-language-formatting-off/if.html new file mode 100644 index 000000000..c003dde3f --- /dev/null +++ b/tests/format/angular/control-flow/embedded-language-formatting-off/if.html @@ -0,0 +1,2 @@ +@if (should. not. format) { } +@else if (should. not. format) { } diff --git a/tests/format/angular/control-flow/embedded-language-formatting-off/jsfmt.spec.js b/tests/format/angular/control-flow/embedded-language-formatting-off/jsfmt.spec.js new file mode 100644 index 000000000..32a58e992 --- /dev/null +++ b/tests/format/angular/control-flow/embedded-language-formatting-off/jsfmt.spec.js @@ -0,0 +1 @@ +run_spec(import.meta, ["angular"], { embeddedLanguageFormatting: "off" }); diff --git a/tests/format/angular/control-flow/for.html b/tests/format/angular/control-flow/for.html new file mode 100644 index 000000000..202387c04 --- /dev/null +++ b/tests/format/angular/control-flow/for.html @@ -0,0 +1,47 @@ +
      +@for ( + let + + item of items;index as +i; + trackBy: trackByFn +) { +
    • {{item.name}}
    • +} +@empty { +The collection is empty +} +
    + + +
  • + + + +
      +@for (item of collection; track item.id; let index = $index, first = $first; let last = $last, even = $even, odd = $odd; let count = $count) { + +
    • {{item.name}} index={{index}} first={{first}} last={{last}} even={{even}} odd={{odd}} count={{count}}
    • + +} +
    + +
      +@for (item of + emptyCollection; track item.id;) { +
    • {{item.name}}
    • +} +@empty { + The collection is empty +} +
    +
  • diff --git a/tests/format/angular/control-flow/if.html b/tests/format/angular/control-flow/if.html new file mode 100644 index 000000000..f3c09225f --- /dev/null +++ b/tests/format/angular/control-flow/if.html @@ -0,0 +1,36 @@ +

    @if and @else

    +
    + +
    +
    +@if (isChecked()) { +Checked +} +@else { +Not checked +} +
    + +@if (users$ | async; as users) { {{ users.length }} } +@else if (users$ | async; as users) { {{ users.length }} } + +
    {{ users.length }}
    + +@else {} + + +@if (isDev) {} +@else if (test) {} + +@if (foo( "quotes")) {} +@else if (foo( 'quotes')) {} + +@if ( widthCategory !== undefined && widthCategory > +topStartToSideStartMaxSize) { + +} + +@if ( widthCategory; as item) { +} + +
    diff --git a/tests/format/angular/control-flow/jsfmt.spec.js b/tests/format/angular/control-flow/jsfmt.spec.js new file mode 100644 index 000000000..c67dba8b4 --- /dev/null +++ b/tests/format/angular/control-flow/jsfmt.spec.js @@ -0,0 +1 @@ +run_spec(import.meta, ["angular"]); diff --git a/tests/format/angular/control-flow/mix.html b/tests/format/angular/control-flow/mix.html new file mode 100644 index 000000000..008dd7dff --- /dev/null +++ b/tests/format/angular/control-flow/mix.html @@ -0,0 +1,10 @@ +@if (user.isHuman) { + +} @else if (user.isRobot) { + +@defer { + +} +} @else { +

    The profile is unknown!

    +} diff --git a/tests/format/angular/control-flow/small-print-width/__snapshots__/jsfmt.spec.js.snap b/tests/format/angular/control-flow/small-print-width/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..4997d47ba --- /dev/null +++ b/tests/format/angular/control-flow/small-print-width/__snapshots__/jsfmt.spec.js.snap @@ -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 +) {} + +================================================================================ +`; diff --git a/tests/format/angular/control-flow/small-print-width/jsfmt.spec.js b/tests/format/angular/control-flow/small-print-width/jsfmt.spec.js new file mode 100644 index 000000000..134176e7e --- /dev/null +++ b/tests/format/angular/control-flow/small-print-width/jsfmt.spec.js @@ -0,0 +1 @@ +run_spec(import.meta, ["angular"], { printWidth: 20 }); diff --git a/tests/format/angular/control-flow/small-print-width/test.html b/tests/format/angular/control-flow/small-print-width/test.html new file mode 100644 index 000000000..dffe53b37 --- /dev/null +++ b/tests/format/angular/control-flow/small-print-width/test.html @@ -0,0 +1,3 @@ +@unknown (long expression;long expression;long expression;long expression;long expression;long expression;long expression;long expression;) { + +} diff --git a/tests/format/angular/control-flow/switch.html b/tests/format/angular/control-flow/switch.html new file mode 100644 index 000000000..f3c2bcc3a --- /dev/null +++ b/tests/format/angular/control-flow/switch.html @@ -0,0 +1,21 @@ +@switch (radioValue()) { +@case (1) { +Case 1 +} +@case (2) { +Case 2 +} +@default { +Default case (Not 1 or 2) +} +} + + +@switch( + should +.be.formatted +) { + @case( + should +.be.formatted) {} +} diff --git a/tests/format/angular/control-flow/unclosed.html b/tests/format/angular/control-flow/unclosed.html new file mode 100644 index 000000000..f25927cc8 --- /dev/null +++ b/tests/format/angular/control-flow/unclosed.html @@ -0,0 +1,3 @@ +@empty{} + +@else {} diff --git a/tests/format/angular/control-flow/unknown-block.html b/tests/format/angular/control-flow/unknown-block.html new file mode 100644 index 000000000..23bdd997c --- /dev/null +++ b/tests/format/angular/control-flow/unknown-block.html @@ -0,0 +1,3 @@ +@unknown (foo) { +} @unknown (foo) { +} diff --git a/tests/format/angular/control-flow/with-comment.html b/tests/format/angular/control-flow/with-comment.html new file mode 100644 index 000000000..0085029f7 --- /dev/null +++ b/tests/format/angular/control-flow/with-comment.html @@ -0,0 +1,17 @@ +
    + + Not checked +
    + + + +@if (isChecked()) { + + Checked + + +} +@else { +Not checked +} + diff --git a/tests/integration/__tests__/parser-api.js b/tests/integration/__tests__/parser-api.js index 5e0d78774..a77483b74 100644 --- a/tests/integration/__tests__/parser-api.js +++ b/tests/integration/__tests__/parser-api.js @@ -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: "
    ", }; for (const [name, { parse }] of parsers) { await expect( diff --git a/tests/unit/__snapshots__/visitor-keys.js.snap b/tests/unit/__snapshots__/visitor-keys.js.snap index aa6243a80..ea17e7219 100644 --- a/tests/unit/__snapshots__/visitor-keys.js.snap +++ b/tests/unit/__snapshots__/visitor-keys.js.snap @@ -1390,6 +1390,11 @@ exports[`visitor keys graphql 1`] = ` exports[`visitor keys html 1`] = ` { "attribute": [], + "block": [ + "children", + "parameters", + ], + "blockParameter": [], "cdata": [], "comment": [], "docType": [],