@vue/compiler-core 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -356,18 +356,59 @@ function isCoreComponent(tag) {
356
356
  }
357
357
  const nonIdentifierRE = /^\d|[^\$\w]/;
358
358
  const isSimpleIdentifier = (name) => !nonIdentifierRE.test(name);
359
- const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[(.+)\])*$/;
359
+ const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/;
360
+ const validIdentCharRE = /[\.\w$\xA0-\uFFFF]/;
361
+ const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g;
362
+ /**
363
+ * Simple lexer to check if an expression is a member expression. This is
364
+ * lax and only checks validity at the root level (i.e. does not validate exps
365
+ * inside square brackets), but it's ok since these are only used on template
366
+ * expressions and false positives are invalid expressions in the first place.
367
+ */
360
368
  const isMemberExpression = (path) => {
361
- if (!path)
362
- return false;
363
- const matched = memberExpRE.exec(path.trim());
364
- if (!matched)
365
- return false;
366
- if (!matched[1])
367
- return true;
368
- if (!/[\[\]]/.test(matched[1]))
369
- return true;
370
- return isMemberExpression(matched[1].trim());
369
+ // remove whitespaces around . or [ first
370
+ path = path.trim().replace(whitespaceRE, s => s.trim());
371
+ let state = 0 /* inMemberExp */;
372
+ let prevState = 0 /* inMemberExp */;
373
+ let currentOpenBracketCount = 0;
374
+ let currentStringType = null;
375
+ for (let i = 0; i < path.length; i++) {
376
+ const char = path.charAt(i);
377
+ switch (state) {
378
+ case 0 /* inMemberExp */:
379
+ if (char === '[') {
380
+ prevState = state;
381
+ state = 1 /* inBrackets */;
382
+ currentOpenBracketCount++;
383
+ }
384
+ else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {
385
+ return false;
386
+ }
387
+ break;
388
+ case 1 /* inBrackets */:
389
+ if (char === `'` || char === `"` || char === '`') {
390
+ prevState = state;
391
+ state = 2 /* inString */;
392
+ currentStringType = char;
393
+ }
394
+ else if (char === `[`) {
395
+ currentOpenBracketCount++;
396
+ }
397
+ else if (char === `]`) {
398
+ if (!--currentOpenBracketCount) {
399
+ state = prevState;
400
+ }
401
+ }
402
+ break;
403
+ case 2 /* inString */:
404
+ if (char === currentStringType) {
405
+ state = prevState;
406
+ currentStringType = null;
407
+ }
408
+ break;
409
+ }
410
+ }
411
+ return !currentOpenBracketCount;
371
412
  };
372
413
  function getInnerRange(loc, offset, length) {
373
414
  const source = loc.source.substr(offset, length);
@@ -1043,41 +1084,17 @@ function parseTag(context, type, parent) {
1043
1084
  }
1044
1085
  }
1045
1086
  let tagType = 0 /* ELEMENT */;
