diff --git a/index.ts b/index.ts index 9569354..1051eba 100644 --- a/index.ts +++ b/index.ts @@ -322,6 +322,48 @@ export class PythonShell extends EventEmitter { }); } + /** + * checks syntax via stdin without writing to a temp file + * @param code The Python code to check + * @returns rejects promise w/ stderr if syntax failure + */ + static checkSyntaxStdin( + code: string, + ): Promise<{ stdout: string; stderr: string }> { + return new Promise((resolve, reject) => { + const pythonPath = this.getPythonPath(); + const child = spawn(pythonPath, [ + '-c', + 'import sys; compile(sys.stdin.read(), "", "exec")', + ]); + + const outputBuffers = { stdout: '', stderr: '' }; + + child.stdout.on('data', (data) => { + outputBuffers.stdout += data.toString(); + }); + + child.stderr.on('data', (data) => { + outputBuffers.stderr += data.toString(); + }); + + child.on('close', (exitCode) => { + if (exitCode === 0) { + resolve(outputBuffers); + return; + } + reject(outputBuffers.stderr || `Process exited with code ${exitCode}`); + }); + + child.on('error', (err) => { + reject(err); + }); + + child.stdin.write(code); + child.stdin.end(); + }); + } + static getPythonPath() { return this.defaultOptions.pythonPath ? this.defaultOptions.pythonPath diff --git a/test/test-python-shell.ts b/test/test-python-shell.ts index 3d5de81..f22fb5e 100644 --- a/test/test-python-shell.ts +++ b/test/test-python-shell.ts @@ -1,8 +1,9 @@ import * as should from 'should'; import { PythonShell } from '..'; import { sep, join } from 'path'; -import { EOL as newline } from 'os'; +import { EOL as newline, tmpdir } from 'os'; import { chdir, cwd } from 'process'; +import { readdirSync } from 'fs'; describe('PythonShell', function () { const pythonFolder = 'test/python'; @@ -98,6 +99,66 @@ describe('PythonShell', function () { }); }); + describe('#checkSyntaxStdin(code:string)', function () { + it('should not create temp files', function (done) { + const tmpFiles = readdirSync(tmpdir()).filter((f) => + f.startsWith('pythonShellSyntaxCheck'), + ); + const beforeCount = tmpFiles.length; + + PythonShell.checkSyntaxStdin('x=1').then(() => { + const afterFiles = readdirSync(tmpdir()).filter((f) => + f.startsWith('pythonShellSyntaxCheck'), + ); + afterFiles.length.should.equal(beforeCount); + done(); + }); + }); + it('should check syntax via stdin', function (done) { + PythonShell.checkSyntaxStdin('x=1').then(() => { + done(); + }); + }); + + it('should check multiline code via stdin', function (done) { + const code = `def hello(): + print("world") +hello()`; + PythonShell.checkSyntaxStdin(code).then(() => { + done(); + }); + }); + + it('should invalidate bad syntax via stdin', function (done) { + PythonShell.checkSyntaxStdin('x=').catch(() => { + done(); + }); + }); + + it('should invalidate syntax error in multiline code via stdin', function (done) { + const code = `def hello() + print("world")`; + PythonShell.checkSyntaxStdin(code).catch(() => { + done(); + }); + }); + + it('should use pythonOptions from defaultOptions', function (done) { + const originalOptions = PythonShell.defaultOptions; + PythonShell.defaultOptions = { pythonOptions: ['-B'] }; + + PythonShell.checkSyntaxStdin('x=1') + .then(() => { + PythonShell.defaultOptions = originalOptions; + done(); + }) + .catch((err) => { + PythonShell.defaultOptions = originalOptions; + done(err); + }); + }); + }); + // #158 these tests are failing on appveyor windows node 8/10 python 2/3 // but they work locally on my windows machine ..... // these methods are not that important so just commenting out tests untill someone fixes them