Tags: Node.JS »»»» JavaScript
The error Unexpected Reserved Word usually means you're using await in a non-async function. But what if Mocha throws this error, citing a test suite file that's clean of such problems? To save spending fruitless hours, read this short article.
The error Unexpected Reserved Word means that the JavaScript compiler saw a reserved word, meaning a keyword special to the compiler, when it wasn't expected. THe typical case is using await
in a function that hasn't been marked async
.
In this case I found Mocha - a popular framework for writing unit tests - to incorrectly report which source file contained the unexpected reserved word. My test suite threw that error message, but Mocha identified the incorrect source file.
Usually a compiler, or a test framework, reporting a syntax error will tell us the actual line of code, and the offending code. But, in this case Mocha does not report the line number, nor the affected code, and to make it worse it reports the error as occuring in a different source file from its actual location.
Because Mocha incorrectly reported the location of the unexpected reserved word error, I spent fruitless hours double and triple checking the syntax of a source file which contained no such error.
Let's start with the usual case:
$ cat unreserved.mjs
function a() {
await console.log(`This will throw an error`);
}
$ node unreserved.mjs
file:///home/david/Projects/akasharender/akasharender/test/unreserved.mjs:3
await console.log(`This will throw an error`);
^^^^^
SyntaxError: Unexpected reserved word
at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:119:18)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:483:14)
at async link (node:internal/modules/esm/module_job:67:21)
This is what we normally see. We've created an ES6 module, unreserved.mjs
, containing a non-async function. Within that function is the await
keyword. That's a use of a reserved word, await
, in the incorrect context. In this case the error report does an excellent job of letting us know what the problem is, and where it occurred.
This is the desired behavior, for the compiler to do a good job of letting us know what's going on. We can go directly to the affected line of source code, and immediately know what to do, after groaning "Doh".
Consider a real package, where an actual (and useful) module has been written, which contains that problem. Then, you go to write a test suite for this module, because you're following good test-first-development practices.
$ cat test-unreserved.mjs
import * as unreserved from './unreserved.mjs';
$ npx mocha test-unreserved.mjs
SyntaxError[ @/home/david/Projects/akasharender/akasharender/test/test-unreserved.mjs ]: Unexpected reserved word
at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:119:18)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:483:14)
Your test suite of course must import the application code to execute test cases. This means the unreserved.mjs
module. Because we just created that file, we can easily remember the error. In this case, Mocha doesn't give us a clean error message. It simply says Unexpected reserved word, and further it identifies test-unreserved.mjs
as containing the problem.
Again, consider that this is instead happening in a real package. The test suite could have 1500 lines of code, and the module being tested has a similar amount of code.
Because Mocha positively identifies the test suite as containing the unexpected reserved word, it's easy to then spend several hours combing through the test suite. You check every use of await
and it's all correct. You check closely every loop, every function, every test case. You use VS Code and collapse every block, and because the editor correctly collapses each block, it seems the editor believes this is correctly constructed JavaScript. You then try commenting out all code, and still the error shows up. Finally you try commenting out the import
statements one by one, and voila the error goes away with one particular import
statement.
Checking inside that file you might then find a non-async function where the async
keyword was used. That's easily fixed, and that particular error goes away. There may have been other problems, and you'll know the original problem was fixed because the error message changes.
$ npx mocha test-cache.mjs
SyntaxError[ @/home/david/Projects/akasharender/akasharender/test/test-cache.mjs ]: Unexpected reserved word
at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:119:18)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:483:14)
$ npx mocha test-cache.mjs
SyntaxError[ @/home/david/Projects/akasharender/akasharender/test/test-cache.mjs ]: Identifier 'setup' has already been declared
at ESMLoader.moduleStrategy (node:internal/modules/esm/translators:119:18)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:483:14)
This is from the real instance where the problem occurred. I had fixed the unexpected reserved word issue, only to learn of another problem -- that I'd created two functions named setup
. Notice that Mocha still reported the wrong source file. But, its clear the problem will be in the other source file and not the test suite.
Conclusion
Mocha is incorrectly reporting the source file containing the code problems.
It correctly identified the problem - that some code used a reserved word incorrectly. But the error was reported in the wrong file, which can easily lead to fruitless hours carefully scrutinizing code that has no such errors.
Our time is valuable, and it's frustrating to spend so much time cleaning up a file that had no problems in it.
Bad Mocha. No soup for you.