1046
- const options = context.options;
1047
- if (!context.inVPre && !options.isCustomElement(tag)) {
1048
- const hasVIs = props.some(p => {
1049
- if (p.name !== 'is')
1050
- return;
1051
- // v-is="xxx" (TODO: deprecate)
1052
- if (p.type === 7 /* DIRECTIVE */) {
1053
- return true;
1054
- }
1055
- // is="vue:xxx"
1056
- if (p.value && p.value.content.startsWith('vue:')) {
1057
- return true;
1058
- }
1059
- // in compat mode, any is usage is considered a component
1060
- if (checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1061
- return true;
1062
- }
1063
- });
1064
- if (options.isNativeTag && !hasVIs) {
1065
- if (!options.isNativeTag(tag))
1066
- tagType = 1 /* COMPONENT */;
1067
- }
1068
- else if (hasVIs ||
1069
- isCoreComponent(tag) ||
1070
- (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
1071
- /^[A-Z]/.test(tag) ||
1072
- tag === 'component') {
1073
- tagType = 1 /* COMPONENT */;
1074
- }
1087
+ if (!context.inVPre) {
1075
1088
  if (tag === 'slot') {
1076
1089
  tagType = 2 /* SLOT */;
1077
1090
  }
1078
- else if (tag === 'template' &&
1079
- props.some(p => p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name))) {
1080
- tagType = 3 /* TEMPLATE */;
1091
+ else if (tag === 'template') {
1092
+ if (props.some(p => p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name))) {
1093
+ tagType = 3 /* TEMPLATE */;
1094
+ }
1095
+ }
1096
+ else if (isComponent(tag, props, context)) {
1097
+ tagType = 1 /* COMPONENT */;
1081
1098
  }
1082
1099
  }
1083
1100
  return {
@@ -1092,6 +1109,49 @@ function parseTag(context, type, parent) {
1092
1109
  codegenNode: undefined // to be created during transform phase
1093
1110
  };
1094
1111
  }
1112
+ function isComponent(tag, props, context) {
1113
+ const options = context.options;
1114
+ if (options.isCustomElement(tag)) {
1115
+ return false;
1116
+ }
1117
+ if (tag === 'component' ||
1118
+ /^[A-Z]/.test(tag) ||
1119
+ isCoreComponent(tag) ||
1120
+ (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
1121
+ (options.isNativeTag && !options.isNativeTag(tag))) {
1122
+ return true;
1123
+ }
1124
+ // at this point the tag should be a native tag, but check for potential "is"
1125
+ // casting
1126
+ for (let i = 0; i < props.length; i++) {
1127
+ const p = props[i];
1128
+ if (p.type === 6 /* ATTRIBUTE */) {
1129
+ if (p.name === 'is' && p.value) {
1130
+ if (p.value.content.startsWith('vue:')) {
1131
+ return true;
1132
+ }
1133
+ else if (checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1134
+ return true;
1135
+ }
1136
+ }
1137
+ }
1138
+ else {
1139
+ // directive
1140
+ // v-is (TODO Deprecate)
1141
+ if (p.name === 'is') {
1142
+ return true;
1143
+ }
1144
+ else if (
1145
+ // :is on plain element - only treat as component in compat mode
1146
+ p.name === 'bind' &&
1147
+ isBindKey(p.arg, 'is') &&
1148
+ true &&
1149
+ checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1150
+ return true;
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1095
1155
  function parseAttributes(context, type) {
1096
1156
  const props = [];
1097
1157
  const attributeNames = new Set();
@@ -3968,16 +4028,10 @@ function resolveComponentType(node, context, ssr = false) {
3968
4028
  let { tag } = node;
3969
4029
  // 1. dynamic component
3970
4030
  const isExplicitDynamic = isComponentTag(tag);
3971
- const isProp = findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'));
4031
+ const isProp = findProp(node, 'is');
3972
4032
  if (isProp) {
3973
- if (!isExplicitDynamic && isProp.type === 6 /* ATTRIBUTE */) {
3974
- // <button is="vue:xxx">
3975
- // if not <component>, only is value that starts with "vue:" will be
3976
- // treated as component by the parse phase and reach here, unless it's
3977
- // compat mode where all is values are considered components
3978
- tag = isProp.value.content.replace(/^vue:/, '');
3979
- }
3980
- else {
4033
+ if (isExplicitDynamic ||
4034
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context))) {
3981
4035
  const exp = isProp.type === 6 /* ATTRIBUTE */
3982
4036
  ? isProp.value && createSimpleExpression(isProp.value.content, true)
3983
4037
  : isProp.exp;
@@ -3987,6 +4041,21 @@ function resolveComponentType(node, context, ssr = false) {
3987
4041
  ]);
3988
4042
  }
3989
4043
  }
4044
+ else if (isProp.type === 6 /* ATTRIBUTE */ &&
4045
+ isProp.value.content.startsWith('vue:')) {
4046
+ // <button is="vue:xxx">
4047
+ // if not <component>, only is value that starts with "vue:" will be
4048
+ // treated as component by the parse phase and reach here, unless it's
4049
+ // compat mode where all is values are considered components
4050
+ tag = isProp.value.content.slice(4);
4051
+ }
4052
+ }
4053
+ // 1.5 v-is (TODO: Deprecate)
4054
+ const isDir = !isExplicitDynamic && findDir(node, 'is');
4055
+ if (isDir && isDir.exp) {
4056
+ return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
4057
+ isDir.exp
4058
+ ]);
3990
4059
  }
3991
4060
  // 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
3992
4061
  const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag);
@@ -4130,7 +4199,9 @@ function buildProps(node, context, props = node.props, ssr = false) {
4130
4199
  }
4131
4200
  // skip is on <component>, or is="vue:xxx"
4132
4201
  if (name === 'is' &&
4133
- (isComponentTag(tag) || (value && value.content.startsWith('vue:')))) {
4202
+ (isComponentTag(tag) ||
4203
+ (value && value.content.startsWith('vue:')) ||
4204
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context)))) {
4134
4205
  continue;
4135
4206
  }
