Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add named classes that work in `use strict`. #2262

Closed
wants to merge 1 commit into from

8 participants

@paulmillr

This will allow use of @constructor.__name__ for building great dynamic apps.

class Tweet
console.log Tweet.__name__
# => 'Tweet'

class View
  initialize: ->
    super
    templateName = underscorize(@constructor.__name__).replace(/_view$/, '')
    @template ?= require "./templates/#{templateName}"

class TweetView extends View

CoffeeScript 1.3.1 already uses @constructor.name for this purpose, but as we've seen, it breaks 'use strict' mode because @constructor.name is a read-only property.

Related: #494, #2052, #2249, #2250, f3a1f46.

@idflood

The patch looks good. Waiting for this fix to be applied since I extensively "use strict".

@vendethiel
Collaborator

+1 for that !

@jashkenas
Owner

Thanks, but no thanks, for all the reasons detailed in the original ticket. We don't want to mint a new "standard" JavaScript property that works for class-defined prototypes, but not regular function-defined ones.

@jashkenas jashkenas closed this
@paulmillr

@jashkenas then why do we have __super__? It doesn't work for regular function-defined ones. Well, it works with additional code, but just as this one. Original ticket ended with your talk with @hornairs and merging of constructor.name patch.

P.S.
It's absolutely not needed for debugging, but it's very useful for dynamic programming.

@michaelficarra
Collaborator

Dynamic programming? I don't think that term means what I think you think it means.

edit: Ah, I haven't heard this usage of the term before. Damn my algorithmic background!

@paulmillr

@michaelficarra yea, didn't know about this one. Won't be using it in the future in the context.

edit: :v:

@chrismcv

coming in late on this, but the lack of a class name property is a real shame for me too.

@aseemk

I too would love to see some classname property; we too use it to auto-detect types. __name__ doesn't seem harmful to me, much like __super__.

@karellm

Coffeescript generated code will throw an exception (only in Safari for now) if you use use strict:

TypeError: Attempted to assign to readonly property.

The property in question is name on a code like Bla.name = 'Bla';.

Whatever the solution you want to come up with, it is a problem with coffeescript that would be nice to fix. Coffeescript should play nicely with 'use strict'.

@jashkenas
Owner

Should be fixed in the latest version of CoffeeScript.

@paulmillr

@karellm coffeescript doesn't generate .name since 1.3.3.

@karellm

Thanks for the prompt answer. That must be the coffee-rails gem that hasn't updated coffeescript yet then.

Edit: Actually was up to date but I needed to edit the files to regenerate them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 13, 2012
  1. @paulmillr
