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 errordi 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:
- Jalanin Theme Check (65 Theme Check Pengenalan, Konfigurasi, Commands) di seluruh theme.
LiquidHTMLSyntaxErrornangkep kode yang gak bisa di-parse, danUnsupportedFilterArgumentsnangkep filter di argumenrender/content_for, dua sumber terbesar error di bab ini. - Cari pola berisiko yang gak selalu ketangkep sebagai error statis, pakai pencarian teks di editor:
&&,||,assignyang berisioratauand,whenyang berisiand,=>,renderyang berisi|atauwith, dan= [(kurung telanjang). - Perbaiki logikanya, bukan cuma sintaksnya. Kode yang dulu “jalan” sebenarnya jalan salah. Setelah
assign x = a or bjadiif a or b, perilaku halaman berubah (jadi benar), jadi cek visual di theme editor dan preview. - Tes di theme unpublished (03 Kustomisasi Theme Milik Merchant dan 05 Shopify CLI
shopify theme dev) sebelum publish, dan lihat errorLiquid syntax error (line N)yang muncul di preview atau di code editor waktu file disimpan. - 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
- 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. - found pipe: filter cuma di
{{ }},assign,echo; tarik ke variabel sebelumrender. - found comparison: gak ada ekspresi kondisional di argumen
render; bungkusrenderdenganifatauassignbersyarat dulu. - found colon:
withgak dicampur key-value (render 'icon', style: 'lock', atauassign stylelalurender 'icon' with style); tiap argumen punya nilai. - found id: operator logika cuma di
if/unless/elsif, gak diassignatauwhen; pindahin keif. - Expected colon: argumen ke
renderharuskunci: nilai, bukanproduct, collectionatauresponsivetelanjang. - Unexpected characters:
and/orbukan&&/||; gak ada tanda kurung (ingat evaluasi kanan ke kiri, cek ulang logika);when 550bukanwhen => 550; komentar#di{% liquid %}di baris sendiri. - [:end_of_string] is not a valid expression:
style:tanpa nilai. - Bare bracket access:
[key]jadiself[key],['product']jadiself['product'];product['title']aman. - 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.