4136
4207
  properties.push(createObjectProperty(createSimpleExpression(name, true, getInnerRange(loc, 0, name.length)), createSimpleExpression(value ? value.content : '', isStatic, value ? value.loc : loc)));
@@ -4153,7 +4224,10 @@ function buildProps(node, context, props = node.props, ssr = false) {
4153
4224
  }
4154
4225
  // skip v-is and :is on <component>
4155
4226
  if (name === 'is' ||
4156
- (isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))) {
4227
+ (isVBind &&
4228
+ isBindKey(arg, 'is') &&
4229
+ (isComponentTag(tag) ||
4230
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context))))) {
4157
4231
  continue;
4158
4232
  }
4159
4233
  // skip v-on in SSR compilation
@@ -4737,7 +4811,7 @@ const transformModel = (dir, node, context) => {
4737
4811
  const maybeRef = context.inline &&
4738
4812
  bindingType &&
4739
4813
  bindingType !== "setup-const" /* SETUP_CONST */;
4740
- if (!isMemberExpression(expString) && !maybeRef) {
4814
+ if (!expString.trim() || (!isMemberExpression(expString) && !maybeRef)) {
4741
4815
  context.onError(createCompilerError(41 /* X_V_MODEL_MALFORMED_EXPRESSION */, exp.loc));
4742
4816
  return createTransformProps();
4743
4817
  }
@@ -355,18 +355,59 @@ function isCoreComponent(tag) {
355
355
  }
356
356
  const nonIdentifierRE = /^\d|[^\$\w]/;
357
357
  const isSimpleIdentifier = (name) => !nonIdentifierRE.test(name);
358
- const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[(.+)\])*$/;
358
+ const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/;
359
+ const validIdentCharRE = /[\.\w$\xA0-\uFFFF]/;
360
+ const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g;
361
+ /**
362
+ * Simple lexer to check if an expression is a member expression. This is
363
+ * lax and only checks validity at the root level (i.e. does not validate exps
364
+ * inside square brackets), but it's ok since these are only used on template
365
+ * expressions and false positives are invalid expressions in the first place.
366
+ */
359
367
  const isMemberExpression = (path) => {
360
- if (!path)
361
- return false;
362
- const matched = memberExpRE.exec(path.trim());
363
- if (!matched)
364
- return false;
365
- if (!matched[1])
366
- return true;
367
- if (!/[\[\]]/.test(matched[1]))
368
- return true;
369
- return isMemberExpression(matched[1].trim());
368
+ // remove whitespaces around . or [ first
369
+ path = path.trim().replace(whitespaceRE, s => s.trim());
370
+ let state = 0 /* inMemberExp */;
371
+ let prevState = 0 /* inMemberExp */;
372
+ let currentOpenBracketCount = 0;
373
+ let currentStringType = null;
374
+ for (let i = 0; i < path.length; i++) {
375
+ const char = path.charAt(i);
376
+ switch (state) {
377
+ case 0 /* inMemberExp */:
378
+ if (char === '[') {
379
+ prevState = state;
380
+ state = 1 /* inBrackets */;
381
+ currentOpenBracketCount++;
382
+ }
383
+ else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {
384
+ return false;
385
+ }
386
+ break;
387
+ case 1 /* inBrackets */:
388
+ if (char === `'` || char === `"` || char === '`') {
389
+ prevState = state;
390
+ state = 2 /* inString */;
391
+ currentStringType = char;
392
+ }
393
+ else if (char === `[`) {
394
+ currentOpenBracketCount++;
395
+ }
396
+ else if (char === `]`) {
397
+ if (!--currentOpenBracketCount) {
398
+ state = prevState;
399
+ }
400
+ }
401
+ break;
402
+ case 2 /* inString */:
403
+ if (char === currentStringType) {
404
+ state = prevState;
405
+ currentStringType = null;
406
+ }
407
+ break;
408
+ }
409
+ }
410
+ return !currentOpenBracketCount;
370
411
  };
371
412
  function getInnerRange(loc, offset, length) {
372
413
  const source = loc.source.substr(offset, length);
@@ -1025,41 +1066,17 @@ function parseTag(context, type, parent) {
1025
1066
  return;
1026
1067
  }
1027
1068
  let tagType = 0 /* ELEMENT */;
1028
- const options = context.options;
1029
- if (!context.inVPre && !options.isCustomElement(tag)) {
1030
- const hasVIs = props.some(p => {
1031
- if (p.name !== 'is')
1032
- return;
1033
- // v-is="xxx" (TODO: deprecate)
1034
- if (p.type === 7 /* DIRECTIVE */) {
1035
- return true;
1036
- }
1037
- // is="vue:xxx"
1038
- if (p.value && p.value.content.startsWith('vue:')) {
1039
- return true;
1040
- }
1041
- // in compat mode, any is usage is considered a component
1042
- if (checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1043
- return true;
1044
- }
1045
- });
1046
- if (options.isNativeTag && !hasVIs) {
1047
- if (!options.isNativeTag(tag))
1048
- tagType = 1 /* COMPONENT */;
1049
- }
1050
- else if (hasVIs ||
1051
- isCoreComponent(tag) ||
1052
- (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
1053
- /^[A-Z]/.test(tag) ||
1054
- tag === 'component') {
1055
- tagType = 1 /* COMPONENT */;
1056
- }
1069
+ if (!context.inVPre) {
1057
1070
  if (tag === 'slot') {
1058
1071
  tagType = 2 /* SLOT */;
1059
1072
  }
1060
- else if (tag === 'template' &&
1061
- props.some(p => p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name))) {
1062
- tagType = 3 /* TEMPLATE */;
1073
+ else if (tag === 'template') {
1074
+ if (props.some(p => p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name))) {
1075
+ tagType = 3 /* TEMPLATE */;
1076
+ }
1077
+ }
1078
+ else if (isComponent(tag, props, context)) {
1079
+ tagType = 1 /* COMPONENT */;
1063
1080
  }
1064
1081
  }
1065
1082
  return {
@@ -1074,6 +1091,49 @@ function parseTag(context, type, parent) {
1074
1091
  codegenNode: undefined // to be created during transform phase
1075
1092
  };
1076
1093
  }
1094
+ function isComponent(tag, props, context) {
1095
+ const options = context.options;
1096
+ if (options.isCustomElement(tag)) {
1097
+ return false;
1098
+ }
1099
+ if (tag === 'component' ||
1100
+ /^[A-Z]/.test(tag) ||
1101
+ isCoreComponent(tag) ||
1102
+ (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
1103
+ (options.isNativeTag && !options.isNativeTag(tag))) {
1104
+ return true;
1105
+ }
1106
+ // at this point the tag should be a native tag, but check for potential "is"
1107
+ // casting
1108
+ for (let i = 0; i < props.length; i++) {
1109
+ const p = props[i];
1110
+ if (p.type === 6 /* ATTRIBUTE */) {
1111
+ if (p.name === 'is' && p.value) {
1112
+ if (p.value.content.startsWith('vue:')) {
1113
+ return true;
1114
+ }
1115
+ else if (checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1116
+ return true;
1117
+ }
1118
+ }
1119
+ }
1120
+ else {
1121
+ // directive
1122
+ // v-is (TODO Deprecate)
1123
+ if (p.name === 'is') {
1124
+ return true;
1125
+ }
1126
+ else if (
1127
+ // :is on plain element - only treat as component in compat mode
1128
+ p.name === 'bind' &&
1129
+ isBindKey(p.arg, 'is') &&
1130
+ true &&
1131
+ checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1132
+ return true;
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1077
1137
  function parseAttributes(context, type) {
1078
1138
  const props = [];
1079
1139
  const attributeNames = new Set();
@@ -3873,16 +3933,10 @@ function resolveComponentType(node, context, ssr = false) {
3873
3933
  let { tag } = node;
3874
3934
  // 1. dynamic component
3875
3935
  const isExplicitDynamic = isComponentTag(tag);
3876
- const isProp = findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'));
3936
+ const isProp = findProp(node, 'is');
3877
3937
  if (isProp) {
3878
- if (!isExplicitDynamic && isProp.type === 6 /* ATTRIBUTE */) {
3879
- // <button is="vue:xxx">
3880
- // if not <component>, only is value that starts with "vue:" will be
3881
- // treated as component by the parse phase and reach here, unless it's
3882
- // compat mode where all is values are considered components
3883
- tag = isProp.value.content.replace(/^vue:/, '');
3884
- }
3885
- else {
3938
+ if (isExplicitDynamic ||
3939
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context))) {
3886
3940
  const exp = isProp.type === 6 /* ATTRIBUTE */
3887
3941
  ? isProp.value && createSimpleExpression(isProp.value.content, true)
3888
3942
  : isProp.exp;
@@ -3892,6 +3946,21 @@ function resolveComponentType(node, context, ssr = false) {
3892
3946
  ]);
3893
3947
  }
3894
3948
  }
3949
+ else if (isProp.type === 6 /* ATTRIBUTE */ &&
3950
+ isProp.value.content.startsWith('vue:')) {
3951
+ // <button is="vue:xxx">
3952
+ // if not <component>, only is value that starts with "vue:" will be
3953
+ // treated as component by the parse phase and reach here, unless it's
3954
+ // compat mode where all is values are considered components
3955
+ tag = isProp.value.content.slice(4);
3956
+ }
3957
+ }
3958
+ // 1.5 v-is (TODO: Deprecate)
3959
+ const isDir = !isExplicitDynamic && findDir(node, 'is');
3960
+ if (isDir && isDir.exp) {
3961
+ return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
3962
+ isDir.exp
3963
+ ]);
3895
3964
  }
3896
3965
  // 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
3897
3966
  const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag);
@@ -4035,7 +4104,9 @@ function buildProps(node, context, props = node.props, ssr = false) {
4035
4104
  }
4036
4105
  // skip is on <component>, or is="vue:xxx"
4037
4106
  if (name === 'is' &&
4038
- (isComponentTag(tag) || (value && value.content.startsWith('vue:')))) {
4107
+ (isComponentTag(tag) ||
4108
+ (value && value.content.startsWith('vue:')) ||
4109
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context)))) {
4039
4110
  continue;
4040
4111
  }
4041
4112
  properties.push(createObjectProperty(createSimpleExpression(name, true, getInnerRange(loc, 0, name.length)), createSimpleExpression(value ? value.content : '', isStatic, value ? value.loc : loc)));
@@ -4058,7 +4129,10 @@ function buildProps(node, context, props = node.props, ssr = false) {
4058
4129
  }
4059
4130
  // skip v-is and :is on <component>
4060
4131
  if (name === 'is' ||
4061
- (isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))) {
4132
+ (isVBind &&
4133
+ isBindKey(arg, 'is') &&
4134
+ (isComponentTag(tag) ||
4135
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context))))) {
4062
4136
  continue;
4063
4137
  }
