Setelah 68 Theme Inspector dan Lighthouse CI, bab terakhir bagian tools ini soal perubahan di bahasa Liquid sendiri. Liquid sekarang dikirim dengan mode error yang lebih ketat. Kalau theme atau theme app extension kamu tiba-tiba ngeluarin Liquid syntax error di kode yang “dulu jalan”, kemungkinan besar penyebabnya ada di bab ini. Dasar sintaksnya ada di 09 Dasar Liquid, 10 Tags Variabel dan Percabangan, dan 12 Tags Khusus Theme.

Kenapa Parser-nya Diperketat

Dulu Liquid nerima pola kode tertentu yang kelihatannya well-formed tapi sebenarnya belum didukung bahasanya. Parser-nya nerima kode itu, terus mengevaluasinya salah, tanpa error, tanpa peringatan. Contoh paling terkenal: assign x = false or true diterima, tapi x dievaluasi jadi false, karena operator logika di assign gak pernah didukung dan sisa ekspresinya dibuang diam-diam.

Biar bahasa Liquid bisa dikembangin supaya pola seperti itu suatu saat didukung dengan benar, parser-nya diperbarui jadi lebih ketat soal kode yang diterima. Sekarang, alih-alih nerima assign x = false or true dan ngasih false, mode ketat melaporkan kode itu gak bisa di-parse. Konsekuensinya: nanti Liquid bisa di-update buat nerima ekspresi itu dan mengevaluasinya jadi true tanpa ngerusak theme yang udah terlanjur bergantung ke perilaku salah, karena theme yang bergantung ke perilaku salah udah “dipaksa” beres duluan.

Contoh dampaknya. Dengan parser lama, syntax error dari ngasih ekspresi logika ke variabel ditekan, dan “Important content” gak pernah dirender:

{% assign x = false or true %}
{% if x %}
  Important content
{% endif %}

Dengan parser ketat, file ini ditolak dengan error yang jelas, dan kamu tahu ada yang harus diperbaiki. Ini sejalan dengan semangat UnsupportedFilterArguments dari 67 Theme Check Referensi Aturan M–V > UnsupportedFilterArguments: gagal keras lebih baik daripada salah diam-diam.

Shopify ngumpulin daftar error umum yang ketemu di theme dan theme app extension. Tiap bagian di bawah dinamai persis seperti pesan error-nya, biar kamu bisa nyari dari pesan yang muncul.

Expected end_of_string but found pipe

1. Filter di Tag render

Filter cuma boleh dipakai di output variabel ({{ }}), assign, dan echo. Di argumen render, filter bikin syntax error:

❌

{% render 'image', image: image, alt: image.alt | escape %}

✅ tarik filternya ke variabel sendiri

{% assign alt_text = image.alt | escape %}
{% render 'image', image: image, alt: alt_text %}

Dulu ini diterima tapi filternya diabaikan, jadi alt yang dikirim gak di-escape. Sekarang jadi error, dan Theme Check nangkepnya lebih dulu lewat UnsupportedFilterArguments.

Expected end_of_string but found comparison

1. Ekspresi Kondisional di Tag render

Ekspresi kondisional sebagai nilai argumen render bikin syntax error:

❌

{%
  render 'product-card',
  product: product,
  show_description: settings.x or settings.y,
%}

✅ bungkus render-nya dengan kondisi

{% if settings.x or settings.y %}
  {%
    render 'product-card',
    product: product,
    show_description: true,
  %}
{% endif %}

Kalau snippet-nya tetap harus dirender di kedua kasus (cuma show_description-nya yang beda), pakai assign bersyarat dulu:

{% liquid
  assign show_description = false
  if settings.x or settings.y
    assign show_description = true
  endif
  render 'product-card', product: product, show_description: show_description
%}

Expected end_of_string but found colon

1. with Dicampur Key-Value

with (12 Tags Khusus Theme > 3. Parameter with) ngirim satu object ke snippet. Nyampurnya dengan pasangan key-value bikin syntax error:

❌

{% render 'icon' with style: 'lock' %}

✅ hapus with

{% render 'icon', style: 'lock' %}

Varian lainnya dilaporin sebagai [:colon, ":"] is not a valid expression, yang muncul kalau ada titik dua langsung setelah with. Perbaikannya sama, hapus with::

- {% render 'icon', with: style: 'lock' %}
+ {% render 'icon', style: 'lock' %}

Kalau kamu memang mau perilaku with (di contoh di atas: nilai style di-assign ke variabel bernama icon di dalam snippet), tulis begini:

{% assign style = 'lock' %}
{% render 'icon' with style %}

2. Argumen Cacat di render

Nilai responsive hilang, jadi parser ketemu width: di tempat yang harusnya nilai:

❌

{% render 'image', lazyload: lazyload, responsive: width: 500 %}

✅

{% render 'image', lazyload: lazyload, responsive: true, width: 500 %}

Expected end_of_string but found id

1. Operator Logika di assign

Operator logika cuma boleh di if (dan unless, elsif). Ini error:

❌

{% liquid
  assign x = settings.y or settings.z
  if x
    # logic
  endif
%}

