diff --git a/packages/@lwc/errors/src/compiler/error-info/index.ts b/packages/@lwc/errors/src/compiler/error-info/index.ts
index 4129bedf60..ac79220451 100644
--- a/packages/@lwc/errors/src/compiler/error-info/index.ts
+++ b/packages/@lwc/errors/src/compiler/error-info/index.ts
@@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
/**
- * Next error code: 1209
+ * Next error code: 1211
*/
export * from './compiler';
diff --git a/packages/@lwc/errors/src/compiler/error-info/template-transform.ts b/packages/@lwc/errors/src/compiler/error-info/template-transform.ts
index 70fa8f14b0..d3a6729647 100644
--- a/packages/@lwc/errors/src/compiler/error-info/template-transform.ts
+++ b/packages/@lwc/errors/src/compiler/error-info/template-transform.ts
@@ -980,7 +980,7 @@ export const ParserDiagnostics = {
url: '',
},
- COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED_COMPLEX: {
+ COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED_CTE_UNQUOTED: {
code: 1207,
message:
'Template expression doesn\'t allow computed property access unless the expression is surrounded by quotes: "{0}"',
@@ -988,11 +988,27 @@ export const ParserDiagnostics = {
url: '',
},
- INVALID_NODE_COMPLEX: {
+ INVALID_NODE_CTE_UNQUOTED: {
code: 1208,
message:
'Template expression doesn\'t allow {0} unless the expression is surrounded by quotes: "{1}"',
level: DiagnosticLevel.Error,
url: '',
},
+
+ COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED_CTE_API_VERSION: {
+ code: 1209,
+ message:
+ "Template expression doesn't allow computed property access. The current component API version ({1}) is insufficient and must be increased to at least {2} for this type of expression.",
+ level: DiagnosticLevel.Error,
+ url: '',
+ },
+
+ INVALID_NODE_CTE_API_VERSION: {
+ code: 1210,
+ message:
+ "Template expression doesn't allow {0}. The current component API version ({1}) is insufficient and must be increased to at least {2} for this type of expression.",
+ level: DiagnosticLevel.Error,
+ url: '',
+ },
};
diff --git a/packages/@lwc/shared/src/api-version.ts b/packages/@lwc/shared/src/api-version.ts
index 72bc130103..bb5ef0b881 100644
--- a/packages/@lwc/shared/src/api-version.ts
+++ b/packages/@lwc/shared/src/api-version.ts
@@ -14,6 +14,9 @@ export const enum APIVersion {
V61_250_SUMMER_24 = 61,
V62_252_WINTER_25 = 62,
V63_254_SPRING_25 = 63,
+ V64_256_SUMMER_25 = 64,
+ V65_258_WINTER_26 = 65,
+ V66_260_SPRING_26 = 66,
}
// These must be updated when the enum is updated.
@@ -27,6 +30,9 @@ const allVersions = [
APIVersion.V61_250_SUMMER_24,
APIVersion.V62_252_WINTER_25,
APIVersion.V63_254_SPRING_25,
+ APIVersion.V64_256_SUMMER_25,
+ APIVersion.V65_258_WINTER_26,
+ APIVersion.V66_260_SPRING_26,
];
const allVersionsSet = /*@__PURE__@*/ new Set(allVersions);
export const LOWEST_API_VERSION = allVersions[0];
@@ -118,31 +124,38 @@ export const enum APIFeature {
}
/**
- *
* @param apiVersionFeature
- * @param apiVersion
*/
-export function isAPIFeatureEnabled(
- apiVersionFeature: APIFeature,
- apiVersion: APIVersion
-): boolean {
+export function minApiVersion(apiVersionFeature: APIFeature): APIVersion {
switch (apiVersionFeature) {
case APIFeature.LOWERCASE_SCOPE_TOKENS:
case APIFeature.TREAT_ALL_PARSE5_ERRORS_AS_ERRORS:
- return apiVersion >= APIVersion.V59_246_WINTER_24;
+ return APIVersion.V59_246_WINTER_24;
case APIFeature.DISABLE_OBJECT_REST_SPREAD_TRANSFORMATION:
case APIFeature.SKIP_UNNECESSARY_REGISTER_DECORATORS:
case APIFeature.USE_COMMENTS_FOR_FRAGMENT_BOOKENDS:
case APIFeature.USE_FRAGMENTS_FOR_LIGHT_DOM_SLOTS:
- return apiVersion >= APIVersion.V60_248_SPRING_24;
+ return APIVersion.V60_248_SPRING_24;
case APIFeature.ENABLE_ELEMENT_INTERNALS_AND_FACE:
case APIFeature.USE_LIGHT_DOM_SLOT_FORWARDING:
- return apiVersion >= APIVersion.V61_250_SUMMER_24;
+ return APIVersion.V61_250_SUMMER_24;
case APIFeature.ENABLE_THIS_DOT_HOST_ELEMENT:
case APIFeature.ENABLE_THIS_DOT_STYLE:
case APIFeature.TEMPLATE_CLASS_NAME_OBJECT_BINDING:
- return apiVersion >= APIVersion.V62_252_WINTER_25;
+ return APIVersion.V62_252_WINTER_25;
case APIFeature.ENABLE_COMPLEX_TEMPLATE_EXPRESSIONS:
- return apiVersion >= APIVersion.V63_254_SPRING_25;
+ return APIVersion.V66_260_SPRING_26;
}
}
+
+/**
+ *
+ * @param apiVersionFeature
+ * @param apiVersion
+ */
+export function isAPIFeatureEnabled(
+ apiVersionFeature: APIFeature,
+ apiVersion: APIVersion
+): boolean {
+ return apiVersion >= minApiVersion(apiVersionFeature);
+}
diff --git a/packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts b/packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts
index 0af9fa2537..559a30ae3d 100644
--- a/packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts
+++ b/packages/@lwc/ssr-compiler/src/__tests__/fixtures.spec.ts
@@ -61,7 +61,7 @@ async function compileFixture({
}: {
entry: string;
dirname: string;
- experimentalComplexExpressions: boolean;
+ experimentalComplexExpressions: boolean | undefined;
}) {
const modulesDir = path.resolve(dirname, './modules');
const outputFile = path.resolve(dirname, './dist/compiled-experimental-ssr.js');
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/expression.ts b/packages/@lwc/ssr-compiler/src/compile-template/expression.ts
index f554571f12..e2e764ae54 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/expression.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/expression.ts
@@ -5,6 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { bindExpression } from '@lwc/template-compiler';
+import { APIFeature, isAPIFeatureEnabled } from '@lwc/shared';
import type {
ComplexExpression as IrComplexExpression,
Expression as IrExpression,
@@ -18,11 +19,17 @@ export function expressionIrToEs(
node: IrExpression | IrComplexExpression,
cxt: TransformerContext
): EsExpression {
+ const isComplexTemplateExpressionEnabled =
+ cxt.templateOptions.experimentalComplexExpressions &&
+ isAPIFeatureEnabled(
+ APIFeature.ENABLE_COMPLEX_TEMPLATE_EXPRESSIONS,
+ cxt.templateOptions.apiVersion
+ );
return bindExpression(
node as IrComplexExpression,
- (n: EsIdentifier) => cxt.isLocalVar((n as EsIdentifier).name),
+ (n: EsIdentifier) => cxt.isLocalVar(n.name),
'instance',
- cxt.templateOptions.experimentalComplexExpressions
+ isComplexTemplateExpressionEnabled
);
}
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/index.ts b/packages/@lwc/ssr-compiler/src/compile-template/index.ts
index c07acc51b1..cd9c1784b8 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/index.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/index.ts
@@ -116,10 +116,12 @@ export default function compileTemplate(
(directive) => directive.name === 'PreserveComments'
)?.value?.value;
const experimentalComplexExpressions = Boolean(options.experimentalComplexExpressions);
+ const apiVersion = Number(options.apiVersion);
const { addImport, getImports, statements, cxt } = templateIrToEsTree(root, {
preserveComments,
experimentalComplexExpressions,
+ apiVersion,
});
addImport(['renderStylesheets', 'hasScopedStaticStylesheets']);
for (const [imports, source] of getStylesheetImports(filename)) {
diff --git a/packages/@lwc/ssr-compiler/src/compile-template/types.ts b/packages/@lwc/ssr-compiler/src/compile-template/types.ts
index 73f244a87d..385132bd78 100644
--- a/packages/@lwc/ssr-compiler/src/compile-template/types.ts
+++ b/packages/@lwc/ssr-compiler/src/compile-template/types.ts
@@ -48,4 +48,5 @@ export interface TransformerContext {
export interface TemplateOpts {
preserveComments: boolean;
experimentalComplexExpressions: boolean;
+ apiVersion: number;
}
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/actual.html b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/actual.html
new file mode 100644
index 0000000000..c392a95ca2
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/actual.html
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/ast.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/ast.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/ast.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/config.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/config.json
new file mode 100644
index 0000000000..00f1c239e3
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/config.json
@@ -0,0 +1,4 @@
+{
+ "experimentalComplexExpressions": true,
+ "apiVersion": 59
+}
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/expected.js b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/expected.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/metadata.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/metadata.json
new file mode 100644
index 0000000000..ba8bf0fe67
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-attribute/metadata.json
@@ -0,0 +1,15 @@
+{
+ "warnings": [
+ {
+ "code": 1210,
+ "message": "Invalid expression {foo()} - LWC1210: Template expression doesn't allow CallExpression. The current component API version (59) is insufficient and must be increased to at least 66 for this type of expression.",
+ "level": 1,
+ "location": {
+ "line": 3,
+ "column": 18,
+ "start": 42,
+ "length": 13
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/actual.html b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/actual.html
new file mode 100644
index 0000000000..e05f5f409c
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/actual.html
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/ast.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/ast.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/ast.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/config.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/config.json
new file mode 100644
index 0000000000..00f1c239e3
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/config.json
@@ -0,0 +1,4 @@
+{
+ "experimentalComplexExpressions": true,
+ "apiVersion": 59
+}
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/expected.js b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/expected.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/metadata.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/metadata.json
new file mode 100644
index 0000000000..d09517a49b
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-call-expr-text-node/metadata.json
@@ -0,0 +1,15 @@
+{
+ "warnings": [
+ {
+ "code": 1210,
+ "message": "Invalid expression {foo()} - LWC1210: Template expression doesn't allow CallExpression. The current component API version (59) is insufficient and must be increased to at least 66 for this type of expression.",
+ "level": 1,
+ "location": {
+ "line": 3,
+ "column": 18,
+ "start": 42,
+ "length": 7
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/actual.html b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/actual.html
new file mode 100644
index 0000000000..2afc794893
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/actual.html
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/ast.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/ast.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/ast.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/config.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/config.json
new file mode 100644
index 0000000000..00f1c239e3
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/config.json
@@ -0,0 +1,4 @@
+{
+ "experimentalComplexExpressions": true,
+ "apiVersion": 59
+}
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/expected.js b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/expected.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/metadata.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/metadata.json
new file mode 100644
index 0000000000..086ed12b46
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-attribute/metadata.json
@@ -0,0 +1,15 @@
+{
+ "warnings": [
+ {
+ "code": 1209,
+ "message": "Invalid expression {classNames[0]} - LWC1209: Template expression doesn't allow computed property access. The current component API version (59) is insufficient and must be increased to at least 66 for this type of expression.",
+ "level": 1,
+ "location": {
+ "line": 3,
+ "column": 12,
+ "start": 36,
+ "length": 23
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/actual.html b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/actual.html
new file mode 100644
index 0000000000..38c09182be
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/actual.html
@@ -0,0 +1,3 @@
+
+ {val[state.foo]}
+
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/ast.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/ast.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/ast.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/config.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/config.json
new file mode 100644
index 0000000000..00f1c239e3
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/config.json
@@ -0,0 +1,4 @@
+{
+ "experimentalComplexExpressions": true,
+ "apiVersion": 59
+}
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/expected.js b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/expected.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/metadata.json b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/metadata.json
new file mode 100644
index 0000000000..d8d1f798f3
--- /dev/null
+++ b/packages/@lwc/template-compiler/src/__tests__/fixtures/expression-complex/invalid/invalid-api-version-computed-prop-node/metadata.json
@@ -0,0 +1,15 @@
+{
+ "warnings": [
+ {
+ "code": 1209,
+ "message": "Invalid expression {val[state.foo]} - LWC1209: Template expression doesn't allow computed property access. The current component API version (59) is insufficient and must be increased to at least 66 for this type of expression.",
+ "level": 1,
+ "location": {
+ "line": 1,
+ "column": 11,
+ "start": 10,
+ "length": 22
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/@lwc/template-compiler/src/parser/attribute.ts b/packages/@lwc/template-compiler/src/parser/attribute.ts
index a71e261b76..ea94e97e05 100644
--- a/packages/@lwc/template-compiler/src/parser/attribute.ts
+++ b/packages/@lwc/template-compiler/src/parser/attribute.ts
@@ -24,7 +24,7 @@ import {
isExpression,
isPotentialExpression,
} from './expression';
-import { isComplexTemplateExpressionEnabled } from './expression-complex';
+
import {
ATTR_NAME,
DATA_RE,
@@ -111,7 +111,9 @@ export function normalizeAttributeValue(
const isQuoted = isQuotedAttribute(rawAttrVal);
const isEscaped = isEscapedAttribute(rawAttrVal);
if (!isEscaped && isExpression(value)) {
- if (isQuoted && !isComplexTemplateExpressionEnabled(ctx)) {
+ // Don't test for the API version here, just check if CTE is enabled.
+ // We can provide more specific errors w.r.t API versions after the expression has been parsed and we know what it is.
+ if (isQuoted && !ctx.config.experimentalComplexExpressions) {
//
// -> ambiguity if the attribute value is a template identifier or a string literal.
diff --git a/packages/@lwc/template-compiler/src/parser/expression.ts b/packages/@lwc/template-compiler/src/parser/expression.ts
index 9d68abe7be..9313965f3c 100644
--- a/packages/@lwc/template-compiler/src/parser/expression.ts
+++ b/packages/@lwc/template-compiler/src/parser/expression.ts
@@ -7,12 +7,13 @@
import { parseExpressionAt, isIdentifierStart, isIdentifierChar } from 'acorn';
import { ParserDiagnostics, invariant } from '@lwc/errors';
+import { APIFeature, minApiVersion } from '@lwc/shared';
import * as t from '../shared/estree';
import { isReservedES6Keyword } from './utils/javascript';
+import { isComplexTemplateExpressionEnabled } from './expression-complex';
import type { Expression, Identifier, SourceLocation } from '../shared/types';
import type ParserCtx from './parser';
-import type { NormalizedConfig } from '../config';
import type { Node } from 'acorn';
export const EXPRESSION_SYMBOL_START = '{';
@@ -30,40 +31,71 @@ export function isPotentialExpression(source: string): boolean {
return !!source.match(POTENTIAL_EXPRESSION_RE);
}
+const minCteApiVersion = minApiVersion(APIFeature.ENABLE_COMPLEX_TEMPLATE_EXPRESSIONS);
+
function validateExpression(
source: string,
node: t.BaseNode,
- config: NormalizedConfig
+ ctx: ParserCtx,
+ unquotedAttributeExpression: boolean
): asserts node is Expression {
- const isValidNode = t.isIdentifier(node) || t.isMemberExpression(node);
- // INVALID_XYZ_COMPLEX provides additional context to the user if CTE is enabled.
- // The author may not have delimited the CTE with quotes, resulting in it being parsed
- // as a legacy expression.
- invariant(
- isValidNode,
- config.experimentalComplexExpressions
- ? ParserDiagnostics.INVALID_NODE_COMPLEX
- : ParserDiagnostics.INVALID_NODE,
- [node.type, source]
- );
-
- if (t.isMemberExpression(node)) {
+ const cteOnlyNode = !t.isIdentifier(node) && !t.isMemberExpression(node);
+
+ // If this node is not an identifier or a member expression (the only two nodes allowed if complexTemplateExpressions are disabled),
+ // then we throw if the following invariants do not hold true.
+ if (cteOnlyNode) {
+ // complexTemplateExpressions must be enabled if this is a cteOnlyNode.
+ invariant(ctx.config.experimentalComplexExpressions, ParserDiagnostics.INVALID_NODE, [
+ node.type,
+ ]);
+ // complexTemplateExpressions must be enabled and the component API version must be sufficient.
invariant(
- config.experimentalComputedMemberExpression || !node.computed,
- config.experimentalComplexExpressions
- ? ParserDiagnostics.COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED_COMPLEX
- : ParserDiagnostics.COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED,
- [source]
+ isComplexTemplateExpressionEnabled(ctx),
+ ParserDiagnostics.INVALID_NODE_CTE_API_VERSION,
+ [node.type, ctx.apiVersion, minCteApiVersion]
);
+ // complexTemplateExpressions must be enabled, the component API version must be sufficient and the expression should not be
+ // an unquoted attribute expression.
+ invariant(
+ isComplexTemplateExpressionEnabled(ctx) && !unquotedAttributeExpression,
+ ParserDiagnostics.INVALID_NODE_CTE_UNQUOTED,
+ [node.type, source]
+ );
+ }
+
+ if (t.isMemberExpression(node)) {
+ // If this is a computed node and experimentalComputedMemberExpressions is not enabled,
+ // then we throw if the following invariants do not hold true.
+ if (!ctx.config.experimentalComputedMemberExpression && node.computed) {
+ // complexTemplateExpressions must be enabled.
+ invariant(
+ ctx.config.experimentalComplexExpressions,
+ ParserDiagnostics.COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED,
+ [source]
+ );
+ // complexTemplateExpressions must be enabled and the component API version must be sufficient.
+ invariant(
+ isComplexTemplateExpressionEnabled(ctx),
+ ParserDiagnostics.COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED_CTE_API_VERSION,
+ [source, ctx.apiVersion, minCteApiVersion]
+ );
+ // complexTemplateExpressions must be enabled, the component API version must be sufficient and the expression
+ // should not be an unquoted attribute expression.
+ invariant(
+ isComplexTemplateExpressionEnabled(ctx) && !unquotedAttributeExpression,
+ ParserDiagnostics.COMPUTED_PROPERTY_ACCESS_NOT_ALLOWED_CTE_UNQUOTED,
+ [source]
+ );
+ }
const { object, property } = node;
if (!t.isIdentifier(object)) {
- validateExpression(source, object, config);
+ validateExpression(source, object, ctx, unquotedAttributeExpression);
}
if (!t.isIdentifier(property)) {
- validateExpression(source, property, config);
+ validateExpression(source, property, ctx, unquotedAttributeExpression);
}
}
}
@@ -109,7 +141,8 @@ export function validateSourceIsParsedExpression(source: string, parsedExpressio
export function parseExpression(
ctx: ParserCtx,
source: string,
- location: SourceLocation
+ location: SourceLocation,
+ unquotedAttributeExpression: boolean
): Expression {
const { ecmaVersion } = ctx;
return ctx.withErrorWrapping(
@@ -122,7 +155,7 @@ export function parseExpression(
});
validateSourceIsParsedExpression(source, parsed);
- validateExpression(source, parsed, ctx.config);
+ validateExpression(source, parsed, ctx, unquotedAttributeExpression);
return { ...parsed, location };
},
diff --git a/packages/@lwc/template-compiler/src/parser/index.ts b/packages/@lwc/template-compiler/src/parser/index.ts
index 8779a79586..eaa268eed4 100644
--- a/packages/@lwc/template-compiler/src/parser/index.ts
+++ b/packages/@lwc/template-compiler/src/parser/index.ts
@@ -61,7 +61,7 @@ import {
SUPPORTED_SVG_TAGS,
VALID_IF_MODIFIER,
} from './constants';
-import { parseComplexExpression } from './expression-complex';
+import { isComplexTemplateExpressionEnabled, parseComplexExpression } from './expression-complex';
import type {
TemplateParseResult,
Attribute,
@@ -480,7 +480,7 @@ function parseText(
let value: Expression | Literal;
if (isExpression(token)) {
- value = parseExpression(ctx, token, sourceLocation);
+ value = parseExpression(ctx, token, sourceLocation, false);
} else {
value = ast.literal(decodeTextContent(token));
}
@@ -562,7 +562,7 @@ function parseTextNode(ctx: ParserCtx, parse5Text: parse5Tools.TextNode): Text[]
const sourceLocation = ast.sourceLocation(location);
- return ctx.config.experimentalComplexExpressions
+ return isComplexTemplateExpressionEnabled(ctx)
? parseTextComplex(ctx, rawText, sourceLocation, location)
: parseText(ctx, rawText, sourceLocation, location);
}
@@ -1927,12 +1927,12 @@ function getTemplateAttribute(
*/
const isPotentialComplexExpression =
quotedExpression && !escapedExpression && value.startsWith(EXPRESSION_SYMBOL_START);
- if (ctx.config.experimentalComplexExpressions && isPotentialComplexExpression) {
+ if (isComplexTemplateExpressionEnabled(ctx) && isPotentialComplexExpression) {
const attributeNameOffset = attribute.name.length + 2; // The +2 accounts for the '="' in the attribute: attr="...
const templateSource = ctx.getSource(attributeLocation.startOffset + attributeNameOffset);
attrValue = parseComplexExpression(ctx, value, templateSource, location).expression;
} else if (isExpression(value) && !escapedExpression) {
- attrValue = parseExpression(ctx, value, location);
+ attrValue = parseExpression(ctx, value, location, !quotedExpression);
} else if (isBooleanAttribute) {
attrValue = ast.literal(true);
} else {
diff --git a/scripts/bundlesize/bundlesize.config.json b/scripts/bundlesize/bundlesize.config.json
index 57c8efea07..085eddc8d4 100644
--- a/scripts/bundlesize/bundlesize.config.json
+++ b/scripts/bundlesize/bundlesize.config.json
@@ -2,7 +2,7 @@
"files": [
{
"path": "packages/@lwc/engine-dom/dist/index.js",
- "maxSize": "24.72KB"
+ "maxSize": "24.79KB"
},
{
"path": "packages/@lwc/synthetic-shadow/dist/index.js",