4064
4138
  // skip v-on in SSR compilation
@@ -4616,7 +4690,7 @@ const transformModel = (dir, node, context) => {
4616
4690
  const maybeRef = context.inline &&
4617
4691
  bindingType &&
4618
4692
  bindingType !== "setup-const" /* SETUP_CONST */;
4619
- if (!isMemberExpression(expString) && !maybeRef) {
4693
+ if (!expString.trim() || (!isMemberExpression(expString) && !maybeRef)) {
4620
4694
  context.onError(createCompilerError(41 /* X_V_MODEL_MALFORMED_EXPRESSION */, exp.loc));
4621
4695
  return createTransformProps();
4622
4696
  }
@@ -557,6 +557,12 @@ export declare const isBuiltInType: (tag: string, expected: string) => boolean;
557
557
 
558
558
  export declare function isCoreComponent(tag: string): symbol | void;
559
559
 
560
+ /**
561
+ * Simple lexer to check if an expression is a member expression. This is
562
+ * lax and only checks validity at the root level (i.e. does not validate exps
563
+ * inside square brackets), but it's ok since these are only used on template
564
+ * expressions and false positives are invalid expressions in the first place.
565
+ */
560
566
  export declare const isMemberExpression: (path: string) => boolean;
561
567
 
562
568
  export declare const isSimpleIdentifier: (name: string) => boolean;
@@ -351,18 +351,59 @@ function isCoreComponent(tag) {
351
351
  }
352
352
  const nonIdentifierRE = /^\d|[^\$\w]/;
353
353
  const isSimpleIdentifier = (name) => !nonIdentifierRE.test(name);
354
- const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[(.+)\])*$/;
354
+ const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/;
355
+ const validIdentCharRE = /[\.\w$\xA0-\uFFFF]/;
356
+ const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g;
357
+ /**
358
+ * Simple lexer to check if an expression is a member expression. This is
359
+ * lax and only checks validity at the root level (i.e. does not validate exps
360
+ * inside square brackets), but it's ok since these are only used on template
361
+ * expressions and false positives are invalid expressions in the first place.
362
+ */
355
363
  const isMemberExpression = (path) => {
356
- if (!path)
357
- return false;
358
- const matched = memberExpRE.exec(path.trim());
359
- if (!matched)
360
- return false;
361
- if (!matched[1])
362
- return true;
363
- if (!/[\[\]]/.test(matched[1]))
364
- return true;
365
- return isMemberExpression(matched[1].trim());
364
+ // remove whitespaces around . or [ first
365
+ path = path.trim().replace(whitespaceRE, s => s.trim());
366
+ let state = 0 /* inMemberExp */;
367
+ let prevState = 0 /* inMemberExp */;
368
+ let currentOpenBracketCount = 0;
369
+ let currentStringType = null;
370
+ for (let i = 0; i < path.length; i++) {
371
+ const char = path.charAt(i);
372
+ switch (state) {
373
+ case 0 /* inMemberExp */:
374
+ if (char === '[') {
375
+ prevState = state;
376
+ state = 1 /* inBrackets */;
377
+ currentOpenBracketCount++;
378
+ }
379
+ else if (!(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)) {
380
+ return false;
381
+ }
382
+ break;
383
+ case 1 /* inBrackets */:
384
+ if (char === `'` || char === `"` || char === '`') {
385
+ prevState = state;
386
+ state = 2 /* inString */;
387
+ currentStringType = char;
388
+ }
389
+ else if (char === `[`) {
390
+ currentOpenBracketCount++;
391
+ }
392
+ else if (char === `]`) {
393
+ if (!--currentOpenBracketCount) {
394
+ state = prevState;
395
+ }
396
+ }
397
+ break;
398
+ case 2 /* inString */:
399
+ if (char === currentStringType) {
400
+ state = prevState;
401
+ currentStringType = null;
402
+ }
403
+ break;
404
+ }
405
+ }
406
+ return !currentOpenBracketCount;
366
407
  };
367
408
  function getInnerRange(loc, offset, length) {
368
409
  const source = loc.source.substr(offset, length);
@@ -1048,41 +1089,17 @@ function parseTag(context, type, parent) {
1048
1089
  }
1049
1090
  }
1050
1091
  let tagType = 0 /* ELEMENT */;
1051
- const options = context.options;
1052
- if (!context.inVPre && !options.isCustomElement(tag)) {
1053
- const hasVIs = props.some(p => {
1054
- if (p.name !== 'is')
1055
- return;
1056
- // v-is="xxx" (TODO: deprecate)
1057
- if (p.type === 7 /* DIRECTIVE */) {
1058
- return true;
1059
- }
1060
- // is="vue:xxx"
1061
- if (p.value && p.value.content.startsWith('vue:')) {
1062
- return true;
1063
- }
1064
- // in compat mode, any is usage is considered a component
1065
- if (checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1066
- return true;
1067
- }
1068
- });
1069
- if (options.isNativeTag && !hasVIs) {
1070
- if (!options.isNativeTag(tag))
1071
- tagType = 1 /* COMPONENT */;
1072
- }
1073
- else if (hasVIs ||
1074
- isCoreComponent(tag) ||
1075
- (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
1076
- /^[A-Z]/.test(tag) ||
1077
- tag === 'component') {
1078
- tagType = 1 /* COMPONENT */;
1079
- }
1092
+ if (!context.inVPre) {
1080
1093
  if (tag === 'slot') {
1081
1094
  tagType = 2 /* SLOT */;
1082
1095
  }
1083
- else if (tag === 'template' &&
1084
- props.some(p => p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name))) {
1085
- tagType = 3 /* TEMPLATE */;
1096
+ else if (tag === 'template') {
1097
+ if (props.some(p => p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name))) {
1098
+ tagType = 3 /* TEMPLATE */;
1099
+ }
1100
+ }
1101
+ else if (isComponent(tag, props, context)) {
1102
+ tagType = 1 /* COMPONENT */;
1086
1103
  }
1087
1104
  }
1088
1105
  return {
@@ -1097,6 +1114,49 @@ function parseTag(context, type, parent) {
1097
1114
  codegenNode: undefined // to be created during transform phase
1098
1115
  };
1099
1116
  }
1117
+ function isComponent(tag, props, context) {
1118
+ const options = context.options;
1119
+ if (options.isCustomElement(tag)) {
1120
+ return false;
1121
+ }
1122
+ if (tag === 'component' ||
1123
+ /^[A-Z]/.test(tag) ||
1124
+ isCoreComponent(tag) ||
1125
+ (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
1126
+ (options.isNativeTag && !options.isNativeTag(tag))) {
1127
+ return true;
1128
+ }
1129
+ // at this point the tag should be a native tag, but check for potential "is"
1130
+ // casting
1131
+ for (let i = 0; i < props.length; i++) {
1132
+ const p = props[i];
1133
+ if (p.type === 6 /* ATTRIBUTE */) {
1134
+ if (p.name === 'is' && p.value) {
1135
+ if (p.value.content.startsWith('vue:')) {
1136
+ return true;
1137
+ }
1138
+ else if (checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1139
+ return true;
1140
+ }
1141
+ }
1142
+ }
1143
+ else {
1144
+ // directive
1145
+ // v-is (TODO Deprecate)
1146
+ if (p.name === 'is') {
1147
+ return true;
1148
+ }
1149
+ else if (
1150
+ // :is on plain element - only treat as component in compat mode
1151
+ p.name === 'bind' &&
1152
+ isBindKey(p.arg, 'is') &&
1153
+ true &&
1154
+ checkCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context, p.loc)) {
1155
+ return true;
1156
+ }
1157
+ }
1158
+ }
1159
+ }
1100
1160
  function parseAttributes(context, type) {
1101
1161
  const props = [];
1102
1162
  const attributeNames = new Set();
@@ -3420,16 +3480,10 @@ function resolveComponentType(node, context, ssr = false) {
3420
3480
  let { tag } = node;
3421
3481
  // 1. dynamic component
3422
3482
  const isExplicitDynamic = isComponentTag(tag);
3423
- const isProp = findProp(node, 'is') || (!isExplicitDynamic && findDir(node, 'is'));
3483
+ const isProp = findProp(node, 'is');
3424
3484
  if (isProp) {
3425
- if (!isExplicitDynamic && isProp.type === 6 /* ATTRIBUTE */) {
3426
- // <button is="vue:xxx">
3427
- // if not <component>, only is value that starts with "vue:" will be
3428
- // treated as component by the parse phase and reach here, unless it's
3429
- // compat mode where all is values are considered components
3430
- tag = isProp.value.content.replace(/^vue:/, '');
3431
- }
3432
- else {
3485
+ if (isExplicitDynamic ||
3486
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context))) {
3433
3487
  const exp = isProp.type === 6 /* ATTRIBUTE */
3434
3488
  ? isProp.value && createSimpleExpression(isProp.value.content, true)
3435
3489
  : isProp.exp;
@@ -3439,6 +3493,21 @@ function resolveComponentType(node, context, ssr = false) {
3439
3493
  ]);
3440
3494
  }
3441
3495
  }
3496
+ else if (isProp.type === 6 /* ATTRIBUTE */ &&
3497
+ isProp.value.content.startsWith('vue:')) {
3498
+ // <button is="vue:xxx">
3499
+ // if not <component>, only is value that starts with "vue:" will be
3500
+ // treated as component by the parse phase and reach here, unless it's
3501
+ // compat mode where all is values are considered components
3502
+ tag = isProp.value.content.slice(4);
3503
+ }
3504
+ }
3505
+ // 1.5 v-is (TODO: Deprecate)
3506
+ const isDir = !isExplicitDynamic && findDir(node, 'is');
3507
+ if (isDir && isDir.exp) {
3508
+ return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
3509
+ isDir.exp
3510
+ ]);
3442
3511
  }
