|
1 | | -/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */ |
| 1 | +/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super, Node, UnaryExpression, TemplateLiteral, BinaryExpression, LogicalExpression, ConditionalExpression } from 'estree' */ |
2 | 2 | /** @import { AST, ExpressionMetadata } from '#compiler' */ |
3 | 3 | /** @import { ComponentClientTransformState } from '../../types' */ |
4 | 4 | import { walk } from 'zimmerframe'; |
@@ -74,6 +74,199 @@ function compare_expressions(a, b) { |
74 | 74 |
|
75 | 75 | return true; |
76 | 76 | } |
| 77 | +export const DYNAMIC = Symbol('DYNAMIC'); |
| 78 | + |
| 79 | +/** |
| 80 | + * |
| 81 | + * @param {Expression | Node} node |
| 82 | + * @param {ComponentClientTransformState} state |
| 83 | + * @returns {any} |
| 84 | + */ |
| 85 | +export function evaluate_static_expression(node, state) { |
| 86 | + if (node == undefined) return DYNAMIC; |
| 87 | + /** |
| 88 | + * |
| 89 | + * @param {BinaryExpression | LogicalExpression} node |
| 90 | + */ |
| 91 | + function handle_left_right_node(node) { |
| 92 | + let left = evaluate_static_expression(node?.left, state); |
| 93 | + let right = evaluate_static_expression(node?.right, state); |
| 94 | + if (left === DYNAMIC || right === DYNAMIC) { |
| 95 | + return DYNAMIC; |
| 96 | + } |
| 97 | + switch (node.operator) { |
| 98 | + case '+': |
| 99 | + return left + right; |
| 100 | + case '-': |
| 101 | + return left - right; |
| 102 | + case '&': |
| 103 | + return left & right; |
| 104 | + case '|': |
| 105 | + return left | right; |
| 106 | + case '<<': |
| 107 | + return left << right; |
| 108 | + case '>>': |
| 109 | + return left >> right; |
| 110 | + case '>': |
| 111 | + return left > right; |
| 112 | + case '<': |
| 113 | + return left < right; |
| 114 | + case '>=': |
| 115 | + return left >= right; |
| 116 | + case '<=': |
| 117 | + return left <= right; |
| 118 | + case '==': |
| 119 | + return left == right; |
| 120 | + case '===': |
| 121 | + return left === right; |
| 122 | + case '||': |
| 123 | + return left || right; |
| 124 | + case '??': |
| 125 | + return left ?? right; |
| 126 | + case '&&': |
| 127 | + return left && right; |
| 128 | + case '%': |
| 129 | + return left % right; |
| 130 | + case '>>>': |
| 131 | + return left >>> right; |
| 132 | + case '^': |
| 133 | + return left ^ right; |
| 134 | + case '**': |
| 135 | + return left ** right; |
| 136 | + case '*': |
| 137 | + return left * right; |
| 138 | + case '/': |
| 139 | + return left / right; |
| 140 | + case '!=': |
| 141 | + return left != right; |
| 142 | + case '!==': |
| 143 | + return left !== right; |
| 144 | + default: |
| 145 | + return DYNAMIC; |
| 146 | + } |
| 147 | + } |
| 148 | + /** |
| 149 | + * |
| 150 | + * @param {UnaryExpression} node |
| 151 | + */ |
| 152 | + function handle_unary_node(node) { |
| 153 | + let argument = evaluate_static_expression(node?.argument, state); |
| 154 | + if (argument === DYNAMIC) return DYNAMIC; |
| 155 | + /** |
| 156 | + * |
| 157 | + * @param {Expression} argument |
| 158 | + */ |
| 159 | + function handle_void(argument) { |
| 160 | + //@ts-ignore |
| 161 | + let evaluated = evaluate_static_expression(argument); |
| 162 | + if (evaluated !== DYNAMIC) { |
| 163 | + return undefined; |
| 164 | + } |
| 165 | + return DYNAMIC; |
| 166 | + } |
| 167 | + switch (node.operator) { |
| 168 | + case '!': |
| 169 | + return !argument; |
| 170 | + case '-': |
| 171 | + return -argument; |
| 172 | + case 'typeof': |
| 173 | + return typeof argument; |
| 174 | + case '~': |
| 175 | + return ~argument; |
| 176 | + case '+': |
| 177 | + return +argument; |
| 178 | + case 'void': |
| 179 | + return handle_void(argument); |
| 180 | + default: |
| 181 | + // `delete` is ignored, since it may have side effects |
| 182 | + return DYNAMIC; |
| 183 | + } |
| 184 | + } |
| 185 | + /** |
| 186 | + * @param {SequenceExpression} node |
| 187 | + */ |
| 188 | + function handle_sequence(node) { |
| 189 | + let is_static = node.expressions.reduce((a, b) => a && evaluate_static_expression(b, state) !== DYNAMIC, true); |
| 190 | + if (is_static) { |
| 191 | + //@ts-ignore |
| 192 | + return evaluate_static_expression(node.expressions.at(-1), state); |
| 193 | + } |
| 194 | + return DYNAMIC; |
| 195 | + } |
| 196 | + /** |
| 197 | + * @param {string} name |
| 198 | + */ |
| 199 | + function handle_ident(name) { |
| 200 | + let scope = state.scope.get(name); |
| 201 | + if (scope?.kind === 'normal') { |
| 202 | + if (scope.initial && !scope.mutated && !scope.reassigned && !scope.updated) { |
| 203 | + //@ts-ignore |
| 204 | + let evaluated = evaluate_static_expression(scope.initial, state); |
| 205 | + return evaluated; |
| 206 | + } |
| 207 | + } |
| 208 | + return DYNAMIC; |
| 209 | + } |
| 210 | + /** |
| 211 | + * @param {TemplateLiteral} node |
| 212 | + */ |
| 213 | + function handle_template(node) { |
| 214 | + const expressions = node.expressions; |
| 215 | + const is_static = expressions.reduce((a, b) => a && evaluate_static_expression(b, state) !== DYNAMIC, true); |
| 216 | + if (is_static) { |
| 217 | + let res = ''; |
| 218 | + let quasis = node.quasis; |
| 219 | + let last_was_quasi = false; |
| 220 | + let expr_index = 0; |
| 221 | + let quasi_index = 0; |
| 222 | + for (let index = 0; index < quasis.length + expressions.length; index++) { |
| 223 | + if (last_was_quasi) { |
| 224 | + res += evaluate_static_expression(expressions[expr_index++], state); |
| 225 | + last_was_quasi = false; |
| 226 | + } else { |
| 227 | + res += quasis[quasi_index++].value.cooked; |
| 228 | + last_was_quasi = true; |
| 229 | + } |
| 230 | + } |
| 231 | + return res; |
| 232 | + } |
| 233 | + return DYNAMIC; |
| 234 | + } |
| 235 | + /** |
| 236 | + * @param {ConditionalExpression} node |
| 237 | + */ |
| 238 | + function handle_ternary(node) { |
| 239 | + let test = evaluate_static_expression(node.test, state); |
| 240 | + if (test !== DYNAMIC) { |
| 241 | + if (test) { |
| 242 | + return evaluate_static_expression(node.consequent, state); |
| 243 | + } else { |
| 244 | + return evaluate_static_expression(node.alternate, state); |
| 245 | + } |
| 246 | + } |
| 247 | + return DYNAMIC; |
| 248 | + } |
| 249 | + switch (node.type) { |
| 250 | + case 'Literal': |
| 251 | + return node.value; |
| 252 | + case 'BinaryExpression': |
| 253 | + return handle_left_right_node(node); |
| 254 | + case 'LogicalExpression': |
| 255 | + return handle_left_right_node(node); |
| 256 | + case 'UnaryExpression': |
| 257 | + return handle_unary_node(node); |
| 258 | + case 'Identifier': |
| 259 | + return handle_ident(node.name); |
| 260 | + case 'SequenceExpression': |
| 261 | + return handle_sequence(node); |
| 262 | + case 'TemplateLiteral': |
| 263 | + return handle_template(node); |
| 264 | + case 'ConditionalExpression': |
| 265 | + return handle_ternary(node); |
| 266 | + default: |
| 267 | + return DYNAMIC; |
| 268 | + } |
| 269 | +} |
77 | 270 |
|
78 | 271 | /** |
79 | 272 | * @param {Array<AST.Text | AST.ExpressionTag>} values |
@@ -110,6 +303,13 @@ export function build_template_chunk( |
110 | 303 | node.expression.name !== 'undefined' || |
111 | 304 | state.scope.get('undefined') |
112 | 305 | ) { |
| 306 | + let evaluated = evaluate_static_expression(node.expression, state); |
| 307 | + if (evaluated !== DYNAMIC) { |
| 308 | + if (evaluated != null) { |
| 309 | + quasi.value.cooked += evaluated + ''; |
| 310 | + } |
| 311 | + continue; |
| 312 | + } |
113 | 313 | let value = memoize( |
114 | 314 | /** @type {Expression} */ (visit(node.expression, state)), |
115 | 315 | node.metadata.expression |
|
0 commit comments