✅ pindahin ekspresinya ke if

{% liquid
  if settings.y or settings.z
    # logic
  endif
%}

Ini kasus assign x = false or true dari pembukaan. Dulu x cuma dapat settings.y dan or settings.z dibuang, jadi logika kamu salah tanpa ketahuan tiap kali settings.y false tapi settings.z true.

2. Operator Logika di case

Sama seperti variabel, when di case gak bisa pakai operator logika:

❌

{% liquid
case template.name
when 'product'
  # product logic
when 'collection' and settings.x
  # collection logic
else
  # all other templates
endcase %}

✅ pindahin kondisi tambahannya ke if di dalam when

{% liquid
case template.name
when 'product'
  # product logic
when 'collection'
  if settings.x
    # collection logic
  endif
else
  # all other templates
endcase %}

Tergantung skenarionya kamu mungkin perlu nyusun ulang logika bisnisnya, misalnya kalau maksud aslinya “collection tanpa settings.x jatuh ke else”. Ingat when boleh nerima beberapa nilai dipisah or atau koma (when 'collection', 'list-collections'), itu bukan operator logika dan tetap valid.

Expected colon but found end_of_string

1. Argumen Tanpa Nilai di Akhir

❌

{% render 'image', lazyload: lazyload, responsive %}

✅

{% render 'image', lazyload: lazyload, responsive: true %}

Expected colon but found comma

1. Variabel Dikirim Tanpa Kunci

Variabel yang dikirim ke snippet harus berbentuk pasangan key-value:

❌

{% render 'related-products', product, collection %}

✅

{% render 'related-products', product: product, collection: collection %}

Dulu ini diterima dan snippet-nya dapat… gak ada apa-apa, karena render gak ngewarisin scope (12 Tags Khusus Theme > 1. Aturan Scope: Snippet Itu Kotak Tertutup), jadi product di dalam snippet kosong dan biasanya “dibungkam” oleh if product.

Unexpected characters

Ini error serba mencakup yang kadang susah dilacak. Kebanyakan penyebabnya typo, dan nomor baris di pesan error ngarahin ke titiknya. Berikut contoh umum yang ditemuin, tapi ini bukan daftar lengkap.

1. && dan || Alih-alih and dan or

Ekspresi kondisional Liquid pakai and dan or. && dan || bikin syntax error:

❌

{% liquid
  if responsive && settings.x || settings.y
    render 'responsive-image', image: image
  endif
%}

✅

{% liquid
  if responsive and settings.x or settings.y
    render 'responsive-image', image: image
  endif
%}

Ini kesalahan paling sering dari orang yang datang dari JavaScript. Dulu parser lama nganggap responsive && settings.x || settings.y sebagai… sesuatu yang bukan maksud kamu.

2. Tanda Kurung buat Ngelompokin Kondisi

Liquid gak punya tanda kurung di ekspresi kondisional. Pakainya bikin syntax error:

❌

{% liquid
  if responsive and (settings.x or settings.y)
    render 'responsive-image', image: image
  endif
%}

✅ hapus kurungnya

{% liquid
  if responsive and settings.x or settings.y
    render 'responsive-image', image: image
  endif
%}

Hati-hati: tanpa kurung, hasilnya belum tentu sama. Liquid ngevaluasi operator dari kanan ke kiri tanpa precedence antara and dan or (09 Dasar Liquid > 2. Urutan Evaluasi), jadi responsive and settings.x or settings.y dibaca sebagai responsive and (settings.x or settings.y), yang kebetulan sama dengan maksud contoh. Tapi buat (a or b) and c kamu perlu nyusun ulang, misalnya jadi c and a or b, atau pecah jadi dua if. Selalu cek ulang logikanya setelah ngehapus kurung.

3. Ekspresi Gak Valid di case

Yang berikut ngehasilin Liquid syntax error (line 3): Unexpected character =:

❌

{% liquid
  case media.preview_image.width
    when => 550
      assign media_largest = media.preview_image | image_url: width: 550 | append: '550w'
    when => 1100
      assign media_largest = media.preview_image | image_url: width: 1100 | append: '1100w'
    # etc...
  endcase
%}

✅ hapus =>

{% liquid
  case media.preview_image.width
    when 550
      assign media_largest = media.preview_image | image_url: width: 550 | append: '550w'
    when 1100
      assign media_largest = media.preview_image | image_url: width: 1100 | append: '1100w'
    # etc...
  endcase
%}

Perhatiin juga: case/when cuma ngecek kesamaan, bukan >=. Kalau maksud aslinya “lebar minimal 550”, case bukan alat yang tepat, pakai if/elsif dengan perbandingan.

4. Komentar Harus di Baris Sendiri

Di dalam tag {% liquid %}, komentar inline # (10 Tags Variabel dan Percabangan > 2. Komentar Inline) harus di barisnya sendiri. Yang berikut ngehasilin Liquid syntax error (line 5): Unexpected character #:

❌

{% liquid
  case media.preview_image.width
    when 550
      assign media_largest = media.preview_image | image_url: width: 550 | append: '550w'
    when 1100 # this is a comment about this size
      assign media_largest = media.preview_image | image_url: width: 1100 | append: '1100w'
    # etc...
  endcase
%}