3443
3512
  // 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
3444
3513
  const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag);
@@ -3522,7 +3591,9 @@ function buildProps(node, context, props = node.props, ssr = false) {
3522
3591
  }
3523
3592
  // skip is on <component>, or is="vue:xxx"
3524
3593
  if (name === 'is' &&
3525
- (isComponentTag(tag) || (value && value.content.startsWith('vue:')))) {
3594
+ (isComponentTag(tag) ||
3595
+ (value && value.content.startsWith('vue:')) ||
3596
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context)))) {
3526
3597
  continue;
3527
3598
  }
3528
3599
  properties.push(createObjectProperty(createSimpleExpression(name, true, getInnerRange(loc, 0, name.length)), createSimpleExpression(value ? value.content : '', isStatic, value ? value.loc : loc)));
@@ -3545,7 +3616,10 @@ function buildProps(node, context, props = node.props, ssr = false) {
3545
3616
  }
3546
3617
  // skip v-is and :is on <component>
3547
3618
  if (name === 'is' ||
3548
- (isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))) {
3619
+ (isVBind &&
3620
+ isBindKey(arg, 'is') &&
3621
+ (isComponentTag(tag) ||
3622
+ (isCompatEnabled("COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */, context))))) {
3549
3623
  continue;
3550
3624
  }
3551
3625
  // skip v-on in SSR compilation
@@ -4089,7 +4163,7 @@ const transformModel = (dir, node, context) => {
4089
4163
  // _unref(exp)
4090
4164
  context.bindingMetadata[rawExp];
4091
4165
  const maybeRef = !true /* SETUP_CONST */;
4092
- if (!isMemberExpression(expString) && !maybeRef) {
4166
+ if (!expString.trim() || (!isMemberExpression(expString) && !maybeRef)) {
4093
4167
  context.onError(createCompilerError(41 /* X_V_MODEL_MALFORMED_EXPRESSION */, exp.loc));
4094
4168
  return createTransformProps();
4095
4169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vue/compiler-core",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "description": "@vue/compiler-core",
5
5
  "main": "index.js",
6
6
  "module": "dist/compiler-core.esm-bundler.js",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-core#readme",
34
34
  "dependencies": {
35
- "@vue/shared": "3.1.1",
35
+ "@vue/shared": "3.1.2",
36
36
  "@babel/parser": "^7.12.0",
37
37
  "@babel/types": "^7.12.0",
38
38
  "estree-walker": "^2.0.1",