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 ( ) ;
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' ;
if ( type === 'NameValue' )
return 'HeaderEntry' ;
2021-05-26 18:04:45 +03:00
return type ;
}
function nullableSuffix ( inner ) {
if ( [ 'int' , 'boolean' ] . includes ( inner . ts ) )
return inner . optional ? '?' : '' ;
return '' ;
}
function inlineType ( type , indent = '' , wrapEnums = false ) {
if ( typeof type === 'string' ) {
const optional = type . endsWith ( '?' ) ;
if ( optional )
type = type . substring ( 0 , type . length - 1 ) ;
if ( type === 'binary' )
return { ts : 'string' , scheme : 'tString' , optional } ;
if ( type === 'json' )
return { ts : 'any' , scheme : 'tAny' , optional } ;
if ( [ 'string' , 'boolean' , 'number' , 'undefined' ] . includes ( type ) ) {
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 ( '?' ) ;
const inner = inlineType ( type . items , indent , true ) ;
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 ( '?' ) ;
const custom = processCustomType ( type , optional ) ;
if ( custom )
return custom ;
const inner = properties ( type . properties , indent + ' ' ) ;
return {
ts : ` { \n ${ inner . ts } \n ${ indent } } ` ,
scheme : ` tObject({ \n ${ inner . scheme } \n ${ indent } }) ` ,
optional
} ;
}
raise ( type ) ;
}
function properties ( properties , indent , onlyOptional ) {
const ts = [ ] ;
const scheme = [ ] ;
const visitProperties = props => {
for ( const [ name , value ] of Object . entries ( props ) ) {
if ( name === 'android' || name === 'electron' )
continue ;
if ( name . startsWith ( '$mixin' ) ) {
visitProperties ( mixins . get ( value ) . properties ) ;
continue ;
}
const inner = inlineType ( value , indent ) ;
if ( onlyOptional && ! inner . optional )
continue ;
ts . push ( '' ) ;
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 } , ` ) ;
}
} ;
visitProperties ( properties ) ;
return { ts : ts . join ( '\n' ) , scheme : scheme . join ( '\n' ) } ;
}
function objectType ( props , indent , onlyOptional = false ) {
if ( ! Object . entries ( props ) . length )
return { ts : ` ${ indent } { \n ${ indent } } ` , scheme : ` tObject({}) ` } ;
const inner = properties ( props , indent + ' ' , onlyOptional ) ;
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 ] ) {
console . error ( '.NET repository needs to be specified as an argument.\n' + ` Usage: node ${ path . relative ( process . cwd ( ) , _ _filename ) } ../playwright-dotnet/src/Playwright/ ` ) ;
process . exit ( 1 ) ;
}
2021-05-26 18:04:45 +03:00
const dir = path . join ( process . argv [ 2 ] , 'Transport' , 'Protocol' , 'Generated' )
fs . mkdirSync ( dir , { recursive : true } ) ;
for ( const [ name , item ] of Object . entries ( protocol ) ) {
if ( item . type === 'interface' ) {
const channelName = name ;
const channels _ts = [ ] ;
const init = objectType ( item . initializer || { } , ' ' ) ;
const initializerName = channelName + 'Initializer' ;
const superName = inherits . get ( name ) ;
2021-08-16 22:49:00 +03:00
channels _ts . push ( ` /*
* 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
channels _ts . push ( 'using System.Collections.Generic;' ) ;
channels _ts . push ( ` ` ) ;
channels _ts . push ( ` namespace Microsoft.Playwright.Transport.Protocol ` ) ;
channels _ts . push ( ` { ` ) ;
channels _ts . push ( ` internal class ${ initializerName } ${ superName ? ' : ' + superName + 'Initializer' : '' } ` ) ;
channels _ts . push ( init . ts ) ;
channels _ts . push ( ` } ` ) ;
channels _ts . push ( ` ` ) ;
writeFile ( ` ${ initializerName } .cs ` , channels _ts . join ( '\n' ) ) ;
}
}
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'
&& inlineType ( type . properties . value ) . ts === 'string' ) {
return { ts : 'HeaderEntry' , scheme : 'tObject()' , optional } ;
}
if ( type . properties . width
&& type . properties . height
&& inlineType ( type . properties . width ) . ts === 'int'
&& inlineType ( type . properties . height ) . ts === 'int' ) {
return { ts : 'ViewportSize' , scheme : 'tObject()' , optional } ;
}
if ( type . properties . url
&& type . properties . lineNumber
&& inlineType ( type . properties . url ) . ts === 'string'
&& inlineType ( type . properties . lineNumber ) . ts === 'int' ) {
return { ts : 'ConsoleMessageLocation' , scheme : 'tObject()' , optional } ;
}
if ( type . properties . name
&& type . properties . descriptor
&& inlineType ( type . properties . name ) . ts === 'string' ) {
return { ts : 'DeviceDescriptorEntry' , scheme : 'tObject()' , optional } ;
}
2021-12-07 00:40:26 +03:00
}