Repository: lukeautry/tsoa (ts)

Issue:
Type aliases should support the @format tag
## Sorting

* **I'm submitting a ...**
  - [ ] bug report
  - [x] feature request
  - [ ] support request

* I confirm that I
  - [x] used the [search](https://github.com/lukeautry/tsoa/search?type=Issues) to make sure that a similar issue hasn't already been submit

## Expected Behavior
Format tag attached to an alias should generate a format requirement
```ts
/**
 * @format email
 */
export type StringAlias = string;
```
```json
"StringAlias": {
  "type": "string",
  "format": "email"
}
```

## Current Behavior
No format is generated for type aliases

```ts
/**
 * @format email
 */
export type StringAlias = string;
```
```json
"StringAlias": {
  "type": "string"
}
```

## Detailed Description
Format specification would be especially useful for type aliases as it would allow centralizing type information. This should be an extremely simple change.

Pull request description:
@format support for type aliases
### All Submissions:

* [x] Have you followed the guidelines in our [Contributing](https://github.com/lukeautry/tsoa/tree/master/docs/CONTRIBUTING.md) document?
* [x] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/lukeautry/tsoa/pulls) for the same update/change?
* [x] Have you written unit tests?
* [x] Have you written unit tests that cover the negative cases (i.e.: if bad data is submitted, does the library respond properly)?
* [x] This PR is associated with an existing issue?

**Closing issues**
closes #619 

### If this is a new feature submission:

* [x] Has the issue had a maintainer respond to the issue and clarify that the feature is something that aligns with the [goals](https://github.com/lukeautry/tsoa#goal) and [philosophy](https://github.com/lukeautry/tsoa#philosophy) of the project?

**Test plan**

Format is required to be generated for type aliases.

Patch:
diff --git a/src/metadataGeneration/typeResolver.ts b/src/metadataGeneration/typeResolver.ts
index ad6e036f..e9e86a6a 100644
--- a/src/metadataGeneration/typeResolver.ts
+++ b/src/metadataGeneration/typeResolver.ts
@@ -437,6 +437,7 @@ export class TypeResolver {
       default: getJSDocComment(declaration, 'default'),
       description: this.getNodeDescription(declaration),
       refName: this.getRefTypeName(name),
+      format: this.getNodeFormat(declaration),
       type: new TypeResolver(declaration.type, this.current, declaration, this.context, this.referencer || referencer).resolve(),
       validators: getPropertyValidators(declaration) || {},
       ...(example && { example }),
diff --git a/src/swagger/specGenerator2.ts b/src/swagger/specGenerator2.ts
index b94515da..6263996a 100644
--- a/src/swagger/specGenerator2.ts
+++ b/src/swagger/specGenerator2.ts
@@ -92,6 +92,7 @@ export class SpecGenerator2 extends SpecGenerator {
         };
       } else if (referenceType.dataType === 'refAlias') {
         const swaggerType = this.getSwaggerType(referenceType.type);
+        const format = referenceType.format as Swagger.DataFormat;
         const validators = Object.keys(referenceType.validators)
           .filter(key => {
             return !key.startsWith('is') && key !== 'minDate' && key !== 'maxDate';
@@ -107,6 +108,7 @@ export class SpecGenerator2 extends SpecGenerator {
           ...(swaggerType as Swagger.Schema),
           default: referenceType.default || swaggerType.default,
           example: referenceType.example,
+          format: format || swaggerType.format,
           description: referenceType.description,
           ...validators,
         };
diff --git a/src/swagger/specGenerator3.ts b/src/swagger/specGenerator3.ts
index 4c84c504..3bccab16 100644
--- a/src/swagger/specGenerator3.ts
+++ b/src/swagger/specGenerator3.ts
@@ -176,6 +176,7 @@ export class SpecGenerator3 extends SpecGenerator {
         }
       } else if (referenceType.dataType === 'refAlias') {
         const swaggerType = this.getSwaggerType(referenceType.type);
+        const format = referenceType.format as Swagger.DataFormat;
         const validators = Object.keys(referenceType.validators)
           .filter(key => {
             return !key.startsWith('is') && key !== 'minDate' && key !== 'maxDate';
@@ -191,6 +192,7 @@ export class SpecGenerator3 extends SpecGenerator {
           ...(swaggerType as Swagger.Schema3),
           default: referenceType.default || swaggerType.default,
           example: referenceType.example,
+          format: format || swaggerType.format,
           description: referenceType.description,
           ...validators,
         };


Test patch:
diff --git a/tests/fixtures/testModel.ts b/tests/fixtures/testModel.ts
index 1c5a974c..686caa91 100644
--- a/tests/fixtures/testModel.ts
+++ b/tests/fixtures/testModel.ts
@@ -173,6 +173,7 @@ type ThreeOrFour = TypeAliasModel3 | TypeAlias4;
 /**
  * A Word shall be a non-empty sting
  * @minLength 1
+ * @format password
  */
 type Word = string;
 
diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
index 7cbf213c..b425854b 100644
--- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
+++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
@@ -542,7 +542,7 @@ describe('Definition generation', () => {
             });
 
             const wordSchema = getValidatedDefinition('Word', currentSpec);
-            expect(wordSchema).to.deep.eq({ type: 'string', description: 'A Word shall be a non-empty sting', example: undefined, default: undefined, minLength: 1 });
+            expect(wordSchema).to.deep.eq({ type: 'string', description: 'A Word shall be a non-empty sting', example: undefined, default: undefined, minLength: 1, format: 'password' });
 
             const fourtyTwoSchema = getValidatedDefinition('FourtyTwo', currentSpec);
             expect(fourtyTwoSchema).to.deep.eq({
@@ -564,6 +564,7 @@ describe('Definition generation', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
 
             const intersectionAliasSchema = getValidatedDefinition('IntersectionAlias', currentSpec);
@@ -577,6 +578,7 @@ describe('Definition generation', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
 
             const nolAliasSchema = getValidatedDefinition('NolAlias', currentSpec);
@@ -590,13 +592,14 @@ describe('Definition generation', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
 
             const genericAliasStringSchema = getValidatedDefinition('GenericAlias_string_', currentSpec);
-            expect(genericAliasStringSchema).to.deep.eq({ type: 'string', description: undefined, example: undefined, default: undefined });
+            expect(genericAliasStringSchema).to.deep.eq({ type: 'string', description: undefined, example: undefined, default: undefined, format: undefined });
 
             const genericAliasModelSchema = getValidatedDefinition('GenericAlias_Model_', currentSpec);
-            expect(genericAliasModelSchema).to.deep.eq({ $ref: '#/definitions/Model', description: undefined, example: undefined, default: undefined });
+            expect(genericAliasModelSchema).to.deep.eq({ $ref: '#/definitions/Model', description: undefined, example: undefined, default: undefined, format: undefined });
 
             const forwardGenericAliasBooleanAndTypeAliasModel1Schema = getValidatedDefinition('ForwardGenericAlias_boolean.TypeAliasModel1_', currentSpec);
             expect(forwardGenericAliasBooleanAndTypeAliasModel1Schema).to.deep.eq({
@@ -604,6 +607,7 @@ describe('Definition generation', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
 
             expect(getValidatedDefinition('GenericAlias_TypeAliasModel1_', currentSpec)).to.deep.eq({
@@ -611,6 +615,7 @@ describe('Definition generation', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
           },
           advancedTypeAliases: (propertyName, propertySchema) => {
@@ -646,6 +651,7 @@ describe('Definition generation', () => {
                 description: 'Construct a type with the properties of T except for those in type K.',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -659,6 +665,7 @@ describe('Definition generation', () => {
                 description: 'From T, pick a set of properties whose keys are in the union K',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -670,6 +677,7 @@ describe('Definition generation', () => {
                 description: 'Construct a type with the properties of T except for those in type K.',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -686,6 +694,7 @@ describe('Definition generation', () => {
                 description: 'From T, pick a set of properties whose keys are in the union K',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -698,6 +707,7 @@ describe('Definition generation', () => {
                 description: 'Make all properties in T optional',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -709,6 +719,7 @@ describe('Definition generation', () => {
                 description: 'Exclude from T those types that are assignable to U',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -720,6 +731,7 @@ describe('Definition generation', () => {
                 description: 'Exclude from T those types that are assignable to U',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -733,6 +745,7 @@ describe('Definition generation', () => {
                 default: undefined,
                 description: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -755,6 +768,7 @@ describe('Definition generation', () => {
                 description: 'Exclude from T those types that are assignable to U',
                 default: undefined,
                 example: undefined,
+                format: undefined,
                 ['x-nullable']: false,
               },
               `for definition linked by ${propertyName}`,
@@ -767,6 +781,7 @@ describe('Definition generation', () => {
                 description: 'Exclude from T those types that are assignable to U',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -801,6 +816,7 @@ describe('Definition generation', () => {
                 description: 'From T, pick a set of properties whose keys are in the union K',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -835,6 +851,7 @@ describe('Definition generation', () => {
                 description: 'Make all properties in T readonly',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for definition linked by ${propertyName}`,
             );
@@ -912,10 +929,13 @@ describe('Definition generation', () => {
             });
 
             const maybeString = getValidatedDefinition('Maybe_string_', currentSpec);
-            expect(maybeString).to.deep.eq({ type: 'string', description: undefined, example: undefined, default: undefined, ['x-nullable']: true }, `for schema linked by property ${propertyName}`);
+            expect(maybeString).to.deep.eq(
+              { type: 'string', description: undefined, format: undefined, example: undefined, default: undefined, ['x-nullable']: true },
+              `for schema linked by property ${propertyName}`,
+            );
 
             const maybeWord = getValidatedDefinition('Maybe_Word_', currentSpec);
-            expect(maybeWord).to.deep.eq({ type: 'object', description: undefined, example: undefined, default: undefined }, `for schema linked by property ${propertyName}`);
+            expect(maybeWord).to.deep.eq({ type: 'object', description: undefined, example: undefined, default: undefined, format: undefined }, `for schema linked by property ${propertyName}`);
           },
         };
 
diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts
index be6fed66..f5099fbc 100644
--- a/tests/unit/swagger/schemaDetails3.spec.ts
+++ b/tests/unit/swagger/schemaDetails3.spec.ts
@@ -2,11 +2,11 @@ import { expect } from 'chai';
 import 'mocha';
 import { SwaggerConfig } from '../../../src/config';
 import { MetadataGenerator } from '../../../src/metadataGeneration/metadataGenerator';
+import { Tsoa } from '../../../src/metadataGeneration/tsoa';
 import { SpecGenerator3 } from '../../../src/swagger/specGenerator3';
 import { Swagger } from '../../../src/swagger/swagger';
 import { getDefaultOptions } from '../../fixtures/defaultOptions';
 import { TestModel } from '../../fixtures/duplicateTestModel';
-import { Tsoa } from '../../../src/metadataGeneration/tsoa';
 
 describe('Definition generation for OpenAPI 3.0.0', () => {
   const metadata = new MetadataGenerator('./tests/fixtures/controllers/getController.ts').Generate();
@@ -520,6 +520,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
               default: undefined,
               description: undefined,
               example: undefined,
+              format: undefined,
             });
           },
           strLiteralArr: (propertyName, propertySchema) => {
@@ -702,7 +703,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
             });
 
             const wordSchema = getComponentSchema('Word', currentSpec);
-            expect(wordSchema).to.deep.eq({ type: 'string', description: 'A Word shall be a non-empty sting', example: undefined, default: undefined, minLength: 1 });
+            expect(wordSchema).to.deep.eq({ type: 'string', description: 'A Word shall be a non-empty sting', example: undefined, default: undefined, minLength: 1, format: 'password' });
 
             const fourtyTwoSchema = getComponentSchema('FourtyTwo', currentSpec);
             expect(fourtyTwoSchema).to.deep.eq({
@@ -724,6 +725,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
 
             const intersectionAliasSchema = getComponentSchema('IntersectionAlias', currentSpec);
@@ -741,6 +743,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
               ],
               description: undefined,
               example: undefined,
+              format: undefined,
               default: undefined,
             });
 
@@ -755,13 +758,14 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
 
             const genericAliasStringSchema = getComponentSchema('GenericAlias_string_', currentSpec);
-            expect(genericAliasStringSchema).to.deep.eq({ type: 'string', default: undefined, description: undefined, example: undefined });
+            expect(genericAliasStringSchema).to.deep.eq({ type: 'string', default: undefined, description: undefined, example: undefined, format: undefined });
 
             const genericAliasModelSchema = getComponentSchema('GenericAlias_Model_', currentSpec);
-            expect(genericAliasModelSchema).to.deep.eq({ $ref: '#/components/schemas/Model', default: undefined, description: undefined, example: undefined });
+            expect(genericAliasModelSchema).to.deep.eq({ $ref: '#/components/schemas/Model', default: undefined, description: undefined, example: undefined, format: undefined });
 
             const forwardGenericAliasBooleanAndTypeAliasModel1Schema = getComponentSchema('ForwardGenericAlias_boolean.TypeAliasModel1_', currentSpec);
             expect(forwardGenericAliasBooleanAndTypeAliasModel1Schema).to.deep.eq({
@@ -769,6 +773,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
 
             expect(getComponentSchema('GenericAlias_TypeAliasModel1_', currentSpec)).to.deep.eq({
@@ -776,6 +781,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
               description: undefined,
               example: undefined,
               default: undefined,
+              format: undefined,
             });
           },
           advancedTypeAliases: (propertyName, propertySchema) => {
@@ -811,6 +817,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'Construct a type with the properties of T except for those in type K.',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -824,6 +831,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'From T, pick a set of properties whose keys are in the union K',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -835,6 +843,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'Construct a type with the properties of T except for those in type K.',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -851,6 +860,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'From T, pick a set of properties whose keys are in the union K',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -863,6 +873,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'Make all properties in T optional',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -874,6 +885,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'Exclude from T those types that are assignable to U',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -885,6 +897,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'Exclude from T those types that are assignable to U',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -898,6 +911,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 default: undefined,
                 description: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -919,6 +933,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'Exclude from T those types that are assignable to U',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -930,6 +945,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'Exclude from T those types that are assignable to U',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -964,6 +980,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'From T, pick a set of properties whose keys are in the union K',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for a schema linked by property ${propertyName}`,
             );
@@ -998,6 +1015,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                 description: 'Make all properties in T readonly',
                 default: undefined,
                 example: undefined,
+                format: undefined,
               },
               `for schema linked by property ${propertyName}`,
             );
@@ -1070,13 +1088,13 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
 
             const maybeString = getComponentSchema('Maybe_string_', currentSpec);
             expect(maybeString).to.deep.eq(
-              { oneOf: [{ type: 'string' }, { type: 'number', enum: ['null'], nullable: true }], description: undefined, default: undefined, example: undefined },
+              { oneOf: [{ type: 'string' }, { type: 'number', enum: ['null'], nullable: true }], description: undefined, default: undefined, example: undefined, format: undefined },
               `for schema linked by property ${propertyName}`,
             );
 
             const maybeWord = getComponentSchema('Maybe_Word_', currentSpec);
             expect(maybeWord).to.deep.eq(
-              { oneOf: [{ $ref: '#/components/schemas/Word' }, { type: 'number', enum: ['null'], nullable: true }], description: undefined, default: undefined, example: undefined },
+              { oneOf: [{ $ref: '#/components/schemas/Word' }, { type: 'number', enum: ['null'], nullable: true }], description: undefined, default: undefined, example: undefined, format: undefined },
               `for schema linked by property ${propertyName}`,
             );
           },