Skip to content

Commit

Permalink
internal: adjust application type to don't allow unknown fields (#27689)
Browse files Browse the repository at this point in the history
  • Loading branch information
mshima authored Oct 26, 2024
1 parent 57b6902 commit 6e1aff6
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 36 deletions.
2 changes: 2 additions & 0 deletions generators/server/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { PackageJson } from 'type-fest';
import type { JavaApplication, JavaSourceType } from '../java/types.js';
import type { GradleSourceType } from '../gradle/types.js';
import type { MavenSourceType } from '../maven/types.js';
Expand Down Expand Up @@ -123,6 +124,7 @@ export type SpringBootApplication = JavaApplication &
SearchEngine &
DatabaseTypeApplication &
GatewayApplication & {
jhipsterPackageJson: PackageJson;
jhipsterDependenciesVersion: string;
springBootDependencies: Record<string, string>;
dockerContainers: Record<string, string>;
Expand Down
23 changes: 17 additions & 6 deletions lib/command/support/merge-union.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
type Compute<T> = { [K in keyof T]: T[K] } | never;
type AllKeys<T> = T extends any ? keyof T : never;
/**
* Based on https://github.com/sindresorhus/type-fest/issues/610
* Based on https://github.com/sindresorhus/type-fest/issues/610#issuecomment-2398118998
*/
export type MergeUnion<T, Keys extends keyof T = keyof T> = Compute<
{ [K in Keys]: T[Keys] } & { [K in AllKeys<T>]?: T extends any ? (K extends keyof T ? T[K] : never) : never }
>;
import type { EmptyObject, IsNever, KeysOfUnion, Simplify, UnionToIntersection } from 'type-fest';

type _MergeUnionKnownKeys<BaseType, Keys extends keyof BaseType = keyof BaseType> = {
[K in Keys]: Keys extends K ? BaseType[Keys] : never;
};

type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

export type MergeUnion<BaseType> =
IsNever<BaseType> extends false
? Simplify<
_MergeUnionKnownKeys<BaseType> & {
[K in KeysOfUnion<BaseType>]?: BaseType extends object ? (K extends keyof BaseType ? BaseType[K] : never) : never;
}
>
: EmptyObject;
29 changes: 24 additions & 5 deletions lib/command/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ type DerivedPropertiesWithInferenceUnionFromParseableConfigs<U extends Parseable
: never;
};

/**
* @example
* ```ts
* type ExplodedCommandChoices = ExplodeCommandChoicesNoInference<{ clientFramework: { choices: ['angular', 'no'], scope: 'storage' }, clientTestFramework: { choices: ['cypress', 'no'], scope: 'storage' } }>
* ```
*/
type ExplodeCommandChoicesNoInference<U extends ParseableConfigs> = {
[K in keyof U]: U[K] extends infer RequiredChoices
? RequiredChoices extends { choices: any }
Expand Down Expand Up @@ -230,19 +236,32 @@ type PrepareConfigsWithType<U extends ParseableConfigs> = Simplify<{
/** Keep Options/Config filtered by choices */
type OnlyChoices<D, C extends boolean> = D extends { choices: JHispterChoices } ? (C extends true ? D : never) : C extends true ? never : D;

/** Keep Options/Config filtered by choices */
type OnlyCofigsWithChoice<D extends ParseableConfigs, C extends boolean> = {
/**
* Keep Options/Config filtered by choices
*
* @example
* ```ts
* type ConfigsWithChoice = OnlyConfigsWithChoice<{ clientFramework: { choices: ['angular', 'no'], scope: 'storage' }, clientTestFramework: { choices: ['cypress', 'no'], scope: 'storage' } }>
* ```
*/
type OnlyConfigsWithChoice<D extends ParseableConfigs, C extends boolean> = {
[K in keyof D as OnlyChoices<D[K], C> extends never ? never : K]: D[K];
};

/**
* @example
* ```
* type Prop = ExportApplicationPropertiesFromCommand<{ configs: { clientFramework: { choices: ['angular', 'no'], scope: 'storage' }, bar: { scope: 'storage' } } }>;
* ```
*/
export type ExportApplicationPropertiesFromCommand<C extends ParseableCommand> =
MergeConfigsOptions<C, 'storage'> extends infer Merged
? Merged extends ParseableConfigs
? // Add value inference to properties with choices
// ? PrepareConfigsWithType<OnlyCofigsWithChoice<F, false>> & ValueOf<ExplodeCommandChoicesWithInference<OnlyCofigsWithChoice<F, true>>>
// ? PrepareConfigsWithType<OnlyConfigsWithChoice<F, false>> & ValueOf<ExplodeCommandChoicesWithInference<OnlyConfigsWithChoice<F, true>>>
Simplify<
PrepareConfigsWithType<OnlyCofigsWithChoice<Merged, false>> &
MergeUnion<ValueOf<ExplodeCommandChoicesNoInference<OnlyCofigsWithChoice<Merged, true>>>>
PrepareConfigsWithType<OnlyConfigsWithChoice<Merged, false>> &
MergeUnion<ValueOf<ExplodeCommandChoicesNoInference<OnlyConfigsWithChoice<Merged, true>>>>
>
: never
: never;
Expand Down
90 changes: 66 additions & 24 deletions lib/command/types.spec.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,48 @@
import type { IsNever } from 'type-fest';
import type {
ExportApplicationPropertiesFromCommand,
ExportGeneratorOptionsFromCommand,
ExportStoragePropertiesFromCommand,
} from './types.js';

type TestCommand = {
type AssertType<Expected extends true | false, _T2 extends Expected, _T3 extends Expected = Expected> = void;

const _testCommand = {
options: {
arrayOptionsType: {
type: ArrayConstructor;
scope: 'storage';
};
};
type: Array,
scope: 'storage',
},
},
configs: {
stringRootType: {
cli: { type: StringConstructor };
scope: 'storage';
};
cli: { type: String },
scope: 'storage',
},
booleanCliType: {
cli: { type: BooleanConstructor };
scope: 'storage';
};
cli: { type: Boolean },
scope: 'storage',
},
none: {
scope: 'none';
};
scope: 'none',
},
choiceType: {
cli: {
type: StringConstructor;
};
choices: ['foo', 'no'];
scope: 'storage';
};
type: String,
},
choices: ['foo', 'no'],
scope: 'storage',
},
unknownType: {
cli: {
type: () => any;
};
scope: 'storage';
};
};
};
type: () => {},
},
scope: 'storage',
},
},
} as const;

