From 141ac4d47e1c2b7e0c15c3c5410bc4701d242b17 Mon Sep 17 00:00:00 2001 From: Matheus Cardoso Date: Thu, 19 Dec 2024 16:36:55 -0300 Subject: [PATCH] fix(ssr-compiler): harmonize some wire errors (#5062) Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com> --- .../src/compiler/error-info/lwc-class.ts | 2 +- .../src/__tests__/utils/expected-failures.ts | 8 ----- .../ssr-compiler/src/compile-js/errors.ts | 25 ++++++++++++++ .../@lwc/ssr-compiler/src/compile-js/index.ts | 34 ++++++++++++++++++- .../@lwc/ssr-compiler/src/compile-js/wire.ts | 13 +++---- 5 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 packages/@lwc/ssr-compiler/src/compile-js/errors.ts diff --git a/packages/@lwc/errors/src/compiler/error-info/lwc-class.ts b/packages/@lwc/errors/src/compiler/error-info/lwc-class.ts index 90522240d9..e171ee9265 100644 --- a/packages/@lwc/errors/src/compiler/error-info/lwc-class.ts +++ b/packages/@lwc/errors/src/compiler/error-info/lwc-class.ts @@ -218,4 +218,4 @@ export const DecoratorErrors = { level: DiagnosticLevel.Error, url: '', }, -}; +} as const; diff --git a/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts b/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts index 9ed58f8444..1be39efe68 100644 --- a/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts +++ b/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts @@ -32,12 +32,4 @@ export const expectedFailures = new Set([ 'superclass/render-in-superclass/unused-default-in-superclass/index.js', 'wire/errors/throws-on-computed-key/index.js', 'wire/errors/throws-when-colliding-prop-then-method/index.js', - 'wire/errors/throws-when-computed-prop-is-expression/index.js', - 'wire/errors/throws-when-computed-prop-is-let-variable/index.js', - 'wire/errors/throws-when-computed-prop-is-regexp-literal/index.js', - 'wire/errors/throws-when-computed-prop-is-template-literal/index.js', - 'wire/errors/throws-when-using-2-wired-decorators/index.js', - 'wire/errors/throws-when-wired-method-is-combined-with-@api/index.js', - 'wire/errors/throws-when-wired-property-is-combined-with-@api/index.js', - 'wire/errors/throws-when-wired-property-is-combined-with-@track/index.js', ]); diff --git a/packages/@lwc/ssr-compiler/src/compile-js/errors.ts b/packages/@lwc/ssr-compiler/src/compile-js/errors.ts new file mode 100644 index 0000000000..1a57ad5e4b --- /dev/null +++ b/packages/@lwc/ssr-compiler/src/compile-js/errors.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: MIT + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT + */ +import { generateErrorMessage, type LWCErrorInfo } from '@lwc/errors'; + +// This type extracts the arguments in a string. Example: "Error {0} {1}" -> [string, string] +type ExtractArguments< + T extends string, + Numbers extends number = never, + Args extends string[] = [], +> = T extends `${string}{${infer N extends number}}${infer R}` + ? N extends Numbers // Is `N` in the union of seen numbers? + ? ExtractArguments // new `N`, add an argument + : ExtractArguments // `N` already accounted for + : Args; // No `N` found, nothing more to check + +export function generateError( + error: T, + ...args: ExtractArguments +): Error { + return new Error(generateErrorMessage(error, args)); +} diff --git a/packages/@lwc/ssr-compiler/src/compile-js/index.ts b/packages/@lwc/ssr-compiler/src/compile-js/index.ts index 047ac3146c..c0fe02058a 100644 --- a/packages/@lwc/ssr-compiler/src/compile-js/index.ts +++ b/packages/@lwc/ssr-compiler/src/compile-js/index.ts @@ -9,6 +9,7 @@ import { generate } from 'astring'; import { traverse, builders as b, is } from 'estree-toolkit'; import { parseModule } from 'meriyah'; +import { DecoratorErrors } from '@lwc/errors'; import { transmogrify } from '../transmogrify'; import { ImportManager } from '../imports'; import { replaceLwcImport, replaceNamedLwcExport, replaceAllLwcExport } from './lwc-import'; @@ -18,8 +19,13 @@ import { addGenerateMarkupFunction } from './generate-markup'; import { catalogWireAdapters } from './wire'; import { removeDecoratorImport } from './remove-decorator-import'; +import { generateError } from './errors'; import type { ComponentTransformOptions } from '../shared'; -import type { Identifier as EsIdentifier, Program as EsProgram } from 'estree'; +import type { + Identifier as EsIdentifier, + Program as EsProgram, + Decorator as EsDecorator, +} from 'estree'; import type { Visitors, ComponentMetaState } from './types'; import type { CompilationMode } from '@lwc/shared'; @@ -92,6 +98,7 @@ const visitors: Visitors = { } const { decorators } = node; + validateUniqueDecorator(decorators); const decoratedExpression = decorators?.[0]?.expression; if (is.identifier(decoratedExpression) && decoratedExpression.name === 'api') { state.publicFields.push(node.key.name); @@ -131,6 +138,7 @@ const visitors: Visitors = { } const { decorators } = node; + validateUniqueDecorator(decorators); // The real type is a subset of `Expression`, which doesn't work with the `is` validators const decoratedExpression = decorators?.[0]?.expression; if ( @@ -205,6 +213,30 @@ const visitors: Visitors = { }, }; +function validateUniqueDecorator(decorators: EsDecorator[]) { + if (decorators.length < 2) { + return; + } + + const expressions = decorators.map(({ expression }) => expression); + + const hasWire = expressions.some( + (expr) => is.callExpression(expr) && is.identifier(expr.callee, { name: 'wire' }) + ); + + const hasApi = expressions.some((expr) => is.identifier(expr, { name: 'api' })); + + if (hasWire && hasApi) { + throw generateError(DecoratorErrors.CONFLICT_WITH_ANOTHER_DECORATOR, 'api'); + } + + const hasTrack = expressions.some((expr) => is.identifier(expr, { name: 'track' })); + + if ((hasWire || hasApi) && hasTrack) { + throw generateError(DecoratorErrors.CONFLICT_WITH_ANOTHER_DECORATOR, 'track'); + } +} + export default function compileJS( src: string, filename: string, diff --git a/packages/@lwc/ssr-compiler/src/compile-js/wire.ts b/packages/@lwc/ssr-compiler/src/compile-js/wire.ts index 2584c6c53d..189153e16e 100644 --- a/packages/@lwc/ssr-compiler/src/compile-js/wire.ts +++ b/packages/@lwc/ssr-compiler/src/compile-js/wire.ts @@ -7,7 +7,9 @@ import { is, builders as b } from 'estree-toolkit'; import { produce } from 'immer'; +import { DecoratorErrors } from '@lwc/errors'; import { esTemplate } from '../estemplate'; +import { generateError } from './errors'; import type { NodePath } from 'estree-toolkit'; import type { @@ -42,8 +44,7 @@ function getWireParams( const { decorators } = node; if (decorators.length > 1) { - // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler` - throw new Error('todo - multiple decorators at once'); + throw generateError(DecoratorErrors.ONE_WIRE_DECORATOR_ALLOWED); } // validate the parameters @@ -93,8 +94,7 @@ function validateWireId( // This is not the exact same validation done in @lwc/babel-plugin-component but it accomplishes the same thing if (path.scope?.getBinding(wireAdapterVar)?.kind !== 'module') { - // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler` - throw new Error('todo - WIRE_ADAPTER_SHOULD_BE_IMPORTED'); + throw generateError(DecoratorErrors.COMPUTED_PROPERTY_MUST_BE_CONSTANT_OR_LITERAL); } } @@ -128,9 +128,10 @@ function validateWireConfig( // A literal can be a regexp, template literal, or primitive; only allow primitives continue; } + } else if (is.templateLiteral(key)) { + throw generateError(DecoratorErrors.COMPUTED_PROPERTY_CANNOT_BE_TEMPLATE_LITERAL); } - // TODO [#5032]: Harmonize errors thrown in `@lwc/ssr-compiler` - throw new Error('todo - COMPUTED_PROPERTY_MUST_BE_CONSTANT_OR_LITERAL'); + throw generateError(DecoratorErrors.COMPUTED_PROPERTY_MUST_BE_CONSTANT_OR_LITERAL); } }