2021-05-26 18:04:45 +03:00
#!/usr/bin/env node
/ * *
* Copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
// @ts-check
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
const yaml = require ( 'yaml' ) ;
const channels = new Set ( ) ;
const inherits = new Map ( ) ;
const mixins = new Map ( ) ;
2022-05-23 19:10:46 +03:00
const COPYRIGHT _HEADER = ` /*
* MIT License
*
* Copyright ( c ) Microsoft Corporation .
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the "Software" ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
* /
` ;
2021-05-26 18:04:45 +03:00
function raise ( item ) {
throw new Error ( 'Invalid item: ' + JSON . stringify ( item , null , 2 ) ) ;
}
function titleCase ( name ) {
return name [ 0 ] . toUpperCase ( ) + name . substring ( 1 ) ;
}
function mapType ( type ) {
if ( type === 'SerializedValue' )
return 'System.Text.Json.JsonElement' ;
if ( type === 'boolean' )
return 'bool' ;
if ( type === 'number' )
return 'int' ;
2021-12-07 00:40:26 +03:00
// TODO: keep the same names in .NET as upstream
2021-05-26 18:04:45 +03:00
if ( type === 'ResourceTiming' )
return 'RequestTimingResult' ;
2021-12-07 00:40:26 +03:00
if ( type === 'LifecycleEvent' )
return 'WaitUntilState' ;
2021-05-26 18:04:45 +03:00
return type ;
}
function nullableSuffix ( inner ) {
if ( [ 'int' , 'boolean' ] . includes ( inner . ts ) )
return inner . optional ? '?' : '' ;
return '' ;
}
2022-05-23 19:10:46 +03:00
function inlineType ( type , indent = '' , name , level ) {
2021-05-26 18:04:45 +03:00
if ( typeof type === 'string' ) {
const optional = type . endsWith ( '?' ) ;
if ( optional )
type = type . substring ( 0 , type . length - 1 ) ;
if ( type === 'binary' )
2022-01-21 17:51:06 +03:00
return { ts : 'byte[]' , scheme : 'tArray(tByte)' , optional } ;
2021-05-26 18:04:45 +03:00
if ( type === 'json' )
return { ts : 'any' , scheme : 'tAny' , optional } ;
2022-05-23 19:10:46 +03:00
if ( [ 'string' , 'boolean' , 'number' , 'undefined' ] . includes ( type ) )
2021-05-26 18:04:45 +03:00
return { ts : mapType ( type ) , scheme : ` t ${ titleCase ( type ) } ` , optional } ;
if ( channels . has ( type ) )
2021-12-07 00:40:26 +03:00
return { ts : ` Core. ${ type } ` , scheme : ` tChannel(' ${ type } ') ` , optional } ;
2021-05-26 18:04:45 +03:00
if ( type === 'Channel' )
return { ts : ` Channel ` , scheme : ` tChannel('*') ` , optional } ;
return { ts : mapType ( type ) , scheme : ` tType(' ${ type } ') ` , optional } ;
}
if ( type . type . startsWith ( 'array' ) ) {
const optional = type . type . endsWith ( '?' ) ;
2022-05-23 19:10:46 +03:00
const inner = inlineType ( type . items , indent , name , level ) ;
2021-05-26 18:04:45 +03:00
return { ts : ` List< ${ inner . ts } > ` , scheme : ` tArray( ${ inner . scheme } ) ` , optional } ;
}
if ( type . type . startsWith ( 'enum' ) ) {
if ( type . literals . includes ( 'networkidle' ) )
return { ts : 'LoadState' , scheme : ` tString ` , optional : false } ;
return { ts : 'string' , scheme : ` tString ` , optional : false } ;
}
if ( type . type . startsWith ( 'object' ) ) {
const optional = type . type . endsWith ( '?' ) ;
2022-05-23 19:10:46 +03:00
2021-05-26 18:04:45 +03:00
const custom = processCustomType ( type , optional ) ;
if ( custom )
return custom ;
2022-05-23 19:10:46 +03:00
if ( level >= 1 ) {
const inner = properties ( type . properties , ' ' , false , name , level ) ;
writeCSharpClass ( name , null , ' {' + inner . ts + '\n }' ) ;
return { ts : name , scheme : 'tObject()' , optional } ;
}
const inner = properties ( type . properties , indent + ' ' , false , name , level ) ;
2021-05-26 18:04:45 +03:00
return {
ts : ` { \n ${ inner . ts } \n ${ indent } } ` ,
scheme : ` tObject({ \n ${ inner . scheme } \n ${ indent } }) ` ,
optional
} ;
}
raise ( type ) ;
}
2022-05-23 19:10:46 +03:00
function properties ( properties , indent , onlyOptional , parentName , level ) {
2021-05-26 18:04:45 +03:00
const ts = [ ] ;
const scheme = [ ] ;
2022-05-23 19:10:46 +03:00
const visitProperties = ( props , parentName ) => {
2021-05-26 18:04:45 +03:00
for ( const [ name , value ] of Object . entries ( props ) ) {
if ( name === 'android' || name === 'electron' )
continue ;
if ( name . startsWith ( '$mixin' ) ) {
2022-05-23 19:10:46 +03:00
visitProperties ( mixins . get ( value ) . properties , parentName + toTitleCase ( name ) ) ;
2021-05-26 18:04:45 +03:00
continue ;
}
2022-05-23 19:10:46 +03:00
const inner = inlineType ( value , indent , parentName + toTitleCase ( name ) , level + 1 ) ;
2021-05-26 18:04:45 +03:00
if ( onlyOptional && ! inner . optional )
continue ;
ts . push ( '' ) ;
2022-05-23 19:10:46 +03:00
ts . push ( ` ${ indent } [JsonPropertyName(" ${ name } ")] ` ) ;
2021-05-26 18:04:45 +03:00
ts . push ( ` ${ indent } public ${ inner . ts } ${ nullableSuffix ( inner ) } ${ toTitleCase ( name ) } { get; set; } ` ) ;
const wrapped = inner . optional ? ` tOptional( ${ inner . scheme } ) ` : inner . scheme ;
scheme . push ( ` ${ indent } ${ name } : ${ wrapped } , ` ) ;
}
} ;
2022-05-23 19:10:46 +03:00
visitProperties ( properties , parentName ) ;
2021-05-26 18:04:45 +03:00
return { ts : ts . join ( '\n' ) , scheme : scheme . join ( '\n' ) } ;
}
2022-05-23 19:10:46 +03:00
function objectType ( props , indent , onlyOptional = false , parentName = '' ) {
2021-05-26 18:04:45 +03:00
if ( ! Object . entries ( props ) . length )
return { ts : ` ${ indent } { \n ${ indent } } ` , scheme : ` tObject({}) ` } ;
2022-05-23 19:10:46 +03:00
const inner = properties ( props , indent + ' ' , onlyOptional , parentName , 0 ) ;
2021-05-26 18:04:45 +03:00
return { ts : ` ${ indent } { ${ inner . ts } \n ${ indent } } ` , scheme : ` tObject({ \n ${ inner . scheme } \n ${ indent } }) ` } ;
}
2021-12-04 05:51:25 +03:00
const yml = fs . readFileSync ( path . join ( _ _dirname , '..' , 'packages' , 'playwright-core' , 'src' , 'protocol' , 'protocol.yml' ) , 'utf-8' ) ;
2021-05-26 18:04:45 +03:00
const protocol = yaml . parse ( yml ) ;
for ( const [ name , value ] of Object . entries ( protocol ) ) {
if ( value . type === 'interface' ) {
channels . add ( name ) ;
if ( value . extends )
inherits . set ( name , value . extends ) ;
}
if ( value . type === 'mixin' )
mixins . set ( name , value ) ;
}
2021-12-04 05:51:25 +03:00
if ( ! process . argv [ 2 ] ) {
2022-05-23 19:10:46 +03:00
console . error ( '.NET repository needs to be specified as an argument.\n' + ` Usage: node ${ path . relative ( process . cwd ( ) , _ _filename ) } ../playwright-dotnet/src/Playwright/ ` ) ;
2021-12-04 05:51:25 +03:00
process . exit ( 1 ) ;
}
2022-05-23 19:10:46 +03:00
const dir = path . join ( process . argv [ 2 ] , 'Transport' , 'Protocol' , 'Generated' ) ;
2021-05-26 18:04:45 +03:00
fs . mkdirSync ( dir , { recursive : true } ) ;
for ( const [ name , item ] of Object . entries ( protocol ) ) {
if ( item . type === 'interface' ) {
2022-09-12 21:01:49 +03:00
const init = objectType ( item . initializer || { } , '' ) ;
2022-05-23 19:10:46 +03:00
const initializerName = name + 'Initializer' ;
const superName = inherits . has ( name ) ? inherits . get ( name ) + 'Initializer' : null ;
writeCSharpClass ( initializerName , superName , init . ts ) ;
} else if ( item . type === 'object' ) {
if ( Object . keys ( item . properties ) . length === 0 )
continue ;
2022-09-12 21:01:49 +03:00
const init = objectType ( item . properties , '' , false , name ) ;
2022-05-23 19:10:46 +03:00
writeCSharpClass ( name , null , init . ts ) ;
}
}
/ * *
2021-08-16 22:49:00 +03:00
*
2022-05-23 19:10:46 +03:00
* @ param { string } className
* @ param { string | undefined } inheritFrom
* @ param { any } serializedProperties
2021-08-16 22:49:00 +03:00
* /
2022-05-23 19:10:46 +03:00
function writeCSharpClass ( className , inheritFrom , serializedProperties ) {
if ( className === 'SerializedArgument' )
return ;
const channels _ts = [ ] ;
channels _ts . push ( COPYRIGHT _HEADER ) ;
channels _ts . push ( 'using System.Collections.Generic;' ) ;
channels _ts . push ( 'using System.Text.Json.Serialization;' ) ;
channels _ts . push ( ` ` ) ;
channels _ts . push ( ` namespace Microsoft.Playwright.Transport.Protocol ` ) ;
channels _ts . push ( ` { ` ) ;
channels _ts . push ( ` internal class ${ className } ${ inheritFrom ? ' : ' + inheritFrom : '' } ` ) ;
channels _ts . push ( serializedProperties ) ;
channels _ts . push ( ` } ` ) ;
channels _ts . push ( ` ` ) ;
writeFile ( ` ${ className } .cs ` , channels _ts . join ( '\n' ) ) ;
2021-05-26 18:04:45 +03:00
}
function writeFile ( file , content ) {
fs . writeFileSync ( path . join ( dir , file ) , content , 'utf8' ) ;
}
/ * *
* @ param { string } name
* @ returns { string }
* /
function toTitleCase ( name ) {
return name . charAt ( 0 ) . toUpperCase ( ) + name . substring ( 1 ) ;
}
function processCustomType ( type , optional ) {
if ( type . properties . name
&& type . properties . value
&& inlineType ( type . properties . name ) . ts === 'string'
2022-05-23 19:10:46 +03:00
&& inlineType ( type . properties . value ) . ts === 'string' )
2021-05-26 18:04:45 +03:00
return { ts : 'HeaderEntry' , scheme : 'tObject()' , optional } ;
2022-05-23 19:10:46 +03:00
2021-05-26 18:04:45 +03:00
if ( type . properties . width
&& type . properties . height
&& inlineType ( type . properties . width ) . ts === 'int'
2022-05-23 19:10:46 +03:00
&& inlineType ( type . properties . height ) . ts === 'int' )
2021-05-26 18:04:45 +03:00
return { ts : 'ViewportSize' , scheme : 'tObject()' , optional } ;
2022-05-23 19:10:46 +03:00
2021-05-26 18:04:45 +03:00
if ( type . properties . url
&& type . properties . lineNumber
&& inlineType ( type . properties . url ) . ts === 'string'
2022-05-23 19:10:46 +03:00
&& inlineType ( type . properties . lineNumber ) . ts === 'int' )
2021-05-26 18:04:45 +03:00
return { ts : 'ConsoleMessageLocation' , scheme : 'tObject()' , optional } ;
2022-05-23 19:10:46 +03:00
2021-05-26 18:04:45 +03:00
if ( type . properties . name
&& type . properties . descriptor
2022-05-23 19:10:46 +03:00
&& inlineType ( type . properties . name ) . ts === 'string' )
2021-05-26 18:04:45 +03:00
return { ts : 'DeviceDescriptorEntry' , scheme : 'tObject()' , optional } ;
2022-05-23 19:10:46 +03:00
2021-12-07 00:40:26 +03:00
}