11import { EventEmitter } from 'events' ;
2- import { ChildProcess , spawn , SpawnOptions } from 'child_process' ;
3- import { EOL as newline } from 'os' ;
4- import { join } from 'path'
2+ import { ChildProcess , spawn , SpawnOptions , exec } from 'child_process' ;
3+ import { EOL as newline , tmpdir } from 'os' ;
4+ import { join , sep } from 'path'
55import { Readable , Writable } from 'stream'
6+ import { writeFile } from 'fs' ;
67
78function toArray < T > ( source ?:T | T [ ] ) :T [ ] {
89 if ( typeof source === 'undefined' || source === null ) {
@@ -67,7 +68,10 @@ export class PythonShell extends EventEmitter{
6768 private _remaining :string
6869 private _endCallback :( err :PythonShellError , exitCode :number , exitSignal :string ) => any
6970
70- //@ts -ignore keeping it initialized to {} for backwards API compatability
71+ // starting 2020 python2 is deprecated so we choose 3 as default
72+ // except for windows which just has "python" command
73+ static defaultPythonPath = process . platform != "win32" ? "python3" : "python" ;
74+
7175 static defaultOptions :Options = { } ; //allow global overrides for options
7276
7377 constructor ( scriptPath :string , options ?:Options ) {
@@ -93,9 +97,7 @@ export class PythonShell extends EventEmitter{
9397 options = < Options > extend ( { } , PythonShell . defaultOptions , options ) ;
9498 let pythonPath ;
9599 if ( ! options . pythonPath ) {
96- // starting 2020 python2 is deprecated so we choose 3 as default
97- // except for windows which just has "python" command
98- pythonPath = process . platform != "win32" ? "python3" : "python"
100+ pythonPath = PythonShell . defaultPythonPath ;
99101 } else pythonPath = options . pythonPath ;
100102 let pythonOptions = toArray ( options . pythonOptions ) ;
101103 let scriptArgs = toArray ( options . args ) ;
@@ -190,6 +192,41 @@ export class PythonShell extends EventEmitter{
190192 }
191193 } ;
192194
195+ /**
196+ * checks syntax without executing code
197+ * @param {string } code
198+ * @returns {Promise } rejects w/ stderr if syntax failure
199+ */
200+ static async checkSyntax ( code :string ) {
201+ let randomInt = Math . floor ( Math . random ( ) * 10000000000 ) ;
202+ let filePath = tmpdir + sep + `pythonShellSyntaxCheck${ randomInt } .py`
203+
204+ // todo: replace this with util.promisify (once we no longer support node v7)
205+ return new Promise ( ( resolve , reject ) => {
206+ writeFile ( filePath , code , ( err ) => {
207+ if ( err ) reject ( err ) ;
208+ resolve ( this . checkSyntaxFile ( filePath ) ) ;
209+ } ) ;
210+ } ) ;
211+ }
212+
213+ /**
214+ * checks syntax without executing code
215+ * @param {string } filePath
216+ * @returns {Promise } rejects w/ stderr if syntax failure
217+ */
218+ static async checkSyntaxFile ( filePath :string ) {
219+
220+ let compileCommand = `${ this . defaultPythonPath } -m py_compile ${ filePath } `
221+
222+ return new Promise ( ( resolve , reject ) => {
223+ exec ( compileCommand , ( error , stdout , stderr ) => {
224+ if ( error == null ) resolve ( )
225+ else reject ( stderr )
226+ } )
227+ } )
228+ }
229+
193230 /**
194231 * Runs a Python script and returns collected messages
195232 * @param {string } scriptPath The path to the script to execute
0 commit comments