Skip to content

Commit

Permalink
fix(ssr-compiler): harmonize some wire errors (#5062)
Browse files Browse the repository at this point in the history
Co-authored-by: Will Harney <[email protected]>
  • Loading branch information
cardoso and wjhsf authored Dec 19, 2024
1 parent 600c33e commit 141ac4d
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 16 deletions.
2 changes: 1 addition & 1 deletion packages/@lwc/errors/src/compiler/error-info/lwc-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,4 @@ export const DecoratorErrors = {
level: DiagnosticLevel.Error,
url: '',
},
};
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]);
25 changes: 25 additions & 0 deletions packages/@lwc/ssr-compiler/src/compile-js/errors.ts
Original file line number Diff line number Diff line change
@@ -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<R, Numbers, Args> // new `N`, add an argument
: ExtractArguments<R, N | Numbers, [string, ...Args]> // `N` already accounted for
: Args; // No `N` found, nothing more to check

export function generateError<const T extends LWCErrorInfo>(
error: T,
...args: ExtractArguments<T['message']>
): Error {
return new Error(generateErrorMessage(error, args));
}
34 changes: 33 additions & 1 deletion packages/@lwc/ssr-compiler/src/compile-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 7 additions & 6 deletions packages/@lwc/ssr-compiler/src/compile-js/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
}

Expand Down

0 comments on commit 141ac4d

Please sign in to comment.