| 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 | "$@"
|