Skip to content

Commit 5a98157

Browse files
aswamyclaude
andcommitted
Add specs for bare-bracket rejection in strict2 and self keyword
- strict2 rejects bare-bracket access (['product'], [key], etc.) - Qualified bracket access (product['title']) remains valid - `self` keyword resolves variables through the scope chain - `self` works in all parsing modes (lax, strict, strict2) - Local assigns shadow the `self` keyword Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1447b40 commit 5a98157

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
---
2+
# Specs for bare-bracket rejection in strict2 and the `self` keyword.
3+
#
4+
# In strict2 mode, bare-bracket variable access (e.g. {{ ['product'] }})
5+
# is rejected. The `self` keyword provides an explicit way to perform
6+
# dynamic variable lookups: {{ self[key] }}.
7+
#
8+
# `self` resolves to a SelfDrop that walks the normal variable scope
9+
# chain (local > file > global) without exposing context internals.
10+
11+
# -- strict2 rejects bare-bracket access --
12+
13+
- name: strict2_rejects_bare_bracket_string_variable
14+
template: "{{ ['product'] }}"
15+
error_mode: strict2
16+
errors:
17+
parse_error:
18+
- "Bare bracket access is not allowed"
19+
hint: |
20+
In strict2 mode, bare-bracket access like ['product'] is rejected.
21+
The parser should raise a SyntaxError when it encounters an open
22+
square bracket at the start of an expression.
23+
24+
- name: strict2_rejects_bare_bracket_double_quoted
25+
template: '{{ ["product"] }}'
26+
error_mode: strict2
27+
errors:
28+
parse_error:
29+
- "Bare bracket access is not allowed"
30+
hint: |
31+
Double-quoted bare-bracket access is also rejected in strict2 mode.
32+
33+
- name: strict2_rejects_bare_bracket_dynamic_lookup
34+
template: "{{ [key] }}"
35+
error_mode: strict2
36+
errors:
37+
parse_error:
38+
- "Bare bracket access is not allowed"
39+
hint: |
40+
Dynamic variable lookup via bare brackets is rejected in strict2 mode.
41+
Use self[key] instead.
42+
43+
- name: strict2_rejects_bare_bracket_in_for
44+
template: "{% for item in ['collection'] %}{{ item }}{% endfor %}"
45+
error_mode: strict2
46+
errors:
47+
parse_error:
48+
- "Bare bracket access is not allowed"
49+
hint: |
50+
Bare brackets in for loop collections are rejected in strict2 mode.
51+
52+
- name: strict2_rejects_bare_bracket_in_if
53+
template: "{% if ['product'] == true %}hello{% endif %}"
54+
error_mode: strict2
55+
errors:
56+
parse_error:
57+
- "Bare bracket access is not allowed"
58+
hint: |
59+
Bare brackets in if conditions are rejected in strict2 mode.
60+
61+
- name: strict2_rejects_bare_bracket_in_case
62+
template: "{% case ['product'] %}{% when 'a' %}hello{% endcase %}"
63+
error_mode: strict2
64+
errors:
65+
parse_error:
66+
- "Bare bracket access is not allowed"
67+
hint: |
68+
Bare brackets in case expressions are rejected in strict2 mode.
69+
70+
- name: strict2_rejects_bare_bracket_in_assign
71+
template: "{% assign x = ['product'] %}"
72+
error_mode: strict2
73+
errors:
74+
parse_error:
75+
- "Bare bracket access is not allowed"
76+
hint: |
77+
Bare brackets in assign values are rejected in strict2 mode.
78+
79+
# -- strict2 accepts qualified bracket access --
80+
81+
- name: strict2_accepts_qualified_bracket_access
82+
template: "{{ product['title'] }}"
83+
environment:
84+
product:
85+
title: Cool
86+
error_mode: strict2
87+
expected: "Cool"
88+
hint: |
89+
Bracket notation on a named variable (product['title']) is still
90+
valid in strict2 mode. Only bare brackets at the start of an
91+
expression are rejected.
92+
93+
- name: strict2_accepts_dot_notation
94+
template: "{{ product.title }}"
95+
environment:
96+
product:
97+
title: Cool
98+
error_mode: strict2
99+
expected: "Cool"
100+
hint: |
101+
Dot notation is always valid in strict2 mode.
102+
103+
# -- `self` keyword works in all modes --
104+
105+
- name: self_bracket_access_resolves_variable
106+
template: "{{ self['product'] }}"
107+
environment:
108+
product: shoes
109+
expected: "shoes"
110+
hint: |
111+
self['product'] resolves the variable 'product' through the normal
112+
scope chain. The `self` keyword returns a SelfDrop that provides
113+
variable-only access to the current context.
114+
115+
- name: self_bracket_access_strict2
116+
template: "{{ self['product'] }}"
117+
environment:
118+
product: shoes
119+
error_mode: strict2
120+
expected: "shoes"
121+
hint: |
122+
self['product'] is valid in strict2 mode - it's the replacement
123+
for bare-bracket access like ['product'].
124+
125+
- name: self_dynamic_lookup
126+
template: "{{ self[key] }}"
127+
environment:
128+
key: target
129+
target: found it
130+
expected: "found it"
131+
hint: |
132+
self[key] performs a dynamic variable lookup: first resolves 'key'
133+
to get 'target', then looks up 'target' in the scope chain.
134+
135+
- name: self_dynamic_lookup_strict2
136+
template: "{{ self[key] }}"
137+
environment:
138+
key: target
139+
target: found it
140+
error_mode: strict2
141+
expected: "found it"
142+
hint: |
143+
self[key] is the strict2-compatible way to do dynamic lookups.
144+
In lax mode, [key] works but is rejected in strict2.
145+
146+
- name: self_sees_local_assigns
147+
template: "{% assign product = 'local' %}{{ self['product'] }}"
148+
environment:
149+
product: global
150+
expected: "local"
151+
hint: |
152+
self walks the normal scope chain (local > file > global).
153+
A local assign shadows the global variable, and self['product']
154+
returns the local value.
155+
156+
- name: self_can_be_assigned
157+
template: "{% assign self = 'hello' %}{{ self }}"
158+
expected: "hello"
159+
hint: |
160+
If 'self' is explicitly assigned as a local variable, the local
161+
value takes precedence over the SelfDrop. This allows templates
162+
that already use 'self' as a variable name to continue working.
163+
164+
- name: self_returns_empty_for_unknown_keys
165+
template: "{{ self['nonexistent'] }}"
166+
environment:
167+
product: shoes
168+
expected: ""
169+
hint: |
170+
self['nonexistent'] returns nil (rendered as empty string) when
171+
the key doesn't exist in any scope.
172+
173+
- name: self_nested_property_access
174+
template: "{{ self['product'].title }}"
175+
environment:
176+
product:
177+
title: Shoes
178+
expected: "Shoes"
179+
hint: |
180+
After resolving self['product'] to the product hash, further
181+
property access (.title) works as expected.
182+
183+
# -- lax mode still allows bare brackets --
184+
185+
- name: lax_allows_bare_bracket_access
186+
template: "{{ ['product'] }}"
187+
environment:
188+
product: shoes
189+
error_mode: :lax
190+
expected: "shoes"
191+
hint: |
192+
Bare-bracket access is still allowed in lax mode for backwards
193+
compatibility. Only strict2 mode rejects it.

0 commit comments

Comments
 (0)