✅

{% liquid
  case media.preview_image.width
    when 550
      assign media_largest = media.preview_image | image_url: width: 550 | append: '550w'
    when 1100
      # this is a comment about this size
      assign media_largest = media.preview_image | image_url: width: 1100 | append: '1100w'
    # etc...
  endcase
%}

[:end_of_string] is not a valid expression

1. Argumen render dengan Titik Dua Tanpa Nilai

❌

{% render 'icon', style: %}

✅

{% render 'icon', style: 'lock' %}

Sering muncul dari kode yang di-generate atau dari kondisi {% if %} di tengah argumen yang ngehasilin string kosong.

Bare bracket access is not allowed

1. Lookup Global Pakai Kurung Siku Telanjang

Di parser lama, sebuah ekspresi boleh dimulai dengan kurung siku, dan Liquid nyari kunci di dalamnya di scope saat ini. Sekarang kurung siku direservasi buat sintaks array literal (fitur masa depan), jadi parser ketat nolak ekspresi yang dimulai dengan kurung siku:

❌

{% assign key = 'product' %}
{% assign global_lookup = [key] %}

✅ taruh self di depan kurungnya

{% assign key = 'product' %}
{% assign global_lookup = self[key] %}

Object self nge-resolve kunci lewat rantai lookup variabel yang sama dengan kurung telanjang, jadi perilakunya gak berubah. Berlaku juga buat kunci literal: {{ ['product'] }} jadi {{ self['product'] }}.

Akses kurung yang udah punya object di depannya, seperti product['title'] atau products[key], gak terpengaruh, itu cara akses normal dari 09 Dasar Liquid > 1. Tiga Cara Object Bisa Diakses.

Tergantung versi parser yang ngelaporin, pesannya bisa Bare bracket access is not allowed. Use self['...'] instead atau Bare bracket access is not allowed in strict2 mode.

Cara Migrasi yang Aman

Dokumentasi resminya cuma ngasih kamus error di atas, jadi langkah berikut adalah rangkuman praktis dari alat yang udah dibahas di bab-bab sebelumnya:

  1. Jalanin Theme Check (65 Theme Check Pengenalan, Konfigurasi, Commands) di seluruh theme. LiquidHTMLSyntaxError nangkep kode yang gak bisa di-parse, dan UnsupportedFilterArguments nangkep filter di argumen render/content_for, dua sumber terbesar error di bab ini.
  2. Cari pola berisiko yang gak selalu ketangkep sebagai error statis, pakai pencarian teks di editor: &&, ||, assign yang berisi or atau and, when yang berisi and, =>, render yang berisi | atau with, dan = [ (kurung telanjang).
  3. Perbaiki logikanya, bukan cuma sintaksnya. Kode yang dulu “jalan” sebenarnya jalan salah. Setelah assign x = a or b jadi if a or b, perilaku halaman berubah (jadi benar), jadi cek visual di theme editor dan preview.
  4. Tes di theme unpublished (03 Kustomisasi Theme Milik Merchant dan 05 Shopify CLI shopify theme dev) sebelum publish, dan lihat error Liquid syntax error (line N) yang muncul di preview atau di code editor waktu file disimpan.
  5. Theme app extension kena aturan yang sama, karena block-nya dirender oleh parser yang sama di storefront merchant. Perbaiki di extension-nya dan deploy versi baru.

Ringkasan

  1. Parser lama nerima pola yang gak didukung dan mengevaluasinya salah diam-diam (assign x = false or true → false); parser ketat nolak kode itu supaya Liquid bisa dikembangin ke depan tanpa ngerusak theme.
  2. found pipe: filter cuma di {{ }}, assign, echo; tarik ke variabel sebelum render.
  3. found comparison: gak ada ekspresi kondisional di argumen render; bungkus render dengan if atau assign bersyarat dulu.
  4. found colon: with gak dicampur key-value (render 'icon', style: 'lock', atau assign style lalu render 'icon' with style); tiap argumen punya nilai.
  5. found id: operator logika cuma di if/unless/elsif, gak di assign atau when; pindahin ke if.
  6. Expected colon: argumen ke render harus kunci: nilai, bukan product, collection atau responsive telanjang.
  7. Unexpected characters: and/or bukan &&/||; gak ada tanda kurung (ingat evaluasi kanan ke kiri, cek ulang logika); when 550 bukan when => 550; komentar # di {% liquid %} di baris sendiri.
  8. [:end_of_string] is not a valid expression: style: tanpa nilai.
  9. Bare bracket access: [key] jadi self[key], ['product'] jadi self['product']; product['title'] aman.
  10. Migrasi: Theme Check, cari pola berisiko, perbaiki logika (perilaku berubah jadi benar), tes di theme unpublished, berlaku juga buat theme app extension.

Bab selanjutnya kita mulai bagian Theme Store: cara kerja marketplace-nya, kenapa mau masuk ke sana, dan daftar requirements teknis, desain, dan fitur yang harus dipenuhi theme sebelum bisa disubmit.