Skip to content

Commit aaa546a

Browse files
authored
Merge pull request #1049 from Shopify/remove-unsupported-parenthesis
Add check to find/fix unsupported parenthesis
2 parents fff687a + 2fb378c commit aaa546a

File tree

5 files changed

+104
-4
lines changed

5 files changed

+104
-4
lines changed

.changeset/cuddly-yaks-run.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/theme-check-common': patch
3+
---
4+
5+
Add check to find/fix unsupported parenthesis

packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/InvalidConditionalNode.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ describe('Module: InvalidConditionalBooleanExpression', () => {
1313
'{% if true or false %}hello{% endif %}',
1414
'{% if 10 > 5 and user.active %}hello{% endif %}',
1515
'{% if price >= 100 or discount %}hello{% endif %}',
16-
'{% if (1 > 0) and (2 < 3) %}hello{% endif %}',
1716
"{% if user.name contains 'admin' or user.role == 'owner' %}hello{% endif %}",
1817
];
1918

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { expect, describe, it, vi, beforeEach } from 'vitest';
2+
import { applyFix, runLiquidCheck } from '../../../test';
3+
import { LiquidHTMLSyntaxError } from '../index';
4+
5+
describe('detectConditionalNodeUnsupportedParenthesis', async () => {
6+
it('should not report no unsupported parenthesis in the markup', async () => {
7+
const testCases = [`{% if condition %}{% endif %}`, `{% if range == (1..2) %}{% endif %}`];
8+
9+
for (const sourceCode of testCases) {
10+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
11+
expect(offenses).to.have.length(0);
12+
}
13+
});
14+
15+
it('should report when there are unsupported parenthesis in the markup', async () => {
16+
const testCases = [
17+
[
18+
`{% if (this and that) or other %}{% endif %}`,
19+
'{% if this and that or other %}{% endif %}',
20+
],
21+
[`{% if (a > b) and (c == d) %}{% endif %}`, '{% if a > b and c == d %}{% endif %}'],
22+
];
23+
24+
for (const [sourceCode, expected] of testCases) {
25+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
26+
expect(offenses).to.have.length(1);
27+
expect(offenses[0].message).to.equal('Syntax is not supported');
28+
29+
const fixed = applyFix(sourceCode, offenses[0]);
30+
expect(fixed).to.equal(expected);
31+
}
32+
});
33+
34+
it('should report when there are multiple instances of the error', async () => {
35+
const sourceCode = `{% if (a or c) %}{% endif %} {% if (a == b) %}{% endif %} {% if condition %}{% endif %}`;
36+
37+
const offenses = await runLiquidCheck(LiquidHTMLSyntaxError, sourceCode);
38+
expect(offenses).to.have.length(2);
39+
expect(offenses[0].message).to.equal('Syntax is not supported');
40+
expect(offenses[1].message).to.equal('Syntax is not supported');
41+
42+
const fixed = applyFix(sourceCode, offenses[0]);
43+
expect(fixed).to.equal(
44+
`{% if a or c %}{% endif %} {% if (a == b) %}{% endif %} {% if condition %}{% endif %}`,
45+
);
46+
47+
const fixed2 = applyFix(sourceCode, offenses[1]);
48+
expect(fixed2).to.equal(
49+
`{% if (a or c) %}{% endif %} {% if a == b %}{% endif %} {% if condition %}{% endif %}`,
50+
);
51+
});
52+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { LiquidTag } from '@shopify/liquid-html-parser';
2+
import { SourceCodeType, Problem } from '../../..';
3+
import {
4+
doesFragmentContainUnsupportedParentheses,
5+
getFragmentsInMarkup,
6+
INVALID_SYNTAX_MESSAGE,
7+
} from './utils';
8+
9+
export function detectConditionalNodeUnsupportedParenthesis(
10+
node: LiquidTag,
11+
): Problem<SourceCodeType.LiquidHtml> | undefined {
12+
if (!['if', 'elsif', 'unless'].includes(node.name)) return;
13+
if (typeof node.markup !== 'string' || !node.markup.trim()) return;
14+
15+
const markupIndex = node.source.indexOf(node.markup, node.position.start);
16+
17+
const fragments = getFragmentsInMarkup(node.markup);
18+
19+
const badFragments = fragments.filter((fragment) =>
20+
doesFragmentContainUnsupportedParentheses(fragment.value),
21+
);
22+
23+
if (badFragments.length) {
24+
return {
25+
message: INVALID_SYNTAX_MESSAGE,
26+
startIndex: markupIndex,
27+
endIndex: markupIndex + node.markup.length,
28+
fix: (corrector) => {
29+
for (const fragment of badFragments) {
30+
corrector.replace(
31+
markupIndex + fragment.index!,
32+
markupIndex + fragment.index! + fragment.value.length,
33+
fragment.value.replace(/[\(\)]/g, ''),
34+
);
35+
}
36+
},
37+
};
38+
}
39+
}

packages/theme-check-common/src/checks/liquid-html-syntax-error/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { detectInvalidEchoValue } from './checks/InvalidEchoValue';
66
import { detectInvalidConditionalNode } from './checks/InvalidConditionalNode';
77
import { detectInvalidLoopRange } from './checks/InvalidLoopRange';
88
import { detectInvalidLoopArguments } from './checks/InvalidLoopArguments';
9+
import { detectConditionalNodeUnsupportedParenthesis } from './checks/InvalidConditionalNodeParenthesis';
910

1011
type LineColPosition = {
1112
line: number;
@@ -58,13 +59,17 @@ export const LiquidHTMLSyntaxError: LiquidCheckDefinition = {
5859
const problems = [
5960
detectMultipleAssignValues(node),
6061
detectInvalidEchoValue(node),
61-
detectInvalidConditionalNode(node),
6262
detectInvalidLoopRange(node),
6363
detectInvalidLoopArguments(node, await tagsPromise),
6464
].filter(Boolean) as Problem<SourceCodeType.LiquidHtml>[];
6565

66-
if (!problems.length) {
67-
return;
66+
// Fixers for `detectConditionalNodeUnsupportedParenthesis` and `detectInvalidConditionalNode` consume
67+
// the whole node markup, so we MUST not run both.
68+
const conditionalNodeProblem =
69+
detectConditionalNodeUnsupportedParenthesis(node) || detectInvalidConditionalNode(node);
70+
71+
if (conditionalNodeProblem) {
72+
problems.push(conditionalNodeProblem);
6873
}
6974

7075
problems.forEach(context.report);

0 commit comments

Comments
 (0)