Skip to content

Commit 1cea78f

Browse files
committed
#138 adding functions to check python syntax
1 parent ae6cee8 commit 1cea78f

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

index.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import {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'
55
import {Readable,Writable} from 'stream'
6+
import { writeFile } from 'fs';
67

78
function 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

test/test-python-shell.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ describe('PythonShell', function () {
3636
});
3737
});
3838

39+
describe('#checkSyntax(code:string)', function () {
40+
41+
// note checkSyntax is a wrapper around checkSyntaxFile
42+
// so this tests checkSyntaxFile as well
43+
44+
it('should check syntax', function ( done) {
45+
PythonShell.checkSyntax("x=1").then(()=>{
46+
done();
47+
})
48+
})
49+
50+
it('should invalidate bad syntax', function ( done) {
51+
PythonShell.checkSyntax("x=").catch(()=>{
52+
done();
53+
})
54+
})
55+
})
56+
3957
describe('#run(script, options)', function () {
4058
it('should run the script and return output data', function (done) {
4159
PythonShell.run('echo_args.py', {

0 commit comments

Comments
 (0)