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

internal: adjust application type to don't allow unknown fields #27689

Merged
merged 3 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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;
21 changes: 20 additions & 1 deletion 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,11 +236,24 @@ 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 */
/**
* Keep Options/Config filtered by choices
*
* @example
* ```ts
* type CofigsWithChoice = OnlyCofigsWithChoice<{ clientFramework: { choices: ['angular', 'no'], scope: 'storage' }, clientTestFramework: { choices: ['cypress', 'no'], scope: 'storage' } }>
mshima marked this conversation as resolved.
Show resolved Hide resolved
* ```
*/
type OnlyCofigsWithChoice<D extends ParseableConfigs, C extends boolean> = {
mshima marked this conversation as resolved.
Show resolved Hide resolved
[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
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;
Loading