transformExpression.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.isBuiltInIdentifier = exports.processExpression = exports.transformExpression = void 0;
  4. const shared_1 = require("@vue/shared");
  5. const compiler_core_1 = require("@vue/compiler-core");
  6. const parser_1 = require("@babel/parser");
  7. const isLiteralWhitelisted = /*#__PURE__*/ (0, shared_1.makeMap)('true,false,null,this');
  8. const transformExpression = (node, context) => {
  9. if (node.type === 5 /* NodeTypes.INTERPOLATION */) {
  10. node.content = processExpression(node.content, context);
  11. }
  12. else if (node.type === 1 /* NodeTypes.ELEMENT */) {
  13. // handle directives on element
  14. for (let i = 0; i < node.props.length; i++) {
  15. const dir = node.props[i];
  16. // do not process for v-on & v-for since they are special handled
  17. if (dir.type === 7 /* NodeTypes.DIRECTIVE */ && dir.name !== 'for') {
  18. const exp = dir.exp;
  19. const arg = dir.arg;
  20. // do not process exp if this is v-on:arg - we need special handling
  21. // for wrapping inline statements.
  22. if (exp &&
  23. exp.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ &&
  24. !(dir.name === 'on' && arg)) {
  25. dir.exp = processExpression(exp, context,
  26. // slot args must be processed as function params
  27. dir.name === 'slot');
  28. }
  29. if (arg && arg.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ && !arg.isStatic) {
  30. dir.arg = processExpression(arg, context);
  31. }
  32. }
  33. }
  34. }
  35. };
  36. exports.transformExpression = transformExpression;
  37. // Important: since this function uses Node.js only dependencies, it should
  38. // always be used with a leading !__BROWSER__ check so that it can be
  39. // tree-shaken from the browser build.
  40. function processExpression(node, context,
  41. // some expressions like v-slot props & v-for aliases should be parsed as
  42. // function params
  43. asParams = false,
  44. // v-on handler values may contain multiple statements
  45. asRawStatements = false, localVars = Object.create(context.identifiers)) {
  46. if (!context.prefixIdentifiers || !node.content.trim()) {
  47. return node;
  48. }
  49. const { inline, bindingMetadata } = context;
  50. const rewriteIdentifier = (raw, parent, id) => {
  51. const type = (0, shared_1.hasOwn)(bindingMetadata, raw) && bindingMetadata[raw];
  52. if (inline) {
  53. // x = y
  54. const isAssignmentLVal = parent && parent.type === 'AssignmentExpression' && parent.left === id;
  55. // x++
  56. const isUpdateArg = parent && parent.type === 'UpdateExpression' && parent.argument === id;
  57. // ({ x } = y)
  58. const isDestructureAssignment = parent && (0, compiler_core_1.isInDestructureAssignment)(parent, parentStack);
  59. if (type === "setup-const" /* BindingTypes.SETUP_CONST */ ||
  60. type === "setup-reactive-const" /* BindingTypes.SETUP_REACTIVE_CONST */ ||
  61. localVars[raw]) {
  62. return raw;
  63. }
  64. else if (type === "setup-ref" /* BindingTypes.SETUP_REF */) {
  65. return `${raw}.value`;
  66. }
  67. else if (type === "setup-maybe-ref" /* BindingTypes.SETUP_MAYBE_REF */) {
  68. // const binding that may or may not be ref
  69. // if it's not a ref, then assignments don't make sense -
  70. // so we ignore the non-ref assignment case and generate code
  71. // that assumes the value to be a ref for more efficiency
  72. return isAssignmentLVal || isUpdateArg || isDestructureAssignment
  73. ? `${raw}.value`
  74. : `${context.helperString(compiler_core_1.UNREF)}(${raw})`;
  75. }
  76. else if (type === "setup-let" /* BindingTypes.SETUP_LET */) {
  77. if (isAssignmentLVal) {
  78. // let binding.
  79. // this is a bit more tricky as we need to cover the case where
  80. // let is a local non-ref value, and we need to replicate the
  81. // right hand side value.
  82. // x = y --> isRef(x) ? x.value = y : x = y
  83. const { right: rVal, operator } = parent;
  84. const rExp = rawExp.slice(rVal.start - 1, rVal.end - 1);
  85. const rExpString = stringifyExpression(processExpression((0, compiler_core_1.createSimpleExpression)(rExp, false), context, false, false, knownIds));
  86. return `${context.helperString(compiler_core_1.IS_REF)}(${raw})${context.isTS ? ` //@ts-ignore\n` : ``} ? ${raw}.value ${operator} ${rExpString} : ${raw}`;
  87. }
  88. else if (isUpdateArg) {
  89. // make id replace parent in the code range so the raw update operator
  90. // is removed
  91. id.start = parent.start;
  92. id.end = parent.end;
  93. const { prefix: isPrefix, operator } = parent;
  94. const prefix = isPrefix ? operator : ``;
  95. const postfix = isPrefix ? `` : operator;
  96. // let binding.
  97. // x++ --> isRef(a) ? a.value++ : a++
  98. return `${context.helperString(compiler_core_1.IS_REF)}(${raw})${context.isTS ? ` //@ts-ignore\n` : ``} ? ${prefix}${raw}.value${postfix} : ${prefix}${raw}${postfix}`;
  99. }
  100. else if (isDestructureAssignment) {
  101. // TODO
  102. // let binding in a destructure assignment - it's very tricky to
  103. // handle both possible cases here without altering the original
  104. // structure of the code, so we just assume it's not a ref here
  105. // for now
  106. return raw;
  107. }
  108. else {
  109. return `${context.helperString(compiler_core_1.UNREF)}(${raw})`;
  110. }
  111. }
  112. else if (type === "props" /* BindingTypes.PROPS */) {
  113. // use __props which is generated by compileScript so in ts mode
  114. // it gets correct type
  115. return (0, shared_1.genPropsAccessExp)(raw);
  116. }
  117. else if (type === "props-aliased" /* BindingTypes.PROPS_ALIASED */) {
  118. // prop with a different local alias (from defineProps() destructure)
  119. return (0, shared_1.genPropsAccessExp)(bindingMetadata.__propsAliases[raw]);
  120. }
  121. }
  122. else {
  123. if (type && type.startsWith('setup')) {
  124. // setup bindings in non-inline mode
  125. return `$setup.${raw}`;
  126. }
  127. else if (type === "props-aliased" /* BindingTypes.PROPS_ALIASED */) {
  128. return `$props['${bindingMetadata.__propsAliases[raw]}']`;
  129. }
  130. else if (type) {
  131. return `$${type}.${raw}`;
  132. }
  133. }
  134. // fallback to ctx
  135. return `_ctx.${raw}`;
  136. };
  137. // fast path if expression is a simple identifier.
  138. const rawExp = node.content;
  139. // bail constant on parens (function invocation) and dot (member access)
  140. const bailConstant = rawExp.indexOf(`(`) > -1 || rawExp.indexOf('.') > 0;
  141. if ((0, compiler_core_1.isSimpleIdentifier)(rawExp)) {
  142. const isScopeVarReference = context.identifiers[rawExp];
  143. const isAllowedGlobal = (0, shared_1.isGloballyWhitelisted)(rawExp);
  144. const isLiteral = isLiteralWhitelisted(rawExp);
  145. const isFilter = context.filters.includes(rawExp);
  146. const isBuiltIn = isBuiltInIdentifier(rawExp);
  147. if (!asParams &&
  148. !isScopeVarReference &&
  149. !isAllowedGlobal &&
  150. !isLiteral &&
  151. !isFilter &&
  152. !isBuiltIn) {
  153. // const bindings exposed from setup can be skipped for patching but
  154. // cannot be hoisted to module scope
  155. if (bindingMetadata[node.content] === "setup-const" /* BindingTypes.SETUP_CONST */) {
  156. node.constType = 1 /* ConstantTypes.CAN_SKIP_PATCH */;
  157. }
  158. node.content = rewriteIdentifier(rawExp);
  159. }
  160. else if (!isScopeVarReference) {
  161. if (isLiteral) {
  162. node.constType = 3 /* ConstantTypes.CAN_STRINGIFY */;
  163. }
  164. else {
  165. node.constType = 2 /* ConstantTypes.CAN_HOIST */;
  166. }
  167. }
  168. return node;
  169. }
  170. let ast;
  171. // exp needs to be parsed differently:
  172. // 1. Multiple inline statements (v-on, with presence of `;`): parse as raw
  173. // exp, but make sure to pad with spaces for consistent ranges
  174. // 2. Expressions: wrap with parens (for e.g. object expressions)
  175. // 3. Function arguments (v-for, v-slot): place in a function argument position
  176. const source = asRawStatements
  177. ? ` ${rawExp} `
  178. : `(${rawExp})${asParams ? `=>{}` : ``}`;
  179. try {
  180. ast = (0, parser_1.parse)(source, {
  181. plugins: context.expressionPlugins,
  182. }).program;
  183. }
  184. catch (e) {
  185. context.onError((0, compiler_core_1.createCompilerError)(45 /* ErrorCodes.X_INVALID_EXPRESSION */, node.loc, undefined, '\n' + source + '\n' + e.message));
  186. return node;
  187. }
  188. const ids = [];
  189. const parentStack = [];
  190. const knownIds = Object.create(context.identifiers);
  191. context.filters.forEach((name) => {
  192. knownIds[name] = 1;
  193. });
  194. (0, compiler_core_1.walkIdentifiers)(ast, (node, parent, _, isReferenced, isLocal) => {
  195. if ((0, compiler_core_1.isStaticPropertyKey)(node, parent)) {
  196. return;
  197. }
  198. const needPrefix = isReferenced && canPrefix(node);
  199. if (needPrefix && !isLocal) {
  200. if ((0, compiler_core_1.isStaticProperty)(parent) && parent.shorthand) {
  201. // property shorthand like { foo }, we need to add the key since
  202. // we rewrite the value
  203. ;
  204. node.prefix = `${node.name}: `;
  205. }
  206. node.name = rewriteIdentifier(node.name, parent, node);
  207. ids.push(node);
  208. }
  209. else {
  210. // The identifier is considered constant unless it's pointing to a
  211. // local scope variable (a v-for alias, or a v-slot prop)
  212. if (!(needPrefix && isLocal) && !bailConstant) {
  213. ;
  214. node.isConstant = true;
  215. }
  216. // also generate sub-expressions for other identifiers for better
  217. // source map support. (except for property keys which are static)
  218. ids.push(node);
  219. }
  220. }, true, // invoke on ALL identifiers
  221. parentStack, knownIds);
  222. // We break up the compound expression into an array of strings and sub
  223. // expressions (for identifiers that have been prefixed). In codegen, if
  224. // an ExpressionNode has the `.children` property, it will be used instead of
  225. // `.content`.
  226. const children = [];
  227. ids.sort((a, b) => a.start - b.start);
  228. ids.forEach((id, i) => {
  229. // range is offset by -1 due to the wrapping parens when parsed
  230. const start = id.start - 1;
  231. const end = id.end - 1;
  232. const last = ids[i - 1];
  233. const leadingText = rawExp.slice(last ? last.end - 1 : 0, start);
  234. if (leadingText.length || id.prefix) {
  235. children.push(leadingText + (id.prefix || ``));
  236. }
  237. const source = rawExp.slice(start, end);
  238. children.push((0, compiler_core_1.createSimpleExpression)(id.name, false, {
  239. source,
  240. start: (0, compiler_core_1.advancePositionWithClone)(node.loc.start, source, start),
  241. end: (0, compiler_core_1.advancePositionWithClone)(node.loc.start, source, end),
  242. }, id.isConstant ? 3 /* ConstantTypes.CAN_STRINGIFY */ : 0 /* ConstantTypes.NOT_CONSTANT */));
  243. if (i === ids.length - 1 && end < rawExp.length) {
  244. children.push(rawExp.slice(end));
  245. }
  246. });
  247. let ret;
  248. if (children.length) {
  249. ret = (0, compiler_core_1.createCompoundExpression)(children, node.loc);
  250. }
  251. else {
  252. ret = node;
  253. ret.constType = bailConstant
  254. ? 0 /* ConstantTypes.NOT_CONSTANT */
  255. : 3 /* ConstantTypes.CAN_STRINGIFY */;
  256. }
  257. ret.identifiers = Object.keys(knownIds);
  258. return ret;
  259. }
  260. exports.processExpression = processExpression;
  261. function canPrefix(id) {
  262. // skip whitelisted globals
  263. if ((0, shared_1.isGloballyWhitelisted)(id.name)) {
  264. return false;
  265. }
  266. // special case for webpack compilation
  267. if (id.name === 'require') {
  268. return false;
  269. }
  270. return true;
  271. }
  272. function stringifyExpression(exp) {
  273. if ((0, shared_1.isString)(exp)) {
  274. return exp;
  275. }
  276. else if (exp.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */) {
  277. return exp.content;
  278. }
  279. else {
  280. return exp.children
  281. .map(stringifyExpression)
  282. .join('');
  283. }
  284. }
  285. const builtInIdentifiers = ['__l'];
  286. function isBuiltInIdentifier(id) {
  287. if (!(0, shared_1.isString)(id)) {
  288. if (id.type !== 4 /* NodeTypes.SIMPLE_EXPRESSION */) {
  289. return false;
  290. }
  291. id = id.content;
  292. }
  293. return builtInIdentifiers.includes(id);
  294. }
  295. exports.isBuiltInIdentifier = isBuiltInIdentifier;