type TestCommand = typeof _testCommand;

type StorageProperties = ExportStoragePropertiesFromCommand<TestCommand>;

Expand Down Expand Up @@ -123,3 +128,40 @@ const _applicationOptionsError = {
// @ts-expect-error unknow field
foo: 'bar',
} satisfies ApplicationOptions;

const _dummyCommand = {
options: {},
configs: {},
} as const;

// Check if the type allows any property.
// @ts-expect-error unknown field
(() => {})(({} as ExportApplicationPropertiesFromCommand<typeof _dummyCommand>).nonExisting);
// @ts-expect-error unknown field
(() => {})(({} as ExportStoragePropertiesFromCommand<typeof _dummyCommand>).nonExisting);

type _DummyCommandAssertions = AssertType<false, IsNever<ExportApplicationPropertiesFromCommand<typeof _dummyCommand>>>;

const _simpleConfig = {
options: {},
configs: {
stringOption: {
cli: { type: String },
scope: 'storage',
},
},
} as const;

type _SimpleConfigAssertions = AssertType<false, IsNever<ExportApplicationPropertiesFromCommand<typeof _simpleConfig>>>;

const _choiceConfig = {
options: {},
configs: {
stringOption: {
choices: ['foo', 'bar'],
scope: 'storage',
},
},
} as const;

type _ChoiceConfigAssertions = AssertType<false, IsNever<ExportApplicationPropertiesFromCommand<typeof _choiceConfig>>>;
7 changes: 6 additions & 1 deletion lib/types/application/application.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/* eslint-disable @typescript-eslint/consistent-type-imports */
import type { BaseApplication, CommonClientServerApplication } from '../../../generators/base-application/types.js';
import type { ClientSourceType } from '../../../generators/client/types.js';
import type { LanguagesSource } from '../../../generators/languages/types.js';
import type { SpringBootSourceType } from '../../../generators/server/types.js';
import type { ExportApplicationPropertiesFromCommand } from '../../command/types.js';

export type ApplicationType<Entity> = BaseApplication &
CommonClientServerApplication<Entity> &
ExportApplicationPropertiesFromCommand<typeof import('../../../generators/spring-boot/command.ts').default>;

export type ApplicationType<Entity> = BaseApplication & Partial<CommonClientServerApplication<Entity>>;
export type BaseApplicationSource = SpringBootSourceType & ClientSourceType & LanguagesSource;

0 comments on commit 6e1aff6

Please sign in to comment.