@@ -2,72 +2,93 @@ import fs from 'node:fs/promises'
2
2
import path from 'node:path'
3
3
import { pathToFileURL } from 'node:url'
4
4
5
- import * as parser from '@babel/parser '
6
- import * as t from '@babel/types'
7
- import recast from 'recast'
5
+ import { parse , print , visit , types as t } from 'recast '
6
+ // @ts -ignore
7
+ import typescriptParser from 'recast/parsers/typescript '
8
8
9
9
const reporterIdentifierName = 'VscodeJsonReporter'
10
10
11
11
// This file is bundle as parser/ats.js at the package of vscode-webdriverio
12
12
// So, the correct reporter path is parent directory
13
13
const VSCODE_REPORTER_PATH = path . resolve ( __dirname , '../reporter.cjs' )
14
+
15
+ /**
16
+ * Create AST nodes using ast-types builders
17
+ */
18
+ const b = t . builders
19
+
14
20
/**
15
21
* Since Windows cannot import by reporter file path due to issues with
16
22
* the `initializePlugin` method of wdio-utils, the policy is to create a temporary configuration file.
17
23
*/
18
24
export async function createTempConfigFile ( filename : string , outDir : string ) {
19
25
const source = await fs . readFile ( filename , { encoding : 'utf8' } )
20
- const ast = recast . parse ( source , {
21
- parser : {
22
- parse ( source : string ) {
23
- return parser . parse ( source , {
24
- sourceType : 'unambiguous' ,
25
- plugins : [ 'typescript' , 'jsx' , 'topLevelAwait' ] ,
26
- } )
27
- } ,
28
- } ,
26
+ const ast = parse ( source , {
27
+ parser : typescriptParser ,
29
28
} )
30
29
31
- const reporterIdentifier = t . identifier ( reporterIdentifierName )
32
- const reporterConfigIdentifier = t . identifier ( `${ reporterIdentifierName } .default || ${ reporterIdentifierName } ` )
33
- const reporterElement = t . arrayExpression ( [
30
+ const reporterIdentifier = b . identifier ( reporterIdentifierName )
31
+ const reporterConfigIdentifier = b . identifier ( `${ reporterIdentifierName } .default || ${ reporterIdentifierName } ` )
32
+ const reporterElement = b . arrayExpression ( [
34
33
reporterConfigIdentifier ,
35
- t . objectExpression ( [
36
- t . objectProperty ( t . identifier ( 'stdout' ) , t . booleanLiteral ( true ) ) ,
37
- t . objectProperty ( t . identifier ( 'outputDir' ) , t . stringLiteral ( outDir ) ) ,
34
+ b . objectExpression ( [
35
+ b . property ( 'init' , b . identifier ( 'stdout' ) , b . literal ( true ) ) ,
36
+ b . property ( 'init' , b . identifier ( 'outputDir' ) , b . literal ( outDir ) ) ,
38
37
] ) ,
39
38
] )
40
39
let hasReporterImport = false
41
40
42
- function addOrUpdateReporters ( configObject : t . Node ) {
43
- if ( ! t . isObjectExpression ( configObject ) ) {
41
+ function addOrUpdateReporters ( configObject : any ) {
42
+ if ( ! t . namedTypes . ObjectExpression . check ( configObject ) ) {
44
43
return
45
44
}
46
45
47
- const reportersProp = configObject . properties . find (
48
- ( prop ) =>
49
- t . isObjectProperty ( prop ) &&
50
- ( ( t . isIdentifier ( prop . key ) && prop . key . name === 'reporters' ) ||
51
- ( t . isStringLiteral ( prop . key ) && prop . key . value === 'reporters' ) )
52
- )
46
+ // Find existing reporters property
47
+ let reportersProp = null
48
+
49
+ for ( let i = 0 ; i < configObject . properties . length ; i ++ ) {
50
+ const prop = configObject . properties [ i ]
51
+
52
+ // Check for both Property and ObjectProperty nodes
53
+ if ( t . namedTypes . Property . check ( prop ) || t . namedTypes . ObjectProperty ?. check ?.( prop ) ) {
54
+ const isReportersKey =
55
+ ( t . namedTypes . Identifier . check ( prop . key ) && prop . key . name === 'reporters' ) ||
56
+ ( t . namedTypes . Literal . check ( prop . key ) && prop . key . value === 'reporters' )
57
+
58
+ if ( isReportersKey ) {
59
+ reportersProp = prop
60
+ break
61
+ }
62
+ }
63
+ }
53
64
54
- if ( reportersProp && t . isObjectProperty ( reportersProp ) && t . isArrayExpression ( reportersProp . value ) ) {
65
+ if ( reportersProp && t . namedTypes . ArrayExpression . check ( reportersProp . value ) ) {
66
+ // Add to existing reporters array
55
67
reportersProp . value . elements . push ( reporterElement )
68
+ } else if ( reportersProp ) {
69
+ // Replace existing non-array reporters with array including existing value
70
+ const existingValue = reportersProp . value
71
+ //@ts -ignore
72
+ reportersProp . value = b . arrayExpression ( [ existingValue , reporterElement ] )
56
73
} else {
74
+ // Add new reporters property
57
75
configObject . properties . push (
58
- t . objectProperty ( t . identifier ( 'reporters' ) , t . arrayExpression ( [ reporterElement ] ) )
76
+ b . property ( 'init' , b . identifier ( 'reporters' ) , b . arrayExpression ( [ reporterElement ] ) )
59
77
)
60
78
}
61
79
}
62
80
63
- recast . types . visit ( ast , {
81
+ visit ( ast , {
64
82
visitImportDeclaration ( path ) {
65
83
const { source, specifiers } = path . node
66
84
if (
67
85
source . value === pathToFileURL ( VSCODE_REPORTER_PATH ) . href &&
68
86
specifiers &&
69
87
//@ts -ignore
70
- specifiers . some ( ( s ) => t . isImportDefaultSpecifier ( s ) && s . local . name === reporterIdentifierName )
88
+ specifiers . some (
89
+ //@ts -ignore
90
+ ( s : any ) => t . namedTypes . ImportDefaultSpecifier . check ( s ) && s . local . name === reporterIdentifierName
91
+ )
71
92
) {
72
93
hasReporterImport = true
73
94
}
@@ -77,21 +98,20 @@ export async function createTempConfigFile(filename: string, outDir: string) {
77
98
visitExportNamedDeclaration ( path ) {
78
99
const decl = path . node . declaration
79
100
80
- // @ts -ignore
81
- if ( t . isVariableDeclaration ( decl ) ) {
101
+ if ( t . namedTypes . VariableDeclaration . check ( decl ) ) {
82
102
const first = decl . declarations [ 0 ]
83
103
84
- if ( t . isVariableDeclarator ( first ) ) {
104
+ if ( t . namedTypes . VariableDeclarator . check ( first ) ) {
85
105
const id = first . id
86
106
const init = first . init
87
107
88
- if ( t . isIdentifier ( id ) && id . name === 'config' ) {
89
- if ( t . isObjectExpression ( init ) ) {
108
+ if ( t . namedTypes . Identifier . check ( id ) && id . name === 'config' ) {
109
+ if ( t . namedTypes . ObjectExpression . check ( init ) ) {
90
110
addOrUpdateReporters ( init )
91
111
} else if (
92
- t . isCallExpression ( init ) &&
112
+ t . namedTypes . CallExpression . check ( init ) &&
93
113
init . arguments . length > 0 &&
94
- t . isObjectExpression ( init . arguments [ 0 ] )
114
+ t . namedTypes . ObjectExpression . check ( init . arguments [ 0 ] )
95
115
) {
96
116
const configObject = init . arguments [ 0 ]
97
117
addOrUpdateReporters ( configObject )
@@ -111,12 +131,10 @@ export async function createTempConfigFile(filename: string, outDir: string) {
111
131
}
112
132
113
133
if (
114
- // @ts -ignore
115
- t . isMemberExpression ( left ) &&
116
- t . isIdentifier ( left . object ) &&
117
- t . isIdentifier ( left . property ) &&
118
- // @ts -ignore
119
- t . isObjectExpression ( right )
134
+ t . namedTypes . MemberExpression . check ( left ) &&
135
+ t . namedTypes . Identifier . check ( left . object ) &&
136
+ t . namedTypes . Identifier . check ( left . property ) &&
137
+ t . namedTypes . ObjectExpression . check ( right )
120
138
) {
121
139
const leftName = `${ left . object . name } .${ left . property . name } `
122
140
if ( [ 'module.exports' , 'exports.config' ] . includes ( leftName ) ) {
@@ -128,13 +146,15 @@ export async function createTempConfigFile(filename: string, outDir: string) {
128
146
} ,
129
147
130
148
visitCallExpression ( path ) {
131
- const node = path . node as t . Node
149
+ const node = path . node
132
150
133
151
if (
134
- t . isCallExpression ( node ) &&
135
- t . isIdentifier ( node . callee , { name : 'require' } ) &&
152
+ t . namedTypes . CallExpression . check ( node ) &&
153
+ t . namedTypes . Identifier . check ( node . callee ) &&
154
+ node . callee . name === 'require' &&
136
155
node . arguments . length === 1 &&
137
- t . isStringLiteral ( node . arguments [ 0 ] ) &&
156
+ t . namedTypes . Literal . check ( node . arguments [ 0 ] ) &&
157
+ typeof node . arguments [ 0 ] . value === 'string' &&
138
158
node . arguments [ 0 ] . value === pathToFileURL ( VSCODE_REPORTER_PATH ) . href
139
159
) {
140
160
hasReporterImport = true
@@ -145,15 +165,15 @@ export async function createTempConfigFile(filename: string, outDir: string) {
145
165
} )
146
166
147
167
if ( ! hasReporterImport ) {
148
- const importedModule = t . importDeclaration (
149
- [ t . importDefaultSpecifier ( reporterIdentifier ) ] ,
150
- t . stringLiteral ( pathToFileURL ( VSCODE_REPORTER_PATH ) . href )
168
+ const importedModule = b . importDeclaration (
169
+ [ b . importDefaultSpecifier ( reporterIdentifier ) ] ,
170
+ b . literal ( pathToFileURL ( VSCODE_REPORTER_PATH ) . href )
151
171
)
152
172
153
173
ast . program . body . unshift ( importedModule )
154
174
}
155
175
156
- const output = recast . print ( ast ) . code
176
+ const output = print ( ast ) . code
157
177
const ext = path . extname ( filename )
158
178
const _filename = path . join ( path . dirname ( filename ) , `wdio-vscode-${ new Date ( ) . getTime ( ) } ${ ext } ` )
159
179
await fs . writeFile ( _filename , output )
0 commit comments