diff --git a/src/generator.ts b/src/generator.ts index 2487877b..3b403b12 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -304,7 +304,7 @@ function generateInterface(ast: TInterface, options: Options): string { ) .map( ([isRequired, keyName, ast, type]) => - (hasComment(ast) && !ast.standaloneName ? generateComment(ast.comment) + '\n' : '') + + (hasComment(ast) && !ast.standaloneName ? generateComment(ast.comment, ast.deprecated) + '\n' : '') + escapeKeyName(keyName) + (isRequired ? '' : '?') + ': ' + @@ -316,13 +316,19 @@ function generateInterface(ast: TInterface, options: Options): string { ) } -function generateComment(comment: string): string { - return ['/**', ...comment.split('\n').map(_ => ' * ' + _), ' */'].join('\n') +function generateComment(comment: string, deprecated?: boolean): string { + const commentLines = ['/**'] + if (deprecated) { + commentLines.push(' * @deprecated') + } + commentLines.push(...comment.split('\n').map(_ => ' * ' + _)) + commentLines.push(' */') + return commentLines.join('\n') } function generateStandaloneEnum(ast: TEnum, options: Options): string { return ( - (hasComment(ast) ? generateComment(ast.comment) + '\n' : '') + + (hasComment(ast) ? generateComment(ast.comment, ast.deprecated) + '\n' : '') + 'export ' + (options.enableConstEnums ? 'const ' : '') + `enum ${toSafeString(ast.standaloneName)} {` + @@ -335,7 +341,7 @@ function generateStandaloneEnum(ast: TEnum, options: Options): string { function generateStandaloneInterface(ast: TNamedInterface, options: Options): string { return ( - (hasComment(ast) ? generateComment(ast.comment) + '\n' : '') + + (hasComment(ast) ? generateComment(ast.comment, ast.deprecated) + '\n' : '') + `export interface ${toSafeString(ast.standaloneName)} ` + (ast.superTypes.length > 0 ? `extends ${ast.superTypes.map(superType => toSafeString(superType.standaloneName)).join(', ')} ` diff --git a/src/parser.ts b/src/parser.ts index 622ac94a..7012d425 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -148,14 +148,16 @@ function parseNonLiteral( keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), params: schema.allOf!.map(_ => parse(_, options, undefined, processed, usedNames)), - type: 'INTERSECTION' + type: 'INTERSECTION', + deprecated: schema.deprecated } case 'ANY': return { ...(options.unknownAny ? T_UNKNOWN : T_ANY), comment: schema.description, keyName, - standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames) + standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), + deprecated: schema.deprecated } case 'ANY_OF': return { @@ -163,14 +165,16 @@ function parseNonLiteral( keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), params: schema.anyOf!.map(_ => parse(_, options, undefined, processed, usedNames)), - type: 'UNION' + type: 'UNION', + deprecated: schema.deprecated } case 'BOOLEAN': return { comment: schema.description, keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'BOOLEAN' + type: 'BOOLEAN', + deprecated: schema.deprecated } case 'CUSTOM_TYPE': return { @@ -178,7 +182,8 @@ function parseNonLiteral( keyName, params: schema.tsType!, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'CUSTOM_TYPE' + type: 'CUSTOM_TYPE', + deprecated: schema.deprecated } case 'NAMED_ENUM': return { @@ -189,7 +194,8 @@ function parseNonLiteral( ast: parseLiteral(_, undefined), keyName: schema.tsEnumNames![n] })), - type: 'ENUM' + type: 'ENUM', + deprecated: schema.deprecated } case 'NAMED_SCHEMA': return newInterface(schema as SchemaSchema, options, processed, usedNames, keyName) @@ -198,28 +204,32 @@ function parseNonLiteral( comment: schema.description, keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'NEVER' + type: 'NEVER', + deprecated: schema.deprecated } case 'NULL': return { comment: schema.description, keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'NULL' + type: 'NULL', + deprecated: schema.deprecated } case 'NUMBER': return { comment: schema.description, keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'NUMBER' + type: 'NUMBER', + deprecated: schema.deprecated } case 'OBJECT': return { comment: schema.description, keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'OBJECT' + type: 'OBJECT', + deprecated: schema.deprecated } case 'ONE_OF': return { @@ -227,7 +237,8 @@ function parseNonLiteral( keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), params: schema.oneOf!.map(_ => parse(_, options, undefined, processed, usedNames)), - type: 'UNION' + type: 'UNION', + deprecated: schema.deprecated } case 'REFERENCE': throw Error(format('Refs should have been resolved by the resolver!', schema)) @@ -236,7 +247,8 @@ function parseNonLiteral( comment: schema.description, keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'STRING' + type: 'STRING', + deprecated: schema.deprecated } case 'TYPED_ARRAY': if (Array.isArray(schema.items)) { @@ -250,7 +262,8 @@ function parseNonLiteral( minItems, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), params: schema.items.map(_ => parse(_, options, undefined, processed, usedNames)), - type: 'TUPLE' + type: 'TUPLE', + deprecated: schema.deprecated } if (schema.additionalItems === true) { arrayType.spreadParam = options.unknownAny ? T_UNKNOWN : T_ANY @@ -264,7 +277,8 @@ function parseNonLiteral( keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), params: parse(schema.items!, options, `{keyNameFromDefinition}Items`, processed, usedNames), - type: 'ARRAY' + type: 'ARRAY', + deprecated: schema.deprecated } } case 'UNION': @@ -276,7 +290,8 @@ function parseNonLiteral( const member: LinkedJSONSchema = {...omit(schema, '$id', 'description', 'title'), type} return parse(maybeStripDefault(member as any), options, undefined, processed, usedNames) }), - type: 'UNION' + type: 'UNION', + deprecated: schema.deprecated } case 'UNNAMED_ENUM': return { @@ -284,7 +299,8 @@ function parseNonLiteral( keyName, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), params: schema.enum!.map(_ => parseLiteral(_, undefined)), - type: 'UNION' + type: 'UNION', + deprecated: schema.deprecated } case 'UNNAMED_SCHEMA': return newInterface(schema as SchemaSchema, options, processed, usedNames, keyName, keyNameFromDefinition) @@ -304,7 +320,8 @@ function parseNonLiteral( // if there is no maximum, then add a spread item to collect the rest spreadParam: maxItems >= 0 ? undefined : params, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'TUPLE' + type: 'TUPLE', + deprecated: schema.deprecated } } @@ -313,7 +330,8 @@ function parseNonLiteral( keyName, params, standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames), - type: 'ARRAY' + type: 'ARRAY', + deprecated: schema.deprecated } } } @@ -347,7 +365,8 @@ function newInterface( params: parseSchema(schema, options, processed, usedNames, name), standaloneName: name, superTypes: parseSuperTypes(schema, options, processed, usedNames), - type: 'INTERFACE' + type: 'INTERFACE', + deprecated: schema.deprecated } } diff --git a/src/types/AST.ts b/src/types/AST.ts index e8f33371..3477cb56 100644 --- a/src/types/AST.ts +++ b/src/types/AST.ts @@ -27,6 +27,7 @@ export interface AbstractAST { keyName?: string standaloneName?: string type: AST_TYPE + deprecated?: boolean } export type ASTWithComment = AST & {comment: string} @@ -34,7 +35,9 @@ export type ASTWithName = AST & {keyName: string} export type ASTWithStandaloneName = AST & {standaloneName: string} export function hasComment(ast: AST): ast is ASTWithComment { - return 'comment' in ast && ast.comment != null && ast.comment !== '' + return ('comment' in ast && ast.comment != null && ast.comment !== '') + // Compare to true because ast.deprecated might be undefined + || ('deprecated' in ast && ast.deprecated === true) } export function hasStandaloneName(ast: AST): ast is ASTWithStandaloneName { diff --git a/src/types/JSONSchema.ts b/src/types/JSONSchema.ts index 29e94957..4c644f3f 100644 --- a/src/types/JSONSchema.ts +++ b/src/types/JSONSchema.ts @@ -34,6 +34,10 @@ export interface JSONSchema extends JSONSchema4 { * schema extension to support custom types */ tsType?: string + /** + * property exists at least in https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.3 + */ + deprecated?: boolean } export const Parent = Symbol('Parent') diff --git a/src/validator.ts b/src/validator.ts index eca428c6..69fb390d 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -37,6 +37,11 @@ rules.set('When minItems exists, minItems >= 0', schema => { } }) +rules.set('deprecated must be a boolean', schema => { + const typeOfDeprecated = typeof schema.deprecated + return typeOfDeprecated === 'boolean' || typeOfDeprecated === 'undefined' +}) + export function validate(schema: LinkedJSONSchema, filename: string): string[] { const errors: string[] = [] rules.forEach((rule, ruleName) => { diff --git a/test/__snapshots__/test/test.ts.md b/test/__snapshots__/test/test.ts.md index 70291306..c89597a0 100644 --- a/test/__snapshots__/test/test.ts.md +++ b/test/__snapshots__/test/test.ts.md @@ -809,6 +809,39 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## deprecated.js + +> Expected output to match snapshot for e2e test: deprecated.js + + `/* eslint-disable */␊ + /**␊ + * This file was automatically generated by json-schema-to-typescript.␊ + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,␊ + * and run json-schema-to-typescript to regenerate this file.␊ + */␊ + ␊ + /**␊ + * @deprecated␊ + * /* comment * /␊ + */␊ + export interface ExampleSchema {␊ + /**␊ + * @deprecated␊ + * /* nested comment * /␊ + */␊ + firstName: string;␊ + /**␊ + * @deprecated␊ + * Hi, my name's Doechii, this will be in a comment␊ + */␊ + middleName?: string;␊ + /**␊ + * /* nested comment * /␊ + */␊ + lastName?: string;␊ + }␊ + ` + ## disjointType.js > Expected output to match snapshot for e2e test: disjointType.js @@ -442215,6 +442248,7 @@ Generated by [AVA](https://avajs.dev). */␊ created_at?: string␊ /**␊ + * @deprecated␊ * deprecated. Please refer to 'comment' instead␊ */␊ email?: string␊ diff --git a/test/__snapshots__/test/test.ts.snap b/test/__snapshots__/test/test.ts.snap index 034aca45..70be4d24 100644 Binary files a/test/__snapshots__/test/test.ts.snap and b/test/__snapshots__/test/test.ts.snap differ diff --git a/test/e2e/deprecated.ts b/test/e2e/deprecated.ts new file mode 100644 index 00000000..e3aa122f --- /dev/null +++ b/test/e2e/deprecated.ts @@ -0,0 +1,28 @@ +export const input = { + "$schema": "http://json-schema.org/draft/2019-09/schema#", + "title": "Example Schema", + "type": "object", + "deprecated": true, + description: '/* comment */', + "properties": { + "firstName": { + "type": "string", + "deprecated": true, + description: '/* nested comment */' + }, + "middleName":{ + "type": "string", + "deprecated": true, + "description": "Hi, my name's Doechii, this will be in a comment" + }, + "lastName":{ + "type": "string", + "deprecated": false, + description: '/* nested comment */' + }, + }, + "additionalProperties": false, + "required": [ + "firstName" + ] + } \ No newline at end of file