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 |
|
30 | set -o nounset
|
31 | set -o pipefail
|
32 | set -o errexit
|
33 |
|
34 | banner() {
|
35 | echo
|
36 | echo "*** $@ ***"
|
37 | echo
|
38 | }
|
39 |
|
40 | js-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 |
|
101 | control-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 |
|
112 | use-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 |
|
153 | branches() {
|
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 |
|
215 | loop() {
|
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 |
|
286 | use-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 | "$@"
|