From e762eba8244e2bf9e048fa576db8a100dd9d5bdb Mon Sep 17 00:00:00 2001 From: SinhSinh An Date: Sat, 18 Apr 2026 13:41:50 -0500 Subject: [PATCH] feat(prettier-plugin-liquid): support prettier-ignore-start / prettier-ignore-end ranges Add range-based ignoring to the Liquid prettier plugin, allowing users to preserve formatting across multiple nodes without needing to add a prettier-ignore comment before each one. Supports both HTML and Liquid comment syntax: / {% # prettier-ignore-start %} / {% # prettier-ignore-end %} Nodes between the start and end markers are printed as raw source text, while nodes outside the range continue to be formatted normally. Closes #1150 --- .changeset/bright-ignore-range.md | 5 ++ .../src/printer/utils/node.ts | 80 ++++++++++++++++++- .../test/prettier-ignore-range/fixed.liquid | 41 ++++++++++ .../test/prettier-ignore-range/index.liquid | 41 ++++++++++ .../test/prettier-ignore-range/index.spec.ts | 6 ++ 5 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 .changeset/bright-ignore-range.md create mode 100644 packages/prettier-plugin-liquid/src/test/prettier-ignore-range/fixed.liquid create mode 100644 packages/prettier-plugin-liquid/src/test/prettier-ignore-range/index.liquid create mode 100644 packages/prettier-plugin-liquid/src/test/prettier-ignore-range/index.spec.ts diff --git a/.changeset/bright-ignore-range.md b/.changeset/bright-ignore-range.md new file mode 100644 index 000000000..cd7a30270 --- /dev/null +++ b/.changeset/bright-ignore-range.md @@ -0,0 +1,5 @@ +--- +'@shopify/prettier-plugin-liquid': minor +--- + +Add support for prettier-ignore-start / prettier-ignore-end range-based ignoring in Liquid templates diff --git a/packages/prettier-plugin-liquid/src/printer/utils/node.ts b/packages/prettier-plugin-liquid/src/printer/utils/node.ts index 42c723bc1..938e60cd9 100644 --- a/packages/prettier-plugin-liquid/src/printer/utils/node.ts +++ b/packages/prettier-plugin-liquid/src/printer/utils/node.ts @@ -156,7 +156,85 @@ export function isPrettierIgnoreNode( } export function hasPrettierIgnore(node: LiquidHtmlNode) { - return isPrettierIgnoreNode(node) || isPrettierIgnoreNode(node.prev); + return ( + isPrettierIgnoreNode(node) || + isPrettierIgnoreNode(node.prev) || + isPrettierIgnoreRangeStartNode(node) || + isPrettierIgnoreRangeEndNode(node) || + isInsidePrettierIgnoreRange(node) + ); +} + +export function isPrettierIgnoreRangeStartHtmlNode( + node: LiquidHtmlNode | undefined, +): node is HtmlComment { + return ( + !!node && + node.type === NodeTypes.HtmlComment && + /^\s*prettier-ignore-start(?=\s|$)/m.test(node.body) + ); +} + +export function isPrettierIgnoreRangeStartLiquidNode( + node: LiquidHtmlNode | undefined, +): node is LiquidTag { + return ( + !!node && + node.type === NodeTypes.LiquidTag && + node.name === '#' && + /^\s*prettier-ignore-start(?=\s|$)/m.test(node.markup) + ); +} + +export function isPrettierIgnoreRangeStartNode( + node: LiquidHtmlNode | undefined, +): node is HtmlComment | LiquidTag { + return isPrettierIgnoreRangeStartHtmlNode(node) || isPrettierIgnoreRangeStartLiquidNode(node); +} + +export function isPrettierIgnoreRangeEndHtmlNode( + node: LiquidHtmlNode | undefined, +): node is HtmlComment { + return ( + !!node && + node.type === NodeTypes.HtmlComment && + /^\s*prettier-ignore-end(?=\s|$)/m.test(node.body) + ); +} + +export function isPrettierIgnoreRangeEndLiquidNode( + node: LiquidHtmlNode | undefined, +): node is LiquidTag { + return ( + !!node && + node.type === NodeTypes.LiquidTag && + node.name === '#' && + /^\s*prettier-ignore-end(?=\s|$)/m.test(node.markup) + ); +} + +export function isPrettierIgnoreRangeEndNode( + node: LiquidHtmlNode | undefined, +): node is HtmlComment | LiquidTag { + return isPrettierIgnoreRangeEndHtmlNode(node) || isPrettierIgnoreRangeEndLiquidNode(node); +} + +/** + * Walk backward through siblings to determine if a node is inside an + * unmatched prettier-ignore-start / prettier-ignore-end range. + */ +export function isInsidePrettierIgnoreRange(node: LiquidHtmlNode): boolean { + let current = node.prev; + while (current) { + if (isPrettierIgnoreRangeEndNode(current)) { + return false; + } + if (isPrettierIgnoreRangeStartNode(current)) { + return true; + } + current = current.prev; + } + return false; } function getPrettierIgnoreAttributeCommentData(value: string): boolean { diff --git a/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/fixed.liquid b/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/fixed.liquid new file mode 100644 index 000000000..5b2ae7dac --- /dev/null +++ b/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/fixed.liquid @@ -0,0 +1,41 @@ +It should preserve formatting inside html prettier-ignore-start / prettier-ignore-end range + +
+ {{ some_variable | complicated_filter: foo: bar }} + {% render 'component', foo: bar %} +
+ + +It should preserve formatting inside liquid comment prettier-ignore-start / prettier-ignore-end range +{% # prettier-ignore-start %} +
+ {{ some_variable | complicated_filter: foo: bar }} + {% render 'component', foo: bar %} +
+{% # prettier-ignore-end %} + +It should only ignore nodes between start and end, formatting nodes outside normally +printWidth: 40 +

{{ a | append: 'b' }}

+ +
{{ some_variable | complicated_filter: foo: bar }}
+ +

{{ a | append: 'b' }}

+ +It should handle multiple ignore ranges in the same parent +printWidth: 40 +

{{ a | append: 'b' }}

+ +
{{ x | y: z }}
+ +

{{ a | append: 'b' }}

+ +{{ m | n: o }} + +

{{ a | append: 'b' }}

+ +It should handle liquid comment ranges with multiple nodes +{% # prettier-ignore-start %} +{%capture foo%}{% for x in (0 .. 1) %}{% cycle a,b,c %}{% endfor %}{%endcapture%} +
{{ x | y: z }}
+{% # prettier-ignore-end %} diff --git a/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/index.liquid b/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/index.liquid new file mode 100644 index 000000000..54053b95e --- /dev/null +++ b/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/index.liquid @@ -0,0 +1,41 @@ +It should preserve formatting inside html prettier-ignore-start / prettier-ignore-end range + +
+ {{ some_variable | complicated_filter: foo: bar }} + {% render 'component', foo: bar %} +
+ + +It should preserve formatting inside liquid comment prettier-ignore-start / prettier-ignore-end range +{% # prettier-ignore-start %} +
+ {{ some_variable | complicated_filter: foo: bar }} + {% render 'component', foo: bar %} +
+{% # prettier-ignore-end %} + +It should only ignore nodes between start and end, formatting nodes outside normally +printWidth: 40 +

{{ a | append: 'b' }}

+ +
{{ some_variable | complicated_filter: foo: bar }}
+ +

{{ a | append: 'b' }}

+ +It should handle multiple ignore ranges in the same parent +printWidth: 40 +

{{ a | append: 'b' }}

+ +
{{ x | y: z }}
+ +

{{ a | append: 'b' }}

+ +{{ m | n: o }} + +

{{ a | append: 'b' }}

+ +It should handle liquid comment ranges with multiple nodes +{% # prettier-ignore-start %} +{%capture foo%}{% for x in (0 .. 1) %}{% cycle a,b,c %}{% endfor %}{%endcapture%} +
{{ x | y: z }}
+{% # prettier-ignore-end %} diff --git a/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/index.spec.ts b/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/index.spec.ts new file mode 100644 index 000000000..b61662cde --- /dev/null +++ b/packages/prettier-plugin-liquid/src/test/prettier-ignore-range/index.spec.ts @@ -0,0 +1,6 @@ +import { test } from 'vitest'; +import { assertFormattedEqualsFixed } from '../test-helpers'; + +test('Unit: prettier-ignore-start / prettier-ignore-end range', async () => { + await assertFormattedEqualsFixed(__dirname); +});