
Loading…
CoffeeScript `use strict` #1547
(subscribe)
+1
@josh: there's now a link at the bottom of issues that allows you to {,un}subscribe without posting. It's actually been there for a few months now.
@michaelficarra: @josh works at GitHub ;)
In any case, I think this ticket makes a very strong argument for having CoffeeScript be "use strict" compliant to the extent statically possible.
The only real downsides here are the inability to use the FutureReservedWords as identifiers ... but it seems like folks don't tend to do that:
So let's clarify this proposal. Do we enforce strict mode restrictions everywhere or only in contexts that begin with a "use strict" directive? If everywhere, do we output a "use strict" directive at the top of our IIFE wrapper?
Side note: the delete undefined example from the OP exposes a bug in our current compilation, but if we're going to disallow deletion of "a direct reference to a variable, function argument, or function name", this problem will just go away.
edit: never mind, @geraldalewis already got it
Good point @michaelficarra
Since strict mode affects some subtle runtime behavior, and since that runtime behavior is not cross-browser/environment, strict mode should be opt-in. For instance, in non-strict, changing the value of a property of the arguments object will change the value of the corresponding argument binding, while in strict there's no dynamic binding between arguments and the individual arguments defined by the formal params.[2]
From @kangax's ES5 Browser Compatibility Table:
strict
((x)-> x = 2; arguments[0] is 1)(1) # true
non-strict
((x)-> x = 2; arguments[0] is 1)(1) # false
That'd probably break some users' code if they're not expecting it (not that there isn't arguments weirdness in different implementations[2]).
Related to the "use strict" directive prologue itself: it might be a good idea for the compiler to communicate that "use strict" shouldn't appear outside an IIFE when compiling with --bare in case the file is concatenated with other js files. Douglas Crockford warns "If a file with a "use strict"; preamble has sloppy code appended to it, the sloppy code will be processed as strict and will probably fail. There is an easy rule: Do not mix strict and sloppy in the same file, but we have already seen some famous websites get this wrong."[3] Would a warning work? (I can't find precedents for warnings outside of compiling with the --lint option; am I missing some?). There may be a use case for "use strict" outside an IIFE I'm not thinking of, however.
Would love to hear about the bug delete undefined exposed -- tried to find it myself last night to no avail.
[1] ES5 Annotated Spec 10.6 Section 1
[2] Angus Croll, The JavaScript arguments object… and beyond
[3] Douglas Crockford, Strict Mode is Coming to Town
I think if we do this, we enforce our static strict mode restrictions everywhere, but we do not put any "use strict" directives into your code for you.
Agreed @jashkenas -- I just came back to clarify that the early strict syntax checks should be made regardless of whether or not the user has chosen strict mode.
In the interest of deepening the discussion, it was proposed in #1002 (duplicate parameter names) that instead of throwing an error, CoffeeScript could rewrite the offending code. The following CoffeeScript snippets show:
1. code that would raise a SyntaxError under `use strict`
2. conversion of that code to a safe form (expressed in CoffeeScript)
3. the sanitized JavaScript output
For the record, I think SyntaxErrors are a better way to handle strict mode restrictions.
oct = 0121
oct = 81 # convert to decimal
var oct = 81 # js output
octEscSeq = "\121"
octEscSeq = "\x51" # convert to hex escape sequence
var octEscSeq = "\x51" # js output
x: console.log('x'), x: 1
x: do -> console.log('x'); 2 # each assignment wrapped into single `IIFE`
{ x: (function() { # js output; return second assignment's value
console.log('x');
return 2; })()
};
x = 1; delete x
x = 1; # remove `delete x`
var x = 1; # js output
13 Function Definition (13.1 Strict Mode Restrictions)
(x,x) ->
(x,x2) -> # rename 2nd param to be unique
function(x, x2) {} # js output
implements = 1
_implements = 1 # `_` prefix added
var _implements = 1; # js output
The eval/arguments issues can be resolved with a rename
try e catch eval
try e catch _eval # `_` prefix added
try { e; } catch (_eval) {}# js output
(arguments)->
(_arguments)-> # `_` prefix added
function(_arguments) {} # js output
class eval
class _eval # `_` prefix added
function _eval(){} # js output
arguments = 1
_arguments = 1 # `_` prefix added
var _arguments = 1 # js output
eval++
_eval++ # `_` prefix added
_eval++ # js output
Obfuscation and Intent
However, these code transformations would subvert the intention behind the strict restrictions. In the case of duplicate param names and duplicate object props, this strategy would further obfuscate hard to detect bugs. A user attempting to delete a var, arg or function declaration should be warned that their action isn't having the intended effect. eval and arguments as identifiers is a bad idea.
Agreed -- much much better to SyntaxError than to change your semantics on the sly.
For the record, I think
SyntaxErrors are a better way to handlestrictmode restrictions.
I think the conversion is better for some, but not all, strict mode restrictions.
oct = 0121 oct = 81 # convert to decimal var oct = 81 # js output
+1
octEscSeq = "\121" octEscSeq = "\x51" # convert to hex escape sequence var octEscSeq = "\x51" # js output
+1
x: console.log('x'), x: 1 x: do -> console.log('x'); 2 # each assignment wrapped into single `IIFE` { x: (function() { # js output; return second assignment's value console.log('x'); return 2; })() };
I'd prefer this conversion:
o = {};
o.x = console.log('x');
o.x = 1;
x = 1; delete x x = 1; # remove `delete x` var x = 1; # js output
-1 no way to preserve semantics: SyntaxError
13 Function Definition (13.1 Strict Mode Restrictions)
(x,x) -> (x,x2) -> # rename 2nd param to be unique function(x, x2) {} # js output
(x,x) -> should be function(_unused, x){}
implements = 1 _implements = 1 # `_` prefix added var _implements = 1; # js output
var implement\u0073 = 1
The
eval/argumentsissues can be resolved with a renametry e catch eval try e catch _eval # `_` prefix added try { e; } catch (_eval) {}# js output
-1 no way to preserve semantics: SyntaxError
(arguments)-> (_arguments)-> # `_` prefix added function(_arguments) {} # js output
-1 no way to preserve semantics: SyntaxError
class eval class _eval # `_` prefix added function _eval(){} # js output
-1 no way to preserve semantics: SyntaxError
arguments = 1 _arguments = 1 # `_` prefix added var _arguments = 1 # js output
-1 no way to preserve semantics: SyntaxError
eval++ _eval++ # `_` prefix added _eval++ # js output
-1 no way to preserve semantics: SyntaxError
Obfuscation and Intent
However, these code transformations would subvert the intention behind the
strictrestrictions. In the case of duplicate param names and duplicate object props, this strategy would further obfuscate hard to detect bugs.A user attempting to
deletea var, arg or function declaration should be warned that their action isn't having the intended effect.
No, it should definitely be a SyntaxError, no question.
evalandargumentsas identifiers is a bad idea.
agreed.
JSLint throws an error if you don't include the "use strict" pragma* . Developers naïvely include it to prevent the error, but do not properly test their code with a browser that supports strict mode. Or code with the global "use strict" directive is concat'ed with non-strict code, breaking it. Bugs are then filed against browsers who correctly throw errors when "use strict" breaks some functionality on a big website.
This may lead to what @BrendanEich calls "strict quirks mode".
Raising SyntaxErrors on "early errors" will not only help devs who incorrectly enable strict mode, but will add a layer of defense for CoffeeScript programs that are concat'ed into strict mode. "use strict" is a good and valuable thing for JavaScript, and therefore it's good for CoffeeScript. We can do our part and require better, safer, and more reliable code from CoffeeScript developers.
* Unless the "sloppy" flag is set to true, something I doubt a dev who uses JSLint would like to do. By calling it "sloppy", Crockford has, frustratingly, created a moral imperative for employing "use strict" (like Knuth did [tongue-in-cheek] with Literate Programming -- "nobody wants to admit to an illiterate program.")
I think this can be closed.
$ coffee -e '"use strict"; p: 1, "p": 2'
.:6
"p": 2
^^^
SyntaxError: Duplicate data property in object literal not allowed in strict mode
$ node -e '"use strict"; "\0"'
$ coffee -e '"\0"'
SyntaxError: octal escape sequences "\0" are not allowed on line 1
I am also wondering about the "\0" octal escape sequences being disallowed as well. If @satyr is correct these are allowed in JS even in strict mode. I have an SQL escape function that needs to replace "\0" with "\\0" and now it won't compile. Any reason why, or is this a bug?
SpiderMonkey shell:
js> "use strict"; "\0"
"\x00"
js> "use strict"; "\00"
typein:2: SyntaxError: octal literals and octal escape sequences are deprecated:
typein:2: "use strict"; "\00"
typein:2: ..............^
js> "use strict"; "\01"
typein:3: SyntaxError: octal literals and octal escape sequences are deprecated:
typein:3: "use strict"; "\01"
typein:3: ..............^
The special case is for "\0" only, not anything else truly octal or "noctal" (not quite octal, e.g. \08, \09, etc.).
@devongovett: it's impossible to say what is going wrong without seeing that function, but it's not anything to do with strict mode as spec'ed or implemented in Firefox.
/be
@BrendanEich Well here is the function:
escapeSQL = (val) ->
val = val.replace /[\0\n\r\b\t\\\'\"\x1a]/g, (s) ->
switch s
when "\0" then "\\0"
when "\n" then "\\n"
when "\r" then "\\r"
when "\b" then "\\b"
when "\t" then "\\t"
when "\x1a" then "\\Z"
else "\\" + s
return "'" + val + "'"The error is thrown by the CoffeeScript compiler, not the JS side of things. CoffeeScript apparently isn't special casing "\0" like SpiderMonkey, V8 and probably others do. Luckily, this is workaroundable for the time being by using "\u0000" instead of "\0", but it still should be fixed.
@devongovett new issue on file?
/be
@devongovett: This should be fixed by 46ff770. Go ahead and try that out.
+1
+1 : Cannot delete any variable.
-module.coffee:101:5: error: delete operand may not be argument or var
delete parser
^^^^^^^^^^^^^^^
L100: delete parser
use strict
ECMAScript 5 introduces a
strictmode that reduces bugs, increases security, and eliminates some difficult to use language features. The coding standardsstrictmode requires should be adopted by CoffeeScript.Better Code
CoffeeScript emits lowest common denominator JavaScript. From a different angle: CoffeeScript emits JavaScript that is not implementation specific. Just as there are constraints on output imposed by IE6's nonconforming behavior (no
function declarations), there too should be constraints on input imposed by better engineered environments.User Expectations
CoffeeScript throws errors when it encounters syntax errors (e.g., one can't write
-> return return). A user employing theuse strictdirective will expect to see syntax errors at compile time (pre-evaluation), just as they would in a JavaScript environment.Best Practices
CoffeeScript emits JavaScript that adheres to best practices. ES5
strictcodifies many best practices, and fixes some unsafe and error-prone operations. CoffeeScript already shares some constraints withstrict(e.g., nowith) and fixes others thatstricttargets (e.g., in CoffeeScript, vars are auto-declared for variable safety).Early Errors
"Early errors" are syntax errors detected prior to evaluation (all others are runtime errors). Most
strictmode errors are syntax errors (of typeSyntaxError). CoffeeScript should only check for these types of errors. 16 Errors states: "An implementation shall not treat other kinds of errors as early errors even if the compiler can prove that a construct cannot execute without error under any circumstances" (emphasis mine).Early Strict Syntax Errors
The following CoffeeScript snippets would be considered "early errors" under ES5
strict.7.8.3 Numeric Literals
B.1.2 String Literals
11.1.5 Object Initialiser
11.4.1 The delete Operator
13 Function Definition (13.1 Strict Mode Restrictions)
7.6.1.2 Future Reserved Words
evalandargumentsrestrictions12.14 The try Statement (12.14.1 Strict Mode Restrictions)
13 Function Definition (13.1 Strict Mode Restrictions)
11.13.1 Simple Assignment
11.3.1 Postfix Increment Operator
as well as Postfix Decrement and Prefix Increment and Decrement
What Users Would Give Up
Some coding conventions would need to be abandoned, namely:
Identical param names
e.g.,
However, as @jashkenas stated "...is a serious antipattern. For the reader who comes across your function, intending to modify it ... not only are they unable to use the first three parameters -- they also have not the slightest clue what the parameters might be."
@satyr also offered a workaround:
({},{},{},buffer) -> #read bufferwould still be legalIdentical prop names in object literals
@satyr's example:
I'd argue that it's likely this feature is more often the cause of bugs
foo: 1, bar: 2, bar: 3 # user meant 'baz'and catching those errors would outweigh its other uses.(and one could still write):
Sources/More Information
Annotated ES5 Spec
ES5 Spec: Annex C "The Strict Mode of ECMAScript "
JavaScript Strict Mode Tests
ECMA-262-5 in detail. by Dmitry Soshnikov
ES5 Browser Compatibility Table by @kangax