From 70d7e1b42e3019023154ebcb7be1945c0f1b1690 Mon Sep 17 00:00:00 2001 From: Will Harney Date: Fri, 20 Dec 2024 12:31:42 -0500 Subject: [PATCH] feat: use const in generic for @wire --- .../src/framework/decorators/wire.ts | 2 +- .../integration-types/src/decorators/wire.ts | 99 +++++++++++++------ 2 files changed, 68 insertions(+), 33 deletions(-) diff --git a/packages/@lwc/engine-core/src/framework/decorators/wire.ts b/packages/@lwc/engine-core/src/framework/decorators/wire.ts index 7abb57b1f3..dca2567ba7 100644 --- a/packages/@lwc/engine-core/src/framework/decorators/wire.ts +++ b/packages/@lwc/engine-core/src/framework/decorators/wire.ts @@ -59,7 +59,7 @@ interface WireDecorator { * } */ export default function wire< - ReactiveConfig extends ConfigValue = ConfigValue, + const ReactiveConfig extends ConfigValue = ConfigValue, Value = any, Context extends ContextValue = ContextValue, Class = LightningElement, diff --git a/packages/@lwc/integration-types/src/decorators/wire.ts b/packages/@lwc/integration-types/src/decorators/wire.ts index 6be62c1ab2..6e49bdadfb 100644 --- a/packages/@lwc/integration-types/src/decorators/wire.ts +++ b/packages/@lwc/integration-types/src/decorators/wire.ts @@ -18,6 +18,8 @@ declare const TestAdapter: WireAdapterConstructor; +declare const someString: string; +declare const someNumber: number; // @ts-expect-error bare decorator cannot be used wire(FakeWireAdapter, { config: 'config' })(); @@ -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; @@ -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 {}; @@ -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; @@ -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) @@ -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) {} @@ -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' }) @@ -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; } @@ -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) {} @@ -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