Closure-compiler: ERROR - This language feature is only supported for ECMASCRIPT6 mode or better: block-scoped function declaration.

Created on 10 Jan 2019  路  8Comments  路  Source: google/closure-compiler

On

$ npx google-closure-compiler --version
Closure Compiler (http://github.com/google/closure-compiler)
Version: v20181210
Built on: 2018-12-12 22:34

compiling

if (performance.now() >= 0) {
  function foo() {
    console.log(performance.now());
  }
  foo();
}

gives

$ npx google-closure-compiler --js=src.js --js_output_file=out.js --language_in ECMASCRIPT5
src.js:2: ERROR - This language feature is only supported for ECMASCRIPT6 mode or better: block-scoped function declaration.
    function foo() {
    ^

1 error(s), 0 warning(s)

I don't think this error is correct. Functions defined inside a block do work in non-ES6 browsers, and the code used to compile properly in older version of Closure compiler when using --language_in ECMASCRIPT5 flag. This issue started to appear after I updated Closure to a newer version. My older Closure version was

Closure Compiler (http://github.com/google/closure-compiler)
Version: v20171023
Built on: 2017-10-26 19:00

I do not expect the function foo in above example to have block scope visibility, but due to reasons X beyond my control, I am compiling code that has instances of functions defined inside blocks - the code as written does not care about scope (in the instances that occur, the functions are used locally at block scope, and don't mind if the visibility of the functions extends to function scope)

Did Closure drop support for defining functions with function scope in ES5? Or is there a flag to allow enabling this? I'd like to update to a newer Closure compiler version, but this causing a bit of headache.

internal-issue-created triage-done

Most helpful comment

There's not a lot of reason to supply --language_in unless you're trying to enforce that your input definitely won't use any newer features.

This is the exact reason why Emscripten is using --language_in ECMASCRIPT5 - it wants to enforce that user JS code does not use new ES6 features when many developers want to build projects that have backwards compatibility far back to old browsers.

The issue isn't older browsers, it's newer ones and differences in behavior between strict and sloppy mode.

I do appreciate and acknowledge the semantical difference with the two scoping rules, but the issue here is not old browsers, or the difference in behavior, but the fact that Closure no longer is able to process valid ES5 code whereas it used to before. Surely Closure does model (used to model?) sloppy mode scope for function definitions e.g. when --language_in=ES5 --language_out=ES5 is used? It feels like a regression that it no longer does.

Could Closure treat the scope according to the --language_in= directive that is passed? I.e. --language_in=ES6 would treat functions with block scope, and --language_in=ES5 (or whichever ES spec first had the semantical change) would treat them with function scope?

All 8 comments

Created internal Google issue http://b/122662956.

Block scoped functions in ES5 had to be nested in another function, like so

function bar() {
    function foo() {
    }
    foo();
}

However, in ES5, blocks like if {} else {} does not create their own scope.

In ES6, block scoped functions where added such that each block {} has its own scope, so you could create block scoped functions in this way

if (condition) {
    function foo() {
    }
    foo();
}

From your snippet it seems that you are indeed using ES6 blocked scoped functions, hence the error message.

From your snippet it seems that you are indeed using ES6 blocked scoped functions, hence the error message.

For example the page

<html>
<body>
<div id='test'></div>
<script>
if (Date.now() >= 0) {
  function foo() {
    document.getElementById('test').innerHTML = Date.now(); 
  }
  foo();
}
</script>
</body>
</html>

runs fine on old or even ancient browsers (tested Firefox 3.0, Chrome 1, Safari 9 and IE9 via browserling and browsershots). The question of what scope foo here has is not the point of this created issue. The point is that the above code is ok in pre-ES6 browsers, and Closure used to be able to process code like above in version v20171023, but it no longer does so in version v20181210. I am not developing ES6 code.

Perhaps ES6 changes the semantics of the above to treat function foo() as block scoped, and that is nice, but the issue here is that Closure no longer is able to process through JavaScript that used to work ok on pre-ES6 browsers, even when --language_in ECMASCRIPT5 is passed. To my understanding the above code is not ill-formed ES5, and not ill-formed even in most ancient implementations of JavaScript? (at least old browsers take it in fine)

@shicks may have more context

The change to consider block scoped function declarations was made about a year ago.
The issue isn't older browsers, it's newer ones and differences in behavior between strict and sloppy mode.
For example:

(function() {
   var x = [];
   f();
   {
     function f() { x.push(2) };
     f();
   }
   function f() { x.push(1) };
   f();
   // logs [1, 2, 1] in strict mode
   // logs [1, 2, 2] in sloppy mode due to hoisting of outer definition
   console.log(x);
})();

More details available here.
https://www.ecma-international.org/ecma-262/6.0/#sec-block-level-function-declarations-web-legacy-compatibility-semantics

We basically decided that the best way to get consistent behavior and avoid nasty surprises
for the user was to insist on treating block-scoped function definitions as an ES6 feature.

Since you say you cannot modify the code you're working with, the best option is probably to just
set --language_in=ES6 --language_out=ES5.

Had another thought.
You could just not specify --language_in at all.
The default if you leave it off is to accept any input language features the compiler considers 'stable'.
Today I believe that is ES_2017.

There's not a lot of reason to supply --language_in unless you're trying to enforce that your input definitely won't use any newer features.

There's not a lot of reason to supply --language_in unless you're trying to enforce that your input definitely won't use any newer features.

This is the exact reason why Emscripten is using --language_in ECMASCRIPT5 - it wants to enforce that user JS code does not use new ES6 features when many developers want to build projects that have backwards compatibility far back to old browsers.

The issue isn't older browsers, it's newer ones and differences in behavior between strict and sloppy mode.

I do appreciate and acknowledge the semantical difference with the two scoping rules, but the issue here is not old browsers, or the difference in behavior, but the fact that Closure no longer is able to process valid ES5 code whereas it used to before. Surely Closure does model (used to model?) sloppy mode scope for function definitions e.g. when --language_in=ES5 --language_out=ES5 is used? It feels like a regression that it no longer does.

Could Closure treat the scope according to the --language_in= directive that is passed? I.e. --language_in=ES6 would treat functions with block scope, and --language_in=ES5 (or whichever ES spec first had the semantical change) would treat them with function scope?

I'm afraid we really cannot reasonably support handling scopes differently for function declarations in ES5 sloppy mode than in ES6.

  1. In ES5 strict mode block-scoped function declarations threw an error. It's arguably a bug that we never generated errors for that.

  2. In sloppy mode ES5 and ES6 behavior for block-scoped function declarations are subtly different in a way that can lead to surprising differences in behavior.

  3. When there were no ES6-capable browsers, closure-compiler knew how to interpret sloppy-mode block-scoped function declarations correctly.

  4. As soon as browsers started implementing ES6 behavior, closure-compiler was no longer reasoning correctly about block-scoped functions for them. Thus it was generating code that could actually be behaving incorrectly on those browsers, and failing to warn code authors about the difference in behavior.

It is our fix for number 4 that led to your problem.
We fixed it the way we did because:

  1. closure-compiler does not know which behavior for block-scoped function declarations the target environment for its output will implement.

  2. closure-compiler does not know which behavior for block-scoped function declarations the author of the code it is compiling expected.

Our preference in such cases is to refuse to compile and point out the problem code so the author can change it to something less ambiguous.

What you propose is that we should interpret --language_in=ES5 to mean that the author's intention was to get ES5-sloppy-mode behavior, but that still doesn't tell us how the target environment will behave. To do what you're asking safely it would be necessary for us to effectively do an ES5->ES5 transpilation in which we forcibly transpile block-scoped function declarations into a form guaranteed to behave the same way on all target environments. We would prefer not to do that work.

I realize that the code you're compiling is outside of your control, but it must be pretty crufty if it is relying on sloppy-mode behavior. Maybe you could push back some changes to replace function foo() {} with var foo = function foo() {}?

Thank you for the detailed explanation.

you propose is that we should interpret --language_in=ES5 to mean that the author's intention was to get ES5-sloppy-mode behavior, but that still doesn't tell us how the target environment will behave.

Maybe you could push back some changes to replace function foo() {} with var foo = function foo() {}?

Trying this out, it looks like Closure does optimize var foo = function foo() {} back to function foo() {}, which is odd thing for it to do if Closure does not know how the target environment will behave.

Anyhow, I'll try to push back on this to get people to write ES6 compliant code, so this problem will not occur.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jart picture jart  路  7Comments

JohannBlake picture JohannBlake  路  3Comments

rongjiecomputer picture rongjiecomputer  路  3Comments

zzo picture zzo  路  6Comments

agrafix picture agrafix  路  5Comments