OILS / demo / survey-static-names.sh View on Github | oils.pub

315 lines, 63 significant
1#!/usr/bin/env bash
2#
3# Survey static name analysis with JS let const
4#
5# Usage:
6# demo/survey-static-names.sh <function name>
7#
8# Static checks - SyntaxError
9# let const
10#
11# Dynamic checks - SyntaxError
12#
13# YSH TODO:
14# - var setvar check should be dynamic, not static
15# - e.g. setvar assumes the cell already exists
16# - var var check - not sure if this can be done statically
17# - depends on block scope, and blocks can be evalInFrame()
18# - const within functions
19# - can we bring this back?
20# - it is OK within loops, now that we have
21#
22# - And I think
23# x = 'const' # always available? For syntax highlighting
24# const x = 'const' # this is the synonym
25#
26# But we prefer
27# - "bare" style in Attr blocks
28# - explicit const everywhere else
29
30set -o nounset
31set -o pipefail
32set -o errexit
33
34banner() {
35 echo
36 echo "*** $@ ***"
37 echo
38}
39
40js-var() {
41 set +o errexit
42
43 banner 'LET LET'
44 nodejs -e '
45 function outer() {
46 let x = "X_outer";
47 let x = "Y_outer";
48 }
49 '
50
51 banner 'LET CONST'
52 nodejs -e '
53 function outer() {
54 let x = "X_outer";
55 const x = "Y_outer";
56 }
57 '
58
59 banner 'OK - LET CONST in inner scope'
60 nodejs -e '
61 function outer() {
62 let x = "X_outer";
63 if (true) {
64 const x = "Y_outer";
65 }
66 }
67 '
68
69 banner 'LET VAR - disallowed'
70 nodejs -e '
71 function outer() {
72 var x = "Y_outer";
73 let x = "X_outer";
74 }
75 '
76
77 banner 'CONST MUTATE'
78 nodejs -e '
79 function outer() {
80 const x = "Y_outer";
81
82 # Oh this is a dynamic check? Does not fail
83 x = "mutate"
84 }
85 '
86
87 banner 'USE BEFORE LET'
88 nodejs -e '
89 function outer() {
90 // fails dynamically with ReferenceError
91 console.log(`use before let: x=${x}`);
92 let x = "foo";
93 }
94
95 outer();
96 '
97
98 echo
99}
100
101control-flow() {
102 banner 'BREAK'
103
104 # this is a syntax error! OK good
105 nodejs -e '
106 function f() {
107 break;
108 }
109'
110}
111
112use-strict-static() {
113 set +o errexit
114
115 banner 'DUPE PARAMS'
116 nodejs -e '
117 "use strict";
118 function f(a, a) {
119 return 42;
120 }
121 '
122
123 banner 'OCTAL'
124 nodejs -e '
125 "use strict";
126 function f(a) {
127 return 0123;
128 }
129 '
130
131 banner 'WITH'
132
133 nodejs -e '
134 "use strict";
135 function f() {
136 with (x) {
137 console.log(x);
138 }
139 }
140 '
141
142 banner 'OBJECT KEYS'
143
144 # Claude AI hallucinated this duplicate object keys, and then corrected itself
145 nodejs -e '
146 "use strict";
147 function f() {
148 return {a:1, a:2};
149 }
150 '
151}
152
153branches() {
154 set +o errexit
155
156 # spec/ysh-user-feedback - Julian
157
158 # JavaScript allows this because it's block scoped
159 banner 'IF'
160 nodejs -e '
161 function f(x) {
162 if (x === 2) {
163 let tmp = "hello"
164 } else {
165 let tmp = "world"
166 // This is an error
167 // let tmp = "z"
168 }
169 //console.log(tmp);
170 }
171
172 f(1);
173 f(2);
174 '
175
176 banner 'SWITCH'
177 nodejs -e '
178 function f(x) {
179 switch (x) {
180 case 1:
181 let tmp = "hello"
182 break;
183 case 2:
184 let tmp = "world"
185 break;
186 }
187 }
188
189 f(1);
190 f(2);
191 '
192
193 banner 'SWITCH BLOCK'
194 nodejs -e '
195 function f(x) {
196 switch (x) {
197 case 1: {
198 let tmp = "hello"
199 console.log(tmp);
200 break;
201 }
202 case 2: {
203 let tmp = "world"
204 console.log(tmp);
205 break;
206 }
207 }
208 }
209
210 f(1);
211 f(2);
212 '
213}
214
215loop() {
216 banner 'MODIFY FOR'
217 nodejs -e '
218 function f() {
219 for (let x of [1, 2]) {
220 console.log(`x = ${x}`);
221 x = 3;
222 console.log(x);
223 }
224 }
225
226 f();
227 '
228
229 # Hm why is this allowed?
230 banner 'LET LET'
231 nodejs -e '
232 function f() {
233 for (let x of [1, 2]) {
234 // console.log(`x = ${x}`);
235 let x = 3;
236 console.log(x);
237 }
238 }
239
240 f();
241 '
242 # Claude AI claims that there are two nested scopes, but I'm not so sure
243 # It seemed to enter an infinite loop where the code analysis didn't agree
244 # with it
245
246 # It also refers to "loop initialization scope" and "loop body scope"
247
248 # Another attempt:
249
250 # "What the specification actually describes is more precise and technical.
251 # For a for...of loop like for (let x of [1,2]) { let x = 3 }, the ECMAScript
252 # spec (as of ES2022) describes the behavior using concepts like:
253
254 # "Per-iteration binding instantiation - Each iteration of the loop creates a
255 # new lexical environment for the loop variable
256 #
257 # "Block scoping - The {} of the loop body creates its own lexical environment
258
259 # "According to the specification, for loops with let declarations create a
260 # fresh binding (variable) for each iteration of the loop. The loop body then
261 # creates another lexical environment (scope) where another binding with the
262 # same name can exist independently.
263
264 # "The precise section in the ECMAScript spec that addresses this is
265 # typically found in sections covering "for statement" execution semantics.
266 # The loop iteration variable and the loop body variable are in different
267 # lexical environments in the specification's terminology, rather than
268 # different "scopes" as I informally described.
269
270 banner 'LET x y'
271 nodejs -e '
272
273 // Uh this is weird too, y = 1?
274 function f() {
275 for (let x = 0, y = x + 1; x < 5; ++x) {
276 console.log(`x = ${x}, y = ${y}`);
277 //let x = 3;
278 }
279 }
280
281 f();
282 '
283}
284
285
286use-strict-dynamic() {
287 set +o errexit
288
289 banner 'STRICT UNDEF'
290 nodejs -e '
291 "use strict";
292
293 function outer() {
294 let y = x + 1; // ReferenceError
295 console.log(`x=${x}`);
296 }
297
298 outer();
299 '
300
301 banner 'STRICT MUTATE'
302 nodejs -e '
303 // Use strict prevents global mutation! But only at runtime
304 "use strict";
305
306 function outer() {
307 x = "mutate"
308 }
309
310 outer();
311 console.log(`was global created? x=${x}`);
312 '
313}
314
315"$@"