Skip to content

Commit

Permalink
fix(plugin): fix optional enums when strict mode enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Feb 18, 2020
1 parent c5ecd8a commit 42eb5d3
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 15 deletions.
47 changes: 38 additions & 9 deletions lib/plugin/utils/plugin-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,9 @@ export function getTypeReferenceAsString(
if (text === Date.name) {
return text;
}
if (isAutoGeneratedUnion(type)) {
return getTypeReferenceAsString(
type.types[type.types.length - 1],
typeChecker
);
if (isAutoGeneratedUnion(type, typeChecker)) {
const types = (type as ts.UnionOrIntersectionType).types;
return getTypeReferenceAsString(types[types.length - 1], typeChecker);
}
if (
text === 'any' ||
Expand Down Expand Up @@ -151,13 +149,44 @@ export function isDynamicallyAdded(identifier: ts.Node) {
* @param type
*/
export function isAutoGeneratedUnion(
type: ts.Type
): type is ts.UnionOrIntersectionType {
type: ts.Type,
typeChecker: ts.TypeChecker
): boolean | ts.Type {
if (type.isUnionOrIntersection() && !isEnum(type)) {
if (type.types && type.types.length === 2) {
if (type.types.some((type: any) => type.intrinsicName === 'undefined')) {
if (!type.types) {
return false;
}
const undefinedTypeIndex = type.types.findIndex(
(type: any) => type.intrinsicName === 'undefined'
);

// "strict" mode for enums
let parentType = undefined;
const isParentSymbolEqual = type.types.every((item, index) => {
if (index === undefinedTypeIndex) {
return true;
}
if (!item.symbol) {
return false;
}
if (!(item.symbol as any).parent) {
return false;
}
const symbolType = typeChecker.getDeclaredTypeOfSymbol(
(item.symbol as any).parent
);
if (symbolType === parentType || !parentType) {
parentType = symbolType;
return true;
}
return false;
});
if (isParentSymbolEqual) {
return parentType;
}
// "strict" mode for non-enum properties
if (type.types.length === 2 && undefinedTypeIndex >= 0) {
return true;
}
}
return false;
Expand Down
8 changes: 7 additions & 1 deletion lib/plugin/visitors/model-class.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getDecoratorOrUndefinedByNames,
getTypeReferenceAsString,
hasPropertyKey,
isAutoGeneratedUnion,
replaceImportPath
} from '../utils/plugin-utils';
import { AbstractFileVisitor } from './abstract.visitor';
Expand Down Expand Up @@ -237,7 +238,12 @@ export class ModelClassVisitor extends AbstractFileVisitor {
}
}
if (!isEnum(type)) {
return undefined;
const typeOrBool = isAutoGeneratedUnion(type, typeChecker);
if (typeof typeOrBool !== 'boolean') {
type = typeOrBool;
} else {
return undefined;
}
}
const enumRef = replaceImportPath(getText(type, typeChecker), hostFilename);
const enumProperty = ts.createPropertyAssignment(
Expand Down
3 changes: 2 additions & 1 deletion test/plugin/fixtures/create-cat.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class CreateCatDto {
age: number = 3;
tags: string[];
status: Status = Status.ENABLED;
status2?: Status;
@ApiProperty({ type: String })
@IsString()
Expand Down Expand Up @@ -45,7 +46,7 @@ export class CreateCatDto {
this.status = Status.ENABLED;
}
static _OPENAPI_METADATA_FACTORY() {
return { name: { required: true, type: () => String }, age: { required: true, type: () => Number, default: 3, minimum: 0, maximum: 10 }, tags: { required: true, type: () => [String] }, status: { required: true, default: Status.ENABLED, enum: Status }, breed: { required: false, type: () => String }, nodes: { required: true, type: () => [Object] }, date: { required: true, type: () => Date } };
return { name: { required: true, type: () => String }, age: { required: true, type: () => Number, default: 3, minimum: 0, maximum: 10 }, tags: { required: true, type: () => [String] }, status: { required: true, default: Status.ENABLED, enum: Status }, status2: { required: false, enum: Status }, breed: { required: false, type: () => String }, nodes: { required: true, type: () => [Object] }, date: { required: true, type: () => Date } };
}
}
__decorate([
Expand Down
12 changes: 8 additions & 4 deletions test/plugin/model-class-visitor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ describe('API model properties', () => {
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ESNext,
newLine: ts.NewLineKind.LineFeed,
noEmitHelpers: true
noEmitHelpers: true,
strict: true
};
const filename = 'create-cat.dto.ts';
const fakeProgram = ts.createProgram([filename], options);
Expand All @@ -43,7 +44,8 @@ describe('API model properties', () => {
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ESNext,
newLine: ts.NewLineKind.LineFeed,
noEmitHelpers: true
noEmitHelpers: true,
strict: true
};
const filename = 'create-cat.dto.ts';
const fakeProgram = ts.createProgram([filename], options);
Expand All @@ -63,7 +65,8 @@ describe('API model properties', () => {
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ESNext,
newLine: ts.NewLineKind.LineFeed,
noEmitHelpers: true
noEmitHelpers: true,
strict: true
};
const filename = 'create-cat-alt2.dto.ts';
const fakeProgram = ts.createProgram([filename], options);
Expand All @@ -83,7 +86,8 @@ describe('API model properties', () => {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
newLine: ts.NewLineKind.LineFeed,
noEmitHelpers: true
noEmitHelpers: true,
strict: true
};
const filename = 'es5-class.dto.ts';
const fakeProgram = ts.createProgram([filename], options);
Expand Down

0 comments on commit 42eb5d3

Please sign in to comment.