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 @@ + 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",