3
3
import crypto from "crypto" ;
4
4
import fs from "fs" ;
5
5
import path from "path" ;
6
- import child_process from "child_process" ;
6
+ import spawn from "await-spawn" ;
7
+ import flattenDeep from "lodash.flattendeep" ;
8
+ import chunk from "lodash.chunk" ;
7
9
8
10
import ts from "typescript" ;
9
11
import { program } from "commander" ;
@@ -65,7 +67,7 @@ program
65
67
. description (
66
68
`Explain all your sqls in your code to test them. \n\nEg: ts-sql-plugin -p ./my_ts_projet/tsconfig.json -c 'psql -c'` ,
67
69
)
68
- . action ( ( ) => {
70
+ . action ( async ( ) => {
69
71
let cli_config : any = program . opts ( ) ;
70
72
if ( typeof cli_config . command !== "string" ) {
71
73
cli_config . command = undefined ;
@@ -78,7 +80,7 @@ program
78
80
}
79
81
const { config : ts_config } = ts . parseConfigFileTextToJson (
80
82
ts_config_path ,
81
- fs . readFileSync ( ts_config_path , { encoding : "utf8" } ) ,
83
+ await fs . promises . readFile ( ts_config_path , { encoding : "utf8" } ) ,
82
84
) ;
83
85
let plugin_config : TsSqlPluginConfig = ( ts_config . compilerOptions . plugins as any [ ] ) ?. find (
84
86
it => it . name === "ts-sql-plugin" ,
@@ -120,105 +122,99 @@ program
120
122
let has_error = false ;
121
123
let report_errors : [ sourceFile : ts . SourceFile , node : ts . Node , message : string , level ?: 1 | 2 ] [ ] = [ ] ;
122
124
console . log ( "-- Start init sql check and emit..." ) ;
123
- initProgram . getSourceFiles ( ) . forEach ( f => {
124
- if ( ! cli_config . exclude . test ( f . fileName ) ) {
125
- delint ( f ) ;
126
- }
127
- } ) ;
125
+ for ( const file of initProgram . getSourceFiles ( ) . filter ( f => ! cli_config . exclude . test ( f . fileName ) ) ) {
126
+ await delint ( file ) ;
127
+ }
128
128
if ( has_error ) {
129
129
console . log ( "\n\n-- Your code can not pass all sql test!!!\n" ) ;
130
130
report_errors . forEach ( args => report ( ...args ) ) ;
131
131
process . exit ( 1 ) ;
132
132
}
133
133
console . log ( "\n\n-- Init sql check and emit finished.\n" ) ;
134
134
135
- function delint ( sourceFile : ts . SourceFile ) {
136
- delintNode ( sourceFile ) ;
135
+ function recursiveAllChildrenNodes ( node : ts . Node ) : ts . Node [ ] {
136
+ return ( node . getChildren ( ) . map ( n => [ n , ...recursiveAllChildrenNodes ( n ) ] ) as unknown ) as ts . Node [ ] ;
137
+ }
138
+
139
+ async function delint ( sourceFile : ts . SourceFile ) {
140
+ const treeNodes = recursiveAllChildrenNodes ( sourceFile ) ;
141
+ const sqlTagNodes = flattenDeep ( treeNodes ) . filter (
142
+ ( n ) : n is ts . TaggedTemplateExpression =>
143
+ n . kind === ts . SyntaxKind . TaggedTemplateExpression &&
144
+ ( n as ts . TaggedTemplateExpression ) . tag . getText ( ) === plugin_config . tags . sql ,
145
+ ) ;
146
+
147
+ await Promise . all ( sqlTagNodes . map ( delintNode ) ) ;
137
148
138
- function delintNode ( node : ts . Node ) {
139
- if ( node . kind === ts . SyntaxKind . TaggedTemplateExpression ) {
140
- let n = node as ts . TaggedTemplateExpression ;
141
- if ( n . tag . getText ( ) === plugin_config . tags . sql ) {
142
- let query_configs = fake_expression ( n ) ;
143
- for ( const qc of query_configs ) {
144
- let s : string = trim_middle_comments ( qc . text ) . replace ( / \? \? / gm, plugin_config . mock ) ;
149
+ async function delintNode ( node : ts . TaggedTemplateExpression ) {
150
+ let query_configs = fake_expression ( node ) ;
151
+ for ( const qc of query_configs ) {
152
+ let s : string = trim_middle_comments ( qc . text ) . replace ( / \? \? / gm, plugin_config . mock ) ;
145
153
146
- const directives = parseDirectives ( s ) ;
147
- if ( cli_config . emit_dir ) {
148
- const emit_directive = directives . find ( d => d . directive === "emit" ) ;
149
- if ( emit_directive ) {
150
- const fileName = ( emit_directive . arg as string ) ?? crypto . createHash ( "sha1" ) . update ( s ) . digest ( "hex" ) ;
151
- const filePath = `${ cli_config . emit_dir } /${ fileName } .sql` ;
152
- try {
153
- fs . writeFileSync ( filePath , s + ";" ) ;
154
- } catch ( err ) {
155
- console . log ( `-- Emit Error occured, when emitting file "${ filePath } "` ) ;
156
- }
157
- }
154
+ const directives = parseDirectives ( s ) ;
155
+ if ( cli_config . emit_dir ) {
156
+ const emit_directive = directives . find ( d => d . directive === "emit" ) ;
157
+ if ( emit_directive ) {
158
+ const fileName = ( emit_directive . arg as string ) ?? crypto . createHash ( "sha1" ) . update ( s ) . digest ( "hex" ) ;
159
+ const filePath = `${ cli_config . emit_dir } /${ fileName } .sql` ;
160
+ try {
161
+ fs . writeFileSync ( filePath , s + ";" ) ;
162
+ } catch ( err ) {
163
+ console . log ( `-- Emit Error occured, when emitting file "${ filePath } "` ) ;
158
164
}
165
+ }
166
+ }
167
+
168
+ console . log ( `\n\n-- EXPLAIN\n${ s } ;` ) ;
169
+ const [ _command , ..._command_args ] = ( shq
170
+ . parse ( plugin_config . command )
171
+ . concat ( "EXPLAIN " + s ) as any ) as string [ ] ;
172
+ const p = await spawn ( _command , _command_args ) . catch ( ( e : Error & { stderr : string } ) => e ) ;
173
+ const stdout = p . toString ( ) ;
174
+ if ( p instanceof Error ) {
175
+ has_error = true ;
176
+ report ( sourceFile , node , p . stderr ) ;
177
+ report_errors . push ( [ sourceFile , node , p . stderr ] ) ;
178
+ break ;
179
+ }
159
180
160
- console . log ( `\n\n-- EXPLAIN\n${ s } ;` ) ;
161
- let stdout = "" ;
162
- // try {
163
- // stdout = child_process.execSync(`${plugin_config.command} ${quote([`EXPLAIN ${s}`])}`, { encoding: 'utf8', windowsHide: true });
164
- // } catch (error) {
165
- // has_error = true;
166
- // report(sourceFile, node, error.stderr);
167
- // break;
168
- // }
169
- const [ _command , ..._command_args ] = ( shq
170
- . parse ( plugin_config . command )
171
- . concat ( "EXPLAIN " + s ) as any ) as string [ ] ;
172
- const p = child_process . spawnSync ( _command , _command_args , { encoding : "utf8" } ) ;
173
- stdout = p . stdout ;
174
- if ( p . status ) {
181
+ if (
182
+ ( plugin_config . error_cost || plugin_config . warn_cost || plugin_config . info_cost ) &&
183
+ ! directives . some ( d => d . directive === "ignore-cost" )
184
+ ) {
185
+ const match = stdout . match ( cost_pattern ) ;
186
+ if ( match ) {
187
+ const [ _ , cost_str ] = match ;
188
+ const cost = Number ( cost_str ) ;
189
+ if ( cost > plugin_config . error_cost ! ) {
175
190
has_error = true ;
176
- report ( sourceFile , node , p . stderr ) ;
177
- report_errors . push ( [ sourceFile , node , p . stderr ] ) ;
191
+ report ( sourceFile , node , `Error: explain cost is too high: ${ cost } \n ${ s } ` , 2 ) ;
192
+ report_errors . push ( [ sourceFile , node , `Error: explain cost is too high: ${ cost } \n ${ s } ` , 2 ] ) ;
178
193
break ;
194
+ } else if ( cost > plugin_config . warn_cost ! ) {
195
+ report ( sourceFile , node , `Warn: explain cost is at warning: ${ cost } \n${ s } ` , 2 ) ;
196
+ } else if ( cost > plugin_config . info_cost ! ) {
197
+ report ( sourceFile , node , `Info: explain cost is ok: ${ cost } \n${ s } ` , 1 ) ;
179
198
}
180
-
181
- if (
182
- ( plugin_config . error_cost || plugin_config . warn_cost || plugin_config . info_cost ) &&
183
- ! directives . some ( d => d . directive === "ignore-cost" )
184
- ) {
185
- const match = stdout . match ( cost_pattern ) ;
186
- if ( match ) {
187
- const [ _ , cost_str ] = match ;
188
- const cost = Number ( cost_str ) ;
189
- if ( cost > plugin_config . error_cost ! ) {
190
- has_error = true ;
191
- report ( sourceFile , node , `Error: explain cost is too high: ${ cost } \n${ s } ` , 2 ) ;
192
- report_errors . push ( [ sourceFile , node , `Error: explain cost is too high: ${ cost } \n${ s } ` , 2 ] ) ;
193
- break ;
194
- } else if ( cost > plugin_config . warn_cost ! ) {
195
- report ( sourceFile , node , `Warn: explain cost is at warning: ${ cost } \n${ s } ` , 2 ) ;
196
- } else if ( cost > plugin_config . info_cost ! ) {
197
- report ( sourceFile , node , `Info: explain cost is ok: ${ cost } \n${ s } ` , 1 ) ;
198
- }
199
- } else {
200
- has_error = true ;
201
- report (
202
- sourceFile ,
203
- node ,
204
- `Error: can not extract cost with cost_pattern: ${ cost_pattern . source } \n${ stdout } \n${ s } ` ,
205
- 2 ,
206
- ) ;
207
- report_errors . push ( [
208
- sourceFile ,
209
- node ,
210
- `Error: can not extract cost with cost_pattern: ${ cost_pattern . source } \n${ stdout } \n${ s } ` ,
211
- 2 ,
212
- ] ) ;
213
- break ;
214
- }
215
- }
199
+ } else {
200
+ has_error = true ;
201
+ report (
202
+ sourceFile ,
203
+ node ,
204
+ `Error: can not extract cost with cost_pattern: ${ cost_pattern . source } \n${ stdout } \n${ s } ` ,
205
+ 2 ,
206
+ ) ;
207
+ report_errors . push ( [
208
+ sourceFile ,
209
+ node ,
210
+ `Error: can not extract cost with cost_pattern: ${ cost_pattern . source } \n${ stdout } \n${ s } ` ,
211
+ 2 ,
212
+ ] ) ;
213
+ break ;
216
214
}
217
215
}
218
216
}
219
-
220
- ts . forEachChild ( node , delintNode ) ;
221
217
}
222
218
}
223
219
} )
224
- . parse ( process . argv ) ;
220
+ . parseAsync ( process . argv ) ;
0 commit comments