diff --git a/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/InvalidLoopArguments.spec.ts b/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/InvalidLoopArguments.spec.ts index 29bacff93..150048d3d 100644 --- a/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/InvalidLoopArguments.spec.ts +++ b/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/InvalidLoopArguments.spec.ts @@ -1,5 +1,5 @@ import { expect, describe, it } from 'vitest'; -import { applyFix, runLiquidCheck } from '../../../test'; +import { applyFix, autofix, runLiquidCheck } from '../../../test'; import { LiquidHTMLSyntaxError } from '../index'; describe('detectInvalidLoopArguments', async () => { @@ -17,6 +17,16 @@ describe('detectInvalidLoopArguments', async () => { } }); + it('does not flag or rewrite a quoted lookup key containing spaces', async () => { + const sourceCode = + "{%- for value in product.options_by_name['hello world'].values -%}{{ value }}{%- endfor -%}"; + const offenses = await testCheck(sourceCode); + + expect(offenses).to.have.length(0); + const fixed = await autofix({ 'file.liquid': sourceCode }, offenses); + expect(fixed['file.liquid']).to.equal(sourceCode); + }); + it('should report when invalid args are found', async () => { const testCases = [ [ diff --git a/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/utils.spec.ts b/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/utils.spec.ts index 2f6d6706c..56c99e572 100644 --- a/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/utils.spec.ts +++ b/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/utils.spec.ts @@ -57,6 +57,17 @@ describe('getFragmentsInMarkup', () => { expect(values).toContainEqual(expect.objectContaining({ value: '( 1 .. 10 )' })); expect(values).toContainEqual(expect.objectContaining({ value: '( 1 ... 10 )' })); }); + + it('should keep spaces inside quoted bracket lookups within one fragment', () => { + const markup = "value in product.options_by_name['hello world'].values"; + const values = getFragmentsInMarkup(markup); + + expect(values.map(({ value }) => value)).toEqual([ + 'value', + 'in', + "product.options_by_name['hello world'].values", + ]); + }); }); describe('fragmentKeyValuePair', () => { diff --git a/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/utils.ts b/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/utils.ts index 8ae1976fb..63878acc3 100644 --- a/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/utils.ts +++ b/packages/theme-check-common/src/checks/liquid-html-syntax-error/checks/utils.ts @@ -20,6 +20,12 @@ const REGULAR_TOKEN = `[^\\s,]+`; // tokens separated by commas or spaces // Quoted strings pattern (combination of double and single quoted) const QUOTED_STRING = `(${DOUBLE_QUOTED_STRING}|${SINGLE_QUOTED_STRING})`; +// Keep a lookup such as `product.options_by_name['hello world'].values` +// together when splitting markup into space-separated fragments. +const BRACKET_LOOKUP = `\\[(?:${QUOTED_STRING}|[^\\]]+)\\]`; +const LOOKUP_ID = `[a-zA-Z_][\\w-]*\\??`; +const VARIABLE_LOOKUP = `(?:${LOOKUP_ID}(?:\\.${LOOKUP_ID})*|${BRACKET_LOOKUP})(?:\\.${LOOKUP_ID}|${BRACKET_LOOKUP})*`; + // Value pattern for key-value pairs (can be quoted, parenthesized, or regular token) const VALUE_PATTERN = `(${QUOTED_STRING}|${RANGE_MARKUP_REGEX}|${REGULAR_TOKEN})`; @@ -27,7 +33,7 @@ const VALUE_PATTERN = `(${QUOTED_STRING}|${RANGE_MARKUP_REGEX}|${REGULAR_TOKEN}) const KEY_VALUE_PAIR = `(\\S+):\\s*${VALUE_PATTERN}`; const MARKUP_FRAGMENTS_PATTERN = new RegExp( - `${QUOTED_STRING}|${RANGE_MARKUP_REGEX}|${KEY_VALUE_PAIR}|${REGULAR_TOKEN}`, + `${QUOTED_STRING}|${RANGE_MARKUP_REGEX}|${KEY_VALUE_PAIR}|${VARIABLE_LOOKUP}|${REGULAR_TOKEN}`, 'g', );