import { TargetLanguage } from "../TargetLanguage" ; import { getOptionValues , Option , OptionValues , EnumOption } from "../RendererOptions" ; import { RenderContext } from "../Renderer" ; import { ConvenienceRenderer } from "../ConvenienceRenderer" ; import { funPrefixNamer , Name , Namer } from "../Naming" ; import { acronymOption , acronymStyle , AcronymStyleOptions } from "../support/Acronyms" ; import { capitalize , ClassProperty , ClassType , combineWords , firstUpperWordStyle , matchType , ObjectType , panic , Sourcelike , splitIntoWords , Type , } from ".." ; import { allLowerWordStyle , utf16StringEscape } from "../support/Strings" ; import { isES3IdentifierStart } from "./JavaScriptUnicodeMaps" ; import { legalizeName } from "./JavaScript" ; import { convertersOption } from "../support/Converters" ; import { directlyReachableSingleNamedType } from "../TypeUtils" ; import { arrayIntercalate } from "collection-utils" ; import { PrimitiveType } from "../Type" ; export const javaScriptPropTypesOptions = { acronymStyle : acronymOption ( AcronymStyleOptions . Pascal ) , converters : convertersOption ( ) , moduleSystem : new EnumOption ( "module-system" , "Which module system to use" , [ [ "common-js" , false ] , [ "es6" , true ] , ] , "es6" ) , } ; export class JavaScriptPropTypesTargetLanguage extends TargetLanguage { protected getOptions ( ) { return [ javaScriptPropTypesOptions . acronymStyle , javaScriptPropTypesOptions . converters ] ; } constructor ( displayName = "JavaScript PropTypes" , names = [ "javascript-prop-types" ] , extension = "js" ) { super ( displayName , names , extension ) ; } protected makeRenderer ( renderContext , untypedOptionValues ) { return new JavaScriptPropTypesRenderer ( this , renderContext , getOptionValues ( javaScriptPropTypesOptions , untypedOptionValues ) ) ; } } const identityNamingFunction = funPrefixNamer ( "properties" , ( s ) => s ) ; export class JavaScriptPropTypesRenderer extends ConvenienceRenderer { constructor ( targetLanguage , renderContext , private readonly _jsOptions ) { super ( targetLanguage , renderContext ) ; } protected nameStyle ( original , upper ) { const acronyms = acronymStyle ( this . _jsOptions . acronymStyle ) ; const words = splitIntoWords ( original ) ; return combineWords ( words , legalizeName , upper ? firstUpperWordStyle : allLowerWordStyle , firstUpperWordStyle , upper ? ( s ) => capitalize ( acronyms ( s ) ) : allLowerWordStyle , acronyms , "" , isES3IdentifierStart ) ; } protected makeNamedTypeNamer ( ) { return funPrefixNamer ( "types" , ( s ) => this . nameStyle ( s , true ) ) ; } protected namerForObjectProperty ( ) { return identityNamingFunction ; } protected makeUnionMemberNamer ( ) { return null ; } protected makeEnumCaseNamer ( ) { return funPrefixNamer ( "enum-cases" , ( s ) => this . nameStyle ( s , false ) ) ; } protected namedTypeToNameForTopLevel ( type ) { return directlyReachableSingleNamedType ( type ) ; } protected makeNameForProperty ( c , className , p , jsonName , _assignedName ) { return super . makeNameForProperty ( c , className , p , jsonName , undefined ) ; } typeMapTypeFor ( t , required = true ) { if ( [ "class" , "object" , "enum" ] . indexOf ( t . kind ) >= 0 ) { return [ "_" , this . nameForNamedType ( t ) ] ; } const match = matchType < Sourcelike > ( t , ( _anyType ) => "PropTypes.any" , ( _nullType ) => "PropTypes.any" , ( _boolType ) => "PropTypes.bool" , ( _integerType ) => "PropTypes.number" , ( _doubleType ) => "PropTypes.number" , ( _stringType ) => "PropTypes.string" , ( arrayType ) => [ "PropTypes.arrayOf(" , this . typeMapTypeFor ( arrayType . items , false ) , ")" ] , ( _classType ) => panic ( "Should already be handled." ) , ( _mapType ) => "PropTypes.object" , ( _enumType ) => panic ( "Should already be handled." ) , ( unionType ) => { const children = Array . from ( unionType . getChildren ( ) ) . map ( ( type ) => this . typeMapTypeFor ( type , false ) ) ; return [ "PropTypes.oneOfType([" , ... arrayIntercalate ( ", " , children ) , "])" ] ; } , ( _transformedStringType ) => { return "PropTypes.string" ; } ) ; if ( required ) { return [ match ] ; } return match ; } typeMapTypeForProperty ( p ) { const typeMap = this . typeMapTypeFor ( p . type ) ; if ( ! p . isOptional ) { return typeMap ; } return [ "PropType.any" ] ; } private importStatement ( lhs , moduleName ) { if ( this . _jsOptions . moduleSystem ) { return [ "import " , lhs , " from " , moduleName , ";" ] ; } else { return [ "const " , lhs , " = require(" , moduleName , ");" ] ; } } protected emitUsageComments ( ) { this . emitCommentLines ( [ "Example usage:" , "" , this . importStatement ( "{ MyShape }" , "./myShape.js" ) , "" , "class MyComponent extends React.Component {" , "  //" , "}" , "" , "MyComponent.propTypes = {" , "  input: MyShape" , "};" , ] , "// " ) ; } protected emitBlock ( source , end , emit ) { this . emitLine ( source , "{" ) ; this . indent ( emit ) ; this . emitLine ( "}" , end ) ; } protected emitImports ( ) { this . ensureBlankLine ( ) ; this . emitLine ( this . importStatement ( "PropTypes" , '"prop-types"' ) ) ; } private emitExport ( name , value ) { if ( this . _jsOptions . moduleSystem ) { this . emitLine ( "export const " , name , " = " , value , ";" ) ; } else { this . emitLine ( "module.exports = exports = { " , name , ": " , value , " };" ) ; } } protected emitTypes ( ) { this . ensureBlankLine ( ) ; this . forEachObject ( "none" , ( _type , name ) => { this . emitLine ( "let _" , name , ";" ) ; } ) ; this . forEachEnum ( "none" , ( enumType , enumName ) => { const options = [ ] ; this . forEachEnumCase ( enumType , "none" , ( name , _jsonName , _position ) => { options . push ( "'" ) ; options . push ( name ) ; options . push ( "'" ) ; options . push ( ", " ) ; } ) ; options . pop ( ) ; this . emitLine ( [ "const _" , enumName , " = PropTypes.oneOfType([" , ... options , "]);" ] ) ; } ) ; const order = [ ] ; const mapKey = [ ] ; const mapValue = [ ] ; this . forEachObject ( "none" , ( type , name ) => { mapKey . push ( name ) ; mapValue . push ( this . gatherSource ( ( ) => this . emitObject ( name , type ) ) ) ; } ) ; mapKey . forEach ( ( _ , index ) => { let ordinal = 0 ; const source = mapValue [ index ] ; const names = source . filter ( ( value ) => value as Name ) ; for ( let i = 0 ; i < names . length ; i ++ ) { const depName = names [ i ] ; for ( let j = 0 ; j < order . length ; j ++ ) { const depIndex = order [ j ] ; if ( mapKey [ depIndex ] === depName ) { ordinal = Math . max ( ordinal , depIndex + 1 ) ; } } } order . splice ( ordinal , 0 , index ) ; } ) ; order . forEach ( ( i ) => this . emitGatheredSource ( mapValue [ i ] ) ) ; this . forEachTopLevel ( "none" , ( type , name ) => { if ( type instanceof PrimitiveType ) { this . ensureBlankLine ( ) ; this . emitExport ( name , this . typeMapTypeFor ( type ) ) ; } else { if ( type . kind === "array" ) { this . ensureBlankLine ( ) ; this . emitExport ( name , [ "PropTypes.arrayOf(" , this . typeMapTypeFor ( ( type as any ) . items ) , ")" ] ) ; } else { this . ensureBlankLine ( ) ; this . emitExport ( name , [ "_" , name ] ) ; } } } ) ; } private emitObject ( name , t ) { this . ensureBlankLine ( ) ; this . emitLine ( "_" , name , " = PropTypes.shape({" ) ; this . indent ( ( ) => { this . forEachClassProperty ( t , "none" , ( _ , jsonName , property ) => { this . emitLine ( `"${ utf16StringEscape ( jsonName ) }"` , ": " , this . typeMapTypeForProperty ( property ) , "," ) ; } ) ; } ) ; this . emitLine ( "});" ) ; } protected emitSourceStructure ( ) { if ( this . leadingComments !== undefined ) { this . emitCommentLines ( this . leadingComments ) ; } else { this . emitUsageComments ( ) ; } this . emitImports ( ) ; this . emitTypes ( ) ; } }