InvalidWriteTarget
InvalidWriteTarget
Severity: error | Type: LiquidHtml
Some writes go into a container rather than replacing it. This check reports one whose target cannot accept it, which raises at runtime.
Two rules, and three tags spell both:
| Write | Needs |
|---|---|
{% assign h['k'] = v %}, {% hash_assign h['k'] = v %}, {% function h['k'] = 'partial' %} |
a Hash with a key, or an Array with a numeric index |
{% assign a << v %}, {% function a << 'partial' %} |
an Array |
The two rules are not the same rule. A subscript write accepts a Hash and refuses a scalar; << accepts an Array and refuses a Hash.
A dot target is a key: {% assign h.k = v %} writes the key k, so an Array refuses it exactly as it refuses h['k'].
{% hash_assign %}is deprecated — prefer{% assign %}, which reaches the same runtime setter and accepts more target spellings. See DeprecatedTag.
How the target's type is known
From the documented return type of whatever assigned it, and from the tag that assigned it:
{% assign x = 1 %}— number;"hello"— string;true— boolean;(1..5)— range{% assign x = list | split: "," %}— array, from the last filter in the chain{% assign x = {"a": 1} %}or{% parse_json x %}{}{% endparse_json %}— object{% graphql x = 'query' %}— object;{% capture x %}— string
The type is tracked per scope: a write inside an {% if %} branch is not a fact after it, and a {% for %} variable does not inherit the type of the name it shadows.
Nothing is reported when the type is unknown — a {% render %} argument, a filter the documentation does not carry a return type for, a variable assigned in another file. Silence is not approval.
Examples
✗ Incorrect Code Example (Avoid using this):
{% assign counter = 0 %}
{% assign counter['key'] = "value" %}
counter is a number — the runtime raises counter is 0, expected Hash or Array.
{% assign items = "a,b,c" | split: "," %}
{% assign items['key'] = "value" %}
items is an Array, so it wants an index — the runtime raises items is an Array, expected index, key was provided.
{% parse_json data %}{}{% endparse_json %}
{% assign data << "value" %}
data is a Hash — the runtime raises data is {}, expected Array. << appends to an Array.
✓ Correct Code Example (Use this instead):
{% parse_json data %}{}{% endparse_json %}
{% assign data['key'] = "value" %}
{% assign data.other = "value" %}
{% assign items = "a,b,c" | split: "," %}
{% assign items[0] = "value" %}
{% assign items << "value" %}
Writing into a container does not replace it, so a Hash stays a Hash and an Array stays an Array for every later write.
What is not reported
- A nested subscript —
{% assign x[0]['k'] = v %}. The runtime checks the intermediate value, which requires knowing the type ofx[0]; the first subscript is still checked. - An append through a subscript —
{% assign x['k'] << v %}— for the same reason: the runtime checks the value at the subscript, not the container. - A subscript that only resolves at runtime —
{% assign x[y] = v %}on an Array, since neither Array rule can be ruled out.
Configuration
The default configuration for this check:
InvalidWriteTarget:
enabled: true
severity: error
Disabling This Check
Disabling this check is not recommended — every case it reports raises at runtime.
Resources
- platformOS
assignandhash_assigntags - LiquidHTMLSyntaxError, which reports a
hash_assigntarget the platform cannot parse at all — one ending in a dot, or one with no subscript