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: allow computed properties in @wire if they are constants or primitives @W-14785085 #3955

Merged
merged 13 commits into from
Jan 22, 2024

Conversation

wjhsf
Copy link
Collaborator

@wjhsf wjhsf commented Jan 18, 2024

Details

We currently allow computed properties in the @wire config object, in order to allow symbols as property keys. The current code, as written, assumes that a property key is an identifier or a string literal. However, that is not necessarily always the case (e.g. { 123: 'NumericLiteral' }, { [true]: 'BooleanLiteral' }). This PR loosens that restriction to also allow numeric, null, boolean, and bigint literals. Regular expression literals are excluded because they are not primitives, and template literals are excluded because they may contain expressions, meaning they are not guaranteed to always result in the same value (e.g. `${Math.random()}`).

Additionally, this PR restricts variables used in computed properties to only allow constants. This helps prevent accidentally using a computed value that changes, which would prevent reactivity from functioning appropriately.

Fixes #3910.

Does this pull request 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

W-14785085

@wjhsf wjhsf changed the title feat: allow computed properties in @wire if they are constants or primitives feat: allow computed properties in @wire if they are constants or primitives @W-14785085 Jan 18, 2024
@wjhsf
Copy link
Collaborator Author

wjhsf commented Jan 19, 2024

Writing up a follow up ticket, I had a better idea that could solve this issue more robustly. #3956 (comment)

validateWireId(id, path, state);
if (config) validateWireConfig(config, path, state);
} else {
// Single argument: should just be id
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As far as I can tell, this never actually occurs (the code prior to this change doesn't handle this case and would have thrown). I don't actually know whether it's true that a single argument would be the ID here, but it seems a reasonable guess.

Copy link
Contributor

@nolanlawson nolanlawson Jan 19, 2024

Choose a reason for hiding this comment

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

The docs are not clear, but it does seem that the config is optional. A quick search for @wire in SourceGraph reveals several instances of @wire being used with just the single argument. We should probably add a test for this.

Also you are correct, this line is not hit per code coverage.

const value = t.isNullLiteral(p.key) ? null : p.key.value;
return t.stringLiteral(String(value));
}
// If it's not an identifier or primitive literal then it's a computed expression
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This fall through will probably never occur, because it should already be covered by the validation, but we should still play it safe.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add this as a comment here? You can also add /* istanbul ignore next */ to drive home the point that this code should never get hit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This diff is git being silly. I deleted the old file because it was a duplicate of another test, and the new file is unrelated.

@wjhsf wjhsf marked this pull request as ready for review January 19, 2024 21:46
@wjhsf wjhsf requested a review from a team as a code owner January 19, 2024 21:46
Copy link
Contributor

@nolanlawson nolanlawson left a comment

Choose a reason for hiding this comment

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

LGTM overall. Nicely done!

const value = t.isNullLiteral(p.key) ? null : p.key.value;
return t.stringLiteral(String(value));
}
// If it's not an identifier or primitive literal then it's a computed expression
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add this as a comment here? You can also add /* istanbul ignore next */ to drive home the point that this code should never get hit.


for (const prop of config.get('properties')) {
// Only validate {[computed]: true} object properties; {static: true} props are all valid
// and we ignore {...spreads} and {methods(){}}
Copy link
Contributor

Choose a reason for hiding this comment

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

I just tested, and you're correct that the existing compiler ignores spreads and methods. This is an oversight in our validation logic.

Could you please open a follow-up GH issue for this? This is the kind of thing that may need to be handled with API versioning, since it may already be "exploited" in the wild (spreads in particular).

for (const prop of config.get('properties')) {
// Only validate {[computed]: true} object properties; {static: true} props are all valid
// and we ignore {...spreads} and {methods(){}}
if (!prop.isObjectProperty() || !prop.node.computed) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not actually enforced in our ESLint rules, but I think by convention we always use curly brackets for cases like this.

Suggested change
if (!prop.isObjectProperty() || !prop.node.computed) continue;
if (!prop.isObjectProperty() || !prop.node.computed) {
continue;
}

Ditto for other cases below.


COMPUTED_PROPERTY_MUST_BE_CONSTANT_OR_LITERAL: {
code: 1200,
message: 'Computed property in @wire config must be a constant or primitive literal.',
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
message: 'Computed property in @wire config must be a constant or primitive literal.',
message: 'Computed property in @wire config must be a constant or primitive literal, such as a string or Symbol.',

Most developers probably don't know what the words "constant or primitive literal" mean – this gives a bit of a hint. 🙂

validateWireId(id, path, state);
if (config) validateWireConfig(config, path, state);
} else {
// Single argument: should just be id
Copy link
Contributor

@nolanlawson nolanlawson Jan 19, 2024

Choose a reason for hiding this comment

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

The docs are not clear, but it does seem that the config is optional. A quick search for @wire in SourceGraph reveals several instances of @wire being used with just the single argument. We should probably add a test for this.

Also you are correct, this line is not hit per code coverage.

@nolanlawson
Copy link
Contributor

🚨 Yes, it does introduce a breaking change.

I agree, but to be clear I don't think this merits a major version bump. We only recently allowed computed properties in @wire, and 250 has not shipped to third-party component authors yet.

@wjhsf wjhsf merged commit 8e90e92 into master Jan 22, 2024
9 checks passed
@wjhsf wjhsf deleted the wjh/helpful-wire-error branch January 22, 2024 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

@wire computed properties should throw more helpful error for non-string literals
2 participants