Unexpected end of script error on WKWebView evaluateJavaScript method - javascript

I am getting error:
Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=1, WKJavaScriptExceptionMessage=SyntaxError: Unexpected end of script, WKJavaScriptExceptionColumnNumber=0, WKJavaScriptExceptionSourceURL=file:///Users/rb/Library/Developer/CoreSimulator/Devices/675AC846-408B-486C-AE49-3F2187728698/data/Containers/Bundle/Application/7D63B436-AD1F-4CE5-857D-698EE857CC7D/flockmail.app/Frameworks/RichEditorView.framework/rich_editor.html, NSLocalizedDescription=A JavaScript exception occurred}
when executing following Javascript function using evaluateJavaScript.
/*jshint esversion: 6 */
function replaceLinkURL(jsonData) {
var userData = JSON.parse(jsonData);
var div = [...document.querySelectorAll('div.class1')].filter(function(el) {
return !el.querySelector('blockquote');
});
var correctDiv = div.filter(function(el) {
return !el.getElementsByClassName('.classLink').length;
});
correctDiv.forEach(function(el) {
el.getElementsByClassName('.classLink').href = userData.link;
});
}
Being new to Javascript, I can't figure out the issue, also I validated Javascript and its correct. There is no other handling I did in WKWebview for this.

Related

Javascript : 'Compilation' error position on new Function('...my code as string') [duplicate]

