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!: use const in generic for @wire #5073

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ interface WireDecorator<Value, Class> {
* }
*/
export default function wire<
ReactiveConfig extends ConfigValue = ConfigValue,
const ReactiveConfig extends ConfigValue = ConfigValue,
Value = any,
Context extends ContextValue = ContextValue,
Class = LightningElement,
Expand Down
99 changes: 67 additions & 32 deletions packages/@lwc/integration-types/src/decorators/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ declare const TestAdapter: WireAdapterConstructor<TestConfig, TestValue, TestCon
declare const AnyAdapter: any;
declare const InvalidAdapter: object;
declare const DeepConfigAdapter: WireAdapterConstructor<DeepConfig, TestValue>;
declare const someString: string;
declare const someNumber: number;

// @ts-expect-error bare decorator cannot be used
wire(FakeWireAdapter, { config: 'config' })();
Expand All @@ -39,12 +41,14 @@ export class PropertyDecorators extends LightningElement {
// Valid - basic
@wire(TestAdapter, { config: 'config' })
basic?: TestValue;
@wire(TestAdapter, { config: '$config' })
@wire(TestAdapter, { config: '$configProp' })
simpleReactive?: TestValue;
@wire(TestAdapter, { config: '$nested.prop' })
nestedReactive?: TestValue;
@wire(TestAdapter, testConfig)
configVariable?: TestValue;
@wire(TestAdapter, { config: someString })
configValueVariable?: TestValue;
// Valid - as const
@wire(TestAdapter, { config: 'config' } as const)
basicAsConst?: TestValue;
Expand Down Expand Up @@ -81,30 +85,54 @@ export class PropertyDecorators extends LightningElement {
tooManyWireParams?: TestValue;
// @ts-expect-error Bad config type
@wire(TestAdapter, { bad: 'value' })
badConfig?: TestValue;
badConfigType?: TestValue;
// @ts-expect-error Really bad config type
@wire(TestAdapter, 'this should definitely not work')
reallyBadConfigType?: TestValue;
// @ts-expect-error Bad config value type
@wire(TestAdapter, { config: someNumber })
badConfigValue?: TestValue;
// @ts-expect-error Bad prop type
@wire(TestAdapter, { config: 'config' })
badPropType?: { bad: 'value' };
// @ts-expect-error Prop must be optional or assigned in constructor
@wire(TestAdapter, { config: 'config' }) notOptional: TestValue;
// @ts-expect-error Referenced reactive prop does not exist
@wire(TestAdapter, { config: '$nonexistentProp' } as const)
@wire(TestAdapter, { config: '$nonexistentProp' })
nonExistentReactiveProp?: TestValue;
// @ts-expect-error Referenced reactive prop does not exist
@wire(TestAdapter, { config: '$nonexistentProp' } as const)
nonExistentReactivePropAsConst?: TestValue;
// @ts-expect-error Referenced reactive prop is the wrong type
@wire(TestAdapter, { config: '$number' } as const)
@wire(TestAdapter, { config: '$number' })
numberReactiveProp?: TestValue;
// @ts-expect-error Referenced reactive prop is the wrong type
@wire(TestAdapter, { config: '$number' } as const)
numberReactivePropAsConst?: TestValue;
// @ts-expect-error Referenced nested reactive prop does not exist
@wire(TestAdapter, { config: '$nested.nonexistent' } as const)
@wire(TestAdapter, { config: '$nested.nonexistent' })
nonexistentNestedReactiveProp?: TestValue;
// @ts-expect-error Referenced nested reactive prop does not exist
@wire(TestAdapter, { config: '$nested.invalid' } as const)
@wire(TestAdapter, { config: '$nested.nonexistent' } as const)
nonexistentNestedReactivePropAsConst?: TestValue;
// @ts-expect-error Referenced nested reactive prop does not exist
@wire(TestAdapter, { config: '$nested.invalid' })
invalidNestedReactiveProp?: TestValue;
// @ts-expect-error Referenced nested reactive prop does not exist
@wire(TestAdapter, { config: '$nested.invalid' } as const)
invalidNestedReactivePropAsConst?: TestValue;
// @ts-expect-error Incorrect non-reactive string literal type
@wire(TestAdapter, { config: 'not reactive' } as const)
@wire(TestAdapter, { config: 'not reactive' })
nonReactiveStringLiteral?: TestValue;
// @ts-expect-error Incorrect non-reactive string literal type
@wire(TestAdapter, { config: 'not reactive' } as const)
nonReactiveStringLiteralAsConst?: TestValue;
// @ts-expect-error Nested props are not reactive - only top level
@wire(DeepConfigAdapter, { deep: { config: '$number' } } as const)
@wire(DeepConfigAdapter, { deep: { config: '$number' } })
deepReactive?: TestValue;
// @ts-expect-error Nested props are not reactive - only top level
@wire(DeepConfigAdapter, { deep: { config: '$number' } } as const)
deepReactiveAsConst?: TestValue;
// @ts-expect-error Looks like a method, but it's actually a prop
@wire(TestAdapter, { config: 'config' })
propValueIsMethod = function (this: PropertyDecorators, _: TestValue): void {};
Expand All @@ -114,13 +142,6 @@ export class PropertyDecorators extends LightningElement {
// Can we be smarter about the type and require a config, but only if the adapter does?
@wire(TestAdapter)
noConfig?: TestValue;
// Because the basic type `string` could be _any_ string, we can't narrow it and compare against
// the component's props, so we must accept all string props, even if they're incorrect.
// We could technically be strict, and enforce that all configs objects use `as const`, but very
// few projects currently use it (there is no need) and the error reported is not simple to
// understand.
@wire(TestAdapter, { config: 'incorrect' })
wrongConfigButInferredAsString?: TestValue;
// People shouldn't do this, and they probably never (heh) will. TypeScript allows it, though.
@wire(TestAdapter, { config: 'config' })
never?: never;
Expand All @@ -141,13 +162,13 @@ export class MethodDecorators extends LightningElement {
basic(_: TestValue) {}
@wire(TestAdapter, { config: 'config' })
async asyncMethod(_: TestValue) {}
@wire(TestAdapter, { config: '$config' })
@wire(TestAdapter, { config: '$configProp' })
simpleReactive(_: TestValue) {}
@wire(TestAdapter, { config: '$nested.prop' })
nestedReactive(_: TestValue) {}
@wire(TestAdapter, { config: '$config' })
@wire(TestAdapter, { config: '$configProp' })
optionalParam(_?: TestValue) {}
@wire(TestAdapter, { config: '$config' })
@wire(TestAdapter, { config: '$configProp' })
noParam() {}
// Valid - as const
@wire(TestAdapter, { config: 'config' } as const)
Expand Down Expand Up @@ -184,23 +205,41 @@ export class MethodDecorators extends LightningElement {
@wire(TestAdapter, { config: 'config' })
badParamType(_: { bad: 'value' }): void {}
// @ts-expect-error Referenced reactive prop does not exist
@wire(TestAdapter, { config: '$nonexistentProp' } as const)
@wire(TestAdapter, { config: '$nonexistentProp' })
nonExistentReactiveProp(_: TestValue): void {}
// @ts-expect-error Referenced reactive prop does not exist
@wire(TestAdapter, { config: '$nonexistentProp' } as const)
nonExistentReactivePropAsConst(_: TestValue): void {}
// @ts-expect-error Referenced reactive prop is the wrong type
@wire(TestAdapter, { config: '$number' } as const)
@wire(TestAdapter, { config: '$number' })
numberReactiveProp(_: TestValue): void {}
// @ts-expect-error Referenced reactive prop is the wrong type
@wire(TestAdapter, { config: '$number' } as const)
numberReactivePropAsConst(_: TestValue): void {}
// @ts-expect-error Referenced nested reactive prop does not exist
@wire(TestAdapter, { config: '$nested.nonexistent' } as const)
@wire(TestAdapter, { config: '$nested.nonexistent' })
nonexistentNestedReactiveProp(_: TestValue): void {}
// @ts-expect-error Referenced nested reactive prop does not exist
@wire(TestAdapter, { config: '$nested.invalid' } as const)
@wire(TestAdapter, { config: '$nested.nonexistent' } as const)
nonexistentNestedReactivePropAsConst(_: TestValue): void {}
// @ts-expect-error Referenced nested reactive prop does not exist
@wire(TestAdapter, { config: '$nested.invalid' })
invalidNestedReactiveProp(_: TestValue): void {}
// @ts-expect-error Referenced nested reactive prop does not exist
@wire(TestAdapter, { config: '$nested.invalid' } as const)
invalidNestedReactivePropAsConst(_: TestValue): void {}
// @ts-expect-error Incorrect non-reactive string literal type
@wire(TestAdapter, { config: 'not reactive' } as const)
@wire(TestAdapter, { config: 'not reactive' })
nonReactiveStringLiteral(_: TestValue): void {}
// @ts-expect-error Incorrect non-reactive string literal type
@wire(TestAdapter, { config: 'not reactive' } as const)
nonReactiveStringLiteralAsConst(_: TestValue): void {}
// @ts-expect-error Nested props are not reactive - only top level
@wire(DeepConfigAdapter, { deep: { config: '$number' } } as const)
@wire(DeepConfigAdapter, { deep: { config: '$number' } })
deepReactive(_: TestValue): void {}
// @ts-expect-error Nested props are not reactive - only top level
@wire(DeepConfigAdapter, { deep: { config: '$number' } } as const)
deepReactiveAsConst(_: TestValue): void {}
// @ts-expect-error Param type looks like decorated method (validating type inference workaround)
@wire(TestAdapter, { config: 'config' })
paramIsMethod(_: (inner: TestValue) => void) {}
Expand All @@ -210,12 +249,6 @@ export class MethodDecorators extends LightningElement {
// Can we be smarter about the type and require a config, but only if the adapter does?
@wire(TestAdapter)
noConfig(_: TestValue): void {}
// Because the basic type `string` could be _any_ string, we can't narrow it and compare against
// the component's props, so we must accept all string props, even if they're incorrect.
// We could technically be strict, and enforce that all configs objects use `as const`, but very
// few projects currently use it (there is no need) and the error reported is not simple to
// understand.
@wire(TestAdapter, { config: 'incorrect' })
wrongConfigButInferredAsString(_: TestValue): void {}
// Wire adapters shouldn't use default params, but the type system doesn't know the difference
@wire(TestAdapter, { config: 'config' })
Expand Down Expand Up @@ -244,7 +277,7 @@ export class GetterDecorators extends LightningElement {
// we must return something. Since we don't have any data to return, we return `undefined`
return undefined;
}
@wire(TestAdapter, { config: '$config' })
@wire(TestAdapter, { config: '$configProp' })
get simpleReactive() {
return testValue;
}
Expand Down Expand Up @@ -355,7 +388,7 @@ export class Setter extends LightningElement {
// Valid - basic
@wire(TestAdapter, { config: 'config' })
set basic(_: TestValue) {}
@wire(TestAdapter, { config: '$config' })
@wire(TestAdapter, { config: '$configProp' })
set simpleReactive(_: TestValue) {}
@wire(TestAdapter, { config: '$nested.prop' })
set nestedReactive(_: TestValue) {}
Expand Down Expand Up @@ -393,6 +426,8 @@ export class Setter extends LightningElement {
@wire(TestAdapter, { config: 'config' })
set badValueType(_: { bad: 'value' }) {}
// @ts-expect-error Referenced reactive prop does not exist
@wire(TestAdapter, { config: '$nonexistentProp' })
// @ts-expect-error Referenced reactive prop does not exist
@wire(TestAdapter, { config: '$nonexistentProp' } as const)
set nonExistentReactiveProp(_: TestValue) {}
// @ts-expect-error Referenced reactive prop is the wrong type
Expand Down