This page is out of date. Refresh to see the latest.
View
2  lib/coffee-script/lexer.js
@@ -9,6 +9,8 @@
exports.Lexer = Lexer = (function() {
+ Lexer.__name__ = 'Lexer';
+
function Lexer() {}
Lexer.prototype.tokenize = function(code, opts) {
View
61 lib/coffee-script/nodes.js
@@ -32,6 +32,8 @@
exports.Base = Base = (function() {
+ Base.__name__ = 'Base';
+
function Base() {}
Base.prototype.compile = function(o, lvl) {
@@ -208,6 +210,8 @@
__extends(Block, _super);
+ Block.__name__ = 'Block';
+
function Block(nodes) {
this.expressions = compact(flatten(nodes || []));
}
@@ -428,6 +432,8 @@
__extends(Literal, _super);
+ Literal.__name__ = 'Literal';
+
function Literal(value) {
this.value = value;
}
@@ -486,6 +492,8 @@
__extends(Return, _super);
+ Return.__name__ = 'Return';
+
function Return(expr) {
if (expr && !expr.unwrap().isUndefined) {
this.expression = expr;
@@ -522,6 +530,8 @@
__extends(Value, _super);
+ Value.__name__ = 'Value';
+
function Value(base, props, tag) {
if (!props && base instanceof Value) {
return base;
@@ -688,6 +698,8 @@
__extends(Comment, _super);
+ Comment.__name__ = 'Comment';
+
function Comment(comment) {
this.comment = comment;
}
@@ -713,6 +725,8 @@
__extends(Call, _super);
+ Call.__name__ = 'Call';
+
function Call(variable, args, soak) {
this.args = args != null ? args : [];
this.soak = soak;
@@ -898,6 +912,8 @@
__extends(Extends, _super);
+ Extends.__name__ = 'Extends';
+
function Extends(child, parent) {
this.child = child;
this.parent = parent;
@@ -917,6 +933,8 @@
__extends(Access, _super);
+ Access.__name__ = 'Access';
+
function Access(name, tag) {
this.name = name;
this.name.asKey = true;
@@ -945,6 +963,8 @@
__extends(Index, _super);
+ Index.__name__ = 'Index';
+
function Index(index) {
this.index = index;
}
@@ -967,6 +987,8 @@
__extends(Range, _super);
+ Range.__name__ = 'Range';
+
Range.prototype.children = ['from', 'to'];
function Range(from, to, tag) {
@@ -1068,6 +1090,8 @@
__extends(Slice, _super);
+ Slice.__name__ = 'Slice';
+
Slice.prototype.children = ['range'];
function Slice(range) {
@@ -1094,6 +1118,8 @@
__extends(Obj, _super);
+ Obj.__name__ = 'Obj';
+
function Obj(props, generated) {
this.generated = generated != null ? generated : false;
this.objects = this.properties = props || [];
@@ -1181,6 +1207,8 @@
__extends(Arr, _super);
+ Arr.__name__ = 'Arr';
+
function Arr(objs) {
this.objects = objs || [];
}
@@ -1235,6 +1263,8 @@
__extends(Class, _super);
+ Class.__name__ = 'Class';
+
function Class(variable, parent, body) {
this.variable = variable;
this.parent = parent;
@@ -1396,6 +1426,9 @@
if (!(this.ctor instanceof Code)) {
this.body.expressions.unshift(this.ctor);
}
+ if (decl) {
+ this.body.expressions.unshift(new Assign(new Value(new Literal(name), [new Access(new Literal('__name__'))]), new Literal("'" + name + "'")));
+ }
this.body.expressions.push(lname);
(_ref2 = this.body.expressions).unshift.apply(_ref2, this.directives);
this.addBoundFunctions(o);
@@ -1422,6 +1455,8 @@
__extends(Assign, _super);
+ Assign.__name__ = 'Assign';
+
function Assign(variable, value, context, options) {
var forbidden, name, _ref2;
this.variable = variable;
@@ -1642,6 +1677,8 @@
__extends(Code, _super);
+ Code.__name__ = 'Code';
+
function Code(params, body, tag) {
this.params = params || [];
this.body = body || new Block;
@@ -1801,6 +1838,8 @@
__extends(Param, _super);
+ Param.__name__ = 'Param';
+
function Param(name, value, splat) {
var _ref2;
this.name = name;
@@ -1887,6 +1926,8 @@
__extends(Splat, _super);
+ Splat.__name__ = 'Splat';
+
Splat.prototype.children = ['name'];
Splat.prototype.isAssignable = YES;
@@ -1957,6 +1998,8 @@
__extends(While, _super);
+ While.__name__ = 'While';
+
function While(condition, options) {
this.condition = (options != null ? options.invert : void 0) ? condition.invert() : condition;
this.guard = options != null ? options.guard : void 0;
@@ -2038,6 +2081,8 @@
__extends(Op, _super);
+ Op.__name__ = 'Op';
+
function Op(op, first, second, flip) {
if (op === 'in') {
return new In(first, second);
@@ -2230,6 +2275,8 @@
__extends(In, _super);
+ In.__name__ = 'In';
+
function In(object, array) {
this.object = object;
this.array = array;
@@ -2310,6 +2357,8 @@
__extends(Try, _super);
+ Try.__name__ = 'Try';
+
function Try(attempt, error, recovery, ensure) {
this.attempt = attempt;
this.error = error;
@@ -2367,6 +2416,8 @@
__extends(Throw, _super);
+ Throw.__name__ = 'Throw';
+
function Throw(expression) {
this.expression = expression;
}
@@ -2391,6 +2442,8 @@
__extends(Existence, _super);
+ Existence.__name__ = 'Existence';
+
function Existence(expression) {
this.expression = expression;
}
@@ -2424,6 +2477,8 @@
__extends(Parens, _super);
+ Parens.__name__ = 'Parens';
+
function Parens(body) {
this.body = body;
}
@@ -2462,6 +2517,8 @@
__extends(For, _super);
+ For.__name__ = 'For';
+
function For(body, source) {
var _ref2;
this.source = source.source, this.guard = source.guard, this.step = source.step, this.name = source.name, this.index = source.index;
@@ -2619,6 +2676,8 @@
__extends(Switch, _super);
+ Switch.__name__ = 'Switch';
+
function Switch(subject, cases, otherwise) {
this.subject = subject;
this.cases = cases;
@@ -2704,6 +2763,8 @@
__extends(If, _super);
+ If.__name__ = 'If';
+
function If(condition, body, options) {
this.body = body;
if (options == null) {
View
2  lib/coffee-script/optparse.js
@@ -4,6 +4,8 @@
exports.OptionParser = OptionParser = (function() {
+ OptionParser.__name__ = 'OptionParser';
+
function OptionParser(rules, banner) {
this.banner = banner;
this.rules = buildRules(rules);
View
208 lib/coffee-script/parser.js
@@ -1,6 +1,5 @@
/* Jison generated parser */
var parser = (function(){
-undefined
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"Root":3,"Body":4,"Block":5,"TERMINATOR":6,"Line":7,"Expression":8,"Statement":9,"Return":10,"Comment":11,"STATEMENT":12,"Value":13,"Invocation":14,"Code":15,"Operation":16,"Assign":17,"If":18,"Try":19,"While":20,"For":21,"Switch":22,"Class":23,"Throw":24,"INDENT":25,"OUTDENT":26,"Identifier":27,"IDENTIFIER":28,"AlphaNumeric":29,"NUMBER":30,"STRING":31,"Literal":32,"JS":33,"REGEX":34,"DEBUGGER":35,"BOOL":36,"Assignable":37,"=":38,"AssignObj":39,"ObjAssignable":40,":":41,"ThisProperty":42,"RETURN":43,"HERECOMMENT":44,"PARAM_START":45,"ParamList":46,"PARAM_END":47,"FuncGlyph":48,"->":49,"=>":50,"OptComma":51,",":52,"Param":53,"ParamVar":54,"...":55,"Array":56,"Object":57,"Splat":58,"SimpleAssignable":59,"Accessor":60,"Parenthetical":61,"Range":62,"This":63,".":64,"?.":65,"::":66,"Index":67,"INDEX_START":68,"IndexValue":69,"INDEX_END":70,"INDEX_SOAK":71,"Slice":72,"{":73,"AssignList":74,"}":75,"CLASS":76,"EXTENDS":77,"OptFuncExist":78,"Arguments":79,"SUPER":80,"FUNC_EXIST":81,"CALL_START":82,"CALL_END":83,"ArgList":84,"THIS":85,"@":86,"[":87,"]":88,"RangeDots":89,"..":90,"Arg":91,"SimpleArgs":92,"TRY":93,"Catch":94,"FINALLY":95,"CATCH":96,"THROW":97,"(":98,")":99,"WhileSource":100,"WHILE":101,"WHEN":102,"UNTIL":103,"Loop":104,"LOOP":105,"ForBody":106,"FOR":107,"ForStart":108,"ForSource":109,"ForVariables":110,"OWN":111,"ForValue":112,"FORIN":113,"FOROF":114,"BY":115,"SWITCH":116,"Whens":117,"ELSE":118,"When":119,"LEADING_WHEN":120,"IfBlock":121,"IF":122,"POST_IF":123,"UNARY":124,"-":125,"+":126,"--":127,"++":128,"?":129,"MATH":130,"SHIFT":131,"COMPARE":132,"LOGIC":133,"RELATION":134,"COMPOUND_ASSIGN":135,"$accept":0,"$end":1},
@@ -77,7 +76,9 @@ break;
case 33:this.$ = (function () {
var val;
val = new yy.Literal($$[$0]);
- if ($$[$0] === 'undefined') val.isUndefined = true;
+ if ($$[$0] === 'undefined') {
+ val.isUndefined = true;
+ }
return val;
}());
break;
@@ -473,103 +474,190 @@ parseError: function parseError(str, hash) {
throw new Error(str);
},
parse: function parse(input) {
- var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
+ var self = this,
+ stack = [0],
+ vstack = [null], // semantic value stack
+ lstack = [], // location stack
+ table = this.table,
+ yytext = '',
+ yylineno = 0,
+ yyleng = 0,
+ recovering = 0,
+ TERROR = 2,
+ EOF = 1;
+
+ //this.reductionCount = this.shiftCount = 0;
+
this.lexer.setInput(input);
this.lexer.yy = this.yy;
this.yy.lexer = this.lexer;
- if (typeof this.lexer.yylloc == "undefined")
+ if (typeof this.lexer.yylloc == 'undefined')
this.lexer.yylloc = {};
var yyloc = this.lexer.yylloc;
lstack.push(yyloc);
- if (typeof this.yy.parseError === "function")
+
+ if (typeof this.yy.parseError === 'function')
this.parseError = this.yy.parseError;
- function popStack(n) {
- stack.length = stack.length - 2 * n;
+
+ function popStack (n) {
+ stack.length = stack.length - 2*n;
vstack.length = vstack.length - n;
lstack.length = lstack.length - n;
}
+
function lex() {
var token;
- token = self.lexer.lex() || 1;
- if (typeof token !== "number") {
+ token = self.lexer.lex() || 1; // $end = 1
+ // if token isn't its numeric value, convert
+ if (typeof token !== 'number') {
token = self.symbols_[token] || token;
}
return token;
}
- var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
+
+ var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
while (true) {
- state = stack[stack.length - 1];
+ // retreive state number from top of stack
+ state = stack[stack.length-1];
+
+ // use default actions if available
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol == null)
symbol = lex();
+ // read action for current state and first input
action = table[state] && table[state][symbol];
}
- if (typeof action === "undefined" || !action.length || !action[0]) {
+
+ // handle parse error
+ _handle_error:
+ if (typeof action === 'undefined' || !action.length || !action[0]) {
+
if (!recovering) {
+ // Report error
expected = [];
- for (p in table[state])
- if (this.terminals_[p] && p > 2) {
- expected.push("'" + this.terminals_[p] + "'");
- }
- var errStr = "";
+ for (p in table[state]) if (this.terminals_[p] && p > 2) {
+ expected.push("'"+this.terminals_[p]+"'");
+ }
+ var errStr = '';
if (this.lexer.showPosition) {
- errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + this.terminals_[symbol] + "'";
+ errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
} else {
- errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
+ errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
+ (symbol == 1 /*EOF*/ ? "end of input" :
+ ("'"+(this.terminals_[symbol] || symbol)+"'"));
}
- this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
+ this.parseError(errStr,
+ {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
}
- }
- if (action[0] instanceof Array && action.length > 1) {
- throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
- }
- switch (action[0]) {
- case 1:
- stack.push(symbol);
- vstack.push(this.lexer.yytext);
- lstack.push(this.lexer.yylloc);
- stack.push(action[1]);
- symbol = null;
- if (!preErrorSymbol) {
+
+ // just recovered from another error
+ if (recovering == 3) {
+ if (symbol == EOF) {
+ throw new Error(errStr || 'Parsing halted.');
+ }
+
+ // discard current lookahead and grab another
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
- if (recovering > 0)
- recovering--;
- } else {
- symbol = preErrorSymbol;
- preErrorSymbol = null;
- }
- break;
- case 2:
- len = this.productions_[action[1]][1];
- yyval.$ = vstack[vstack.length - len];
- yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
- r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
- if (typeof r !== "undefined") {
- return r;
+ symbol = lex();
}
- if (len) {
- stack = stack.slice(0, -1 * len * 2);
- vstack = vstack.slice(0, -1 * len);
- lstack = lstack.slice(0, -1 * len);
+
+ // try to recover from error
+ while (1) {
+ // check for error recovery rule in this state
+ if ((TERROR.toString()) in table[state]) {
+ break;
+ }
+ if (state == 0) {
+ throw new Error(errStr || 'Parsing halted.');
+ }
+ popStack(1);
+ state = stack[stack.length-1];
}
- stack.push(this.productions_[action[1]][0]);
- vstack.push(yyval.$);
- lstack.push(yyval._$);
- newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
- stack.push(newState);
- break;
- case 3:
- return true;
+
+ preErrorSymbol = symbol; // save the lookahead token
+ symbol = TERROR; // insert generic error symbol as new lookahead
+ state = stack[stack.length-1];
+ action = table[state] && table[state][TERROR];
+ recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
+ }
+
+ // this shouldn't happen, unless resolve defaults are off
+ if (action[0] instanceof Array && action.length > 1) {
+ throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
}
+
+ switch (action[0]) {
+
+ case 1: // shift
+ //this.shiftCount++;
+
+ stack.push(symbol);
+ vstack.push(this.lexer.yytext);
+ lstack.push(this.lexer.yylloc);
+ stack.push(action[1]); // push state
+ symbol = null;
+ if (!preErrorSymbol) { // normal execution/no error
+ yyleng = this.lexer.yyleng;
+ yytext = this.lexer.yytext;
+ yylineno = this.lexer.yylineno;
+ yyloc = this.lexer.yylloc;
+ if (recovering > 0)
+ recovering--;
+ } else { // error just occurred, resume old lookahead f/ before error
+ symbol = preErrorSymbol;
+ preErrorSymbol = null;
+ }
+ break;
+
+ case 2: // reduce
+ //this.reductionCount++;
+
+ len = this.productions_[action[1]][1];
+
+ // perform semantic action
+ yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
+ // default location, uses first token for firsts, last for lasts
+ yyval._$ = {
+ first_line: lstack[lstack.length-(len||1)].first_line,
+ last_line: lstack[lstack.length-1].last_line,
+ first_column: lstack[lstack.length-(len||1)].first_column,
+ last_column: lstack[lstack.length-1].last_column
+ };
+ r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
+
+ if (typeof r !== 'undefined') {
+ return r;
+ }
+
+ // pop off stack
+ if (len) {
+ stack = stack.slice(0,-1*len*2);
+ vstack = vstack.slice(0, -1*len);
+ lstack = lstack.slice(0, -1*len);
+ }
+
+ stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
+ vstack.push(yyval.$);
+ lstack.push(yyval._$);
+ // goto new state = table[STATE][NONTERMINAL]
+ newState = table[stack[stack.length-2]][stack[stack.length-1]];
+ stack.push(newState);
+ break;
+
+ case 3: // accept
+ return true;
+ }
+
}
+
return true;
-}
-};
+}};
+undefined
return parser;
})();
if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
View
2  lib/coffee-script/rewriter.js
@@ -6,6 +6,8 @@
exports.Rewriter = (function() {
+ Rewriter.__name__ = 'Rewriter';
+
function Rewriter() {}
Rewriter.prototype.rewrite = function(tokens) {
View
2  lib/coffee-script/scope.js
@@ -6,6 +6,8 @@
exports.Scope = Scope = (function() {
+ Scope.__name__ = 'Scope';
+
Scope.root = null;
function Scope(parent, expressions, method) {
View
2  src/nodes.coffee
@@ -966,6 +966,8 @@ exports.Class = class Class extends Base
@ensureConstructor name
@body.spaced = yes
@body.expressions.unshift @ctor unless @ctor instanceof Code
+ if decl
+ @body.expressions.unshift new Assign (new Value (new Literal name), [new Access new Literal '__name__']), (new Literal "'#{name}'")
@body.expressions.push lname
@body.expressions.unshift @directives...
@addBoundFunctions o
View
8 test/classes.coffee
@@ -676,3 +676,11 @@ test "#2052: classes should work in strict mode", ->
class A
catch e
ok no
+
+test '#494: Named classes', ->
+ class A
+ eq A.__name__, 'A'
+ class A.B
+ eq A.B.__name__, 'B'
+ class A.B['C']
+ ok A.B.C.__name__ isnt 'C'
Something went wrong with that request. Please try again.