When creating new function from JavaScript code using new Function(params,body) constructor, passing invalid string in body yelds SyntaxError. While this exception contains error message (ie: Unexpected token =), but does not seem to contain context (ie. line/column or character where error was found).
Example fiddle: https://jsfiddle.net/gheh1m8p/
var testWithSyntaxError = "{\n\n\n=2;}";
try {
var f=new Function('',testWithSyntaxError);
} catch(e) {
console.log(e instanceof SyntaxError);
console.log(e.message);
console.log(e.name);
console.log(e.fileName);
console.log(e.lineNumber);
console.log(e.columnNumber);
console.log(e.stack);
}
Output:
true
(index):54 Unexpected token =
(index):55 SyntaxError
(index):56 undefined
(index):57 undefined
(index):58 undefined
(index):59 SyntaxError: Unexpected token =
at Function (native)
at window.onload (https://fiddle.jshell.net/_display/:51:8)
How can I, without using external dependencies, pinpoint SyntaxError location withinn passed string? I require solution both for browser and nodejs.
Please note: I do have a valid reason to use eval-equivalent code.
In Chromium-based browsers, as you've seen, putting try/catch around something that throws a SyntaxError while V8 is parsing the code (before actually running it) won't produce anything helpful; it will describe the line that caused the evaluation of the problematic script in the stack trace, but no details on where the problem was in said script.
But, there's a cross-browser workaround. Instead of using try/catch, you can add an error listener to window, and the first argument provided to the callback will be an ErrorEvent which has useful lineno and colno properties:
window.addEventListener('error', (errorEvent) => {
const { lineno, colno } = errorEvent;
console.log(`Error thrown at: ${lineno}:${colno}`);
// Don't pollute the console with additional info:
errorEvent.preventDefault();
});
const checkSyntax = (str) => {
// Using setTimeout because when an error is thrown without a catch,
// even if the error listener calls preventDefault(),
// the current thread will stop
setTimeout(() => {
eval(str);
});
};
checkSyntax(`console.log('foo') bar baz`);
checkSyntax(`foo bar baz`);
Look in your browser console to see this in action, not in the snippet console
Check the results in your browser console:
Error thrown at: 1:20
Error thrown at: 1:5
Which is what we want! Character 20 corresponds to
console.log('foo') bar baz
^
and character 5 corresponds to
foo bar baz
^
There are a couple issues, though: it would be good to make sure in the error listened for is an error thrown when running checkSyntax. Also, try/catch can be used for runtime errors (including syntax errors) after the script text has been parsed into an AST by the interpreter. So, you might have checkSyntax only check that the Javascript is initially parsable, and nothing else, and then use try/catch (if you want to run the code for real) to catch runtime errors. You can do this by inserting throw new Error to the top of the text that's evaled.
Here's a convenient Promise-based function which can accomplish that:
// Use an IIFE to keep from polluting the global scope
(async () => {
let stringToEval;
let checkSyntaxResolve;
const cleanup = () => {
stringToEval = null;
checkSyntaxResolve = null; // not necessary, but makes things clearer
};
window.addEventListener('error', (errorEvent) => {
if (!stringToEval) {
// The error was caused by something other than the checkSyntax function below; ignore it
return;
}
const stringToEvalToPrint = stringToEval.split('\n').slice(1).join('\n');
// Don't pollute the console with additional info:
errorEvent.preventDefault();
if (errorEvent.message === 'Uncaught Error: Parsing successful!') {
console.log(`Parsing successful for: ${stringToEvalToPrint}`);
checkSyntaxResolve();
cleanup();
return;
}
const { lineno, colno } = errorEvent;
console.log(`Syntax error thrown at: ${lineno - 1}:${colno}`);
console.log(describeError(stringToEval, lineno, colno));
// checkSyntaxResolve should *always* be defined at this point - checkSyntax's eval was just called (synchronously)
checkSyntaxResolve();
cleanup();
});
const checkSyntax = (str) => {
console.log('----------------------------------------');
return new Promise((resolve) => {
checkSyntaxResolve = resolve;
// Using setTimeout because when an error is thrown without a catch,
// even if the 'error' listener calls preventDefault(),
// the current thread will stop
setTimeout(() => {
// If we only want to check the syntax for initial parsing validity,
// but not run the code for real, throw an error at the top:
stringToEval = `throw new Error('Parsing successful!');\n${str}`;
eval(stringToEval);
});
});
};
const describeError = (stringToEval, lineno, colno) => {
const lines = stringToEval.split('\n');
const line = lines[lineno - 1];
return `${line}\n${' '.repeat(colno - 1) + '^'}`;
};
await checkSyntax(`console.log('I will throw') bar baz`);
await checkSyntax(`foo bar baz will throw too`);
await checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
await checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
})();
Look in your browser console to see this in action, not in the snippet console
await checkSyntax(`console.log('I will throw') bar baz`);
await checkSyntax(`foo bar baz will throw too`);
await checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
await checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
Result:
----------------------------------------
Syntax error thrown at: 1:29
console.log('I will throw') bar baz
^
----------------------------------------
Syntax error thrown at: 1:5
foo bar baz will throw too
^
----------------------------------------
Parsing successful for: console.log('A snippet without compile errors'); const foo = bar;
----------------------------------------
Syntax error thrown at: 2:6
With a syntax error on the second line
^
If the fact that an error is thrown at window is a problem (for example, if something else is already listening for window errors, which you don't want to disturb, and you can't attach your listener first and call stopImmediatePropagation() on the event), another option is to use a web worker instead, which has its own execution context completely separate from the original window:
// Worker:
const getErrorEvent = (() => {
const workerFn = () => {
const doEvalAndReply = (jsText) => {
self.addEventListener(
'error',
(errorEvent) => {
// Don't pollute the browser console:
errorEvent.preventDefault();
// The properties we want are actually getters on the prototype;
// they won't be retrieved when just stringifying
// so, extract them manually, and put them into a new object:
const { lineno, colno, message } = errorEvent;
const plainErrorEventObj = { lineno, colno, message };
self.postMessage(JSON.stringify(plainErrorEventObj));
},
{ once: true }
);
eval(jsText);
};
self.addEventListener('message', (e) => {
doEvalAndReply(e.data);
});
};
const blob = new Blob(
[ `(${workerFn})();`],
{ type: "text/javascript" }
);
const worker = new Worker(window.URL.createObjectURL(blob));
// Use a queue to ensure processNext only calls the worker once the worker is idle
const processingQueue = [];
let processing = false;
const processNext = () => {
processing = true;
const { resolve, jsText } = processingQueue.shift();
worker.addEventListener(
'message',
({ data }) => {
resolve(JSON.parse(data));
if (processingQueue.length) {
processNext();
} else {
processing = false;
}
},
{ once: true }
);
worker.postMessage(jsText);
};
return (jsText) => new Promise((resolve) => {
processingQueue.push({ resolve, jsText });
if (!processing) {
processNext();
}
});
})();
// Calls worker:
(async () => {
const checkSyntax = async (str) => {
console.log('----------------------------------------');
const stringToEval = `throw new Error('Parsing successful!');\n${str}`;
const { lineno, colno, message } = await getErrorEvent(stringToEval);
if (message === 'Uncaught Error: Parsing successful!') {
console.log(`Parsing successful for: ${str}`);
return;
}
console.log(`Syntax error thrown at: ${lineno - 1}:${colno}`);
console.log(describeError(stringToEval, lineno, colno));
};
const describeError = (stringToEval, lineno, colno) => {
const lines = stringToEval.split('\n');
const line = lines[lineno - 1];
return `${line}\n${' '.repeat(colno - 1) + '^'}`;
};
await checkSyntax(`console.log('I will throw') bar baz`);
await checkSyntax(`foo bar baz will throw too`);
await checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
await checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
})();
Look in your browser console to see this in action, not in the snippet console
Essentially, what checkSyntax is doing is checking to see if the code provided can be parsed into an Abstract Syntax Tree by the current interpreter. You can also use packages like #babel/parser or acorn to attempt to parse the string, though you'll have to configure it for the syntax permitted in the current environment (which will change as new syntax gets added to the language).
const checkSyntax = (str) => {
try {
acorn.Parser.parse(str);
console.log('Parsing successful');
} catch(e){
console.error(e.message);
}
};
checkSyntax(`console.log('I will throw') bar baz`);
checkSyntax(`foo bar baz will throw too`);
checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
<script src="https://cdn.jsdelivr.net/npm/acorn#6.1.1/dist/acorn.min.js"></script>
The above works for browsers. In Node, the situation is different: listening for an uncaughtException can't be used to intercept the details of syntax errors, AFAIK. However, you can use vm module to attempt to compile the code, and if it throws a SyntaxError before running, you'll see something like this. Running
console.log('I will throw') bar baz
results in a stack of
evalmachine.<anonymous>:1
console.log('I will throw') bar baz
^^^
SyntaxError: Unexpected identifier
at createScript (vm.js:80:10)
at Object.runInNewContext (vm.js:135:10)
<etc>
So, just look at the first item in the stack to get the line number, and at the number of spaces before the ^ to get the column number. Using a similar technique as earlier, throw an error on the first line if parsing is successful:
const vm = require('vm');
const checkSyntax = (code) => {
console.log('---------------------------');
try {
vm.runInNewContext(`throw new Error();\n${code}`);
}
catch (e) {
describeError(e.stack);
}
};
const describeError = (stack) => {
const match = stack
.match(/^\D+(\d+)\n(.+\n( *)\^+)\n\n(SyntaxError.+)/);
if (!match) {
console.log('Parse successful!');
return;
}
const [, linenoPlusOne, caretString, colSpaces, message] = match;
const lineno = linenoPlusOne - 1;
const colno = colSpaces.length + 1;
console.log(`${lineno}:${colno}: ${message}\n${caretString}`);
};
checkSyntax(`console.log('I will throw') bar baz`);
checkSyntax(`foo bar baz will throw too`);
checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
Result:
---------------------------
1:29: SyntaxError: Unexpected identifier
console.log('I will throw') bar baz
^^^
---------------------------
1:5: SyntaxError: Unexpected identifier
foo bar baz will throw too
^^^
---------------------------
Parse successful!
---------------------------
2:6: SyntaxError: Unexpected identifier
With a syntax error on the second line
^
That said:
How can I, without using external dependencies, pinpoint SyntaxError location withinn passed string? I require solution both for browser and nodejs.
Unless you have to achieve this without an external library, using a library really would be the easiest (and tried-and-tested) solution. Acorn, as shown earlier (and other parsers) work in Node as well.
I'm sumarizing comments and some additional research:
Simple anwer: currently impossible
There is currently no cross-platform way to retrive syntax error position from new Function() or eval() call.
Partial solutions
Firefox support non-standard properties error.lineNumber and error.e.columnNumber. This can be used with feature detection if position of error is not critical.
There are filled bug reports/feature request for v8 that could bring support of (1) to chrome/node.js: Issue #1281, #1914, #2589
Use separate javascript parser, based on JSLint or PEG.js.
Write custom javascript parser for the job.
Solutions 1 and 2 are incomplete, rely on features that are not part of standard. They can be suitable if this information is a help, not an requirement.
Solution 3 depends on external codebase, which was explicitly required by original question. It is suitable if this information is required and larger codebase is acceptable compromise.
Solution 4 is impractical.
Credits: #user3896470, #ivan-kuckir, #aprillion
Solution for browser:
You can use latest Firefox to get the required information like error line number and column number inside the string.
Example:
var testWithSyntaxError = "{\n\n\n\nvar x=3;\n =2;}";
try {
var f=new Function('',testWithSyntaxError);
} catch(e) {
console.log(e instanceof SyntaxError);
console.log(e.message);
console.log(e.name);
console.log(e.fileName);
console.log(e.lineNumber);
console.log(e.columnNumber);
console.log(e.stack);
}
Output in Firefox console:
undefined
true
expected expression, got '='
SyntaxError
debugger eval code
6
1
#debugger eval code:4:11
Where 6 is the line number and 1 is the column number of error inside the string.
It won't work in Chrome. There are bugs filed regarding this issue for chrome browser. See:
https://bugs.chromium.org/p/v8/issues/detail?id=1281
https://bugs.chromium.org/p/v8/issues/detail?id=1914
https://bugs.chromium.org/p/v8/issues/detail?id=2589

request.form() throws 'undefined is not a function' error in Node

I want to create a form on a request in Node to add attachments to an email. As far as I can tell here under the multipart/form-data (Multipart Form Uploads) section I should be able to create a request form using
var r = request.form()
But I am getting this error in the console.
var fd = request.form();
^
TypeError: undefined is not a function
Can anyone tell me why this is happening and how to fix it. Thanks in advance!
UPDATE
Looking at the linked documentation, and on the advice of Chris G below, again I changed my code to look like this:
var r = request.post(reqOptions, function (err, resp) {
//console.log(resp.body);
//console.log(resp.statusCode);
if (err) {
return deferred.reject({
code: _.get(resp, 'statusCode'),
reason: err.message || err.errmsg
});
} ...
);
var form = r.form();
form.append('attachmentA',fs.createReadStream(path.join(__dirname, 'abc.jpg')));
Now I get the following error.
{ code: undefined, reason: 'write after end' }

Valid JSON (for real) is throwing error in JSON.Parse

I am at a dead end. I am baffled. I am passing a stringified dictionary from Python (using json.dumps()) through UDP to an Ionic 2 (Typescript) application.
The python code generating the messages:
message = { 'time' : str(round(float(op('indexFraction')[0]),3)) }
messageJSON = json.dumps(message)
#messageJSON = json.JSONEncoder().encode(message)
print(messageJSON)
op('udpout').send(messageJSON) #sending out of TouchDesigner
My callback function on the Ionic side looks like this:
socket.on('message', function (message, remoteAddress) {
if (message.length > 0) {
console.log(message, typeof(message));
// alert(message);
// process response message
var data = JSON.parse(message);
console.log(data);
if (data.time) {
alert(data.time);
}
}
});
A sample message looks like this (typeof string):
{"time": "0.934"}
// this is a string, as evidenced by the Console.log
JSON.parse() throws the following:
index.html:1 Uncaught SyntaxError: Unexpected token in JSON at position 17
I've tried all kinds of variants on the object. It passes as valid on JSONlint. It's so simple, no wacky characters. Any ideas?
Thanks,
Marc

Uncaught SyntaxError: Unexpected token function (async)

I am trying to execute the following function using async/await, but I getting the following error:
Uncaught SyntaxError: Unexpected token function
Using the following code:
async function getCsrfToken() {
let token = '';
await $.get(domain + '/mobile/token', result => {
token = result;
});
return token;
}
My Editor isn't complaining about the syntax, but Chrome within Crosswalk is complaining about it. Am I doing something wrong or do I need to do something within crosswalk?

Find details of SyntaxError thrown by javascript new Function() constructor

When creating new function from JavaScript code using new Function(params,body) constructor, passing invalid string in body yelds SyntaxError. While this exception contains error message (ie: Unexpected token =), but does not seem to contain context (ie. line/column or character where error was found).
Example fiddle: https://jsfiddle.net/gheh1m8p/
var testWithSyntaxError = "{\n\n\n=2;}";
try {
var f=new Function('',testWithSyntaxError);
} catch(e) {
console.log(e instanceof SyntaxError);
console.log(e.message);
console.log(e.name);
console.log(e.fileName);
console.log(e.lineNumber);
console.log(e.columnNumber);
console.log(e.stack);
}
Output:
true
(index):54 Unexpected token =
(index):55 SyntaxError
(index):56 undefined
(index):57 undefined
(index):58 undefined
(index):59 SyntaxError: Unexpected token =
at Function (native)
at window.onload (https://fiddle.jshell.net/_display/:51:8)
How can I, without using external dependencies, pinpoint SyntaxError location withinn passed string? I require solution both for browser and nodejs.
Please note: I do have a valid reason to use eval-equivalent code.
In Chromium-based browsers, as you've seen, putting try/catch around something that throws a SyntaxError while V8 is parsing the code (before actually running it) won't produce anything helpful; it will describe the line that caused the evaluation of the problematic script in the stack trace, but no details on where the problem was in said script.
But, there's a cross-browser workaround. Instead of using try/catch, you can add an error listener to window, and the first argument provided to the callback will be an ErrorEvent which has useful lineno and colno properties:
window.addEventListener('error', (errorEvent) => {
const { lineno, colno } = errorEvent;
console.log(`Error thrown at: ${lineno}:${colno}`);
// Don't pollute the console with additional info:
errorEvent.preventDefault();
});
const checkSyntax = (str) => {
// Using setTimeout because when an error is thrown without a catch,
// even if the error listener calls preventDefault(),
// the current thread will stop
setTimeout(() => {
eval(str);
});
};
checkSyntax(`console.log('foo') bar baz`);
checkSyntax(`foo bar baz`);
Look in your browser console to see this in action, not in the snippet console
Check the results in your browser console:
Error thrown at: 1:20
Error thrown at: 1:5
Which is what we want! Character 20 corresponds to
console.log('foo') bar baz
^
and character 5 corresponds to
foo bar baz
^
There are a couple issues, though: it would be good to make sure in the error listened for is an error thrown when running checkSyntax. Also, try/catch can be used for runtime errors (including syntax errors) after the script text has been parsed into an AST by the interpreter. So, you might have checkSyntax only check that the Javascript is initially parsable, and nothing else, and then use try/catch (if you want to run the code for real) to catch runtime errors. You can do this by inserting throw new Error to the top of the text that's evaled.
Here's a convenient Promise-based function which can accomplish that:
// Use an IIFE to keep from polluting the global scope
(async () => {
let stringToEval;
let checkSyntaxResolve;
const cleanup = () => {
stringToEval = null;
checkSyntaxResolve = null; // not necessary, but makes things clearer
};
window.addEventListener('error', (errorEvent) => {
if (!stringToEval) {
// The error was caused by something other than the checkSyntax function below; ignore it
return;
}
const stringToEvalToPrint = stringToEval.split('\n').slice(1).join('\n');
// Don't pollute the console with additional info:
errorEvent.preventDefault();
if (errorEvent.message === 'Uncaught Error: Parsing successful!') {
console.log(`Parsing successful for: ${stringToEvalToPrint}`);
checkSyntaxResolve();
cleanup();
return;
}
const { lineno, colno } = errorEvent;
console.log(`Syntax error thrown at: ${lineno - 1}:${colno}`);
console.log(describeError(stringToEval, lineno, colno));
// checkSyntaxResolve should *always* be defined at this point - checkSyntax's eval was just called (synchronously)
checkSyntaxResolve();
cleanup();
});
const checkSyntax = (str) => {
console.log('----------------------------------------');
return new Promise((resolve) => {
checkSyntaxResolve = resolve;
// Using setTimeout because when an error is thrown without a catch,
// even if the 'error' listener calls preventDefault(),
// the current thread will stop
setTimeout(() => {
// If we only want to check the syntax for initial parsing validity,
// but not run the code for real, throw an error at the top:
stringToEval = `throw new Error('Parsing successful!');\n${str}`;
eval(stringToEval);
});
});
};
const describeError = (stringToEval, lineno, colno) => {
const lines = stringToEval.split('\n');
const line = lines[lineno - 1];
return `${line}\n${' '.repeat(colno - 1) + '^'}`;
};
await checkSyntax(`console.log('I will throw') bar baz`);
await checkSyntax(`foo bar baz will throw too`);
await checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
await checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
})();
Look in your browser console to see this in action, not in the snippet console
await checkSyntax(`console.log('I will throw') bar baz`);
await checkSyntax(`foo bar baz will throw too`);
await checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
await checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
Result:
----------------------------------------
Syntax error thrown at: 1:29
console.log('I will throw') bar baz
^
----------------------------------------
Syntax error thrown at: 1:5
foo bar baz will throw too
^
----------------------------------------
Parsing successful for: console.log('A snippet without compile errors'); const foo = bar;
----------------------------------------
Syntax error thrown at: 2:6
With a syntax error on the second line
^
If the fact that an error is thrown at window is a problem (for example, if something else is already listening for window errors, which you don't want to disturb, and you can't attach your listener first and call stopImmediatePropagation() on the event), another option is to use a web worker instead, which has its own execution context completely separate from the original window:
// Worker:
const getErrorEvent = (() => {
const workerFn = () => {
const doEvalAndReply = (jsText) => {
self.addEventListener(
'error',
(errorEvent) => {
// Don't pollute the browser console:
errorEvent.preventDefault();
// The properties we want are actually getters on the prototype;
// they won't be retrieved when just stringifying
// so, extract them manually, and put them into a new object:
const { lineno, colno, message } = errorEvent;
const plainErrorEventObj = { lineno, colno, message };
self.postMessage(JSON.stringify(plainErrorEventObj));
},
{ once: true }
);
eval(jsText);
};
self.addEventListener('message', (e) => {
doEvalAndReply(e.data);
});
};
const blob = new Blob(
[ `(${workerFn})();`],
{ type: "text/javascript" }
);
const worker = new Worker(window.URL.createObjectURL(blob));
// Use a queue to ensure processNext only calls the worker once the worker is idle
const processingQueue = [];
let processing = false;
const processNext = () => {
processing = true;
const { resolve, jsText } = processingQueue.shift();
worker.addEventListener(
'message',
({ data }) => {
resolve(JSON.parse(data));
if (processingQueue.length) {
processNext();
} else {
processing = false;
}
},
{ once: true }
);
worker.postMessage(jsText);
};
return (jsText) => new Promise((resolve) => {
processingQueue.push({ resolve, jsText });
if (!processing) {
processNext();
}
});
})();
// Calls worker:
(async () => {
const checkSyntax = async (str) => {
console.log('----------------------------------------');
const stringToEval = `throw new Error('Parsing successful!');\n${str}`;
const { lineno, colno, message } = await getErrorEvent(stringToEval);
if (message === 'Uncaught Error: Parsing successful!') {
console.log(`Parsing successful for: ${str}`);
return;
}
console.log(`Syntax error thrown at: ${lineno - 1}:${colno}`);
console.log(describeError(stringToEval, lineno, colno));
};
const describeError = (stringToEval, lineno, colno) => {
const lines = stringToEval.split('\n');
const line = lines[lineno - 1];
return `${line}\n${' '.repeat(colno - 1) + '^'}`;
};
await checkSyntax(`console.log('I will throw') bar baz`);
await checkSyntax(`foo bar baz will throw too`);
await checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
await checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
})();
Look in your browser console to see this in action, not in the snippet console
Essentially, what checkSyntax is doing is checking to see if the code provided can be parsed into an Abstract Syntax Tree by the current interpreter. You can also use packages like #babel/parser or acorn to attempt to parse the string, though you'll have to configure it for the syntax permitted in the current environment (which will change as new syntax gets added to the language).
const checkSyntax = (str) => {
try {
acorn.Parser.parse(str);
console.log('Parsing successful');
} catch(e){
console.error(e.message);
}
};
checkSyntax(`console.log('I will throw') bar baz`);
checkSyntax(`foo bar baz will throw too`);
checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
<script src="https://cdn.jsdelivr.net/npm/acorn#6.1.1/dist/acorn.min.js"></script>
The above works for browsers. In Node, the situation is different: listening for an uncaughtException can't be used to intercept the details of syntax errors, AFAIK. However, you can use vm module to attempt to compile the code, and if it throws a SyntaxError before running, you'll see something like this. Running
console.log('I will throw') bar baz
results in a stack of
evalmachine.<anonymous>:1
console.log('I will throw') bar baz
^^^
SyntaxError: Unexpected identifier
at createScript (vm.js:80:10)
at Object.runInNewContext (vm.js:135:10)
<etc>
So, just look at the first item in the stack to get the line number, and at the number of spaces before the ^ to get the column number. Using a similar technique as earlier, throw an error on the first line if parsing is successful:
const vm = require('vm');
const checkSyntax = (code) => {
console.log('---------------------------');
try {
vm.runInNewContext(`throw new Error();\n${code}`);
}
catch (e) {
describeError(e.stack);
}
};
const describeError = (stack) => {
const match = stack
.match(/^\D+(\d+)\n(.+\n( *)\^+)\n\n(SyntaxError.+)/);
if (!match) {
console.log('Parse successful!');
return;
}
const [, linenoPlusOne, caretString, colSpaces, message] = match;
const lineno = linenoPlusOne - 1;
const colno = colSpaces.length + 1;
console.log(`${lineno}:${colno}: ${message}\n${caretString}`);
};
checkSyntax(`console.log('I will throw') bar baz`);
checkSyntax(`foo bar baz will throw too`);
checkSyntax(`console.log('A snippet without compile errors'); const foo = bar;`);
checkSyntax(`console.log('A multi line snippet');
With a syntax error on the second line`);
Result:
---------------------------
1:29: SyntaxError: Unexpected identifier
console.log('I will throw') bar baz
^^^
---------------------------
1:5: SyntaxError: Unexpected identifier
foo bar baz will throw too
^^^
---------------------------
Parse successful!
---------------------------
2:6: SyntaxError: Unexpected identifier
With a syntax error on the second line
^
That said:
How can I, without using external dependencies, pinpoint SyntaxError location withinn passed string? I require solution both for browser and nodejs.
Unless you have to achieve this without an external library, using a library really would be the easiest (and tried-and-tested) solution. Acorn, as shown earlier (and other parsers) work in Node as well.
I'm sumarizing comments and some additional research:
Simple anwer: currently impossible
There is currently no cross-platform way to retrive syntax error position from new Function() or eval() call.
Partial solutions
Firefox support non-standard properties error.lineNumber and error.e.columnNumber. This can be used with feature detection if position of error is not critical.
There are filled bug reports/feature request for v8 that could bring support of (1) to chrome/node.js: Issue #1281, #1914, #2589
Use separate javascript parser, based on JSLint or PEG.js.
Write custom javascript parser for the job.
Solutions 1 and 2 are incomplete, rely on features that are not part of standard. They can be suitable if this information is a help, not an requirement.
Solution 3 depends on external codebase, which was explicitly required by original question. It is suitable if this information is required and larger codebase is acceptable compromise.
Solution 4 is impractical.
Credits: #user3896470, #ivan-kuckir, #aprillion
Solution for browser:
You can use latest Firefox to get the required information like error line number and column number inside the string.
Example:
var testWithSyntaxError = "{\n\n\n\nvar x=3;\n =2;}";
try {
var f=new Function('',testWithSyntaxError);
} catch(e) {
console.log(e instanceof SyntaxError);
console.log(e.message);
console.log(e.name);
console.log(e.fileName);
console.log(e.lineNumber);
console.log(e.columnNumber);
console.log(e.stack);
}
Output in Firefox console:
undefined
true
expected expression, got '='
SyntaxError
debugger eval code
6
1
#debugger eval code:4:11
Where 6 is the line number and 1 is the column number of error inside the string.
It won't work in Chrome. There are bugs filed regarding this issue for chrome browser. See:
https://bugs.chromium.org/p/v8/issues/detail?id=1281
https://bugs.chromium.org/p/v8/issues/detail?id=1914
https://bugs.chromium.org/p/v8/issues/detail?id=2589

Categories