Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ssr-compiler): validate api decorator usage #5071

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

cardoso
Copy link
Contributor

@cardoso cardoso commented Dec 20, 2024

Details

I'm a bit short on time today to finish polishing this up, so I'm sending this as a draft.

Follow up to #5062
Partially addresses #5032

This PR aims to replicate the api decorator validation done in babel-plugin-component and also related tests.

Some of the validation done in babel-plugin-component seems to be done after everything is collected, while here it's currently all done during traversal. Maybe that's just for better error reporting, which I think can be considered in a separate issue, but need to make sure this is not introducing undesirable behavior here.

TODOS:

  • Move decorator validation logic to separate folder or file
  • Remove new CompilationError class in favor of generateCompilerError from @lwc/errors
  • (?) Replicate the valid tests to make sure no extra errors are introduced
  • (?) Move copied constants to @lwc/shared or @lwc/errors
  • (?) Add filename to ComponentMetaState in order to replicate the error structure
  • (?) Add loc to error structure (line, column, start, length)

Does this pull request introduce a breaking change?

  • 😮‍💨 No, it does not introduce a breaking change.
  • 💔 Yes, it does introduce a breaking change.

Does this pull request introduce an observable change?

  • 🤞 No, it does not introduce an observable change.
  • 🔬 Yes, it does include an observable change.

GUS work item

@cardoso cardoso requested a review from a team as a code owner December 20, 2024 17:08
@cardoso cardoso marked this pull request as draft December 20, 2024 17:08
Comment on lines 188 to 203
const field = state.publicFields.get(node.key.name);

if (field) {
if (
(is.methodDefinition(field, { kind: (k) => k === 'get' || k === 'set' }) &&
node.kind === 'get') ||
node.kind === 'set'
) {
throw generateError(
DecoratorErrors.SINGLE_DECORATOR_ON_SETTER_GETTER_PAIR,
node.key.name
);
} else {
throw generateError(DecoratorErrors.DUPLICATE_API_PROPERTY, node.key.name);
}
}
Copy link
Contributor Author

@cardoso cardoso Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean this up & move to its own function

@cardoso cardoso marked this pull request as ready for review January 7, 2025 16:32
@cardoso
Copy link
Contributor Author

cardoso commented Jan 7, 2025

I'm a bit unsure about the scope of this PR, so I'm opening it up for review.

The main remaining TODO is to replicate the valid tests, but on second thought that might already be covered by the engine-server test suite?

Copy link
Contributor Author

@cardoso cardoso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach taken here is to mirror behavior and tests from babel-plugin-component. I focused on the api decorator errors and didn't introduce location info to keep the PR short.

Comment on lines +197 to +219

/**
* This set is for attributes that have a camel cased property name
* For example, div.tabIndex.
* We do not want users to define `@api` properties with these names
* Because the template will never call them. It'll always call the camel
* cased version.
*/
export const AMBIGUOUS_PROP_SET = /*@__PURE__@*/ new Map([
['bgcolor', 'bgColor'],
['accesskey', 'accessKey'],
['contenteditable', 'contentEditable'],
['tabindex', 'tabIndex'],
['maxlength', 'maxLength'],
['maxvalue', 'maxValue'],
]);

/**
* This set is for attributes that can never be defined
* by users on their components.
* We throw for these.
*/
export const DISALLOWED_PROP_SET = /*@__PURE__@*/ new Set(['is', 'class', 'slot', 'style']);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @lwc/errors would be a better place for this. Not sure.

Comment on lines 23 to 46
function validateName(definition: ApiDefinition) {
if (definition.computed) {
throw generateError(DecoratorErrors.PROPERTY_CANNOT_BE_COMPUTED);
}

const propertyName = definition.key.name;

switch (true) {
case propertyName === 'part':
throw generateError(DecoratorErrors.PROPERTY_NAME_PART_IS_RESERVED, propertyName);
case propertyName.startsWith('on'):
throw generateError(DecoratorErrors.PROPERTY_NAME_CANNOT_START_WITH_ON, propertyName);
case propertyName.startsWith('data') && propertyName.length > 4:
throw generateError(DecoratorErrors.PROPERTY_NAME_CANNOT_START_WITH_DATA, propertyName);
case DISALLOWED_PROP_SET.has(propertyName):
throw generateError(DecoratorErrors.PROPERTY_NAME_IS_RESERVED, propertyName);
case AMBIGUOUS_PROP_SET.has(propertyName):
throw generateError(
DecoratorErrors.PROPERTY_NAME_IS_AMBIGUOUS,
propertyName,
AMBIGUOUS_PROP_SET.get(propertyName)!
);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole file mirrors the structure and logic from babel-plugin-component:

function validatePropertyName(property: NodePath<types.ClassMethod>, state: LwcBabelPluginPass) {
if (property.node.computed) {
throw generateError(
property,
{
errorInfo: DecoratorErrors.PROPERTY_CANNOT_BE_COMPUTED,
},
state
);
}

@cardoso cardoso marked this pull request as draft January 8, 2025 21:09
Copy link
Contributor Author

@cardoso cardoso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved back to draft as I learned some stuff in #5114 that will make this better 😅

Comment on lines 20 to 35
class CompilationError extends Error {
constructor(
message: string,
public code: number
) {
super(message);
this.name = 'CompilationError';
this.code = code;
}
}

export function generateError<const T extends LWCErrorInfo>(
error: T,
...args: ExtractArguments<T['message']>
): Error {
return new Error(generateErrorMessage(error, args));
): CompilationError {
return new CompilationError(generateErrorMessage(error, args), error.code);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use CompilerError from @lwc/errors instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant