feat(ssr-compiler): validate api decorator usage#5071
feat(ssr-compiler): validate api decorator usage#5071wjhsf merged 27 commits intosalesforce:masterfrom
Conversation
…alidate-api-decorator
…alidate-api-decorator
…alidate-api-decorator
…alidate-api-decorator
|
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 |
cardoso
left a comment
There was a problem hiding this comment.
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.
|
|
||
| /** | ||
| * 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']); |
There was a problem hiding this comment.
Maybe @lwc/errors would be a better place for this. Not sure.
There was a problem hiding this comment.
This isn't specifically related to errors, @lwc/shared is fine.
| 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)! | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
This whole file mirrors the structure and logic from babel-plugin-component:
…alidate-api-decorator
…alidate-api-decorator
…alidate-api-decorator
…alidate-api-decorator
wjhsf
left a comment
There was a problem hiding this comment.
I'll need to have a second look, later, to fully parse this, but here's a few minor code quality comments from a quick glance at this.
|
|
||
| /** | ||
| * 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']); |
There was a problem hiding this comment.
This isn't specifically related to errors, @lwc/shared is fine.
|
|
||
| const propertyName = definition.key.name; | ||
|
|
||
| switch (true) { |
There was a problem hiding this comment.
Why switch (true) rather than a regular if/else?
There was a problem hiding this comment.
It's just more readable and maintainable. Here's the code it's based on in babel-plugin-component:
lwc/packages/@lwc/babel-plugin-component/src/decorators/api/validate.ts
Lines 70 to 117 in 9e8bcc0
There was a problem hiding this comment.
I've used this pattern on occasion and I'm generally okay with it. However, if other folks on the team prefer the if/else pattern, that's fine with me too. Sometimes it's better to conform to the norm of the codebase, even if something else would be more easily readable.
There was a problem hiding this comment.
Yeah, I don't feel strongly about this. It was just easier for me to write and understand what was going on in the original code.
packages/@lwc/ssr-compiler/src/compile-js/decorators/api/validate.ts
Outdated
Show resolved
Hide resolved
…alidate-api-decorator
Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com>
Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com>
Co-authored-by: Will Harney <62956339+wjhsf@users.noreply.github.com>
divmain
left a comment
There was a problem hiding this comment.
No objections to this PR, but I'll let Will give the final 👍 once he is satisfied.
|
|
||
| const propertyName = definition.key.name; | ||
|
|
||
| switch (true) { |
There was a problem hiding this comment.
I've used this pattern on occasion and I'm generally okay with it. However, if other folks on the team prefer the if/else pattern, that's fine with me too. Sometimes it's better to conform to the norm of the codebase, even if something else would be more easily readable.
|
/nucleus test |
…alidate-api-decorator
…alidate-api-decorator
|
/nucleus test |
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 and #5114
Partially addresses #5032
This PR aims to replicate the api decorator validation done in
babel-plugin-componentand also related tests.Some of the validation done in
babel-plugin-componentseems 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.Does this pull request introduce a breaking change?
Does this pull request introduce an observable change?
GUS work item