From 2b6e706b96c292763bb7dc2a8edea5e6485f3358 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Sun, 27 Nov 2016 21:34:19 +0000 Subject: [PATCH 01/32] Compile classes to ES2015 classes Rather than compiling classes to named functions with prototype and class assignments, they are now compiled to ES2015 class declarations. Backwards compatibility has been maintained by compiling ES2015- incompatible properties as prototype or class assignments. `super` continues to be compiled as before. Where possible, classes will be compiled "bare", without an enclosing IIFE. This is possible when the class contains only ES2015 compatible expressions (methods and static methods), and has no parent (this last constraint is a result of the legacy `super` compilation, and could be removed once ES2015 `super` is being used). Classes are still assigned to variables to maintain compatibility for assigned class expressions. There are a few changes to existing functionality that could break backwards compatibility: - Derived constructors that deliberately don't call `super` are no longer possible. ES2015 derived classes can't use `this` unless the parent constructor has been called, so it's now called implicitly when not present. - As a consequence of the above, derived constructors with @ parameters or bound methods and explicit `super` calls are not allowed. The implicit `super` must be used in these cases. --- lib/coffee-script/lexer.js | 128 +- lib/coffee-script/nodes.js | 5984 +++++++++++++++++--------------- lib/coffee-script/optparse.js | 16 +- lib/coffee-script/rewriter.js | 809 ++--- lib/coffee-script/scope.js | 56 +- lib/coffee-script/sourcemap.js | 187 +- src/nodes.coffee | 497 ++- test/assignment.coffee | 2 +- test/classes.coffee | 44 +- test/comments.coffee | 12 +- test/modules.coffee | 2 +- 11 files changed, 4170 insertions(+), 3567 deletions(-) diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index 18376fc4ae..663fd858ee 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -7,10 +7,8 @@ ref1 = require('./helpers'), count = ref1.count, starts = ref1.starts, compact = ref1.compact, repeat = ref1.repeat, invertLiterate = ref1.invertLiterate, locationDataToString = ref1.locationDataToString, throwSyntaxError = ref1.throwSyntaxError; - exports.Lexer = Lexer = (function() { - function Lexer() {} - - Lexer.prototype.tokenize = function(code, opts = {}) { + exports.Lexer = Lexer = class Lexer { + tokenize(code, opts = {}) { var consumed, end, i, ref2; this.literate = opts.literate; this.indent = 0; @@ -48,9 +46,9 @@ return this.tokens; } return (new Rewriter).rewrite(this.tokens); - }; + } - Lexer.prototype.clean = function(code) { + clean(code) { if (code.charCodeAt(0) === BOM) { code = code.slice(1); } @@ -63,9 +61,9 @@ code = invertLiterate(code); } return code; - }; + } - Lexer.prototype.identifierToken = function() { + identifierToken() { var alias, colon, colonOffset, id, idLength, input, match, poppedToken, prev, ref2, ref3, ref4, ref5, ref6, ref7, tag, tagToken; if (!(match = IDENTIFIER.exec(this.chunk))) { return 0; @@ -176,9 +174,9 @@ this.token(':', ':', colonOffset, colon.length); } return input.length; - }; + } - Lexer.prototype.numberToken = function() { + numberToken() { var base, lexedLength, match, number, numberValue, tag; if (!(match = NUMBER.exec(this.chunk))) { return 0; @@ -222,9 +220,9 @@ tag = numberValue === 2e308 ? 'INFINITY' : 'NUMBER'; this.token(tag, number, 0, lexedLength); return lexedLength; - }; + } - Lexer.prototype.stringToken = function() { + stringToken() { var $, attempt, delimiter, doc, end, heredoc, i, indent, indentRegex, match, quote, ref2, ref3, regex, token, tokens; quote = (STRING_START.exec(this.chunk) || [])[0]; if (!quote) { @@ -302,9 +300,9 @@ }); } return end; - }; + } - Lexer.prototype.commentToken = function() { + commentToken() { var comment, here, match; if (!(match = this.chunk.match(COMMENT))) { return 0; @@ -323,9 +321,9 @@ this.token('HERECOMMENT', here, 0, comment.length); } return comment.length; - }; + } - Lexer.prototype.jsToken = function() { + jsToken() { var match, script; if (!(this.chunk.charAt(0) === '`' && (match = HERE_JSTOKEN.exec(this.chunk) || JSTOKEN.exec(this.chunk)))) { return 0; @@ -335,9 +333,9 @@ }); this.token('JS', script, 0, match[0].length); return match[0].length; - }; + } - Lexer.prototype.regexToken = function() { + regexToken() { var body, closed, end, flags, index, match, origin, prev, ref2, ref3, ref4, regex, tokens; switch (false) { case !(match = REGEX_ILLEGAL.exec(this.chunk)): @@ -406,9 +404,9 @@ this.token('REGEX_END', ')', end, 0); } return end; - }; + } - Lexer.prototype.lineToken = function() { + lineToken() { var diff, indent, match, minLiteralLength, newIndentLiteral, noNewlines, size; if (!(match = MULTI_DENT.exec(this.chunk))) { return 0; @@ -468,9 +466,9 @@ this.outdentToken(this.indent - size, noNewlines, indent.length); } return indent.length; - }; + } - Lexer.prototype.outdentToken = function(moveOut, noNewlines, outdentLength) { + outdentToken(moveOut, noNewlines, outdentLength) { var decreasedIndent, dent, lastIndent, ref2; decreasedIndent = this.indent - moveOut; while (moveOut > 0) { @@ -507,9 +505,9 @@ this.indent = decreasedIndent; this.indentLiteral = this.indentLiteral.slice(0, decreasedIndent); return this; - }; + } - Lexer.prototype.whitespaceToken = function() { + whitespaceToken() { var match, nline, prev, ref2; if (!((match = WHITESPACE.exec(this.chunk)) || (nline = this.chunk.charAt(0) === '\n'))) { return 0; @@ -523,9 +521,9 @@ } else { return 0; } - }; + } - Lexer.prototype.newlineToken = function(offset) { + newlineToken(offset) { while (this.value() === ';') { this.tokens.pop(); } @@ -533,16 +531,16 @@ this.token('TERMINATOR', '\n', offset, 0); } return this; - }; + } - Lexer.prototype.suppressNewlines = function() { + suppressNewlines() { if (this.value() === '\\') { this.tokens.pop(); } return this; - }; + } - Lexer.prototype.literalToken = function() { + literalToken() { var match, message, origin, prev, ref2, ref3, ref4, ref5, ref6, skipToken, tag, token, value; if (match = OPERATOR.exec(this.chunk)) { value = match[0]; @@ -628,9 +626,9 @@ } this.tokens.push(token); return value.length; - }; + } - Lexer.prototype.tagParameters = function() { + tagParameters() { var i, stack, tok, tokens; if (this.tag() !== ')') { return this; @@ -657,13 +655,13 @@ } } return this; - }; + } - Lexer.prototype.closeIndentation = function() { + closeIndentation() { return this.outdentToken(this.indent); - }; + } - Lexer.prototype.matchWithInterpolations = function(regex, delimiter) { + matchWithInterpolations(regex, delimiter) { var close, column, firstToken, index, lastToken, line, nested, offsetInChunk, open, ref2, ref3, ref4, str, strPart, tokens; tokens = []; offsetInChunk = delimiter.length; @@ -721,9 +719,9 @@ tokens: tokens, index: offsetInChunk + delimiter.length }; - }; + } - Lexer.prototype.mergeInterpolationTokens = function(tokens, options, fn) { + mergeInterpolationTokens(tokens, options, fn) { var converted, firstEmptyStringIndex, firstIndex, i, j, lastToken, len, locationToken, lparen, plusToken, rparen, tag, token, tokensToPush, value; if (tokens.length > 1) { lparen = this.token('STRING_START', '(', 0, 0); @@ -786,9 +784,9 @@ last_column: lastToken[2].last_column }; } - }; + } - Lexer.prototype.pair = function(tag) { + pair(tag) { var lastIndent, prev, ref2, ref3, wanted; ref2 = this.ends, prev = ref2[ref2.length - 1]; if (tag !== (wanted = prev != null ? prev.tag : void 0)) { @@ -800,9 +798,9 @@ return this.pair(tag); } return this.ends.pop(); - }; + } - Lexer.prototype.getLineAndColumnFromChunk = function(offset) { + getLineAndColumnFromChunk(offset) { var column, lastLine, lineCount, ref2, string; if (offset === 0) { return [this.chunkLine, this.chunkColumn]; @@ -821,9 +819,9 @@ column += string.length; } return [this.chunkLine + lineCount, column]; - }; + } - Lexer.prototype.makeToken = function(tag, value, offsetInChunk = 0, length = value.length) { + makeToken(tag, value, offsetInChunk = 0, length = value.length) { var lastCharacter, locationData, ref2, ref3, token; locationData = {}; ref2 = this.getLineAndColumnFromChunk(offsetInChunk), locationData.first_line = ref2[0], locationData.first_column = ref2[1]; @@ -831,9 +829,9 @@ ref3 = this.getLineAndColumnFromChunk(offsetInChunk + lastCharacter), locationData.last_line = ref3[0], locationData.last_column = ref3[1]; token = [tag, value, locationData]; return token; - }; + } - Lexer.prototype.token = function(tag, value, offsetInChunk, length, origin) { + token(tag, value, offsetInChunk, length, origin) { var token; token = this.makeToken(tag, value, offsetInChunk, length); if (origin) { @@ -841,34 +839,34 @@ } this.tokens.push(token); return token; - }; + } - Lexer.prototype.tag = function() { + tag() { var ref2, token; ref2 = this.tokens, token = ref2[ref2.length - 1]; return token != null ? token[0] : void 0; - }; + } - Lexer.prototype.value = function() { + value() { var ref2, token; ref2 = this.tokens, token = ref2[ref2.length - 1]; return token != null ? token[1] : void 0; - }; + } - Lexer.prototype.unfinished = function() { + unfinished() { var ref2; return LINE_CONTINUER.test(this.chunk) || ((ref2 = this.tag()) === '\\' || ref2 === '.' || ref2 === '?.' || ref2 === '?::' || ref2 === 'UNARY' || ref2 === 'MATH' || ref2 === 'UNARY_MATH' || ref2 === '+' || ref2 === '-' || ref2 === '**' || ref2 === 'SHIFT' || ref2 === 'RELATION' || ref2 === 'COMPARE' || ref2 === '&' || ref2 === '^' || ref2 === '|' || ref2 === '&&' || ref2 === '||' || ref2 === 'BIN?' || ref2 === 'THROW' || ref2 === 'EXTENDS'); - }; + } - Lexer.prototype.formatString = function(str) { + formatString(str) { return str.replace(STRING_OMIT, '$1'); - }; + } - Lexer.prototype.formatHeregex = function(str) { + formatHeregex(str) { return str.replace(HEREGEX_OMIT, '$1$2'); - }; + } - Lexer.prototype.validateEscapes = function(str, options = {}) { + validateEscapes(str, options = {}) { var before, hex, invalidEscape, match, message, octal, ref2, unicode; match = INVALID_ESCAPE.exec(str); if (!match) { @@ -884,9 +882,9 @@ offset: ((ref2 = options.offsetInChunk) != null ? ref2 : 0) + match.index + before.length, length: invalidEscape.length }); - }; + } - Lexer.prototype.makeDelimitedLiteral = function(body, options = {}) { + makeDelimitedLiteral(body, options = {}) { var regex; if (body === '' && options.delimiter === '/') { body = '(?:)'; @@ -921,9 +919,9 @@ } }); return `${options.delimiter}${body}${options.delimiter}`; - }; + } - Lexer.prototype.error = function(message, options = {}) { + error(message, options = {}) { var first_column, first_line, location, ref2, ref3, ref4; location = 'first_line' in options ? options : ((ref3 = this.getLineAndColumnFromChunk((ref2 = options.offset) != null ? ref2 : 0), first_line = ref3[0], first_column = ref3[1], ref3), { first_line: first_line, @@ -931,11 +929,9 @@ last_column: first_column + ((ref4 = options.length) != null ? ref4 : 1) - 1 }); return throwSyntaxError(message, location); - }; - - return Lexer; + } - })(); + }; isUnassignable = function(name, displayName = name) { switch (false) { diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 5635202f05..b5d70ebecf 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1,8 +1,6 @@ // Generated by CoffeeScript 2.0.0-alpha (function() { - var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility, - extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty, + var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Class, ClassDeclaration, Code, CodeFragment, Comment, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, slice = [].slice; @@ -35,21 +33,19 @@ return this; }; - exports.CodeFragment = CodeFragment = (function() { - function CodeFragment(parent, code) { + exports.CodeFragment = CodeFragment = class CodeFragment { + constructor(parent, code) { var ref3; this.code = `${code}`; this.locationData = parent != null ? parent.locationData : void 0; this.type = (parent != null ? (ref3 = parent.constructor) != null ? ref3.name : void 0 : void 0) || 'unknown'; } - CodeFragment.prototype.toString = function() { + toString() { return `${this.code}${(this.locationData ? ": " + locationDataToString(this.locationData) : '')}`; - }; - - return CodeFragment; + } - })(); + }; fragmentsToText = function(fragments) { var fragment; @@ -65,166 +61,217 @@ }; exports.Base = Base = (function() { - function Base() {} - - Base.prototype.compile = function(o, lvl) { - return fragmentsToText(this.compileToFragments(o, lvl)); - }; - - Base.prototype.compileToFragments = function(o, lvl) { - var node; - o = extend({}, o); - if (lvl) { - o.level = lvl; + class Base { + compile(o, lvl) { + return fragmentsToText(this.compileToFragments(o, lvl)); } - node = this.unfoldSoak(o) || this; - node.tab = o.indent; - if (o.level === LEVEL_TOP || !node.isStatement(o)) { - return node.compileNode(o); - } else { - return node.compileClosure(o); - } - }; - Base.prototype.compileClosure = function(o) { - var args, argumentsNode, func, jumpNode, meth, parts, ref3, ref4; - if (jumpNode = this.jumps()) { - jumpNode.error('cannot use a pure statement in an expression'); - } - o.sharedScope = true; - func = new Code([], Block.wrap([this])); - args = []; - if ((argumentsNode = this.contains(isLiteralArguments)) || this.contains(isLiteralThis)) { - args = [new ThisLiteral]; - if (argumentsNode) { - meth = 'apply'; - args.push(new IdentifierLiteral('arguments')); + compileToFragments(o, lvl) { + var node; + o = extend({}, o); + if (lvl) { + o.level = lvl; + } + node = this.unfoldSoak(o) || this; + node.tab = o.indent; + if (o.level === LEVEL_TOP || !node.isStatement(o)) { + return node.compileNode(o); } else { - meth = 'call'; + return node.compileClosure(o); } - func = new Value(func, [new Access(new PropertyName(meth))]); } - parts = (new Call(func, args)).compileNode(o); - switch (false) { - case !(func.isGenerator || ((ref3 = func.base) != null ? ref3.isGenerator : void 0)): - parts.unshift(this.makeCode("(yield* ")); - parts.push(this.makeCode(")")); - break; - case !(func.isAsync || ((ref4 = func.base) != null ? ref4.isAsync : void 0)): - parts.unshift(this.makeCode("(await ")); - parts.push(this.makeCode(")")); + + compileClosure(o) { + var args, argumentsNode, func, jumpNode, meth, parts, ref3, ref4; + if (jumpNode = this.jumps()) { + jumpNode.error('cannot use a pure statement in an expression'); + } + o.sharedScope = true; + func = new Code([], Block.wrap([this])); + args = []; + if ((argumentsNode = this.contains(isLiteralArguments)) || this.contains(isLiteralThis)) { + args = [new ThisLiteral]; + if (argumentsNode) { + meth = 'apply'; + args.push(new IdentifierLiteral('arguments')); + } else { + meth = 'call'; + } + func = new Value(func, [new Access(new PropertyName(meth))]); + } + parts = (new Call(func, args)).compileNode(o); + switch (false) { + case !(func.isGenerator || ((ref3 = func.base) != null ? ref3.isGenerator : void 0)): + parts.unshift(this.makeCode("(yield* ")); + parts.push(this.makeCode(")")); + break; + case !(func.isAsync || ((ref4 = func.base) != null ? ref4.isAsync : void 0)): + parts.unshift(this.makeCode("(await ")); + parts.push(this.makeCode(")")); + } + return parts; } - return parts; - }; - Base.prototype.cache = function(o, level, isComplex) { - var complex, ref, sub; - complex = isComplex != null ? isComplex(this) : this.isComplex(); - if (complex) { - ref = new IdentifierLiteral(o.scope.freeVariable('ref')); - sub = new Assign(ref, this); - if (level) { - return [sub.compileToFragments(o, level), [this.makeCode(ref.value)]]; + cache(o, level, isComplex) { + var complex, ref, sub; + complex = isComplex != null ? isComplex(this) : this.isComplex(); + if (complex) { + ref = new IdentifierLiteral(o.scope.freeVariable('ref')); + sub = new Assign(ref, this); + if (level) { + return [sub.compileToFragments(o, level), [this.makeCode(ref.value)]]; + } else { + return [sub, ref]; + } } else { - return [sub, ref]; + ref = level ? this.compileToFragments(o, level) : this; + return [ref, ref]; } - } else { - ref = level ? this.compileToFragments(o, level) : this; - return [ref, ref]; } - }; - Base.prototype.cacheToCodeFragments = function(cacheValues) { - return [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]; - }; + hoist() { + var compileNode, compileToFragments, target; + this.hoisted = true; + target = new HoistTarget(this); + compileNode = this.compileNode; + compileToFragments = this.compileToFragments; + this.compileNode = function(o) { + return target.update(compileNode, o); + }; + this.compileToFragments = function(o) { + return target.update(compileToFragments, o); + }; + return target; + } - Base.prototype.makeReturn = function(res) { - var me; - me = this.unwrapAll(); - if (res) { - return new Call(new Literal(`${res}.push`), [me]); - } else { - return new Return(me); + cacheToCodeFragments(cacheValues) { + return [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]; } - }; - Base.prototype.contains = function(pred) { - var node; - node = void 0; - this.traverseChildren(false, function(n) { - if (pred(n)) { - node = n; - return false; + makeReturn(res) { + var me; + me = this.unwrapAll(); + if (res) { + return new Call(new Literal(`${res}.push`), [me]); + } else { + return new Return(me); } - }); - return node; - }; + } + + contains(pred) { + var node; + node = void 0; + this.traverseChildren(false, function(n) { + if (pred(n)) { + node = n; + return false; + } + }); + return node; + } - Base.prototype.lastNonComment = function(list) { - var i; - i = list.length; - while (i--) { - if (!(list[i] instanceof Comment)) { - return list[i]; + lastNonComment(list) { + var i; + i = list.length; + while (i--) { + if (!(list[i] instanceof Comment)) { + return list[i]; + } } + return null; } - return null; - }; - Base.prototype.toString = function(idt = '', name = this.constructor.name) { - var tree; - tree = '\n' + idt + name; - if (this.soak) { - tree += '?'; + toString(idt = '', name = this.constructor.name) { + var tree; + tree = '\n' + idt + name; + if (this.soak) { + tree += '?'; + } + this.eachChild(function(node) { + return tree += node.toString(idt + TAB); + }); + return tree; } - this.eachChild(function(node) { - return tree += node.toString(idt + TAB); - }); - return tree; - }; - Base.prototype.eachChild = function(func) { - var attr, child, j, k, len1, len2, ref3, ref4; - if (!this.children) { + eachChild(func) { + var attr, child, j, k, len1, len2, ref3, ref4; + if (!this.children) { + return this; + } + ref3 = this.children; + for (j = 0, len1 = ref3.length; j < len1; j++) { + attr = ref3[j]; + if (this[attr]) { + ref4 = flatten([this[attr]]); + for (k = 0, len2 = ref4.length; k < len2; k++) { + child = ref4[k]; + if (func(child) === false) { + return this; + } + } + } + } return this; } - ref3 = this.children; - for (j = 0, len1 = ref3.length; j < len1; j++) { - attr = ref3[j]; - if (this[attr]) { - ref4 = flatten([this[attr]]); - for (k = 0, len2 = ref4.length; k < len2; k++) { - child = ref4[k]; - if (func(child) === false) { - return this; - } + + traverseChildren(crossScope, func) { + return this.eachChild(function(child) { + var recur; + recur = func(child); + if (recur !== false) { + return child.traverseChildren(crossScope, func); } + }); + } + + invert() { + return new Op('!', this); + } + + unwrapAll() { + var node; + node = this; + while (node !== (node = node.unwrap())) { + continue; } + return node; } - return this; - }; - Base.prototype.traverseChildren = function(crossScope, func) { - return this.eachChild(function(child) { - var recur; - recur = func(child); - if (recur !== false) { - return child.traverseChildren(crossScope, func); + updateLocationDataIfMissing(locationData) { + if (this.locationData) { + return this; } - }); - }; + this.locationData = locationData; + return this.eachChild(function(child) { + return child.updateLocationDataIfMissing(locationData); + }); + } - Base.prototype.invert = function() { - return new Op('!', this); - }; + error(message) { + return throwSyntaxError(message, this.locationData); + } + + makeCode(code) { + return new CodeFragment(this, code); + } + + wrapInBraces(fragments) { + return [].concat(this.makeCode('('), fragments, this.makeCode(')')); + } - Base.prototype.unwrapAll = function() { - var node; - node = this; - while (node !== (node = node.unwrap())) { - continue; + joinFragmentArrays(fragmentsList, joinStr) { + var answer, fragments, i, j, len1; + answer = []; + for (i = j = 0, len1 = fragmentsList.length; j < len1; i = ++j) { + fragments = fragmentsList[i]; + if (i) { + answer.push(this.makeCode(joinStr)); + } + answer = answer.concat(fragments); + } + return answer; } - return node; + }; Base.prototype.children = []; @@ -247,383 +294,403 @@ Base.prototype.assigns = NO; - Base.prototype.updateLocationDataIfMissing = function(locationData) { - if (this.locationData) { - return this; - } - this.locationData = locationData; - return this.eachChild(function(child) { - return child.updateLocationDataIfMissing(locationData); - }); - }; - - Base.prototype.error = function(message) { - return throwSyntaxError(message, this.locationData); - }; - - Base.prototype.makeCode = function(code) { - return new CodeFragment(this, code); - }; + return Base; - Base.prototype.wrapInBraces = function(fragments) { - return [].concat(this.makeCode('('), fragments, this.makeCode(')')); - }; + })(); - Base.prototype.joinFragmentArrays = function(fragmentsList, joinStr) { - var answer, fragments, i, j, len1; - answer = []; - for (i = j = 0, len1 = fragmentsList.length; j < len1; i = ++j) { - fragments = fragmentsList[i]; - if (i) { - answer.push(this.makeCode(joinStr)); + exports.HoistTarget = HoistTarget = (function(superClass) { + class HoistTarget extends superClass { + static expand(fragments) { + var fragment, i, j, ref3; + for (i = j = fragments.length - 1; j >= 0; i = j += -1) { + fragment = fragments[i]; + if (fragment.fragments) { + [].splice.apply(fragments, [i, i - i + 1].concat(ref3 = fragment.fragments)), ref3; + } } - answer = answer.concat(fragments); + return fragments; } - return answer; - }; - return Base; + constructor(source1) { + super(...arguments); + this.source = source1; + this.options = {}; + this.targetFragments = { + fragments: [] + }; + } - })(); + isStatement(o) { + return this.source.isStatement(o); + } + + update(compile, o) { + return this.targetFragments.fragments = compile.call(this.source, merge(o, this.options)); + } - exports.Block = Block = (function(superClass1) { - extend1(Block, superClass1); + compileToFragments(o, level) { + this.options.indent = o.indent; + this.options.level = level != null ? level : o.level; + return [this.targetFragments]; + } - function Block(nodes) { - this.expressions = compact(flatten(nodes || [])); - } + compileNode(o) { + return this.compileToFragments(o); + } - Block.prototype.children = ['expressions']; + compileClosure(o) { + return this.compileToFragments(o); + } - Block.prototype.push = function(node) { - this.expressions.push(node); - return this; }; - Block.prototype.pop = function() { - return this.expressions.pop(); - }; + HoistTarget.__super__ = superClass.prototype; - Block.prototype.unshift = function(node) { - this.expressions.unshift(node); - return this; - }; + return HoistTarget; - Block.prototype.unwrap = function() { - if (this.expressions.length === 1) { - return this.expressions[0]; - } else { + })(Base); + + exports.Block = Block = (function(superClass) { + class Block extends superClass { + constructor(nodes) { + super(...arguments); + this.expressions = compact(flatten(nodes || [])); + } + + push(node) { + this.expressions.push(node); return this; } - }; - Block.prototype.isEmpty = function() { - return !this.expressions.length; - }; + pop() { + return this.expressions.pop(); + } - Block.prototype.isStatement = function(o) { - var exp, j, len1, ref3; - ref3 = this.expressions; - for (j = 0, len1 = ref3.length; j < len1; j++) { - exp = ref3[j]; - if (exp.isStatement(o)) { - return true; + unshift(node) { + this.expressions.unshift(node); + return this; + } + + unwrap() { + if (this.expressions.length === 1) { + return this.expressions[0]; + } else { + return this; } } - return false; - }; - Block.prototype.jumps = function(o) { - var exp, j, jumpNode, len1, ref3; - ref3 = this.expressions; - for (j = 0, len1 = ref3.length; j < len1; j++) { - exp = ref3[j]; - if (jumpNode = exp.jumps(o)) { - return jumpNode; + isEmpty() { + return !this.expressions.length; + } + + isStatement(o) { + var exp, j, len1, ref3; + ref3 = this.expressions; + for (j = 0, len1 = ref3.length; j < len1; j++) { + exp = ref3[j]; + if (exp.isStatement(o)) { + return true; + } } + return false; } - }; - Block.prototype.makeReturn = function(res) { - var expr, len; - len = this.expressions.length; - while (len--) { - expr = this.expressions[len]; - if (!(expr instanceof Comment)) { - this.expressions[len] = expr.makeReturn(res); - if (expr instanceof Return && !expr.expression) { - this.expressions.splice(len, 1); + jumps(o) { + var exp, j, jumpNode, len1, ref3; + ref3 = this.expressions; + for (j = 0, len1 = ref3.length; j < len1; j++) { + exp = ref3[j]; + if (jumpNode = exp.jumps(o)) { + return jumpNode; } - break; } } - return this; - }; - Block.prototype.compileToFragments = function(o = {}, level) { - if (o.scope) { - return Block.__super__.compileToFragments.call(this, o, level); - } else { - return this.compileRoot(o); + makeReturn(res) { + var expr, len; + len = this.expressions.length; + while (len--) { + expr = this.expressions[len]; + if (!(expr instanceof Comment)) { + this.expressions[len] = expr.makeReturn(res); + if (expr instanceof Return && !expr.expression) { + this.expressions.splice(len, 1); + } + break; + } + } + return this; } - }; - Block.prototype.compileNode = function(o) { - var answer, compiledNodes, fragments, index, j, len1, node, ref3, top; - this.tab = o.indent; - top = o.level === LEVEL_TOP; - compiledNodes = []; - ref3 = this.expressions; - for (index = j = 0, len1 = ref3.length; j < len1; index = ++j) { - node = ref3[index]; - node = node.unwrapAll(); - node = node.unfoldSoak(o) || node; - if (node instanceof Block) { - compiledNodes.push(node.compileNode(o)); - } else if (top) { - node.front = true; - fragments = node.compileToFragments(o); - if (!node.isStatement(o)) { - fragments.unshift(this.makeCode(`${this.tab}`)); - fragments.push(this.makeCode(";")); - } - compiledNodes.push(fragments); + compileToFragments(o = {}, level) { + if (o.scope) { + return Block.__super__.compileToFragments.call(this, o, level); } else { - compiledNodes.push(node.compileToFragments(o, LEVEL_LIST)); + return this.compileRoot(o); } } - if (top) { - if (this.spaced) { - return [].concat(this.joinFragmentArrays(compiledNodes, '\n\n'), this.makeCode("\n")); + + compileNode(o) { + var answer, compiledNodes, fragments, index, j, len1, node, ref3, top; + this.tab = o.indent; + top = o.level === LEVEL_TOP; + compiledNodes = []; + ref3 = this.expressions; + for (index = j = 0, len1 = ref3.length; j < len1; index = ++j) { + node = ref3[index]; + node = node.unwrapAll(); + node = node.unfoldSoak(o) || node; + if (node instanceof Block) { + compiledNodes.push(node.compileNode(o)); + } else if (node.hoisted) { + node.compileToFragments(o); + } else if (top) { + node.front = true; + fragments = node.compileToFragments(o); + if (!node.isStatement(o)) { + fragments.unshift(this.makeCode(`${this.tab}`)); + fragments.push(this.makeCode(";")); + } + compiledNodes.push(fragments); + } else { + compiledNodes.push(node.compileToFragments(o, LEVEL_LIST)); + } + } + if (top) { + if (this.spaced) { + return [].concat(this.joinFragmentArrays(compiledNodes, '\n\n'), this.makeCode("\n")); + } else { + return this.joinFragmentArrays(compiledNodes, '\n'); + } + } + if (compiledNodes.length) { + answer = this.joinFragmentArrays(compiledNodes, ', '); } else { - return this.joinFragmentArrays(compiledNodes, '\n'); + answer = [this.makeCode("void 0")]; + } + if (compiledNodes.length > 1 && o.level >= LEVEL_LIST) { + return this.wrapInBraces(answer); + } else { + return answer; } } - if (compiledNodes.length) { - answer = this.joinFragmentArrays(compiledNodes, ', '); - } else { - answer = [this.makeCode("void 0")]; - } - if (compiledNodes.length > 1 && o.level >= LEVEL_LIST) { - return this.wrapInBraces(answer); - } else { - return answer; - } - }; - Block.prototype.compileRoot = function(o) { - var exp, fragments, i, j, len1, name, prelude, preludeExps, ref3, ref4, rest; - o.indent = o.bare ? '' : TAB; - o.level = LEVEL_TOP; - this.spaced = true; - o.scope = new Scope(null, this, null, (ref3 = o.referencedVars) != null ? ref3 : []); - ref4 = o.locals || []; - for (j = 0, len1 = ref4.length; j < len1; j++) { - name = ref4[j]; - o.scope.parameter(name); - } - prelude = []; - if (!o.bare) { - preludeExps = (function() { - var k, len2, ref5, results; - ref5 = this.expressions; - results = []; - for (i = k = 0, len2 = ref5.length; k < len2; i = ++k) { - exp = ref5[i]; - if (!(exp.unwrap() instanceof Comment)) { - break; + compileRoot(o) { + var exp, fragments, i, j, len1, name, prelude, preludeExps, ref3, ref4, rest; + o.indent = o.bare ? '' : TAB; + o.level = LEVEL_TOP; + this.spaced = true; + o.scope = new Scope(null, this, null, (ref3 = o.referencedVars) != null ? ref3 : []); + ref4 = o.locals || []; + for (j = 0, len1 = ref4.length; j < len1; j++) { + name = ref4[j]; + o.scope.parameter(name); + } + prelude = []; + if (!o.bare) { + preludeExps = (function() { + var k, len2, ref5, results; + ref5 = this.expressions; + results = []; + for (i = k = 0, len2 = ref5.length; k < len2; i = ++k) { + exp = ref5[i]; + if (!(exp.unwrap() instanceof Comment)) { + break; + } + results.push(exp); } - results.push(exp); + return results; + }).call(this); + rest = this.expressions.slice(preludeExps.length); + this.expressions = preludeExps; + if (preludeExps.length) { + prelude = this.compileNode(merge(o, { + indent: '' + })); + prelude.push(this.makeCode("\n")); } - return results; - }).call(this); - rest = this.expressions.slice(preludeExps.length); - this.expressions = preludeExps; - if (preludeExps.length) { - prelude = this.compileNode(merge(o, { - indent: '' - })); - prelude.push(this.makeCode("\n")); + this.expressions = rest; } - this.expressions = rest; - } - fragments = this.compileWithDeclarations(o); - if (o.bare) { - return fragments; + fragments = this.compileWithDeclarations(o); + HoistTarget.expand(fragments); + if (o.bare) { + return fragments; + } + return [].concat(prelude, this.makeCode("(function() {\n"), fragments, this.makeCode("\n}).call(this);\n")); } - return [].concat(prelude, this.makeCode("(function() {\n"), fragments, this.makeCode("\n}).call(this);\n")); - }; - Block.prototype.compileWithDeclarations = function(o) { - var assigns, declars, exp, fragments, i, j, len1, post, ref3, ref4, ref5, rest, scope, spaced; - fragments = []; - post = []; - ref3 = this.expressions; - for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { - exp = ref3[i]; - exp = exp.unwrap(); - if (!(exp instanceof Comment || exp instanceof Literal)) { - break; - } - } - o = merge(o, { - level: LEVEL_TOP - }); - if (i) { - rest = this.expressions.splice(i, 9e9); - ref4 = [this.spaced, false], spaced = ref4[0], this.spaced = ref4[1]; - ref5 = [this.compileNode(o), spaced], fragments = ref5[0], this.spaced = ref5[1]; - this.expressions = rest; - } - post = this.compileNode(o); - scope = o.scope; - if (scope.expressions === this) { - declars = o.scope.hasDeclarations(); - assigns = scope.hasAssignments; - if (declars || assigns) { - if (i) { - fragments.push(this.makeCode('\n')); - } - fragments.push(this.makeCode(`${this.tab}var `)); - if (declars) { - fragments.push(this.makeCode(scope.declaredVariables().join(', '))); + compileWithDeclarations(o) { + var assigns, declars, exp, fragments, i, j, len1, post, ref3, ref4, ref5, rest, scope, spaced; + fragments = []; + post = []; + ref3 = this.expressions; + for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { + exp = ref3[i]; + exp = exp.unwrap(); + if (!(exp instanceof Comment || exp instanceof Literal)) { + break; } - if (assigns) { + } + o = merge(o, { + level: LEVEL_TOP + }); + if (i) { + rest = this.expressions.splice(i, 9e9); + ref4 = [this.spaced, false], spaced = ref4[0], this.spaced = ref4[1]; + ref5 = [this.compileNode(o), spaced], fragments = ref5[0], this.spaced = ref5[1]; + this.expressions = rest; + } + post = this.compileNode(o); + scope = o.scope; + if (scope.expressions === this) { + declars = o.scope.hasDeclarations(); + assigns = scope.hasAssignments; + if (declars || assigns) { + if (i) { + fragments.push(this.makeCode('\n')); + } + fragments.push(this.makeCode(`${this.tab}var `)); if (declars) { - fragments.push(this.makeCode(`,\n${this.tab + TAB}`)); + fragments.push(this.makeCode(scope.declaredVariables().join(', '))); + } + if (assigns) { + if (declars) { + fragments.push(this.makeCode(`,\n${this.tab + TAB}`)); + } + fragments.push(this.makeCode(scope.assignedVariables().join(`,\n${this.tab + TAB}`))); } - fragments.push(this.makeCode(scope.assignedVariables().join(`,\n${this.tab + TAB}`))); + fragments.push(this.makeCode(`;\n${(this.spaced ? '\n' : '')}`)); + } else if (fragments.length && post.length) { + fragments.push(this.makeCode("\n")); } - fragments.push(this.makeCode(`;\n${(this.spaced ? '\n' : '')}`)); - } else if (fragments.length && post.length) { - fragments.push(this.makeCode("\n")); } + return fragments.concat(post); } - return fragments.concat(post); - }; - Block.wrap = function(nodes) { - if (nodes.length === 1 && nodes[0] instanceof Block) { - return nodes[0]; + static wrap(nodes) { + if (nodes.length === 1 && nodes[0] instanceof Block) { + return nodes[0]; + } + return new Block(nodes); } - return new Block(nodes); + }; + Block.__super__ = superClass.prototype; + + Block.prototype.children = ['expressions']; + return Block; })(Base); - exports.Literal = Literal = (function(superClass1) { - extend1(Literal, superClass1); + exports.Literal = Literal = (function(superClass) { + class Literal extends superClass { + constructor(value1) { + super(...arguments); + this.value = value1; + } + + assigns(name) { + return name === this.value; + } - function Literal(value1) { - this.value = value1; - } + compileNode(o) { + return [this.makeCode(this.value)]; + } - Literal.prototype.isComplex = NO; + toString() { + return ` ${(this.isStatement() ? Literal.__super__.toString.call(this, ...arguments) : this.constructor.name)}: ${this.value}`; + } - Literal.prototype.assigns = function(name) { - return name === this.value; }; - Literal.prototype.compileNode = function(o) { - return [this.makeCode(this.value)]; - }; + Literal.__super__ = superClass.prototype; - Literal.prototype.toString = function() { - return ` ${(this.isStatement() ? Literal.__super__.toString.call(this, ...arguments) : this.constructor.name)}: ${this.value}`; - }; + Literal.prototype.isComplex = NO; return Literal; })(Base); - exports.NumberLiteral = NumberLiteral = (function(superClass1) { - extend1(NumberLiteral, superClass1); + exports.NumberLiteral = NumberLiteral = (function(superClass) { + class NumberLiteral extends superClass {}; - function NumberLiteral() { - return NumberLiteral.__super__.constructor.apply(this, arguments); - } + NumberLiteral.__super__ = superClass.prototype; return NumberLiteral; })(Literal); - exports.InfinityLiteral = InfinityLiteral = (function(superClass1) { - extend1(InfinityLiteral, superClass1); - - function InfinityLiteral() { - return InfinityLiteral.__super__.constructor.apply(this, arguments); - } + exports.InfinityLiteral = InfinityLiteral = (function(superClass) { + class InfinityLiteral extends superClass { + compileNode() { + return [this.makeCode('2e308')]; + } - InfinityLiteral.prototype.compileNode = function() { - return [this.makeCode('2e308')]; }; + InfinityLiteral.__super__ = superClass.prototype; + return InfinityLiteral; })(NumberLiteral); - exports.NaNLiteral = NaNLiteral = (function(superClass1) { - extend1(NaNLiteral, superClass1); - - function NaNLiteral() { - NaNLiteral.__super__.constructor.call(this, 'NaN'); - } + exports.NaNLiteral = NaNLiteral = (function(superClass) { + class NaNLiteral extends superClass { + constructor() { + super('NaN'); + } - NaNLiteral.prototype.compileNode = function(o) { - var code; - code = [this.makeCode('0/0')]; - if (o.level >= LEVEL_OP) { - return this.wrapInBraces(code); - } else { - return code; + compileNode(o) { + var code; + code = [this.makeCode('0/0')]; + if (o.level >= LEVEL_OP) { + return this.wrapInBraces(code); + } else { + return code; + } } + }; + NaNLiteral.__super__ = superClass.prototype; + return NaNLiteral; })(NumberLiteral); - exports.StringLiteral = StringLiteral = (function(superClass1) { - extend1(StringLiteral, superClass1); + exports.StringLiteral = StringLiteral = (function(superClass) { + class StringLiteral extends superClass {}; - function StringLiteral() { - return StringLiteral.__super__.constructor.apply(this, arguments); - } + StringLiteral.__super__ = superClass.prototype; return StringLiteral; })(Literal); - exports.RegexLiteral = RegexLiteral = (function(superClass1) { - extend1(RegexLiteral, superClass1); + exports.RegexLiteral = RegexLiteral = (function(superClass) { + class RegexLiteral extends superClass {}; - function RegexLiteral() { - return RegexLiteral.__super__.constructor.apply(this, arguments); - } + RegexLiteral.__super__ = superClass.prototype; return RegexLiteral; })(Literal); - exports.PassthroughLiteral = PassthroughLiteral = (function(superClass1) { - extend1(PassthroughLiteral, superClass1); + exports.PassthroughLiteral = PassthroughLiteral = (function(superClass) { + class PassthroughLiteral extends superClass {}; - function PassthroughLiteral() { - return PassthroughLiteral.__super__.constructor.apply(this, arguments); - } + PassthroughLiteral.__super__ = superClass.prototype; return PassthroughLiteral; })(Literal); - exports.IdentifierLiteral = IdentifierLiteral = (function(superClass1) { - extend1(IdentifierLiteral, superClass1); + exports.IdentifierLiteral = IdentifierLiteral = (function(superClass) { + class IdentifierLiteral extends superClass {}; - function IdentifierLiteral() { - return IdentifierLiteral.__super__.constructor.apply(this, arguments); - } + IdentifierLiteral.__super__ = superClass.prototype; IdentifierLiteral.prototype.isAssignable = YES; @@ -631,12 +698,10 @@ })(Literal); - exports.PropertyName = PropertyName = (function(superClass1) { - extend1(PropertyName, superClass1); + exports.PropertyName = PropertyName = (function(superClass) { + class PropertyName extends superClass {}; - function PropertyName() { - return PropertyName.__super__.constructor.apply(this, arguments); - } + PropertyName.__super__ = superClass.prototype; PropertyName.prototype.isAssignable = YES; @@ -644,94 +709,125 @@ })(Literal); - exports.StatementLiteral = StatementLiteral = (function(superClass1) { - extend1(StatementLiteral, superClass1); + exports.StatementLiteral = StatementLiteral = (function(superClass) { + class StatementLiteral extends superClass { + jumps(o) { + if (this.value === 'break' && !((o != null ? o.loop : void 0) || (o != null ? o.block : void 0))) { + return this; + } + if (this.value === 'continue' && !(o != null ? o.loop : void 0)) { + return this; + } + } - function StatementLiteral() { - return StatementLiteral.__super__.constructor.apply(this, arguments); - } + compileNode(o) { + return [this.makeCode(`${this.tab}${this.value};`)]; + } - StatementLiteral.prototype.isStatement = YES; + }; - StatementLiteral.prototype.makeReturn = THIS; + StatementLiteral.__super__ = superClass.prototype; - StatementLiteral.prototype.jumps = function(o) { - if (this.value === 'break' && !((o != null ? o.loop : void 0) || (o != null ? o.block : void 0))) { - return this; - } - if (this.value === 'continue' && !(o != null ? o.loop : void 0)) { - return this; - } - }; + StatementLiteral.prototype.isStatement = YES; - StatementLiteral.prototype.compileNode = function(o) { - return [this.makeCode(`${this.tab}${this.value};`)]; - }; + StatementLiteral.prototype.makeReturn = THIS; return StatementLiteral; })(Literal); - exports.ThisLiteral = ThisLiteral = (function(superClass1) { - extend1(ThisLiteral, superClass1); + exports.ThisLiteral = ThisLiteral = (function(superClass) { + class ThisLiteral extends superClass { + constructor() { + super('this'); + } - function ThisLiteral() { - ThisLiteral.__super__.constructor.call(this, 'this'); - } + compileNode(o) { + var code, ref3; + code = ((ref3 = o.scope.method) != null ? ref3.bound : void 0) ? o.scope.method.context : this.value; + return [this.makeCode(code)]; + } - ThisLiteral.prototype.compileNode = function(o) { - var code, ref3; - code = ((ref3 = o.scope.method) != null ? ref3.bound : void 0) ? o.scope.method.context : this.value; - return [this.makeCode(code)]; }; + ThisLiteral.__super__ = superClass.prototype; + return ThisLiteral; })(Literal); - exports.UndefinedLiteral = UndefinedLiteral = (function(superClass1) { - extend1(UndefinedLiteral, superClass1); + exports.UndefinedLiteral = UndefinedLiteral = (function(superClass) { + class UndefinedLiteral extends superClass { + constructor() { + super('undefined'); + } - function UndefinedLiteral() { - UndefinedLiteral.__super__.constructor.call(this, 'undefined'); - } + compileNode(o) { + return [this.makeCode(o.level >= LEVEL_ACCESS ? '(void 0)' : 'void 0')]; + } - UndefinedLiteral.prototype.compileNode = function(o) { - return [this.makeCode(o.level >= LEVEL_ACCESS ? '(void 0)' : 'void 0')]; }; + UndefinedLiteral.__super__ = superClass.prototype; + return UndefinedLiteral; })(Literal); - exports.NullLiteral = NullLiteral = (function(superClass1) { - extend1(NullLiteral, superClass1); + exports.NullLiteral = NullLiteral = (function(superClass) { + class NullLiteral extends superClass { + constructor() { + super('null'); + } - function NullLiteral() { - NullLiteral.__super__.constructor.call(this, 'null'); - } + }; + + NullLiteral.__super__ = superClass.prototype; return NullLiteral; })(Literal); - exports.BooleanLiteral = BooleanLiteral = (function(superClass1) { - extend1(BooleanLiteral, superClass1); + exports.BooleanLiteral = BooleanLiteral = (function(superClass) { + class BooleanLiteral extends superClass {}; - function BooleanLiteral() { - return BooleanLiteral.__super__.constructor.apply(this, arguments); - } + BooleanLiteral.__super__ = superClass.prototype; return BooleanLiteral; })(Literal); - exports.Return = Return = (function(superClass1) { - extend1(Return, superClass1); + exports.Return = Return = (function(superClass) { + class Return extends superClass { + constructor(expression1) { + super(...arguments); + this.expression = expression1; + } - function Return(expression) { - this.expression = expression; - } + compileToFragments(o, level) { + var expr, ref3; + expr = (ref3 = this.expression) != null ? ref3.makeReturn() : void 0; + if (expr && !(expr instanceof Return)) { + return expr.compileToFragments(o, level); + } else { + return Return.__super__.compileToFragments.call(this, o, level); + } + } + + compileNode(o) { + var answer; + answer = []; + answer.push(this.makeCode(this.tab + `return${(this.expression ? " " : "")}`)); + if (this.expression) { + answer = answer.concat(this.expression.compileToFragments(o, LEVEL_PAREN)); + } + answer.push(this.makeCode(";")); + return answer; + } + + }; + + Return.__super__ = superClass.prototype; Return.prototype.children = ['expression']; @@ -741,1030 +837,1249 @@ Return.prototype.jumps = THIS; - Return.prototype.compileToFragments = function(o, level) { - var expr, ref3; - expr = (ref3 = this.expression) != null ? ref3.makeReturn() : void 0; - if (expr && !(expr instanceof Return)) { - return expr.compileToFragments(o, level); - } else { - return Return.__super__.compileToFragments.call(this, o, level); - } - }; - - Return.prototype.compileNode = function(o) { - var answer; - answer = []; - answer.push(this.makeCode(this.tab + `return${(this.expression ? " " : "")}`)); - if (this.expression) { - answer = answer.concat(this.expression.compileToFragments(o, LEVEL_PAREN)); - } - answer.push(this.makeCode(";")); - return answer; - }; - return Return; })(Base); - exports.YieldReturn = YieldReturn = (function(superClass1) { - extend1(YieldReturn, superClass1); - - function YieldReturn() { - return YieldReturn.__super__.constructor.apply(this, arguments); - } - - YieldReturn.prototype.compileNode = function(o) { - if (o.scope.parent == null) { - this.error('yield can only occur inside functions'); + exports.YieldReturn = YieldReturn = (function(superClass) { + class YieldReturn extends superClass { + compileNode(o) { + if (o.scope.parent == null) { + this.error('yield can only occur inside functions'); + } + return YieldReturn.__super__.compileNode.call(this, ...arguments); } - return YieldReturn.__super__.compileNode.call(this, ...arguments); + }; + YieldReturn.__super__ = superClass.prototype; + return YieldReturn; })(Return); - exports.AwaitReturn = AwaitReturn = (function(superClass1) { - extend1(AwaitReturn, superClass1); - - function AwaitReturn() { - return AwaitReturn.__super__.constructor.apply(this, arguments); - } - - AwaitReturn.prototype.compileNode = function(o) { - if (o.scope.parent == null) { - this.error('await can only occur inside functions'); + exports.AwaitReturn = AwaitReturn = (function(superClass) { + class AwaitReturn extends superClass { + compileNode(o) { + if (o.scope.parent == null) { + this.error('await can only occur inside functions'); + } + return AwaitReturn.__super__.compileNode.call(this, ...arguments); } - return AwaitReturn.__super__.compileNode.call(this, ...arguments); + }; + AwaitReturn.__super__ = superClass.prototype; + return AwaitReturn; })(Return); - exports.Value = Value = (function(superClass1) { - extend1(Value, superClass1); - - function Value(base, props, tag) { - if (!props && base instanceof Value) { - return base; - } - this.base = base; - this.properties = props || []; - if (tag) { - this[tag] = true; + exports.Value = Value = (function(superClass) { + class Value extends superClass { + constructor(base, props, tag) { + super(...arguments); + if (!props && base instanceof Value) { + return base; + } + this.base = base; + this.properties = props || []; + if (tag) { + this[tag] = true; + } + return this; } - return this; - } - - Value.prototype.children = ['base', 'properties']; - Value.prototype.add = function(props) { - this.properties = this.properties.concat(props); - return this; - }; + add(props) { + this.properties = this.properties.concat(props); + return this; + } - Value.prototype.hasProperties = function() { - return !!this.properties.length; - }; + hasProperties() { + return !!this.properties.length; + } - Value.prototype.bareLiteral = function(type) { - return !this.properties.length && this.base instanceof type; - }; + bareLiteral(type) { + return !this.properties.length && this.base instanceof type; + } - Value.prototype.isArray = function() { - return this.bareLiteral(Arr); - }; + isArray() { + return this.bareLiteral(Arr); + } - Value.prototype.isRange = function() { - return this.bareLiteral(Range); - }; + isRange() { + return this.bareLiteral(Range); + } - Value.prototype.isComplex = function() { - return this.hasProperties() || this.base.isComplex(); - }; + isComplex() { + return this.hasProperties() || this.base.isComplex(); + } - Value.prototype.isAssignable = function() { - return this.hasProperties() || this.base.isAssignable(); - }; + isAssignable() { + return this.hasProperties() || this.base.isAssignable(); + } - Value.prototype.isNumber = function() { - return this.bareLiteral(NumberLiteral); - }; + isNumber() { + return this.bareLiteral(NumberLiteral); + } - Value.prototype.isString = function() { - return this.bareLiteral(StringLiteral); - }; + isString() { + return this.bareLiteral(StringLiteral); + } - Value.prototype.isRegex = function() { - return this.bareLiteral(RegexLiteral); - }; + isRegex() { + return this.bareLiteral(RegexLiteral); + } - Value.prototype.isUndefined = function() { - return this.bareLiteral(UndefinedLiteral); - }; + isUndefined() { + return this.bareLiteral(UndefinedLiteral); + } - Value.prototype.isNull = function() { - return this.bareLiteral(NullLiteral); - }; + isNull() { + return this.bareLiteral(NullLiteral); + } - Value.prototype.isBoolean = function() { - return this.bareLiteral(BooleanLiteral); - }; + isBoolean() { + return this.bareLiteral(BooleanLiteral); + } - Value.prototype.isAtomic = function() { - var j, len1, node, ref3; - ref3 = this.properties.concat(this.base); - for (j = 0, len1 = ref3.length; j < len1; j++) { - node = ref3[j]; - if (node.soak || node instanceof Call) { - return false; + isAtomic() { + var j, len1, node, ref3; + ref3 = this.properties.concat(this.base); + for (j = 0, len1 = ref3.length; j < len1; j++) { + node = ref3[j]; + if (node.soak || node instanceof Call) { + return false; + } } + return true; } - return true; - }; - - Value.prototype.isNotCallable = function() { - return this.isNumber() || this.isString() || this.isRegex() || this.isArray() || this.isRange() || this.isSplice() || this.isObject() || this.isUndefined() || this.isNull() || this.isBoolean(); - }; - - Value.prototype.isStatement = function(o) { - return !this.properties.length && this.base.isStatement(o); - }; - - Value.prototype.assigns = function(name) { - return !this.properties.length && this.base.assigns(name); - }; - - Value.prototype.jumps = function(o) { - return !this.properties.length && this.base.jumps(o); - }; - Value.prototype.isObject = function(onlyGenerated) { - if (this.properties.length) { - return false; + isNotCallable() { + return this.isNumber() || this.isString() || this.isRegex() || this.isArray() || this.isRange() || this.isSplice() || this.isObject() || this.isUndefined() || this.isNull() || this.isBoolean(); } - return (this.base instanceof Obj) && (!onlyGenerated || this.base.generated); - }; - - Value.prototype.isSplice = function() { - var lastProp, ref3; - ref3 = this.properties, lastProp = ref3[ref3.length - 1]; - return lastProp instanceof Slice; - }; - Value.prototype.looksStatic = function(className) { - var ref3; - return this.base.value === className && this.properties.length === 1 && ((ref3 = this.properties[0].name) != null ? ref3.value : void 0) !== 'prototype'; - }; + isStatement(o) { + return !this.properties.length && this.base.isStatement(o); + } - Value.prototype.unwrap = function() { - if (this.properties.length) { - return this; - } else { - return this.base; + assigns(name) { + return !this.properties.length && this.base.assigns(name); } - }; - Value.prototype.cacheReference = function(o) { - var base, bref, name, nref, ref3; - ref3 = this.properties, name = ref3[ref3.length - 1]; - if (this.properties.length < 2 && !this.base.isComplex() && !(name != null ? name.isComplex() : void 0)) { - return [this, this]; + jumps(o) { + return !this.properties.length && this.base.jumps(o); } - base = new Value(this.base, this.properties.slice(0, -1)); - if (base.isComplex()) { - bref = new IdentifierLiteral(o.scope.freeVariable('base')); - base = new Value(new Parens(new Assign(bref, base))); + + isObject(onlyGenerated) { + if (this.properties.length) { + return false; + } + return (this.base instanceof Obj) && (!onlyGenerated || this.base.generated); } - if (!name) { - return [base, bref]; + + isSplice() { + var lastProp, ref3; + ref3 = this.properties, lastProp = ref3[ref3.length - 1]; + return lastProp instanceof Slice; } - if (name.isComplex()) { - nref = new IdentifierLiteral(o.scope.freeVariable('name')); - name = new Index(new Assign(nref, name.index)); - nref = new Index(nref); + + looksStatic(className) { + var ref3; + return (this["this"] || this.base instanceof ThisLiteral || this.base.value === className) && this.properties.length === 1 && ((ref3 = this.properties[0].name) != null ? ref3.value : void 0) !== 'prototype'; } - return [base.add(name), new Value(bref || base.base, [nref || name])]; - }; - Value.prototype.compileNode = function(o) { - var fragments, j, len1, prop, props; - this.base.front = this.front; - props = this.properties; - fragments = this.base.compileToFragments(o, (props.length ? LEVEL_ACCESS : null)); - if (props.length && SIMPLENUM.test(fragmentsToText(fragments))) { - fragments.push(this.makeCode('.')); + unwrap() { + if (this.properties.length) { + return this; + } else { + return this.base; + } } - for (j = 0, len1 = props.length; j < len1; j++) { - prop = props[j]; - fragments.push(...prop.compileToFragments(o)); + + cacheReference(o) { + var base, bref, name, nref, ref3; + ref3 = this.properties, name = ref3[ref3.length - 1]; + if (this.properties.length < 2 && !this.base.isComplex() && !(name != null ? name.isComplex() : void 0)) { + return [this, this]; + } + base = new Value(this.base, this.properties.slice(0, -1)); + if (base.isComplex()) { + bref = new IdentifierLiteral(o.scope.freeVariable('base')); + base = new Value(new Parens(new Assign(bref, base))); + } + if (!name) { + return [base, bref]; + } + if (name.isComplex()) { + nref = new IdentifierLiteral(o.scope.freeVariable('name')); + name = new Index(new Assign(nref, name.index)); + nref = new Index(nref); + } + return [base.add(name), new Value(bref || base.base, [nref || name])]; } - return fragments; - }; - Value.prototype.unfoldSoak = function(o) { - return this.unfoldedSoak != null ? this.unfoldedSoak : this.unfoldedSoak = (() => { - var fst, i, ifn, j, len1, prop, ref, ref3, snd; - if (ifn = this.base.unfoldSoak(o)) { - ifn.body.properties.push(...this.properties); - return ifn; + compileNode(o) { + var fragments, j, len1, prop, props; + this.base.front = this.front; + props = this.properties; + fragments = this.base.compileToFragments(o, (props.length ? LEVEL_ACCESS : null)); + if (props.length && SIMPLENUM.test(fragmentsToText(fragments))) { + fragments.push(this.makeCode('.')); } - ref3 = this.properties; - for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { - prop = ref3[i]; - if (!prop.soak) { - continue; + for (j = 0, len1 = props.length; j < len1; j++) { + prop = props[j]; + fragments.push(...prop.compileToFragments(o)); + } + return fragments; + } + + unfoldSoak(o) { + return this.unfoldedSoak != null ? this.unfoldedSoak : this.unfoldedSoak = (() => { + var fst, i, ifn, j, len1, prop, ref, ref3, snd; + if (ifn = this.base.unfoldSoak(o)) { + ifn.body.properties.push(...this.properties); + return ifn; } - prop.soak = false; - fst = new Value(this.base, this.properties.slice(0, i)); - snd = new Value(this.base, this.properties.slice(i)); - if (fst.isComplex()) { - ref = new IdentifierLiteral(o.scope.freeVariable('ref')); - fst = new Parens(new Assign(ref, fst)); - snd.base = ref; + ref3 = this.properties; + for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { + prop = ref3[i]; + if (!prop.soak) { + continue; + } + prop.soak = false; + fst = new Value(this.base, this.properties.slice(0, i)); + snd = new Value(this.base, this.properties.slice(i)); + if (fst.isComplex()) { + ref = new IdentifierLiteral(o.scope.freeVariable('ref')); + fst = new Parens(new Assign(ref, fst)); + snd.base = ref; + } + return new If(new Existence(fst), snd, { + soak: true + }); } - return new If(new Existence(fst), snd, { - soak: true - }); - } - return false; - })(); + return false; + })(); + } + }; + Value.__super__ = superClass.prototype; + + Value.prototype.children = ['base', 'properties']; + return Value; })(Base); - exports.Comment = Comment = (function(superClass1) { - extend1(Comment, superClass1); + exports.Comment = Comment = (function(superClass) { + class Comment extends superClass { + constructor(comment1) { + super(...arguments); + this.comment = comment1; + } - function Comment(comment1) { - this.comment = comment1; - } + compileNode(o, level) { + var code, comment; + comment = this.comment.replace(/^(\s*)#(?=\s)/gm, "$1 *"); + code = `/*${multident(comment, this.tab)}${(indexOf.call(comment, '\n') >= 0 ? `\n${this.tab}` : '')} */`; + if ((level || o.level) === LEVEL_TOP) { + code = o.indent + code; + } + return [this.makeCode("\n"), this.makeCode(code)]; + } + + }; + + Comment.__super__ = superClass.prototype; Comment.prototype.isStatement = YES; Comment.prototype.makeReturn = THIS; - Comment.prototype.compileNode = function(o, level) { - var code, comment; - comment = this.comment.replace(/^(\s*)#(?=\s)/gm, "$1 *"); - code = `/*${multident(comment, this.tab)}${(indexOf.call(comment, '\n') >= 0 ? `\n${this.tab}` : '')} */`; - if ((level || o.level) === LEVEL_TOP) { - code = o.indent + code; - } - return [this.makeCode("\n"), this.makeCode(code)]; - }; - return Comment; })(Base); - exports.Call = Call = (function(superClass1) { - extend1(Call, superClass1); - - function Call(variable1, args1, soak1) { - var args1; - this.variable = variable1; - this.args = args1 != null ? args1 : []; - this.soak = soak1; - this.isNew = false; - if (this.variable instanceof Value && this.variable.isNotCallable()) { - this.variable.error("literal is not a function"); + exports.Call = Call = (function(superClass) { + class Call extends superClass { + constructor(variable1, args1, soak1) { + var args1; + super(...arguments); + this.variable = variable1; + this.args = args1 != null ? args1 : []; + this.soak = soak1; + this.isNew = false; + if (this.variable instanceof Value && this.variable.isNotCallable()) { + this.variable.error("literal is not a function"); + } } - } - - Call.prototype.children = ['variable', 'args']; - Call.prototype.newInstance = function() { - var base, ref3; - base = ((ref3 = this.variable) != null ? ref3.base : void 0) || this.variable; - if (base instanceof Call && !base.isNew) { - base.newInstance(); - } else { - this.isNew = true; + newInstance() { + var base, ref3; + base = ((ref3 = this.variable) != null ? ref3.base : void 0) || this.variable; + if (base instanceof Call && !base.isNew) { + base.newInstance(); + } else { + this.isNew = true; + } + return this; } - return this; - }; - Call.prototype.unfoldSoak = function(o) { - var call, ifn, j, left, len1, list, ref3, ref4, rite; - if (this.soak) { - if (this instanceof SuperCall) { - left = new Literal(this.superReference(o)); - rite = new Value(left); - } else { - if (ifn = unfoldSoak(o, this, 'variable')) { - return ifn; + unfoldSoak(o) { + var call, ifn, j, left, len1, list, ref3, ref4, rite; + if (this.soak) { + if (this instanceof SuperCall) { + left = new Literal(this.superReference(o)); + rite = new Value(left); + } else { + if (ifn = unfoldSoak(o, this, 'variable')) { + return ifn; + } + ref3 = new Value(this.variable).cacheReference(o), left = ref3[0], rite = ref3[1]; } - ref3 = new Value(this.variable).cacheReference(o), left = ref3[0], rite = ref3[1]; + rite = new Call(rite, this.args); + rite.isNew = this.isNew; + left = new Literal(`typeof ${left.compile(o)} === \"function\"`); + return new If(left, new Value(rite), { + soak: true + }); } - rite = new Call(rite, this.args); - rite.isNew = this.isNew; - left = new Literal(`typeof ${left.compile(o)} === \"function\"`); - return new If(left, new Value(rite), { - soak: true - }); - } - call = this; - list = []; - while (true) { - if (call.variable instanceof Call) { + call = this; + list = []; + while (true) { + if (call.variable instanceof Call) { + list.push(call); + call = call.variable; + continue; + } + if (!(call.variable instanceof Value)) { + break; + } list.push(call); - call = call.variable; - continue; - } - if (!(call.variable instanceof Value)) { - break; + if (!((call = call.variable.base) instanceof Call)) { + break; + } } - list.push(call); - if (!((call = call.variable.base) instanceof Call)) { - break; + ref4 = list.reverse(); + for (j = 0, len1 = ref4.length; j < len1; j++) { + call = ref4[j]; + if (ifn) { + if (call.variable instanceof Call) { + call.variable = ifn; + } else { + call.variable.base = ifn; + } + } + ifn = unfoldSoak(o, call, 'variable'); } + return ifn; } - ref4 = list.reverse(); - for (j = 0, len1 = ref4.length; j < len1; j++) { - call = ref4[j]; - if (ifn) { - if (call.variable instanceof Call) { - call.variable = ifn; + + compileNode(o) { + var arg, argIndex, compiledArgs, fragments, j, len1, preface, ref3, ref4; + if ((ref3 = this.variable) != null) { + ref3.front = this.front; + } + compiledArgs = []; + ref4 = this.args; + for (argIndex = j = 0, len1 = ref4.length; j < len1; argIndex = ++j) { + arg = ref4[argIndex]; + if (argIndex) { + compiledArgs.push(this.makeCode(", ")); + } + compiledArgs.push(...arg.compileToFragments(o, LEVEL_LIST)); + } + fragments = []; + if (this instanceof SuperCall) { + preface = this.superReference(o); + if (preface === 'super') { + preface += '('; } else { - call.variable.base = ifn; + preface += `.call(${this.superThis(o)}`; + if (compiledArgs.length) { + preface += ", "; + } + } + fragments.push(this.makeCode(preface)); + } else { + if (this.isNew) { + fragments.push(this.makeCode('new ')); } + fragments.push(...this.variable.compileToFragments(o, LEVEL_ACCESS)); + fragments.push(this.makeCode("(")); } - ifn = unfoldSoak(o, call, 'variable'); + fragments.push(...compiledArgs); + fragments.push(this.makeCode(")")); + return fragments; } - return ifn; - }; - Call.prototype.compileNode = function(o) { - var arg, argIndex, compiledArgs, fragments, j, len1, preface, ref3, ref4; - if ((ref3 = this.variable) != null) { - ref3.front = this.front; - } - compiledArgs = []; - ref4 = this.args; - for (argIndex = j = 0, len1 = ref4.length; j < len1; argIndex = ++j) { - arg = ref4[argIndex]; - if (argIndex) { - compiledArgs.push(this.makeCode(", ")); - } - compiledArgs.push(...arg.compileToFragments(o, LEVEL_LIST)); - } - fragments = []; - if (this instanceof SuperCall) { - preface = this.superReference(o) + `.call(${this.superThis(o)}`; - if (compiledArgs.length) { - preface += ", "; - } - fragments.push(this.makeCode(preface)); - } else { - if (this.isNew) { - fragments.push(this.makeCode('new ')); - } - fragments.push(...this.variable.compileToFragments(o, LEVEL_ACCESS)); - fragments.push(this.makeCode("(")); - } - fragments.push(...compiledArgs); - fragments.push(this.makeCode(")")); - return fragments; }; - return Call; + Call.__super__ = superClass.prototype; - })(Base); + Call.prototype.children = ['variable', 'args']; - exports.SuperCall = SuperCall = (function(superClass1) { - extend1(SuperCall, superClass1); + return Call; - function SuperCall(args) { - SuperCall.__super__.constructor.call(this, null, args != null ? args : [new Splat(new IdentifierLiteral('arguments'))]); - this.isBare = args != null; - } + })(Base); - SuperCall.prototype.superReference = function(o) { - var accesses, base, bref, klass, method, name, nref, variable; - method = o.scope.namedMethod(); - if (method != null ? method.klass : void 0) { - klass = method.klass, name = method.name, variable = method.variable; - if (klass.isComplex()) { - bref = new IdentifierLiteral(o.scope.parent.freeVariable('base')); - base = new Value(new Parens(new Assign(bref, klass))); - variable.base = base; - variable.properties.splice(0, klass.properties.length); - } - if (name.isComplex() || (name instanceof Index && name.index.isAssignable())) { - nref = new IdentifierLiteral(o.scope.parent.freeVariable('name')); - name = new Index(new Assign(nref, name.index)); - variable.properties.pop(); - variable.properties.push(name); - } - accesses = [new Access(new PropertyName('__super__'))]; - if (method["static"]) { - accesses.push(new Access(new PropertyName('constructor'))); + exports.SuperCall = SuperCall = (function(superClass) { + class SuperCall extends superClass { + constructor(args) { + super(null, args != null ? args : [new Splat(new IdentifierLiteral('arguments'))]); + this.isBare = args != null; + } + + superReference(o) { + var accesses, base, bref, klass, method, name, nref, variable; + method = o.scope.namedMethod(); + if (method != null ? method.ctor : void 0) { + return 'super'; + } else if (method != null ? method.klass : void 0) { + klass = method.klass, name = method.name, variable = method.variable; + if (klass.isComplex()) { + bref = new IdentifierLiteral(o.scope.parent.freeVariable('base')); + base = new Value(new Parens(new Assign(bref, klass))); + variable.base = base; + variable.properties.splice(0, klass.properties.length); + } + if (name.isComplex() || (name instanceof Index && name.index.isAssignable())) { + nref = new IdentifierLiteral(o.scope.parent.freeVariable('name')); + name.index = new Assign(nref, name.index); + } + accesses = [new Access(new PropertyName('__super__'))]; + if (method["static"]) { + accesses.push(new Access(new PropertyName('constructor'))); + } + accesses.push(nref != null ? new Index(nref) : name); + return (new Value(bref != null ? bref : klass, accesses)).compile(o); + } else { + return this.error('cannot call super outside of an instance method.'); } - accesses.push(nref != null ? new Index(nref) : name); - return (new Value(bref != null ? bref : klass, accesses)).compile(o); - } else if (method != null ? method.ctor : void 0) { - return `${method.name}.__super__.constructor`; - } else { - return this.error('cannot call super outside of an instance method.'); } - }; - SuperCall.prototype.superThis = function(o) { - var method; - method = o.scope.method; - return (method && !method.klass && method.context) || "this"; + superThis(o) { + var method; + method = o.scope.method; + return (method && !method.klass && method.context) || "this"; + } + }; + SuperCall.__super__ = superClass.prototype; + return SuperCall; })(Call); - exports.RegexWithInterpolations = RegexWithInterpolations = (function(superClass1) { - extend1(RegexWithInterpolations, superClass1); + exports.RegexWithInterpolations = RegexWithInterpolations = (function(superClass) { + class RegexWithInterpolations extends superClass { + constructor(args = []) { + super(new Value(new IdentifierLiteral('RegExp')), args, false); + } - function RegexWithInterpolations(args = []) { - RegexWithInterpolations.__super__.constructor.call(this, new Value(new IdentifierLiteral('RegExp')), args, false); - } + }; + + RegexWithInterpolations.__super__ = superClass.prototype; return RegexWithInterpolations; })(Call); - exports.TaggedTemplateCall = TaggedTemplateCall = (function(superClass1) { - extend1(TaggedTemplateCall, superClass1); + exports.TaggedTemplateCall = TaggedTemplateCall = (function(superClass) { + class TaggedTemplateCall extends superClass { + constructor(variable, arg, soak) { + if (arg instanceof StringLiteral) { + arg = new StringWithInterpolations(Block.wrap([new Value(arg)])); + } + super(variable, [arg], soak); + } - function TaggedTemplateCall(variable, arg, soak) { - if (arg instanceof StringLiteral) { - arg = new StringWithInterpolations(Block.wrap([new Value(arg)])); + compileNode(o) { + return this.variable.compileToFragments(o, LEVEL_ACCESS).concat(this.args[0].compileToFragments(o, LEVEL_LIST)); } - TaggedTemplateCall.__super__.constructor.call(this, variable, [arg], soak); - } - TaggedTemplateCall.prototype.compileNode = function(o) { - return this.variable.compileToFragments(o, LEVEL_ACCESS).concat(this.args[0].compileToFragments(o, LEVEL_LIST)); }; + TaggedTemplateCall.__super__ = superClass.prototype; + return TaggedTemplateCall; })(Call); - exports.Extends = Extends = (function(superClass1) { - extend1(Extends, superClass1); - - function Extends(child1, parent1) { - this.child = child1; - this.parent = parent1; - } + exports.Extends = Extends = (function(superClass) { + class Extends extends superClass { + constructor(child1, parent1) { + super(...arguments); + this.child = child1; + this.parent = parent1; + } - Extends.prototype.children = ['child', 'parent']; + compileToFragments(o) { + return new Call(new Value(new Literal(utility('extend', o))), [this.child, this.parent]).compileToFragments(o); + } - Extends.prototype.compileToFragments = function(o) { - return new Call(new Value(new Literal(utility('extend', o))), [this.child, this.parent]).compileToFragments(o); }; - return Extends; - - })(Base); + Extends.__super__ = superClass.prototype; - exports.Access = Access = (function(superClass1) { - extend1(Access, superClass1); + Extends.prototype.children = ['child', 'parent']; - function Access(name1, tag) { - this.name = name1; - this.soak = tag === 'soak'; - } + return Extends; - Access.prototype.children = ['name']; + })(Base); - Access.prototype.compileToFragments = function(o) { - var name, node, ref3; - name = this.name.compileToFragments(o); - node = this.name.unwrap(); - if (node instanceof PropertyName) { - if (ref3 = node.value, indexOf.call(JS_FORBIDDEN, ref3) >= 0) { - return [this.makeCode('["'), ...name, this.makeCode('"]')]; + exports.Access = Access = (function(superClass) { + class Access extends superClass { + constructor(name1, tag) { + super(...arguments); + this.name = name1; + this.soak = tag === 'soak'; + } + + compileToFragments(o) { + var name, node, ref3; + name = this.name.compileToFragments(o); + node = this.name.unwrap(); + if (node instanceof PropertyName) { + if (ref3 = node.value, indexOf.call(JS_FORBIDDEN, ref3) >= 0) { + return [this.makeCode('["'), ...name, this.makeCode('"]')]; + } else { + return [this.makeCode('.'), ...name]; + } } else { - return [this.makeCode('.'), ...name]; + return [this.makeCode('['), ...name, this.makeCode(']')]; } - } else { - return [this.makeCode('['), ...name, this.makeCode(']')]; } + }; + Access.__super__ = superClass.prototype; + + Access.prototype.children = ['name']; + Access.prototype.isComplex = NO; return Access; })(Base); - exports.Index = Index = (function(superClass1) { - extend1(Index, superClass1); + exports.Index = Index = (function(superClass) { + class Index extends superClass { + constructor(index1) { + super(...arguments); + this.index = index1; + } - function Index(index1) { - this.index = index1; - } + compileToFragments(o) { + return [].concat(this.makeCode("["), this.index.compileToFragments(o, LEVEL_PAREN), this.makeCode("]")); + } - Index.prototype.children = ['index']; + isComplex() { + return this.index.isComplex(); + } - Index.prototype.compileToFragments = function(o) { - return [].concat(this.makeCode("["), this.index.compileToFragments(o, LEVEL_PAREN), this.makeCode("]")); }; - Index.prototype.isComplex = function() { - return this.index.isComplex(); - }; + Index.__super__ = superClass.prototype; + + Index.prototype.children = ['index']; return Index; })(Base); - exports.Range = Range = (function(superClass1) { - extend1(Range, superClass1); - - Range.prototype.children = ['from', 'to']; - - function Range(from1, to1, tag) { - this.from = from1; - this.to = to1; - this.exclusive = tag === 'exclusive'; - this.equals = this.exclusive ? '' : '='; - } - - Range.prototype.compileVariables = function(o) { - var isComplex, ref3, ref4, ref5, step; - o = merge(o, { - top: true - }); - isComplex = del(o, 'isComplex'); - ref3 = this.cacheToCodeFragments(this.from.cache(o, LEVEL_LIST, isComplex)), this.fromC = ref3[0], this.fromVar = ref3[1]; - ref4 = this.cacheToCodeFragments(this.to.cache(o, LEVEL_LIST, isComplex)), this.toC = ref4[0], this.toVar = ref4[1]; - if (step = del(o, 'step')) { - ref5 = this.cacheToCodeFragments(step.cache(o, LEVEL_LIST, isComplex)), this.step = ref5[0], this.stepVar = ref5[1]; - } - this.fromNum = this.from.isNumber() ? Number(this.fromVar) : null; - this.toNum = this.to.isNumber() ? Number(this.toVar) : null; - return this.stepNum = (step != null ? step.isNumber() : void 0) ? Number(this.stepVar) : null; - }; - - Range.prototype.compileNode = function(o) { - var cond, condPart, from, gt, idx, idxName, known, lt, namedIndex, ref3, ref4, stepPart, to, varPart; - if (!this.fromVar) { - this.compileVariables(o); + exports.Range = Range = (function(superClass) { + class Range extends superClass { + constructor(from1, to1, tag) { + super(...arguments); + this.from = from1; + this.to = to1; + this.exclusive = tag === 'exclusive'; + this.equals = this.exclusive ? '' : '='; } - if (!o.index) { - return this.compileArray(o); - } - known = (this.fromNum != null) && (this.toNum != null); - idx = del(o, 'index'); - idxName = del(o, 'name'); - namedIndex = idxName && idxName !== idx; - varPart = `${idx} = ${this.fromC}`; - if (this.toC !== this.toVar) { - varPart += `, ${this.toC}`; - } - if (this.step !== this.stepVar) { - varPart += `, ${this.step}`; + + compileVariables(o) { + var isComplex, ref3, ref4, ref5, step; + o = merge(o, { + top: true + }); + isComplex = del(o, 'isComplex'); + ref3 = this.cacheToCodeFragments(this.from.cache(o, LEVEL_LIST, isComplex)), this.fromC = ref3[0], this.fromVar = ref3[1]; + ref4 = this.cacheToCodeFragments(this.to.cache(o, LEVEL_LIST, isComplex)), this.toC = ref4[0], this.toVar = ref4[1]; + if (step = del(o, 'step')) { + ref5 = this.cacheToCodeFragments(step.cache(o, LEVEL_LIST, isComplex)), this.step = ref5[0], this.stepVar = ref5[1]; + } + this.fromNum = this.from.isNumber() ? Number(this.fromVar) : null; + this.toNum = this.to.isNumber() ? Number(this.toVar) : null; + return this.stepNum = (step != null ? step.isNumber() : void 0) ? Number(this.stepVar) : null; } - ref3 = [`${idx} <${this.equals}`, `${idx} >${this.equals}`], lt = ref3[0], gt = ref3[1]; - condPart = this.stepNum != null ? this.stepNum > 0 ? `${lt} ${this.toVar}` : `${gt} ${this.toVar}` : known ? ((ref4 = [this.fromNum, this.toNum], from = ref4[0], to = ref4[1], ref4), from <= to ? `${lt} ${to}` : `${gt} ${to}`) : (cond = this.stepVar ? `${this.stepVar} > 0` : `${this.fromVar} <= ${this.toVar}`, `${cond} ? ${lt} ${this.toVar} : ${gt} ${this.toVar}`); - stepPart = this.stepVar ? `${idx} += ${this.stepVar}` : known ? namedIndex ? from <= to ? `++${idx}` : `--${idx}` : from <= to ? `${idx}++` : `${idx}--` : namedIndex ? `${cond} ? ++${idx} : --${idx}` : `${cond} ? ${idx}++ : ${idx}--`; - if (namedIndex) { - varPart = `${idxName} = ${varPart}`; + + compileNode(o) { + var cond, condPart, from, gt, idx, idxName, known, lt, namedIndex, ref3, ref4, stepPart, to, varPart; + if (!this.fromVar) { + this.compileVariables(o); + } + if (!o.index) { + return this.compileArray(o); + } + known = (this.fromNum != null) && (this.toNum != null); + idx = del(o, 'index'); + idxName = del(o, 'name'); + namedIndex = idxName && idxName !== idx; + varPart = `${idx} = ${this.fromC}`; + if (this.toC !== this.toVar) { + varPart += `, ${this.toC}`; + } + if (this.step !== this.stepVar) { + varPart += `, ${this.step}`; + } + ref3 = [`${idx} <${this.equals}`, `${idx} >${this.equals}`], lt = ref3[0], gt = ref3[1]; + condPart = this.stepNum != null ? this.stepNum > 0 ? `${lt} ${this.toVar}` : `${gt} ${this.toVar}` : known ? ((ref4 = [this.fromNum, this.toNum], from = ref4[0], to = ref4[1], ref4), from <= to ? `${lt} ${to}` : `${gt} ${to}`) : (cond = this.stepVar ? `${this.stepVar} > 0` : `${this.fromVar} <= ${this.toVar}`, `${cond} ? ${lt} ${this.toVar} : ${gt} ${this.toVar}`); + stepPart = this.stepVar ? `${idx} += ${this.stepVar}` : known ? namedIndex ? from <= to ? `++${idx}` : `--${idx}` : from <= to ? `${idx}++` : `${idx}--` : namedIndex ? `${cond} ? ++${idx} : --${idx}` : `${cond} ? ${idx}++ : ${idx}--`; + if (namedIndex) { + varPart = `${idxName} = ${varPart}`; + } + if (namedIndex) { + stepPart = `${idxName} = ${stepPart}`; + } + return [this.makeCode(`${varPart}; ${condPart}; ${stepPart}`)]; } - if (namedIndex) { - stepPart = `${idxName} = ${stepPart}`; + + compileArray(o) { + var args, body, cond, hasArgs, i, idt, j, known, post, pre, range, ref3, ref4, result, results, vars; + known = (this.fromNum != null) && (this.toNum != null); + if (known && Math.abs(this.fromNum - this.toNum) <= 20) { + range = (function() { + results = []; + for (var j = ref3 = this.fromNum, ref4 = this.toNum; ref3 <= ref4 ? j <= ref4 : j >= ref4; ref3 <= ref4 ? j++ : j--){ results.push(j); } + return results; + }).apply(this); + if (this.exclusive) { + range.pop(); + } + return [this.makeCode(`[${range.join(', ')}]`)]; + } + idt = this.tab + TAB; + i = o.scope.freeVariable('i', { + single: true + }); + result = o.scope.freeVariable('results'); + pre = `\n${idt}${result} = [];`; + if (known) { + o.index = i; + body = fragmentsToText(this.compileNode(o)); + } else { + vars = `${i} = ${this.fromC}` + (this.toC !== this.toVar ? `, ${this.toC}` : ''); + cond = `${this.fromVar} <= ${this.toVar}`; + body = `var ${vars}; ${cond} ? ${i} <${this.equals} ${this.toVar} : ${i} >${this.equals} ${this.toVar}; ${cond} ? ${i}++ : ${i}--`; + } + post = `{ ${result}.push(${i}); }\n${idt}return ${result};\n${o.indent}`; + hasArgs = function(node) { + return node != null ? node.contains(isLiteralArguments) : void 0; + }; + if (hasArgs(this.from) || hasArgs(this.to)) { + args = ', arguments'; + } + return [this.makeCode(`(function() {${pre}\n${idt}for (${body})${post}}).apply(this${args != null ? args : ''})`)]; } - return [this.makeCode(`${varPart}; ${condPart}; ${stepPart}`)]; - }; - Range.prototype.compileArray = function(o) { - var args, body, cond, hasArgs, i, idt, j, known, post, pre, range, ref3, ref4, result, results, vars; - known = (this.fromNum != null) && (this.toNum != null); - if (known && Math.abs(this.fromNum - this.toNum) <= 20) { - range = (function() { - results = []; - for (var j = ref3 = this.fromNum, ref4 = this.toNum; ref3 <= ref4 ? j <= ref4 : j >= ref4; ref3 <= ref4 ? j++ : j--){ results.push(j); } - return results; - }).apply(this); - if (this.exclusive) { - range.pop(); - } - return [this.makeCode(`[${range.join(', ')}]`)]; - } - idt = this.tab + TAB; - i = o.scope.freeVariable('i', { - single: true - }); - result = o.scope.freeVariable('results'); - pre = `\n${idt}${result} = [];`; - if (known) { - o.index = i; - body = fragmentsToText(this.compileNode(o)); - } else { - vars = `${i} = ${this.fromC}` + (this.toC !== this.toVar ? `, ${this.toC}` : ''); - cond = `${this.fromVar} <= ${this.toVar}`; - body = `var ${vars}; ${cond} ? ${i} <${this.equals} ${this.toVar} : ${i} >${this.equals} ${this.toVar}; ${cond} ? ${i}++ : ${i}--`; - } - post = `{ ${result}.push(${i}); }\n${idt}return ${result};\n${o.indent}`; - hasArgs = function(node) { - return node != null ? node.contains(isLiteralArguments) : void 0; - }; - if (hasArgs(this.from) || hasArgs(this.to)) { - args = ', arguments'; - } - return [this.makeCode(`(function() {${pre}\n${idt}for (${body})${post}}).apply(this${args != null ? args : ''})`)]; }; - return Range; - - })(Base); + Range.__super__ = superClass.prototype; - exports.Slice = Slice = (function(superClass1) { - extend1(Slice, superClass1); + Range.prototype.children = ['from', 'to']; - Slice.prototype.children = ['range']; + return Range; - function Slice(range1) { - this.range = range1; - Slice.__super__.constructor.call(this); - } + })(Base); - Slice.prototype.compileNode = function(o) { - var compiled, compiledText, from, fromCompiled, ref3, to, toStr; - ref3 = this.range, to = ref3.to, from = ref3.from; - fromCompiled = from && from.compileToFragments(o, LEVEL_PAREN) || [this.makeCode('0')]; - if (to) { - compiled = to.compileToFragments(o, LEVEL_PAREN); - compiledText = fragmentsToText(compiled); - if (!(!this.range.exclusive && +compiledText === -1)) { - toStr = ', ' + (this.range.exclusive ? compiledText : to.isNumber() ? `${+compiledText + 1}` : (compiled = to.compileToFragments(o, LEVEL_ACCESS), `+${fragmentsToText(compiled)} + 1 || 9e9`)); + exports.Slice = Slice = (function(superClass) { + class Slice extends superClass { + constructor(range1) { + super(...arguments); + this.range = range1; + } + + compileNode(o) { + var compiled, compiledText, from, fromCompiled, ref3, to, toStr; + ref3 = this.range, to = ref3.to, from = ref3.from; + fromCompiled = from && from.compileToFragments(o, LEVEL_PAREN) || [this.makeCode('0')]; + if (to) { + compiled = to.compileToFragments(o, LEVEL_PAREN); + compiledText = fragmentsToText(compiled); + if (!(!this.range.exclusive && +compiledText === -1)) { + toStr = ', ' + (this.range.exclusive ? compiledText : to.isNumber() ? `${+compiledText + 1}` : (compiled = to.compileToFragments(o, LEVEL_ACCESS), `+${fragmentsToText(compiled)} + 1 || 9e9`)); + } } + return [this.makeCode(`.slice(${fragmentsToText(fromCompiled)}${toStr || ''})`)]; } - return [this.makeCode(`.slice(${fragmentsToText(fromCompiled)}${toStr || ''})`)]; - }; - return Slice; + }; - })(Base); + Slice.__super__ = superClass.prototype; - exports.Obj = Obj = (function(superClass1) { - extend1(Obj, superClass1); + Slice.prototype.children = ['range']; - function Obj(props, generated) { - var generated; - this.generated = generated != null ? generated : false; - this.objects = this.properties = props || []; - } + return Slice; - Obj.prototype.children = ['properties']; + })(Base); - Obj.prototype.compileNode = function(o) { - var answer, i, idt, indent, j, join, k, key, lastNoncom, len1, len2, node, prop, props, ref3, value; - props = this.properties; - if (this.generated) { - for (j = 0, len1 = props.length; j < len1; j++) { - node = props[j]; - if (node instanceof Value) { - node.error('cannot have an implicit value in an implicit object'); + exports.Obj = Obj = (function(superClass) { + class Obj extends superClass { + constructor(props, generated) { + var generated; + super(...arguments); + this.generated = generated != null ? generated : false; + this.objects = this.properties = props || []; + } + + compileNode(o) { + var answer, i, idt, indent, j, join, k, key, lastNoncom, len1, len2, node, prop, props, ref3, value; + props = this.properties; + if (this.generated) { + for (j = 0, len1 = props.length; j < len1; j++) { + node = props[j]; + if (node instanceof Value) { + node.error('cannot have an implicit value in an implicit object'); + } } } - } - idt = o.indent += TAB; - lastNoncom = this.lastNonComment(this.properties); - answer = []; - answer.push(this.makeCode(`{${(props.length === 0 ? '}' : '\n')}`)); - for (i = k = 0, len2 = props.length; k < len2; i = ++k) { - prop = props[i]; - join = i === props.length - 1 ? '' : prop === lastNoncom || prop instanceof Comment ? '\n' : ',\n'; - indent = prop instanceof Comment ? '' : idt; - if (prop instanceof Assign) { - if (prop.context !== 'object') { - prop.operatorToken.error(`unexpected ${prop.operatorToken.value}`); + idt = o.indent += TAB; + lastNoncom = this.lastNonComment(this.properties); + answer = []; + answer.push(this.makeCode(`{${(props.length === 0 ? '}' : '\n')}`)); + for (i = k = 0, len2 = props.length; k < len2; i = ++k) { + prop = props[i]; + join = i === props.length - 1 ? '' : prop === lastNoncom || prop instanceof Comment ? '\n' : ',\n'; + indent = prop instanceof Comment ? '' : idt; + if (prop instanceof Assign) { + if (prop.context !== 'object') { + prop.operatorToken.error(`unexpected ${prop.operatorToken.value}`); + } + if (prop.variable instanceof Value && prop.variable.hasProperties()) { + prop.variable.error('invalid object key'); + } } - if (prop.variable instanceof Value && prop.variable.hasProperties()) { - prop.variable.error('invalid object key'); + if (prop instanceof Value && prop["this"]) { + prop = new Assign(prop.properties[0].name, prop, 'object'); } - } - if (prop instanceof Value && prop["this"]) { - prop = new Assign(prop.properties[0].name, prop, 'object'); - } - if (!(prop instanceof Comment) && !(prop instanceof Assign)) { - if (prop.isComplex()) { - ref3 = prop.base.cache(o), key = ref3[0], value = ref3[1]; - if (key instanceof IdentifierLiteral) { - key = new PropertyName(key.value); + if (!(prop instanceof Comment) && !(prop instanceof Assign)) { + if (prop.isComplex()) { + ref3 = prop.base.cache(o), key = ref3[0], value = ref3[1]; + if (key instanceof IdentifierLiteral) { + key = new PropertyName(key.value); + } + prop = new Assign(key, value, 'object'); + } else { + prop = new Assign(prop, prop, 'object'); } - prop = new Assign(key, value, 'object'); - } else { - prop = new Assign(prop, prop, 'object'); + } + if (indent) { + answer.push(this.makeCode(indent)); + } + answer.push(...prop.compileToFragments(o, LEVEL_TOP)); + if (join) { + answer.push(this.makeCode(join)); } } - if (indent) { - answer.push(this.makeCode(indent)); + if (props.length !== 0) { + answer.push(this.makeCode(`\n${this.tab}}`)); } - answer.push(...prop.compileToFragments(o, LEVEL_TOP)); - if (join) { - answer.push(this.makeCode(join)); + if (this.front) { + return this.wrapInBraces(answer); + } else { + return answer; } } - if (props.length !== 0) { - answer.push(this.makeCode(`\n${this.tab}}`)); - } - if (this.front) { - return this.wrapInBraces(answer); - } else { - return answer; - } - }; - Obj.prototype.assigns = function(name) { - var j, len1, prop, ref3; - ref3 = this.properties; - for (j = 0, len1 = ref3.length; j < len1; j++) { - prop = ref3[j]; - if (prop.assigns(name)) { - return true; + assigns(name) { + var j, len1, prop, ref3; + ref3 = this.properties; + for (j = 0, len1 = ref3.length; j < len1; j++) { + prop = ref3[j]; + if (prop.assigns(name)) { + return true; + } } + return false; } - return false; + }; - return Obj; + Obj.__super__ = superClass.prototype; - })(Base); + Obj.prototype.children = ['properties']; - exports.Arr = Arr = (function(superClass1) { - extend1(Arr, superClass1); + return Obj; - function Arr(objs) { - this.objects = objs || []; - } + })(Base); - Arr.prototype.children = ['objects']; + exports.Arr = Arr = (function(superClass) { + class Arr extends superClass { + constructor(objs) { + super(...arguments); + this.objects = objs || []; + } - Arr.prototype.compileNode = function(o) { - var answer, compiledObjs, fragments, index, j, len1, obj; - if (!this.objects.length) { - return [this.makeCode('[]')]; + compileNode(o) { + var answer, compiledObjs, fragments, index, j, len1, obj; + if (!this.objects.length) { + return [this.makeCode('[]')]; + } + o.indent += TAB; + answer = []; + compiledObjs = (function() { + var j, len1, ref3, results; + ref3 = this.objects; + results = []; + for (j = 0, len1 = ref3.length; j < len1; j++) { + obj = ref3[j]; + results.push(obj.compileToFragments(o, LEVEL_LIST)); + } + return results; + }).call(this); + for (index = j = 0, len1 = compiledObjs.length; j < len1; index = ++j) { + fragments = compiledObjs[index]; + if (index) { + answer.push(this.makeCode(", ")); + } + answer.push(...fragments); + } + if (fragmentsToText(answer).indexOf('\n') >= 0) { + answer.unshift(this.makeCode(`[\n${o.indent}`)); + answer.push(this.makeCode(`\n${this.tab}]`)); + } else { + answer.unshift(this.makeCode("[")); + answer.push(this.makeCode("]")); + } + return answer; } - o.indent += TAB; - answer = []; - compiledObjs = (function() { - var j, len1, ref3, results; + + assigns(name) { + var j, len1, obj, ref3; ref3 = this.objects; - results = []; for (j = 0, len1 = ref3.length; j < len1; j++) { obj = ref3[j]; - results.push(obj.compileToFragments(o, LEVEL_LIST)); - } - return results; - }).call(this); - for (index = j = 0, len1 = compiledObjs.length; j < len1; index = ++j) { - fragments = compiledObjs[index]; - if (index) { - answer.push(this.makeCode(", ")); + if (obj.assigns(name)) { + return true; + } } - answer.push(...fragments); - } - if (fragmentsToText(answer).indexOf('\n') >= 0) { - answer.unshift(this.makeCode(`[\n${o.indent}`)); - answer.push(this.makeCode(`\n${this.tab}]`)); - } else { - answer.unshift(this.makeCode("[")); - answer.push(this.makeCode("]")); + return false; } - return answer; - }; - Arr.prototype.assigns = function(name) { - var j, len1, obj, ref3; - ref3 = this.objects; - for (j = 0, len1 = ref3.length; j < len1; j++) { - obj = ref3[j]; - if (obj.assigns(name)) { - return true; - } - } - return false; }; - return Arr; - - })(Base); - - exports.Class = Class = (function(superClass1) { - extend1(Class, superClass1); + Arr.__super__ = superClass.prototype; - function Class(variable1, parent1, body1) { - var body1; - this.variable = variable1; - this.parent = parent1; - this.body = body1 != null ? body1 : new Block; - this.boundFuncs = []; - this.body.classBody = true; - } + Arr.prototype.children = ['objects']; - Class.prototype.children = ['variable', 'parent', 'body']; + return Arr; - Class.prototype.defaultClassVariableName = '_Class'; + })(Base); - Class.prototype.determineName = function() { - var message, name, node, ref3, tail; - if (!this.variable) { - return this.defaultClassVariableName; + exports.Class = Class = (function(superClass) { + class Class extends superClass { + constructor(variable1, parent1, body1) { + var body1; + super(...arguments); + this.variable = variable1; + this.parent = parent1; + this.body = body1 != null ? body1 : new Block; + this.properties = []; + } + + compileNode(o) { + var args, argumentsNode, declaration, directives, emptyBody, externalCtor, ident, j, jumpNode, klass, len1, name, params, parent, properties, property, ref3, wrapper; + if (jumpNode = this.body.jumps()) { + jumpNode.error('Class bodies cannot contain pure statements'); + } + if (argumentsNode = this.body.contains(isLiteralArguments)) { + argumentsNode.error("Class bodies shouldn't reference arguments"); + } + name = this.determineName(); + ref3 = this.walkBody(name), directives = ref3[0], properties = ref3[1]; + this.setContext(name); + emptyBody = !this.parent && !this.externalCtor && directives.length === 0 && (this.body.isEmpty() || this.body.expressions.length <= properties.length); + if (emptyBody) { + klass = new ClassDeclaration(name, this.parent, new Block(properties)); + } else { + ident = new IdentifierLiteral(name); + params = []; + args = []; + wrapper = new Code(params, this.body); + klass = new Call(wrapper, args); + this.body.spaced = true; + o.classScope = wrapper.makeScope(o.scope); + declaration = new ClassDeclaration(name); + for (j = 0, len1 = properties.length; j < len1; j++) { + property = properties[j]; + declaration.body.push(property.hoist()); + } + if (this.externalCtor) { + externalCtor = new IdentifierLiteral(o.classScope.freeVariable('ctor', { + reserve: false + })); + declaration.externalCtor = externalCtor; + this.externalCtor.variable.base = externalCtor; + } + if (this.parent) { + parent = new IdentifierLiteral(o.classScope.freeVariable('superClass', { + reserve: false + })); + declaration.parent = parent; + params.push(new Param(parent)); + args.push(this.parent); + this.body.unshift(new Literal(`${name}.__super__ = ${parent.value}.prototype`)); + } + this.body.expressions.unshift(...directives, declaration); + this.body.push(ident); + } + if (this.variable) { + return new Assign(this.variable, klass, null, { + moduleDeclaration: this.moduleDeclaration + }).compileToFragments(o); + } else { + return klass.compileToFragments(o); + } } - ref3 = this.variable.properties, tail = ref3[ref3.length - 1]; - node = tail ? tail instanceof Access && tail.name : this.variable.base; - if (!(node instanceof IdentifierLiteral || node instanceof PropertyName)) { - return this.defaultClassVariableName; + + determineName() { + var message, name, node, ref3, tail; + if (!this.variable) { + return this.defaultClassVariableName; + } + ref3 = this.variable.properties, tail = ref3[ref3.length - 1]; + node = tail ? tail instanceof Access && tail.name : this.variable.base; + if (!(node instanceof IdentifierLiteral || node instanceof PropertyName)) { + return this.defaultClassVariableName; + } + name = node.value; + if (!tail) { + message = isUnassignable(name); + if (message) { + this.variable.error(message); + } + } + if (indexOf.call(JS_FORBIDDEN, name) >= 0) { + return `_${name}`; + } else { + return name; + } } - name = node.value; - if (!tail) { - message = isUnassignable(name); - if (message) { - this.variable.error(message); + + walkBody(name) { + var assign, comment, directives, expr, i, j, k, len1, len2, method, properties, ref3, ref4; + directives = []; + properties = []; + while (expr = this.body.expressions[0]) { + if (!(expr instanceof Comment || expr instanceof Value && expr.isString())) { + break; + } + directives.push(this.body.expressions.shift()); + } + comment = null; + ref3 = this.body.expressions.slice(); + for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { + expr = ref3[i]; + if (expr instanceof Value && expr.isObject(true)) { + ref4 = expr.base.properties; + for (i = k = 0, len2 = ref4.length; k < len2; i = ++k) { + assign = ref4[i]; + if (this.validInitializerMethod(name, assign)) { + expr.base.properties[i] = method = this.addInitializerMethod(name, assign); + if (comment) { + properties.push(comment); + } + properties.push(method); + } + comment = assign instanceof Comment ? assign : null; + } + } else if (expr instanceof Assign && expr.variable.looksStatic(name)) { + if (this.validInitializerMethod(name, expr)) { + this.body.expressions[i] = method = this.addInitializerMethod(name, expr); + if (comment) { + properties.push(comment); + } + properties.push(method); + } + } + comment = expr instanceof Comment ? expr : null; } + this.traverseChildren(false, (child) => { + var cont, l, len3, node, ref5; + cont = true; + if (child instanceof Class) { + return false; + } + if (child instanceof Block) { + ref5 = child.expressions; + for (i = l = 0, len3 = ref5.length; l < len3; i = ++l) { + node = ref5[i]; + if (node instanceof Value && node.isObject(true)) { + cont = false; + child.expressions[i] = this.addProperties(node.base.properties); + } else if (node instanceof Assign && node.variable.looksStatic(name)) { + node.value["static"] = true; + } + } + child.expressions = flatten(child.expressions); + } + return cont && !(child instanceof Class); + }); + return [directives, properties]; } - if (indexOf.call(JS_FORBIDDEN, name) >= 0) { - return `_${name}`; - } else { - return name; + + setContext(name) { + return this.body.traverseChildren(false, function(node) { + if (node.classBody) { + return false; + } + if (node instanceof ThisLiteral) { + return node.value = name; + } else if (node instanceof Code) { + if (node.bound) { + return node.context = name; + } + } + }); } - }; - Class.prototype.setContext = function(name) { - return this.body.traverseChildren(false, function(node) { - if (node.classBody) { + validInitializerMethod(name, node) { + if (!(node instanceof Assign && node.value instanceof Code)) { return false; } - if (node instanceof ThisLiteral) { - return node.value = name; - } else if (node instanceof Code) { - if (node.bound) { - return node.context = name; - } + if (!node.variable.hasProperties()) { + return true; } - }); - }; + return node.variable.looksStatic(name); + } - Class.prototype.addBoundFunctions = function(o) { - var bvar, j, len1, lhs, ref3; - ref3 = this.boundFuncs; - for (j = 0, len1 = ref3.length; j < len1; j++) { - bvar = ref3[j]; - lhs = (new Value(new ThisLiteral, [new Access(bvar)])).compile(o); - this.ctor.body.unshift(new Literal(`${lhs} = ${utility('bind', o)}(${lhs}, this)`)); + addInitializerMethod(name, assign) { + var method, methodName, variable; + variable = assign.variable; + method = assign.value; + method.isMethod = true; + method["static"] = variable.looksStatic(name); + method.klass = new IdentifierLiteral(name); + method.variable = variable; + if (method["static"]) { + method.name = variable.properties[0]; + } else { + methodName = variable.base; + method.ctor = methodName.value === 'constructor'; + method.name = new (methodName.isComplex() ? Index : Access)(methodName); + if (method.bound && method.ctor) { + method.error('Cannot define a constructor as a bound function'); + } + } + return method; } - }; - Class.prototype.addProperties = function(node, name, o) { - var acc, assign, base, exprs, func, props; - props = node.base.properties.slice(0); - exprs = (function() { - var results; - results = []; - while (assign = props.shift()) { - if (assign instanceof Assign) { - base = assign.variable.base; + addProperties(assigns) { + var assign, base, name, prototype, result, value, variable; + result = (function() { + var j, len1, results; + results = []; + for (j = 0, len1 = assigns.length; j < len1; j++) { + assign = assigns[j]; + variable = assign.variable; + base = variable != null ? variable.base : void 0; + value = assign.value; delete assign.context; - func = assign.value; - if (base.value === 'constructor') { - if (this.ctor) { - assign.error('cannot define more than one constructor in a class'); - } - if (func.bound) { - assign.error('cannot define a constructor as a bound function'); - } - if (func instanceof Code) { - assign = this.ctor = func; - } else { - this.externalCtor = o.classScope.freeVariable('ctor'); - assign = new Assign(new IdentifierLiteral(this.externalCtor), func); - } - } else { - if (assign.variable["this"]) { - func["static"] = true; - } else { - acc = base.isComplex() ? new Index(base) : new Access(base); - assign.variable = new Value(new IdentifierLiteral(name), [new Access(new PropertyName('prototype')), acc]); - if (func instanceof Code && func.bound) { - this.boundFuncs.push(base); - func.bound = false; - } + if (assign instanceof Comment || assign.isMethod) { + + } else if (base.value === 'constructor') { + if (value instanceof Code) { + base.error("constructors must be defined at the top level of a class body"); } + assign = this.externalCtor = new Assign(new Value, value); + } else if (!assign.variable["this"]) { + name = new (base.isComplex() ? Index : Access)(base); + prototype = new Access(new PropertyName('prototype')); + variable = new Value(new ThisLiteral(), [prototype, name]); + assign.variable = variable; } + results.push(assign); } - results.push(assign); - } - return results; - }).call(this); - return compact(exprs); + return results; + }).call(this); + return compact(result); + } + }; - Class.prototype.walkBody = function(name, o) { - return this.traverseChildren(false, (child) => { - var cont, exps, i, j, len1, node, ref3; - cont = true; - if (child instanceof Class) { - return false; - } - if (child instanceof Block) { - ref3 = exps = child.expressions; - for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { - node = ref3[i]; - if (node instanceof Assign && node.variable.looksStatic(name)) { - node.value["static"] = true; - } else if (node instanceof Value && node.isObject(true)) { - cont = false; - exps[i] = this.addProperties(node, name, o); - } + Class.__super__ = superClass.prototype; + + Class.prototype.children = ['variable', 'parent', 'body']; + + Class.prototype.defaultClassVariableName = '_Class'; + + return Class; + + })(Base); + + exports.ClassDeclaration = ClassDeclaration = (function(superClass) { + class ClassDeclaration extends superClass { + constructor(name1, parent1, body1) { + var body1, name1, parent1; + super(...arguments); + this.name = name1 != null ? name1 : null; + this.parent = parent1 != null ? parent1 : null; + this.body = body1 != null ? body1 : new Block; + } + + compileNode(o) { + var boundMethods, ctor, ref3, result; + ref3 = this.walkBody(), ctor = ref3[0], boundMethods = ref3[1]; + if (this.externalCtor || boundMethods.length) { + if (ctor == null) { + ctor = this.makeDefaultConstructor(); } - child.expressions = exps = flatten(exps); } - return cont && !(child instanceof Class); - }); - }; + if (ctor != null) { + ctor.noReturn = true; + } + this.proxyBoundMethods(o, ctor, boundMethods); + if (this.parent) { + this.ensureConstructorSuperCall(ctor, boundMethods); + } + o.indent += TAB; + result = []; + result.push(this.makeCode("class ")); + if (this.name) { + result.push(this.makeCode(`${this.name} `)); + } + if (this.parent) { + result.push(this.makeCode('extends '), ...this.parent.compileToFragments(o), this.makeCode(' ')); + } + result.push(this.makeCode(`{${(this.body.isEmpty() ? '' : '\n')}`)); + this.body.spaced = true; + if (!this.body.isEmpty()) { + result.push(...this.body.compileToFragments(o, LEVEL_TOP)); + } + result.push(this.makeCode(`${(this.body.isEmpty() ? '' : `\n${this.tab}`)}}`)); + return result; + } - Class.prototype.hoistDirectivePrologue = function() { - var expressions, index, node; - index = 0; - expressions = this.body.expressions; - while ((node = expressions[index]) && node instanceof Comment || node instanceof Value && node.isString()) { - ++index; + walkBody() { + var boundMethods, ctor, expression, j, len1, method, ref3, ref4; + ctor = null; + boundMethods = []; + ref3 = this.body.expressions; + for (j = 0, len1 = ref3.length; j < len1; j++) { + expression = ref3[j]; + method = (ref4 = expression.source) != null ? ref4 : expression; + if (method instanceof Comment) { + continue; + } + if (!(method instanceof Code)) { + method.error('A class declaration can only contain functions'); + } + if (ctor && method.ctor) { + method.error('Cannot define more than one constructor in a class'); + } + if (method.bound && !method["static"]) { + boundMethods.push(method.name); + method.bound = false; + } + if (method.ctor) { + ctor = method; + } + } + return [ctor, boundMethods]; } - return this.directives = expressions.splice(0, index); - }; - Class.prototype.ensureConstructor = function(name) { - if (!this.ctor) { - this.ctor = new Code; + makeDefaultConstructor() { + var applyArgs, applyCtor, ctor; + ctor = new Code; + ctor.ctor = true; + ctor.name = new Access(new PropertyName('constructor')); + ctor.isMethod = true; + this.body.unshift(ctor); if (this.externalCtor) { - this.ctor.body.push(new Literal(`${this.externalCtor}.apply(this, arguments)`)); - } else if (this.parent) { - this.ctor.body.push(new Literal(`${name}.__super__.constructor.apply(this, arguments)`)); + applyCtor = new Value(this.externalCtor, [new Access(new PropertyName('apply'))]); + applyArgs = [new ThisLiteral, new IdentifierLiteral('arguments')]; + ctor.body.push(new Call(applyCtor, applyArgs)); + ctor.body.makeReturn(); } - this.ctor.body.makeReturn(); - this.body.expressions.unshift(this.ctor); + return ctor; } - this.ctor.ctor = this.ctor.name = name; - this.ctor.klass = null; - return this.ctor.noReturn = true; - }; - Class.prototype.compileNode = function(o) { - var args, argumentsNode, func, jumpNode, klass, lname, name, superClass; - if (jumpNode = this.body.jumps()) { - jumpNode.error('Class bodies cannot contain pure statements'); - } - if (argumentsNode = this.body.contains(isLiteralArguments)) { - argumentsNode.error("Class bodies shouldn't reference arguments"); - } - name = this.determineName(); - lname = new IdentifierLiteral(name); - func = new Code([], Block.wrap([this.body])); - args = []; - o.classScope = func.makeScope(o.scope); - this.hoistDirectivePrologue(); - this.setContext(name); - this.walkBody(name, o); - this.ensureConstructor(name); - this.addBoundFunctions(o); - this.body.spaced = true; - this.body.expressions.push(lname); - if (this.parent) { - superClass = new IdentifierLiteral(o.classScope.freeVariable('superClass', { - reserve: false - })); - this.body.expressions.unshift(new Extends(lname, superClass)); - func.params.push(new Param(superClass)); - args.push(this.parent); - } - this.body.expressions.unshift(...this.directives); - klass = new Parens(new Call(func, args)); - if (this.variable) { - klass = new Assign(this.variable, klass, null, { - moduleDeclaration: this.moduleDeclaration - }); + proxyBoundMethods(o, ctor, boundMethods) { + var j, name; + if (!ctor || boundMethods.length === 0) { + return; + } + for (j = boundMethods.length - 1; j >= 0; j += -1) { + name = boundMethods[j]; + name = new Value(new ThisLiteral, [name]).compile(o); + ctor.body.unshift(new Literal(`${name} = ${utility('bind', o)}(${name}, this)`)); + } + return null; + } + + ensureConstructorSuperCall(ctor, boundMethods) { + var hasThisParam, j, len1, param, ref3, superCall; + if (!ctor) { + return; + } + hasThisParam = false; + ref3 = ctor.params; + for (j = 0, len1 = ref3.length; j < len1; j++) { + param = ref3[j]; + if (param.name["this"]) { + hasThisParam = true; + } + } + superCall = ctor.superCall(); + if (hasThisParam && superCall) { + superCall.error('super not allowed with `@` parameters in derived constructors'); + } + if (boundMethods.length && superCall) { + superCall.error('super not allowed with bound functions in derived constructors'); + } + if (!superCall) { + return ctor.body.unshift(new SuperCall); + } } - return klass.compileToFragments(o); + }; - return Class; + ClassDeclaration.__super__ = superClass.prototype; + + ClassDeclaration.prototype.children = ['parent', 'body']; + + return ClassDeclaration; })(Base); - exports.ModuleDeclaration = ModuleDeclaration = (function(superClass1) { - extend1(ModuleDeclaration, superClass1); + exports.ModuleDeclaration = ModuleDeclaration = (function(superClass) { + class ModuleDeclaration extends superClass { + constructor(clause, source1) { + super(...arguments); + this.clause = clause; + this.source = source1; + this.checkSource(); + } + + checkSource() { + if ((this.source != null) && this.source instanceof StringWithInterpolations) { + return this.source.error('the name of the module to be imported from must be an uninterpolated string'); + } + } - function ModuleDeclaration(clause, source1) { - this.clause = clause; - this.source = source1; - this.checkSource(); - } + checkScope(o, moduleDeclarationType) { + if (o.indent.length !== 0) { + return this.error(`${moduleDeclarationType} statements must be at top-level scope`); + } + } + + }; + + ModuleDeclaration.__super__ = superClass.prototype; ModuleDeclaration.prototype.children = ['clause', 'source']; @@ -1774,492 +2089,408 @@ ModuleDeclaration.prototype.makeReturn = THIS; - ModuleDeclaration.prototype.checkSource = function() { - if ((this.source != null) && this.source instanceof StringWithInterpolations) { - return this.source.error('the name of the module to be imported from must be an uninterpolated string'); - } - }; - - ModuleDeclaration.prototype.checkScope = function(o, moduleDeclarationType) { - if (o.indent.length !== 0) { - return this.error(`${moduleDeclarationType} statements must be at top-level scope`); - } - }; - return ModuleDeclaration; })(Base); - exports.ImportDeclaration = ImportDeclaration = (function(superClass1) { - extend1(ImportDeclaration, superClass1); - - function ImportDeclaration() { - return ImportDeclaration.__super__.constructor.apply(this, arguments); - } - - ImportDeclaration.prototype.compileNode = function(o) { - var code, ref3; - this.checkScope(o, 'import'); - o.importedSymbols = []; - code = []; - code.push(this.makeCode(`${this.tab}import `)); - if (this.clause != null) { - code.push(...this.clause.compileNode(o)); - } - if (((ref3 = this.source) != null ? ref3.value : void 0) != null) { - if (this.clause !== null) { - code.push(this.makeCode(' from ')); + exports.ImportDeclaration = ImportDeclaration = (function(superClass) { + class ImportDeclaration extends superClass { + compileNode(o) { + var code, ref3; + this.checkScope(o, 'import'); + o.importedSymbols = []; + code = []; + code.push(this.makeCode(`${this.tab}import `)); + if (this.clause != null) { + code.push(...this.clause.compileNode(o)); + } + if (((ref3 = this.source) != null ? ref3.value : void 0) != null) { + if (this.clause !== null) { + code.push(this.makeCode(' from ')); + } + code.push(this.makeCode(this.source.value)); } - code.push(this.makeCode(this.source.value)); + code.push(this.makeCode(';')); + return code; } - code.push(this.makeCode(';')); - return code; + }; + ImportDeclaration.__super__ = superClass.prototype; + return ImportDeclaration; })(ModuleDeclaration); - exports.ImportClause = ImportClause = (function(superClass1) { - extend1(ImportClause, superClass1); - - function ImportClause(defaultBinding, namedImports) { - this.defaultBinding = defaultBinding; - this.namedImports = namedImports; - } - - ImportClause.prototype.children = ['defaultBinding', 'namedImports']; - - ImportClause.prototype.compileNode = function(o) { - var code; - code = []; - if (this.defaultBinding != null) { - code.push(...this.defaultBinding.compileNode(o)); + exports.ImportClause = ImportClause = (function(superClass) { + class ImportClause extends superClass { + constructor(defaultBinding, namedImports) { + super(...arguments); + this.defaultBinding = defaultBinding; + this.namedImports = namedImports; + } + + compileNode(o) { + var code; + code = []; + if (this.defaultBinding != null) { + code.push(...this.defaultBinding.compileNode(o)); + if (this.namedImports != null) { + code.push(this.makeCode(', ')); + } + } if (this.namedImports != null) { - code.push(this.makeCode(', ')); + code.push(...this.namedImports.compileNode(o)); } + return code; } - if (this.namedImports != null) { - code.push(...this.namedImports.compileNode(o)); - } - return code; + }; - return ImportClause; + ImportClause.__super__ = superClass.prototype; - })(Base); + ImportClause.prototype.children = ['defaultBinding', 'namedImports']; - exports.ExportDeclaration = ExportDeclaration = (function(superClass1) { - extend1(ExportDeclaration, superClass1); + return ImportClause; - function ExportDeclaration() { - return ExportDeclaration.__super__.constructor.apply(this, arguments); - } + })(Base); - ExportDeclaration.prototype.compileNode = function(o) { - var code, ref3; - this.checkScope(o, 'export'); - code = []; - code.push(this.makeCode(`${this.tab}export `)); - if (this instanceof ExportDefaultDeclaration) { - code.push(this.makeCode('default ')); - } - if (!(this instanceof ExportDefaultDeclaration) && (this.clause instanceof Assign || this.clause instanceof Class)) { - if (this.clause instanceof Class && !this.clause.variable) { - this.clause.error('anonymous classes cannot be exported'); + exports.ExportDeclaration = ExportDeclaration = (function(superClass) { + class ExportDeclaration extends superClass { + compileNode(o) { + var code, ref3; + this.checkScope(o, 'export'); + code = []; + code.push(this.makeCode(`${this.tab}export `)); + if (this instanceof ExportDefaultDeclaration) { + code.push(this.makeCode('default ')); } - code.push(this.makeCode('var ')); - this.clause.moduleDeclaration = 'export'; - } - if ((this.clause.body != null) && this.clause.body instanceof Block) { - code = code.concat(this.clause.compileToFragments(o, LEVEL_TOP)); - } else { - code = code.concat(this.clause.compileNode(o)); - } - if (((ref3 = this.source) != null ? ref3.value : void 0) != null) { - code.push(this.makeCode(` from ${this.source.value}`)); + if (!(this instanceof ExportDefaultDeclaration) && (this.clause instanceof Assign || this.clause instanceof Class)) { + if (this.clause instanceof Class && !this.clause.variable) { + this.clause.error('anonymous classes cannot be exported'); + } + code.push(this.makeCode('var ')); + this.clause.moduleDeclaration = 'export'; + } + if ((this.clause.body != null) && this.clause.body instanceof Block) { + code = code.concat(this.clause.compileToFragments(o, LEVEL_TOP)); + } else { + code = code.concat(this.clause.compileNode(o)); + } + if (((ref3 = this.source) != null ? ref3.value : void 0) != null) { + code.push(this.makeCode(` from ${this.source.value}`)); + } + code.push(this.makeCode(';')); + return code; } - code.push(this.makeCode(';')); - return code; + }; + ExportDeclaration.__super__ = superClass.prototype; + return ExportDeclaration; })(ModuleDeclaration); - exports.ExportNamedDeclaration = ExportNamedDeclaration = (function(superClass1) { - extend1(ExportNamedDeclaration, superClass1); + exports.ExportNamedDeclaration = ExportNamedDeclaration = (function(superClass) { + class ExportNamedDeclaration extends superClass {}; - function ExportNamedDeclaration() { - return ExportNamedDeclaration.__super__.constructor.apply(this, arguments); - } + ExportNamedDeclaration.__super__ = superClass.prototype; return ExportNamedDeclaration; })(ExportDeclaration); - exports.ExportDefaultDeclaration = ExportDefaultDeclaration = (function(superClass1) { - extend1(ExportDefaultDeclaration, superClass1); + exports.ExportDefaultDeclaration = ExportDefaultDeclaration = (function(superClass) { + class ExportDefaultDeclaration extends superClass {}; - function ExportDefaultDeclaration() { - return ExportDefaultDeclaration.__super__.constructor.apply(this, arguments); - } + ExportDefaultDeclaration.__super__ = superClass.prototype; return ExportDefaultDeclaration; })(ExportDeclaration); - exports.ExportAllDeclaration = ExportAllDeclaration = (function(superClass1) { - extend1(ExportAllDeclaration, superClass1); + exports.ExportAllDeclaration = ExportAllDeclaration = (function(superClass) { + class ExportAllDeclaration extends superClass {}; - function ExportAllDeclaration() { - return ExportAllDeclaration.__super__.constructor.apply(this, arguments); - } + ExportAllDeclaration.__super__ = superClass.prototype; return ExportAllDeclaration; })(ExportDeclaration); - exports.ModuleSpecifierList = ModuleSpecifierList = (function(superClass1) { - extend1(ModuleSpecifierList, superClass1); - - function ModuleSpecifierList(specifiers) { - this.specifiers = specifiers; - } - - ModuleSpecifierList.prototype.children = ['specifiers']; + exports.ModuleSpecifierList = ModuleSpecifierList = (function(superClass) { + class ModuleSpecifierList extends superClass { + constructor(specifiers) { + super(...arguments); + this.specifiers = specifiers; + } - ModuleSpecifierList.prototype.compileNode = function(o) { - var code, compiledList, fragments, index, j, len1, specifier; - code = []; - o.indent += TAB; - compiledList = (function() { - var j, len1, ref3, results; - ref3 = this.specifiers; - results = []; - for (j = 0, len1 = ref3.length; j < len1; j++) { - specifier = ref3[j]; - results.push(specifier.compileToFragments(o, LEVEL_LIST)); - } - return results; - }).call(this); - if (this.specifiers.length !== 0) { - code.push(this.makeCode(`{\n${o.indent}`)); - for (index = j = 0, len1 = compiledList.length; j < len1; index = ++j) { - fragments = compiledList[index]; - if (index) { - code.push(this.makeCode(`,\n${o.indent}`)); + compileNode(o) { + var code, compiledList, fragments, index, j, len1, specifier; + code = []; + o.indent += TAB; + compiledList = (function() { + var j, len1, ref3, results; + ref3 = this.specifiers; + results = []; + for (j = 0, len1 = ref3.length; j < len1; j++) { + specifier = ref3[j]; + results.push(specifier.compileToFragments(o, LEVEL_LIST)); + } + return results; + }).call(this); + if (this.specifiers.length !== 0) { + code.push(this.makeCode(`{\n${o.indent}`)); + for (index = j = 0, len1 = compiledList.length; j < len1; index = ++j) { + fragments = compiledList[index]; + if (index) { + code.push(this.makeCode(`,\n${o.indent}`)); + } + code.push(...fragments); } - code.push(...fragments); + code.push(this.makeCode("\n}")); + } else { + code.push(this.makeCode('{}')); } - code.push(this.makeCode("\n}")); - } else { - code.push(this.makeCode('{}')); + return code; } - return code; + }; + ModuleSpecifierList.__super__ = superClass.prototype; + + ModuleSpecifierList.prototype.children = ['specifiers']; + return ModuleSpecifierList; })(Base); - exports.ImportSpecifierList = ImportSpecifierList = (function(superClass1) { - extend1(ImportSpecifierList, superClass1); + exports.ImportSpecifierList = ImportSpecifierList = (function(superClass) { + class ImportSpecifierList extends superClass {}; - function ImportSpecifierList() { - return ImportSpecifierList.__super__.constructor.apply(this, arguments); - } + ImportSpecifierList.__super__ = superClass.prototype; return ImportSpecifierList; })(ModuleSpecifierList); - exports.ExportSpecifierList = ExportSpecifierList = (function(superClass1) { - extend1(ExportSpecifierList, superClass1); + exports.ExportSpecifierList = ExportSpecifierList = (function(superClass) { + class ExportSpecifierList extends superClass {}; - function ExportSpecifierList() { - return ExportSpecifierList.__super__.constructor.apply(this, arguments); - } + ExportSpecifierList.__super__ = superClass.prototype; return ExportSpecifierList; })(ModuleSpecifierList); - exports.ModuleSpecifier = ModuleSpecifier = (function(superClass1) { - extend1(ModuleSpecifier, superClass1); + exports.ModuleSpecifier = ModuleSpecifier = (function(superClass) { + class ModuleSpecifier extends superClass { + constructor(original, alias, moduleDeclarationType1) { + super(...arguments); + this.original = original; + this.alias = alias; + this.moduleDeclarationType = moduleDeclarationType1; + this.identifier = this.alias != null ? this.alias.value : this.original.value; + } + + compileNode(o) { + var code; + o.scope.add(this.identifier, this.moduleDeclarationType); + code = []; + code.push(this.makeCode(this.original.value)); + if (this.alias != null) { + code.push(this.makeCode(` as ${this.alias.value}`)); + } + return code; + } + + }; - function ModuleSpecifier(original, alias, moduleDeclarationType1) { - this.original = original; - this.alias = alias; - this.moduleDeclarationType = moduleDeclarationType1; - this.identifier = this.alias != null ? this.alias.value : this.original.value; - } + ModuleSpecifier.__super__ = superClass.prototype; ModuleSpecifier.prototype.children = ['original', 'alias']; - ModuleSpecifier.prototype.compileNode = function(o) { - var code; - o.scope.add(this.identifier, this.moduleDeclarationType); - code = []; - code.push(this.makeCode(this.original.value)); - if (this.alias != null) { - code.push(this.makeCode(` as ${this.alias.value}`)); - } - return code; - }; - return ModuleSpecifier; })(Base); - exports.ImportSpecifier = ImportSpecifier = (function(superClass1) { - extend1(ImportSpecifier, superClass1); - - function ImportSpecifier(imported, local) { - ImportSpecifier.__super__.constructor.call(this, imported, local, 'import'); - } + exports.ImportSpecifier = ImportSpecifier = (function(superClass) { + class ImportSpecifier extends superClass { + constructor(imported, local) { + super(imported, local, 'import'); + } - ImportSpecifier.prototype.compileNode = function(o) { - var ref3; - if ((ref3 = this.identifier, indexOf.call(o.importedSymbols, ref3) >= 0) || o.scope.check(this.identifier)) { - this.error(`'${this.identifier}' has already been declared`); - } else { - o.importedSymbols.push(this.identifier); + compileNode(o) { + var ref3; + if ((ref3 = this.identifier, indexOf.call(o.importedSymbols, ref3) >= 0) || o.scope.check(this.identifier)) { + this.error(`'${this.identifier}' has already been declared`); + } else { + o.importedSymbols.push(this.identifier); + } + return ImportSpecifier.__super__.compileNode.call(this, o); } - return ImportSpecifier.__super__.compileNode.call(this, o); + }; + ImportSpecifier.__super__ = superClass.prototype; + return ImportSpecifier; })(ModuleSpecifier); - exports.ImportDefaultSpecifier = ImportDefaultSpecifier = (function(superClass1) { - extend1(ImportDefaultSpecifier, superClass1); + exports.ImportDefaultSpecifier = ImportDefaultSpecifier = (function(superClass) { + class ImportDefaultSpecifier extends superClass {}; - function ImportDefaultSpecifier() { - return ImportDefaultSpecifier.__super__.constructor.apply(this, arguments); - } + ImportDefaultSpecifier.__super__ = superClass.prototype; return ImportDefaultSpecifier; })(ImportSpecifier); - exports.ImportNamespaceSpecifier = ImportNamespaceSpecifier = (function(superClass1) { - extend1(ImportNamespaceSpecifier, superClass1); + exports.ImportNamespaceSpecifier = ImportNamespaceSpecifier = (function(superClass) { + class ImportNamespaceSpecifier extends superClass {}; - function ImportNamespaceSpecifier() { - return ImportNamespaceSpecifier.__super__.constructor.apply(this, arguments); - } + ImportNamespaceSpecifier.__super__ = superClass.prototype; return ImportNamespaceSpecifier; })(ImportSpecifier); - exports.ExportSpecifier = ExportSpecifier = (function(superClass1) { - extend1(ExportSpecifier, superClass1); + exports.ExportSpecifier = ExportSpecifier = (function(superClass) { + class ExportSpecifier extends superClass { + constructor(local, exported) { + super(local, exported, 'export'); + } - function ExportSpecifier(local, exported) { - ExportSpecifier.__super__.constructor.call(this, local, exported, 'export'); - } + }; + + ExportSpecifier.__super__ = superClass.prototype; return ExportSpecifier; })(ModuleSpecifier); - exports.Assign = Assign = (function(superClass1) { - extend1(Assign, superClass1); - - function Assign(variable1, value1, context, options = {}) { - this.variable = variable1; - this.value = value1; - this.context = context; - this.param = options.param, this.subpattern = options.subpattern, this.operatorToken = options.operatorToken, this.moduleDeclaration = options.moduleDeclaration; - } - - Assign.prototype.children = ['variable', 'value']; + exports.Assign = Assign = (function(superClass) { + class Assign extends superClass { + constructor(variable1, value1, context, options = {}) { + super(...arguments); + this.variable = variable1; + this.value = value1; + this.context = context; + this.param = options.param, this.subpattern = options.subpattern, this.operatorToken = options.operatorToken, this.moduleDeclaration = options.moduleDeclaration; + } - Assign.prototype.isStatement = function(o) { - return (o != null ? o.level : void 0) === LEVEL_TOP && (this.context != null) && (this.moduleDeclaration || indexOf.call(this.context, "?") >= 0); - }; + isStatement(o) { + return (o != null ? o.level : void 0) === LEVEL_TOP && (this.context != null) && (this.moduleDeclaration || indexOf.call(this.context, "?") >= 0); + } - Assign.prototype.checkAssignability = function(o, varBase) { - if (Object.prototype.hasOwnProperty.call(o.scope.positions, varBase.value) && o.scope.variables[o.scope.positions[varBase.value]].type === 'import') { - return varBase.error(`'${varBase.value}' is read-only`); + checkAssignability(o, varBase) { + if (Object.prototype.hasOwnProperty.call(o.scope.positions, varBase.value) && o.scope.variables[o.scope.positions[varBase.value]].type === 'import') { + return varBase.error(`'${varBase.value}' is read-only`); + } } - }; - Assign.prototype.assigns = function(name) { - return this[this.context === 'object' ? 'value' : 'variable'].assigns(name); - }; + assigns(name) { + return this[this.context === 'object' ? 'value' : 'variable'].assigns(name); + } - Assign.prototype.unfoldSoak = function(o) { - return unfoldSoak(o, this, 'variable'); - }; + unfoldSoak(o) { + return unfoldSoak(o, this, 'variable'); + } - Assign.prototype.compileNode = function(o) { - var answer, compiledName, isValue, j, name, properties, prototype, ref3, ref4, ref5, ref6, ref7, ref8, val, varBase; - if (isValue = this.variable instanceof Value) { - if (this.variable.isArray() || this.variable.isObject()) { - return this.compilePatternMatch(o); - } - if (this.variable.isSplice()) { - return this.compileSplice(o); - } - if ((ref3 = this.context) === '||=' || ref3 === '&&=' || ref3 === '?=') { - return this.compileConditional(o); - } - if ((ref4 = this.context) === '**=' || ref4 === '//=' || ref4 === '%%=') { - return this.compileSpecialMath(o); + compileNode(o) { + var answer, compiledName, isValue, j, name, properties, prototype, ref3, ref4, ref5, ref6, ref7, ref8, val, varBase; + if (isValue = this.variable instanceof Value) { + if (this.variable.isArray() || this.variable.isObject()) { + return this.compilePatternMatch(o); + } + if (this.variable.isSplice()) { + return this.compileSplice(o); + } + if ((ref3 = this.context) === '||=' || ref3 === '&&=' || ref3 === '?=') { + return this.compileConditional(o); + } + if ((ref4 = this.context) === '**=' || ref4 === '//=' || ref4 === '%%=') { + return this.compileSpecialMath(o); + } } - } - if (this.value instanceof Code) { - if (this.value["static"]) { - this.value.klass = this.variable.base; - this.value.name = this.variable.properties[0]; - this.value.variable = this.variable; - } else if (((ref5 = this.variable.properties) != null ? ref5.length : void 0) >= 2) { - ref6 = this.variable.properties, properties = 3 <= ref6.length ? slice.call(ref6, 0, j = ref6.length - 2) : (j = 0, []), prototype = ref6[j++], name = ref6[j++]; - if (((ref7 = prototype.name) != null ? ref7.value : void 0) === 'prototype') { - this.value.klass = new Value(this.variable.base, properties); - this.value.name = name; + if (this.value instanceof Code) { + if (this.value["static"]) { + this.value.klass = this.variable.base; + this.value.name = this.variable.properties[0]; this.value.variable = this.variable; + } else if (((ref5 = this.variable.properties) != null ? ref5.length : void 0) >= 2) { + ref6 = this.variable.properties, properties = 3 <= ref6.length ? slice.call(ref6, 0, j = ref6.length - 2) : (j = 0, []), prototype = ref6[j++], name = ref6[j++]; + if (((ref7 = prototype.name) != null ? ref7.value : void 0) === 'prototype') { + this.value.klass = new Value(this.variable.base, properties); + this.value.name = name; + this.value.variable = this.variable; + } } } - } - if (!this.context) { - varBase = this.variable.unwrapAll(); - if (!varBase.isAssignable()) { - this.variable.error(`'${this.variable.compile(o)}' can't be assigned`); - } - if (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) { - if (this.moduleDeclaration) { - this.checkAssignability(o, varBase); - o.scope.add(varBase.value, this.moduleDeclaration); - } else if (this.param) { - o.scope.add(varBase.value, 'var'); - } else { - this.checkAssignability(o, varBase); - o.scope.find(varBase.value); + if (!this.context) { + varBase = this.variable.unwrapAll(); + if (!varBase.isAssignable()) { + this.variable.error(`'${this.variable.compile(o)}' can't be assigned`); + } + if (!(typeof varBase.hasProperties === "function" ? varBase.hasProperties() : void 0)) { + if (this.moduleDeclaration) { + this.checkAssignability(o, varBase); + o.scope.add(varBase.value, this.moduleDeclaration); + } else if (this.param) { + o.scope.add(varBase.value, 'var'); + } else { + this.checkAssignability(o, varBase); + o.scope.find(varBase.value); + } } } - } - val = this.value.compileToFragments(o, LEVEL_LIST); - if (isValue && this.variable.base instanceof Obj) { - this.variable.front = true; - } - compiledName = this.variable.compileToFragments(o, LEVEL_LIST); - if (this.context === 'object') { - if (this.variable.isComplex()) { - compiledName.unshift(this.makeCode('[')); - compiledName.push(this.makeCode(']')); - } else if (ref8 = fragmentsToText(compiledName), indexOf.call(JS_FORBIDDEN, ref8) >= 0) { - compiledName.unshift(this.makeCode('"')); - compiledName.push(this.makeCode('"')); - } - return compiledName.concat(this.makeCode(": "), val); - } - answer = compiledName.concat(this.makeCode(` ${this.context || '='} `), val); - if (o.level <= LEVEL_LIST) { - return answer; - } else { - return this.wrapInBraces(answer); - } - }; - - Assign.prototype.compilePatternMatch = function(o) { - var acc, assigns, code, defaultValue, expandedIdx, fragments, i, idx, isObject, ivar, j, len1, message, name, obj, objects, olen, ref, ref3, ref4, ref5, ref6, rest, top, val, value, vvar, vvarText; - top = o.level === LEVEL_TOP; - value = this.value; - objects = this.variable.base.objects; - if (!(olen = objects.length)) { - code = value.compileToFragments(o); - if (o.level >= LEVEL_OP) { - return this.wrapInBraces(code); - } else { - return code; + val = this.value.compileToFragments(o, LEVEL_LIST); + if (isValue && this.variable.base instanceof Obj) { + this.variable.front = true; } - } - obj = objects[0]; - if (olen === 1 && obj instanceof Expansion) { - obj.error('Destructuring assignment has no target'); - } - isObject = this.variable.isObject(); - if (top && olen === 1 && !(obj instanceof Splat)) { - defaultValue = null; - if (obj instanceof Assign && obj.context === 'object') { - ref3 = obj, (ref4 = ref3.variable, idx = ref4.base), obj = ref3.value; - if (obj instanceof Assign) { - defaultValue = obj.value; - obj = obj.variable; + compiledName = this.variable.compileToFragments(o, LEVEL_LIST); + if (this.context === 'object') { + if (this.variable.isComplex()) { + compiledName.unshift(this.makeCode('[')); + compiledName.push(this.makeCode(']')); + } else if (ref8 = fragmentsToText(compiledName), indexOf.call(JS_FORBIDDEN, ref8) >= 0) { + compiledName.unshift(this.makeCode('"')); + compiledName.push(this.makeCode('"')); } + return compiledName.concat(this.makeCode(": "), val); + } + answer = compiledName.concat(this.makeCode(` ${this.context || '='} `), val); + if (o.level <= LEVEL_LIST) { + return answer; } else { - if (obj instanceof Assign) { - defaultValue = obj.value; - obj = obj.variable; - } - idx = isObject ? obj["this"] ? obj.properties[0].name : new PropertyName(obj.unwrap().value) : new NumberLiteral(0); + return this.wrapInBraces(answer); } - acc = idx.unwrap() instanceof PropertyName; - value = new Value(value); - value.properties.push(new (acc ? Access : Index)(idx)); - message = isUnassignable(obj.unwrap().value); - if (message) { - obj.error(message); - } - if (defaultValue) { - value = new Op('?', value, defaultValue); - } - return new Assign(obj, value, null, { - param: this.param - }).compileToFragments(o, LEVEL_TOP); - } - vvar = value.compileToFragments(o, LEVEL_LIST); - vvarText = fragmentsToText(vvar); - assigns = []; - expandedIdx = false; - if (!(value.unwrap() instanceof IdentifierLiteral) || this.variable.assigns(vvarText)) { - assigns.push([this.makeCode(`${(ref = o.scope.freeVariable('ref'))} = `), ...vvar]); - vvar = [this.makeCode(ref)]; - vvarText = ref; - } - for (i = j = 0, len1 = objects.length; j < len1; i = ++j) { - obj = objects[i]; - idx = i; - if (!expandedIdx && obj instanceof Splat) { - name = obj.name.unwrap().value; - obj = obj.unwrap(); - val = `${olen} <= ${vvarText}.length ? ${utility('slice', o)}.call(${vvarText}, ${i}`; - if (rest = olen - i - 1) { - ivar = o.scope.freeVariable('i', { - single: true - }); - val += `, ${ivar} = ${vvarText}.length - ${rest}) : (${ivar} = ${i}, [])`; + } + + compilePatternMatch(o) { + var acc, assigns, code, defaultValue, expandedIdx, fragments, i, idx, isObject, ivar, j, len1, message, name, obj, objects, olen, ref, ref3, ref4, ref5, ref6, rest, top, val, value, vvar, vvarText; + top = o.level === LEVEL_TOP; + value = this.value; + objects = this.variable.base.objects; + if (!(olen = objects.length)) { + code = value.compileToFragments(o); + if (o.level >= LEVEL_OP) { + return this.wrapInBraces(code); } else { - val += ") : []"; - } - val = new Literal(val); - expandedIdx = `${ivar}++`; - } else if (!expandedIdx && obj instanceof Expansion) { - if (rest = olen - i - 1) { - if (rest === 1) { - expandedIdx = `${vvarText}.length - 1`; - } else { - ivar = o.scope.freeVariable('i', { - single: true - }); - val = new Literal(`${ivar} = ${vvarText}.length - ${rest}`); - expandedIdx = `${ivar}++`; - assigns.push(val.compileToFragments(o, LEVEL_LIST)); - } - } - continue; - } else { - if (obj instanceof Splat || obj instanceof Expansion) { - obj.error("multiple splats/expansions are disallowed in an assignment"); + return code; } + } + obj = objects[0]; + if (olen === 1 && obj instanceof Expansion) { + obj.error('Destructuring assignment has no target'); + } + isObject = this.variable.isObject(); + if (top && olen === 1 && !(obj instanceof Splat)) { defaultValue = null; if (obj instanceof Assign && obj.context === 'object') { - ref5 = obj, (ref6 = ref5.variable, idx = ref6.base), obj = ref5.value; + ref3 = obj, (ref4 = ref3.variable, idx = ref4.base), obj = ref3.value; if (obj instanceof Assign) { defaultValue = obj.value; obj = obj.variable; @@ -2269,917 +2500,1078 @@ defaultValue = obj.value; obj = obj.variable; } - idx = isObject ? obj["this"] ? obj.properties[0].name : new PropertyName(obj.unwrap().value) : new Literal(expandedIdx || idx); + idx = isObject ? obj["this"] ? obj.properties[0].name : new PropertyName(obj.unwrap().value) : new NumberLiteral(0); } - name = obj.unwrap().value; acc = idx.unwrap() instanceof PropertyName; - val = new Value(new Literal(vvarText), [new (acc ? Access : Index)(idx)]); + value = new Value(value); + value.properties.push(new (acc ? Access : Index)(idx)); + message = isUnassignable(obj.unwrap().value); + if (message) { + obj.error(message); + } if (defaultValue) { - val = new Op('?', val, defaultValue); + value = new Op('?', value, defaultValue); } + return new Assign(obj, value, null, { + param: this.param + }).compileToFragments(o, LEVEL_TOP); } - if (name != null) { - message = isUnassignable(name); - if (message) { - obj.error(message); + vvar = value.compileToFragments(o, LEVEL_LIST); + vvarText = fragmentsToText(vvar); + assigns = []; + expandedIdx = false; + if (!(value.unwrap() instanceof IdentifierLiteral) || this.variable.assigns(vvarText)) { + assigns.push([this.makeCode(`${(ref = o.scope.freeVariable('ref'))} = `), ...vvar]); + vvar = [this.makeCode(ref)]; + vvarText = ref; + } + for (i = j = 0, len1 = objects.length; j < len1; i = ++j) { + obj = objects[i]; + idx = i; + if (!expandedIdx && obj instanceof Splat) { + name = obj.name.unwrap().value; + obj = obj.unwrap(); + val = `${olen} <= ${vvarText}.length ? ${utility('slice', o)}.call(${vvarText}, ${i}`; + if (rest = olen - i - 1) { + ivar = o.scope.freeVariable('i', { + single: true + }); + val += `, ${ivar} = ${vvarText}.length - ${rest}) : (${ivar} = ${i}, [])`; + } else { + val += ") : []"; + } + val = new Literal(val); + expandedIdx = `${ivar}++`; + } else if (!expandedIdx && obj instanceof Expansion) { + if (rest = olen - i - 1) { + if (rest === 1) { + expandedIdx = `${vvarText}.length - 1`; + } else { + ivar = o.scope.freeVariable('i', { + single: true + }); + val = new Literal(`${ivar} = ${vvarText}.length - ${rest}`); + expandedIdx = `${ivar}++`; + assigns.push(val.compileToFragments(o, LEVEL_LIST)); + } + } + continue; + } else { + if (obj instanceof Splat || obj instanceof Expansion) { + obj.error("multiple splats/expansions are disallowed in an assignment"); + } + defaultValue = null; + if (obj instanceof Assign && obj.context === 'object') { + ref5 = obj, (ref6 = ref5.variable, idx = ref6.base), obj = ref5.value; + if (obj instanceof Assign) { + defaultValue = obj.value; + obj = obj.variable; + } + } else { + if (obj instanceof Assign) { + defaultValue = obj.value; + obj = obj.variable; + } + idx = isObject ? obj["this"] ? obj.properties[0].name : new PropertyName(obj.unwrap().value) : new Literal(expandedIdx || idx); + } + name = obj.unwrap().value; + acc = idx.unwrap() instanceof PropertyName; + val = new Value(new Literal(vvarText), [new (acc ? Access : Index)(idx)]); + if (defaultValue) { + val = new Op('?', val, defaultValue); + } } + if (name != null) { + message = isUnassignable(name); + if (message) { + obj.error(message); + } + } + assigns.push(new Assign(obj, val, null, { + param: this.param, + subpattern: true + }).compileToFragments(o, LEVEL_LIST)); } - assigns.push(new Assign(obj, val, null, { - param: this.param, - subpattern: true - }).compileToFragments(o, LEVEL_LIST)); - } - if (!(top || this.subpattern)) { - assigns.push(vvar); - } - fragments = this.joinFragmentArrays(assigns, ', '); - if (o.level < LEVEL_LIST) { - return fragments; - } else { - return this.wrapInBraces(fragments); - } - }; - - Assign.prototype.compileConditional = function(o) { - var fragments, left, ref3, right; - ref3 = this.variable.cacheReference(o), left = ref3[0], right = ref3[1]; - if (!left.properties.length && left.base instanceof Literal && !(left.base instanceof ThisLiteral) && !o.scope.check(left.base.value)) { - this.variable.error(`the variable \"${left.base.value}\" can't be assigned with ${this.context} because it has not been declared before`); - } - if (indexOf.call(this.context, "?") >= 0) { - o.isExistentialEquals = true; - return new If(new Existence(left), right, { - type: 'if' - }).addElse(new Assign(right, this.value, '=')).compileToFragments(o); - } else { - fragments = new Op(this.context.slice(0, -1), left, new Assign(right, this.value, '=')).compileToFragments(o); - if (o.level <= LEVEL_LIST) { + if (!(top || this.subpattern)) { + assigns.push(vvar); + } + fragments = this.joinFragmentArrays(assigns, ', '); + if (o.level < LEVEL_LIST) { return fragments; } else { return this.wrapInBraces(fragments); } } - }; - - Assign.prototype.compileSpecialMath = function(o) { - var left, ref3, right; - ref3 = this.variable.cacheReference(o), left = ref3[0], right = ref3[1]; - return new Assign(left, new Op(this.context.slice(0, -1), right, this.value)).compileToFragments(o); - }; - Assign.prototype.compileSplice = function(o) { - var answer, exclusive, from, fromDecl, fromRef, name, ref3, ref4, ref5, to, valDef, valRef; - ref3 = this.variable.properties.pop().range, from = ref3.from, to = ref3.to, exclusive = ref3.exclusive; - name = this.variable.compile(o); - if (from) { - ref4 = this.cacheToCodeFragments(from.cache(o, LEVEL_OP)), fromDecl = ref4[0], fromRef = ref4[1]; - } else { - fromDecl = fromRef = '0'; - } - if (to) { - if ((from != null ? from.isNumber() : void 0) && to.isNumber()) { - to = to.compile(o) - fromRef; - if (!exclusive) { - to += 1; - } + compileConditional(o) { + var fragments, left, ref3, right; + ref3 = this.variable.cacheReference(o), left = ref3[0], right = ref3[1]; + if (!left.properties.length && left.base instanceof Literal && !(left.base instanceof ThisLiteral) && !o.scope.check(left.base.value)) { + this.variable.error(`the variable \"${left.base.value}\" can't be assigned with ${this.context} because it has not been declared before`); + } + if (indexOf.call(this.context, "?") >= 0) { + o.isExistentialEquals = true; + return new If(new Existence(left), right, { + type: 'if' + }).addElse(new Assign(right, this.value, '=')).compileToFragments(o); } else { - to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef; - if (!exclusive) { - to += ' + 1'; + fragments = new Op(this.context.slice(0, -1), left, new Assign(right, this.value, '=')).compileToFragments(o); + if (o.level <= LEVEL_LIST) { + return fragments; + } else { + return this.wrapInBraces(fragments); } } - } else { - to = "9e9"; - } - ref5 = this.value.cache(o, LEVEL_LIST), valDef = ref5[0], valRef = ref5[1]; - answer = [].concat(this.makeCode(`[].splice.apply(${name}, [${fromDecl}, ${to}].concat(`), valDef, this.makeCode(")), "), valRef); - if (o.level > LEVEL_TOP) { - return this.wrapInBraces(answer); - } else { - return answer; } - }; - - return Assign; - })(Base); - - exports.Code = Code = (function(superClass1) { - extend1(Code, superClass1); + compileSpecialMath(o) { + var left, ref3, right; + ref3 = this.variable.cacheReference(o), left = ref3[0], right = ref3[1]; + return new Assign(left, new Op(this.context.slice(0, -1), right, this.value)).compileToFragments(o); + } - function Code(params, body, tag) { - this.params = params || []; - this.body = body || new Block; - this.bound = tag === 'boundfunc'; - this.isGenerator = false; - this.isAsync = false; - this.body.traverseChildren(false, (node) => { - if ((node instanceof Op && node.isYield()) || node instanceof YieldReturn) { - this.isGenerator = true; + compileSplice(o) { + var answer, exclusive, from, fromDecl, fromRef, name, ref3, ref4, ref5, to, valDef, valRef; + ref3 = this.variable.properties.pop().range, from = ref3.from, to = ref3.to, exclusive = ref3.exclusive; + name = this.variable.compile(o); + if (from) { + ref4 = this.cacheToCodeFragments(from.cache(o, LEVEL_OP)), fromDecl = ref4[0], fromRef = ref4[1]; + } else { + fromDecl = fromRef = '0'; } - if ((node instanceof Op && node.isAwait()) || node instanceof AwaitReturn) { - this.isAsync = true; + if (to) { + if ((from != null ? from.isNumber() : void 0) && to.isNumber()) { + to = to.compile(o) - fromRef; + if (!exclusive) { + to += 1; + } + } else { + to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef; + if (!exclusive) { + to += ' + 1'; + } + } + } else { + to = "9e9"; } - if (this.isGenerator && this.isAsync) { - return node.error("function can't contain both yield and await"); + ref5 = this.value.cache(o, LEVEL_LIST), valDef = ref5[0], valRef = ref5[1]; + answer = [].concat(this.makeCode(`[].splice.apply(${name}, [${fromDecl}, ${to}].concat(`), valDef, this.makeCode(")), "), valRef); + if (o.level > LEVEL_TOP) { + return this.wrapInBraces(answer); + } else { + return answer; } - }); - } - - Code.prototype.children = ['params', 'body']; + } - Code.prototype.isStatement = function() { - return !!this.ctor; }; - Code.prototype.jumps = NO; + Assign.__super__ = superClass.prototype; - Code.prototype.makeScope = function(parentScope) { - return new Scope(parentScope, this.body, this); - }; + Assign.prototype.children = ['variable', 'value']; + + return Assign; + + })(Base); + + exports.Code = Code = (function(superClass) { + class Code extends superClass { + constructor(params, body, tag) { + super(...arguments); + this.params = params || []; + this.body = body || new Block; + this.bound = tag === 'boundfunc'; + this.isGenerator = false; + this.isAsync = false; + this.isMethod = false; + this.body.traverseChildren(false, (node) => { + if ((node instanceof Op && node.isYield()) || node instanceof YieldReturn) { + this.isGenerator = true; + } + if ((node instanceof Op && node.isAwait()) || node instanceof AwaitReturn) { + this.isAsync = true; + } + if (this.isGenerator && this.isAsync) { + return node.error("function can't contain both yield and await"); + } + }); + } + + isStatement() { + return this.isMethod; + } + + makeScope(parentScope) { + return new Scope(parentScope, this.body, this); + } - Code.prototype.compileNode = function(o) { - var answer, code, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, splatParamName, val, wasEmpty; - if (this.bound) { - if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { - this.context = o.scope.method.context; + compileNode(o) { + var answer, body, condition, exprs, haveSplatParam, haveThisParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, signature, splatParamName, val, wasEmpty; + if (this.bound) { + if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { + this.context = o.scope.method.context; + } + if (!this.context) { + this.context = 'this'; + } } - if (!this.context) { - this.context = 'this'; - } - } - o.scope = del(o, 'classScope') || this.makeScope(o.scope); - o.scope.shared = del(o, 'sharedScope'); - o.indent += TAB; - delete o.bare; - delete o.isExistentialEquals; - params = []; - exprs = []; - paramsAfterSplat = []; - haveSplatParam = false; - paramNames = []; - this.eachParamName((name, node) => { - if (indexOf.call(paramNames, name) >= 0) { - node.error(`multiple parameters named '${name}'`); - } - return paramNames.push(name); - }); - ref4 = this.params; - for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { - param = ref4[i]; - if (param.splat || param instanceof Expansion) { - if (haveSplatParam) { - param.error('only one splat or expansion parameter is allowed per function definition'); - } else if (param instanceof Expansion && this.params.length === 1) { - param.error('an expansion parameter cannot be the only parameter in a function definition'); - } - haveSplatParam = true; - if (param.splat) { - params.push(ref = param.asReference(o)); - splatParamName = fragmentsToText(ref.compileNode(o)); - if (param.isComplex()) { - exprs.push(new Assign(new Value(param.name), ref, '=', { - param: true - })); - } - } else { - splatParamName = o.scope.freeVariable('args'); - params.push(new Value(new IdentifierLiteral(splatParamName))); + o.scope = del(o, 'classScope') || this.makeScope(o.scope); + o.scope.shared = del(o, 'sharedScope'); + o.indent += TAB; + delete o.bare; + delete o.isExistentialEquals; + params = []; + exprs = []; + paramsAfterSplat = []; + haveSplatParam = false; + haveThisParam = false; + paramNames = []; + this.eachParamName((name, node) => { + if (indexOf.call(paramNames, name) >= 0) { + node.error(`multiple parameters named '${name}'`); } - o.scope.parameter(splatParamName); - } else { - if (param.isComplex()) { - val = ref = param.asReference(o); - if (param.value) { - val = new Op('?', ref, param.value); - } - exprs.push(new Assign(new Value(param.name), val, '=', { - param: true - })); + paramNames.push(name); + if (node["this"]) { + return haveThisParam = true; } - if (!haveSplatParam) { - if (!param.isComplex()) { - ref = param.value != null ? new Assign(new Value(param.name), param.value, '=') : param; + }); + if (this.ctor && haveThisParam && this.body.expressions[0] instanceof SuperCall) { + exprs.push(this.body.expressions.shift()); + } + ref4 = this.params; + for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { + param = ref4[i]; + if (param.splat || param instanceof Expansion) { + if (haveSplatParam) { + param.error('only one splat or expansion parameter is allowed per function definition'); + } else if (param instanceof Expansion && this.params.length === 1) { + param.error('an expansion parameter cannot be the only parameter in a function definition'); } - o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); - params.push(ref); + haveSplatParam = true; + if (param.splat) { + params.push(ref = param.asReference(o)); + splatParamName = fragmentsToText(ref.compileNode(o)); + if (param.isComplex()) { + exprs.push(new Assign(new Value(param.name), ref, '=', { + param: true + })); + } + } else { + splatParamName = o.scope.freeVariable('args'); + params.push(new Value(new IdentifierLiteral(splatParamName))); + } + o.scope.parameter(splatParamName); } else { - paramsAfterSplat.push(param); - if ((param.value != null) && !param.isComplex()) { - condition = new Literal(param.name.value + ' === undefined'); - ifTrue = new Assign(new Value(param.name), param.value, '='); - exprs.push(new If(condition, ifTrue)); + if (param.isComplex()) { + val = ref = param.asReference(o); + if (param.value) { + val = new Op('?', ref, param.value); + } + exprs.push(new Assign(new Value(param.name), val, '=', { + param: true + })); } - if (((ref5 = param.name) != null ? ref5.value : void 0) != null) { - o.scope.add(param.name.value, 'var', true); + if (!haveSplatParam) { + if (!param.isComplex()) { + ref = param.value != null ? new Assign(new Value(param.name), param.value, '=') : param; + } + o.scope.parameter(fragmentsToText((param.value != null ? param : ref).compileToFragments(o))); + params.push(ref); + } else { + paramsAfterSplat.push(param); + if ((param.value != null) && !param.isComplex()) { + condition = new Literal(param.name.value + ' === undefined'); + ifTrue = new Assign(new Value(param.name), param.value, '='); + exprs.push(new If(condition, ifTrue)); + } + if (((ref5 = param.name) != null ? ref5.value : void 0) != null) { + o.scope.add(param.name.value, 'var', true); + } } } } - } - if (paramsAfterSplat.length !== 0) { - exprs.unshift(new Assign(new Value(new Arr([ - new Splat(new IdentifierLiteral(splatParamName)), ...(function() { - var k, len2, results; - results = []; - for (k = 0, len2 = paramsAfterSplat.length; k < len2; k++) { - param = paramsAfterSplat[k]; - results.push(param.asReference(o)); - } - return results; - })() - ])), new Value(new IdentifierLiteral(splatParamName)))); - } - wasEmpty = this.body.isEmpty(); - if (exprs.length) { - this.body.expressions.unshift(...exprs); - } - if (!(wasEmpty || this.noReturn)) { - this.body.makeReturn(); - } - code = ''; - if (this.isAsync) { - code += 'async '; - } - if (!this.bound) { - code += 'function'; + if (paramsAfterSplat.length !== 0) { + exprs.unshift(new Assign(new Value(new Arr([ + new Splat(new IdentifierLiteral(splatParamName)), ...(function() { + var k, len2, results; + results = []; + for (k = 0, len2 = paramsAfterSplat.length; k < len2; k++) { + param = paramsAfterSplat[k]; + results.push(param.asReference(o)); + } + return results; + })() + ])), new Value(new IdentifierLiteral(splatParamName)))); + } + wasEmpty = this.body.isEmpty(); + if (exprs.length) { + this.body.expressions.unshift(...exprs); + } + if (!(wasEmpty || this.noReturn)) { + this.body.makeReturn(); + } + modifiers = []; + if (this["static"]) { + modifiers.push('static'); + } + if (this.isAsync) { + modifiers.push('async'); + } + if (!this.isMethod && !this.bound) { + modifiers.push('function'); + } if (this.isGenerator) { - code += '*'; + modifiers.push('*'); } - if (this.ctor) { - code += ' ' + this.name; + signature = [this.makeCode('(')]; + for (i = k = 0, len2 = params.length; k < len2; i = ++k) { + param = params[i]; + if (i) { + signature.push(this.makeCode(', ')); + } + if (haveSplatParam && i === params.length - 1) { + signature.push(this.makeCode('...')); + } + signature.push(...param.compileToFragments(o)); } - } - code += '('; - answer = [this.makeCode(code)]; - for (i = k = 0, len2 = params.length; k < len2; i = ++k) { - param = params[i]; - if (i) { - answer.push(this.makeCode(', ')); + signature.push(this.makeCode(')')); + if (!this.body.isEmpty()) { + body = this.body.compileWithDeclarations(o); } - if (haveSplatParam && i === params.length - 1) { - answer.push(this.makeCode('...')); + if (this.isMethod) { + name = this.name.compileToFragments(o); + if (name[0].code === '.') { + name.shift(); + } + } + answer = this.joinFragmentArrays((function() { + var l, len3, results; + results = []; + for (l = 0, len3 = modifiers.length; l < len3; l++) { + m = modifiers[l]; + results.push(this.makeCode(m)); + } + return results; + }).call(this), ' '); + if (modifiers.length && name) { + answer.push(this.makeCode(' ')); + } + if (name) { + answer.push(...name); + } + answer.push(...signature); + if (this.bound && !this.isMethod) { + answer.push(this.makeCode(' =>')); + } + answer.push(this.makeCode(' {')); + if (body != null ? body.length : void 0) { + answer.push(this.makeCode('\n'), ...body, this.makeCode(`\n${this.tab}`)); + } + answer.push(this.makeCode('}')); + if (this.isMethod) { + return [this.makeCode(this.tab), ...answer]; + } + if (this.front || (o.level >= LEVEL_ACCESS)) { + return this.wrapInBraces(answer); + } else { + return answer; } - answer.push(...param.compileToFragments(o)); - } - answer.push(this.makeCode(!this.bound ? ') {' : ') => {')); - if (!this.body.isEmpty()) { - answer = answer.concat(this.makeCode("\n"), this.body.compileWithDeclarations(o), this.makeCode(`\n${this.tab}`)); - } - answer.push(this.makeCode('}')); - if (this.ctor) { - return [this.makeCode(this.tab), ...answer]; } - if (this.front || (o.level >= LEVEL_ACCESS)) { - return this.wrapInBraces(answer); - } else { - return answer; + + eachParamName(iterator) { + var j, len1, param, ref3, results; + ref3 = this.params; + results = []; + for (j = 0, len1 = ref3.length; j < len1; j++) { + param = ref3[j]; + results.push(param.eachName(iterator)); + } + return results; } - }; - Code.prototype.eachParamName = function(iterator) { - var j, len1, param, ref3, results; - ref3 = this.params; - results = []; - for (j = 0, len1 = ref3.length; j < len1; j++) { - param = ref3[j]; - results.push(param.eachName(iterator)); + traverseChildren(crossScope, func) { + if (crossScope) { + return Code.__super__.traverseChildren.call(this, crossScope, func); + } } - return results; - }; - Code.prototype.traverseChildren = function(crossScope, func) { - if (crossScope) { - return Code.__super__.traverseChildren.call(this, crossScope, func); + superCall() { + var superCall; + superCall = null; + this.traverseChildren(true, function(child) { + if (child instanceof SuperCall) { + superCall = child; + } + return !superCall && (!(child instanceof Code) || child.bound); + }); + return superCall; } - }; - return Code; + }; - })(Base); + Code.__super__ = superClass.prototype; - exports.Param = Param = (function(superClass1) { - extend1(Param, superClass1); + Code.prototype.children = ['params', 'body']; - function Param(name1, value1, splat) { - var message, token; - this.name = name1; - this.value = value1; - this.splat = splat; - message = isUnassignable(this.name.unwrapAll().value); - if (message) { - this.name.error(message); - } - if (this.name instanceof Obj && this.name.generated) { - token = this.name.objects[0].operatorToken; - token.error(`unexpected ${token.value}`); - } - } + Code.prototype.jumps = NO; - Param.prototype.children = ['name', 'value']; + return Code; - Param.prototype.compileToFragments = function(o) { - return this.name.compileToFragments(o, LEVEL_LIST); - }; + })(Base); - Param.prototype.asReference = function(o) { - var name, node; - if (this.reference) { - return this.reference; - } - node = this.name; - if (node["this"]) { - name = node.properties[0].name.value; - if (indexOf.call(JS_FORBIDDEN, name) >= 0) { - name = `_${name}`; + exports.Param = Param = (function(superClass) { + class Param extends superClass { + constructor(name1, value1, splat) { + var message, token; + super(...arguments); + this.name = name1; + this.value = value1; + this.splat = splat; + message = isUnassignable(this.name.unwrapAll().value); + if (message) { + this.name.error(message); + } + if (this.name instanceof Obj && this.name.generated) { + token = this.name.objects[0].operatorToken; + token.error(`unexpected ${token.value}`); } - node = new IdentifierLiteral(o.scope.freeVariable(name)); - } else if (node.isComplex()) { - node = new IdentifierLiteral(o.scope.freeVariable('arg')); } - node = new Value(node); - node.updateLocationDataIfMissing(this.locationData); - return this.reference = node; - }; - Param.prototype.isComplex = function() { - return this.name.isComplex(); - }; + compileToFragments(o) { + return this.name.compileToFragments(o, LEVEL_LIST); + } - Param.prototype.eachName = function(iterator, name = this.name) { - var atParam, j, len1, node, obj, ref3, ref4; - atParam = function(obj) { - return iterator(`@${obj.properties[0].name.value}`, obj); - }; - if (name instanceof Literal) { - return iterator(name.value, name); - } - if (name instanceof Value) { - return atParam(name); - } - ref4 = (ref3 = name.objects) != null ? ref3 : []; - for (j = 0, len1 = ref4.length; j < len1; j++) { - obj = ref4[j]; - if (obj instanceof Assign && (obj.context == null)) { - obj = obj.variable; - } - if (obj instanceof Assign) { - if (obj.value instanceof Assign) { - obj = obj.value; - } - this.eachName(iterator, obj.value.unwrap()); - } else if (obj instanceof Splat) { - node = obj.name.unwrap(); - iterator(node.value, node); - } else if (obj instanceof Value) { - if (obj.isArray() || obj.isObject()) { - this.eachName(iterator, obj.base); - } else if (obj["this"]) { - atParam(obj); - } else { - iterator(obj.base.value, obj.base); + asReference(o) { + var name, node; + if (this.reference) { + return this.reference; + } + node = this.name; + if (node["this"]) { + name = node.properties[0].name.value; + if (indexOf.call(JS_FORBIDDEN, name) >= 0) { + name = `_${name}`; } - } else if (!(obj instanceof Expansion)) { - obj.error(`illegal parameter ${obj.compile()}`); + node = new IdentifierLiteral(o.scope.freeVariable(name)); + } else if (node.isComplex()) { + node = new IdentifierLiteral(o.scope.freeVariable('arg')); } + node = new Value(node); + node.updateLocationDataIfMissing(this.locationData); + return this.reference = node; } - }; - return Param; - - })(Base); + isComplex() { + return this.name.isComplex(); + } - exports.Splat = Splat = (function(superClass1) { - extend1(Splat, superClass1); + eachName(iterator, name = this.name) { + var atParam, j, len1, node, obj, ref3, ref4; + atParam = function(obj) { + return iterator(`@${obj.properties[0].name.value}`, obj); + }; + if (name instanceof Literal) { + return iterator(name.value, name); + } + if (name instanceof Value) { + return atParam(name); + } + ref4 = (ref3 = name.objects) != null ? ref3 : []; + for (j = 0, len1 = ref4.length; j < len1; j++) { + obj = ref4[j]; + if (obj instanceof Assign && (obj.context == null)) { + obj = obj.variable; + } + if (obj instanceof Assign) { + if (obj.value instanceof Assign) { + obj = obj.value; + } + this.eachName(iterator, obj.value.unwrap()); + } else if (obj instanceof Splat) { + node = obj.name.unwrap(); + iterator(node.value, node); + } else if (obj instanceof Value) { + if (obj.isArray() || obj.isObject()) { + this.eachName(iterator, obj.base); + } else if (obj["this"]) { + atParam(obj); + } else { + iterator(obj.base.value, obj.base); + } + } else if (!(obj instanceof Expansion)) { + obj.error(`illegal parameter ${obj.compile()}`); + } + } + } - Splat.prototype.children = ['name']; + }; - Splat.prototype.isAssignable = YES; + Param.__super__ = superClass.prototype; - function Splat(name) { - this.name = name.compile ? name : new Literal(name); - } + Param.prototype.children = ['name', 'value']; - Splat.prototype.assigns = function(name) { - return this.name.assigns(name); - }; + return Param; - Splat.prototype.compileToFragments = function(o) { - return [this.makeCode('...'), ...this.name.compileToFragments(o)]; - }; + })(Base); + + exports.Splat = Splat = (function(superClass) { + class Splat extends superClass { + constructor(name) { + super(...arguments); + this.name = name.compile ? name : new Literal(name); + } + + assigns(name) { + return this.name.assigns(name); + } + + compileToFragments(o) { + return [this.makeCode('...'), ...this.name.compileToFragments(o)]; + } + + unwrap() { + return this.name; + } - Splat.prototype.unwrap = function() { - return this.name; }; + Splat.__super__ = superClass.prototype; + + Splat.prototype.children = ['name']; + + Splat.prototype.isAssignable = YES; + return Splat; })(Base); - exports.Expansion = Expansion = (function(superClass1) { - extend1(Expansion, superClass1); + exports.Expansion = Expansion = (function(superClass) { + class Expansion extends superClass { + compileNode(o) { + return this.error('Expansion must be used inside a destructuring assignment or parameter list'); + } - function Expansion() { - return Expansion.__super__.constructor.apply(this, arguments); - } + asReference(o) { + return this; + } - Expansion.prototype.isComplex = NO; + eachName(iterator) {} - Expansion.prototype.compileNode = function(o) { - return this.error('Expansion must be used inside a destructuring assignment or parameter list'); }; - Expansion.prototype.asReference = function(o) { - return this; - }; + Expansion.__super__ = superClass.prototype; - Expansion.prototype.eachName = function(iterator) {}; + Expansion.prototype.isComplex = NO; return Expansion; })(Base); - exports.While = While = (function(superClass1) { - extend1(While, superClass1); - - function While(condition, options) { - this.condition = (options != null ? options.invert : void 0) ? condition.invert() : condition; - this.guard = options != null ? options.guard : void 0; - } - - While.prototype.children = ['condition', 'guard', 'body']; + exports.While = While = (function(superClass) { + class While extends superClass { + constructor(condition, options) { + super(...arguments); + this.condition = (options != null ? options.invert : void 0) ? condition.invert() : condition; + this.guard = options != null ? options.guard : void 0; + } - While.prototype.isStatement = YES; + makeReturn(res) { + if (res) { + return While.__super__.makeReturn.call(this, ...arguments); + } else { + this.returns = !this.jumps({ + loop: true + }); + return this; + } + } - While.prototype.makeReturn = function(res) { - if (res) { - return While.__super__.makeReturn.call(this, ...arguments); - } else { - this.returns = !this.jumps({ - loop: true - }); + addBody(body1) { + this.body = body1; return this; } - }; - - While.prototype.addBody = function(body1) { - this.body = body1; - return this; - }; - While.prototype.jumps = function() { - var expressions, j, jumpNode, len1, node; - expressions = this.body.expressions; - if (!expressions.length) { - return false; - } - for (j = 0, len1 = expressions.length; j < len1; j++) { - node = expressions[j]; - if (jumpNode = node.jumps({ - loop: true - })) { - return jumpNode; + jumps() { + var expressions, j, jumpNode, len1, node; + expressions = this.body.expressions; + if (!expressions.length) { + return false; + } + for (j = 0, len1 = expressions.length; j < len1; j++) { + node = expressions[j]; + if (jumpNode = node.jumps({ + loop: true + })) { + return jumpNode; + } } + return false; } - return false; - }; - While.prototype.compileNode = function(o) { - var answer, body, rvar, set; - o.indent += TAB; - set = ''; - body = this.body; - if (body.isEmpty()) { - body = this.makeCode(''); - } else { - if (this.returns) { - body.makeReturn(rvar = o.scope.freeVariable('results')); - set = `${this.tab}${rvar} = [];\n`; - } - if (this.guard) { - if (body.expressions.length > 1) { - body.expressions.unshift(new If((new Parens(this.guard)).invert(), new StatementLiteral("continue"))); - } else { - if (this.guard) { - body = Block.wrap([new If(this.guard, body)]); + compileNode(o) { + var answer, body, rvar, set; + o.indent += TAB; + set = ''; + body = this.body; + if (body.isEmpty()) { + body = this.makeCode(''); + } else { + if (this.returns) { + body.makeReturn(rvar = o.scope.freeVariable('results')); + set = `${this.tab}${rvar} = [];\n`; + } + if (this.guard) { + if (body.expressions.length > 1) { + body.expressions.unshift(new If((new Parens(this.guard)).invert(), new StatementLiteral("continue"))); + } else { + if (this.guard) { + body = Block.wrap([new If(this.guard, body)]); + } } } + body = [].concat(this.makeCode("\n"), body.compileToFragments(o, LEVEL_TOP), this.makeCode(`\n${this.tab}`)); } - body = [].concat(this.makeCode("\n"), body.compileToFragments(o, LEVEL_TOP), this.makeCode(`\n${this.tab}`)); - } - answer = [].concat(this.makeCode(set + this.tab + "while ("), this.condition.compileToFragments(o, LEVEL_PAREN), this.makeCode(") {"), body, this.makeCode("}")); - if (this.returns) { - answer.push(this.makeCode(`\n${this.tab}return ${rvar};`)); + answer = [].concat(this.makeCode(set + this.tab + "while ("), this.condition.compileToFragments(o, LEVEL_PAREN), this.makeCode(") {"), body, this.makeCode("}")); + if (this.returns) { + answer.push(this.makeCode(`\n${this.tab}return ${rvar};`)); + } + return answer; } - return answer; + }; + While.__super__ = superClass.prototype; + + While.prototype.children = ['condition', 'guard', 'body']; + + While.prototype.isStatement = YES; + return While; })(Base); - exports.Op = Op = (function(superClass1) { + exports.Op = Op = (function(superClass) { var CONVERSIONS, INVERSIONS; - extend1(Op, superClass1); - - function Op(op, first, second, flip) { - if (op === 'in') { - return new In(first, second); - } - if (op === 'do') { - return this.generateDo(first); - } - if (op === 'new') { - if (first instanceof Call && !first["do"] && !first.isNew) { - return first.newInstance(); + class Op extends superClass { + constructor(op, first, second, flip) { + super(...arguments); + if (op === 'in') { + return new In(first, second); + } + if (op === 'do') { + return this.generateDo(first); } - if (first instanceof Code && first.bound || first["do"]) { - first = new Parens(first); + if (op === 'new') { + if (first instanceof Call && !first["do"] && !first.isNew) { + return first.newInstance(); + } + if (first instanceof Code && first.bound || first["do"]) { + first = new Parens(first); + } } + this.operator = CONVERSIONS[op] || op; + this.first = first; + this.second = second; + this.flip = !!flip; + return this; } - this.operator = CONVERSIONS[op] || op; - this.first = first; - this.second = second; - this.flip = !!flip; - return this; - } - CONVERSIONS = { - '==': '===', - '!=': '!==', - 'of': 'in', - 'yieldfrom': 'yield*' - }; + isNumber() { + var ref3; + return this.isUnary() && ((ref3 = this.operator) === '+' || ref3 === '-') && this.first instanceof Value && this.first.isNumber(); + } - INVERSIONS = { - '!==': '===', - '===': '!==' - }; + isAwait() { + return this.operator === 'await'; + } - Op.prototype.children = ['first', 'second']; + isYield() { + var ref3; + return (ref3 = this.operator) === 'yield' || ref3 === 'yield*'; + } - Op.prototype.isNumber = function() { - var ref3; - return this.isUnary() && ((ref3 = this.operator) === '+' || ref3 === '-') && this.first instanceof Value && this.first.isNumber(); - }; + isUnary() { + return !this.second; + } - Op.prototype.isAwait = function() { - return this.operator === 'await'; - }; + isComplex() { + return !this.isNumber(); + } - Op.prototype.isYield = function() { - var ref3; - return (ref3 = this.operator) === 'yield' || ref3 === 'yield*'; - }; + isChainable() { + var ref3; + return (ref3 = this.operator) === '<' || ref3 === '>' || ref3 === '>=' || ref3 === '<=' || ref3 === '===' || ref3 === '!=='; + } - Op.prototype.isUnary = function() { - return !this.second; - }; + invert() { + var allInvertable, curr, fst, op, ref3; + if (this.isChainable() && this.first.isChainable()) { + allInvertable = true; + curr = this; + while (curr && curr.operator) { + allInvertable && (allInvertable = curr.operator in INVERSIONS); + curr = curr.first; + } + if (!allInvertable) { + return new Parens(this).invert(); + } + curr = this; + while (curr && curr.operator) { + curr.invert = !curr.invert; + curr.operator = INVERSIONS[curr.operator]; + curr = curr.first; + } + return this; + } else if (op = INVERSIONS[this.operator]) { + this.operator = op; + if (this.first.unwrap() instanceof Op) { + this.first.invert(); + } + return this; + } else if (this.second) { + return new Parens(this).invert(); + } else if (this.operator === '!' && (fst = this.first.unwrap()) instanceof Op && ((ref3 = fst.operator) === '!' || ref3 === 'in' || ref3 === 'instanceof')) { + return fst; + } else { + return new Op('!', this); + } + } - Op.prototype.isComplex = function() { - return !this.isNumber(); - }; + unfoldSoak(o) { + var ref3; + return ((ref3 = this.operator) === '++' || ref3 === '--' || ref3 === 'delete') && unfoldSoak(o, this, 'first'); + } - Op.prototype.isChainable = function() { - var ref3; - return (ref3 = this.operator) === '<' || ref3 === '>' || ref3 === '>=' || ref3 === '<=' || ref3 === '===' || ref3 === '!=='; - }; + generateDo(exp) { + var call, func, j, len1, param, passedParams, ref, ref3; + passedParams = []; + func = exp instanceof Assign && (ref = exp.value.unwrap()) instanceof Code ? ref : exp; + ref3 = func.params || []; + for (j = 0, len1 = ref3.length; j < len1; j++) { + param = ref3[j]; + if (param.value) { + passedParams.push(param.value); + delete param.value; + } else { + passedParams.push(param); + } + } + call = new Call(exp, passedParams); + call["do"] = true; + return call; + } - Op.prototype.invert = function() { - var allInvertable, curr, fst, op, ref3; - if (this.isChainable() && this.first.isChainable()) { - allInvertable = true; - curr = this; - while (curr && curr.operator) { - allInvertable && (allInvertable = curr.operator in INVERSIONS); - curr = curr.first; + compileNode(o) { + var answer, isChain, lhs, message, ref3, rhs; + isChain = this.isChainable() && this.first.isChainable(); + if (!isChain) { + this.first.front = this.front; } - if (!allInvertable) { - return new Parens(this).invert(); + if (this.operator === 'delete' && o.scope.check(this.first.unwrapAll().value)) { + this.error('delete operand may not be argument or var'); } - curr = this; - while (curr && curr.operator) { - curr.invert = !curr.invert; - curr.operator = INVERSIONS[curr.operator]; - curr = curr.first; + if ((ref3 = this.operator) === '--' || ref3 === '++') { + message = isUnassignable(this.first.unwrapAll().value); + if (message) { + this.first.error(message); + } } - return this; - } else if (op = INVERSIONS[this.operator]) { - this.operator = op; - if (this.first.unwrap() instanceof Op) { - this.first.invert(); + if (this.isYield() || this.isAwait()) { + return this.compileContinuation(o); + } + if (this.isUnary()) { + return this.compileUnary(o); + } + if (isChain) { + return this.compileChain(o); + } + switch (this.operator) { + case '?': + return this.compileExistence(o); + case '**': + return this.compilePower(o); + case '//': + return this.compileFloorDivision(o); + case '%%': + return this.compileModulo(o); + default: + lhs = this.first.compileToFragments(o, LEVEL_OP); + rhs = this.second.compileToFragments(o, LEVEL_OP); + answer = [].concat(lhs, this.makeCode(` ${this.operator} `), rhs); + if (o.level <= LEVEL_OP) { + return answer; + } else { + return this.wrapInBraces(answer); + } } - return this; - } else if (this.second) { - return new Parens(this).invert(); - } else if (this.operator === '!' && (fst = this.first.unwrap()) instanceof Op && ((ref3 = fst.operator) === '!' || ref3 === 'in' || ref3 === 'instanceof')) { - return fst; - } else { - return new Op('!', this); } - }; - Op.prototype.unfoldSoak = function(o) { - var ref3; - return ((ref3 = this.operator) === '++' || ref3 === '--' || ref3 === 'delete') && unfoldSoak(o, this, 'first'); - }; + compileChain(o) { + var fragments, fst, ref3, shared; + ref3 = this.first.second.cache(o), this.first.second = ref3[0], shared = ref3[1]; + fst = this.first.compileToFragments(o, LEVEL_OP); + fragments = fst.concat(this.makeCode(` ${(this.invert ? '&&' : '||')} `), shared.compileToFragments(o), this.makeCode(` ${this.operator} `), this.second.compileToFragments(o, LEVEL_OP)); + return this.wrapInBraces(fragments); + } - Op.prototype.generateDo = function(exp) { - var call, func, j, len1, param, passedParams, ref, ref3; - passedParams = []; - func = exp instanceof Assign && (ref = exp.value.unwrap()) instanceof Code ? ref : exp; - ref3 = func.params || []; - for (j = 0, len1 = ref3.length; j < len1; j++) { - param = ref3[j]; - if (param.value) { - passedParams.push(param.value); - delete param.value; + compileExistence(o) { + var fst, ref; + if (this.first.isComplex()) { + ref = new IdentifierLiteral(o.scope.freeVariable('ref')); + fst = new Parens(new Assign(ref, this.first)); } else { - passedParams.push(param); + fst = this.first; + ref = fst; } + return new If(new Existence(fst), ref, { + type: 'if' + }).addElse(this.second).compileToFragments(o); } - call = new Call(exp, passedParams); - call["do"] = true; - return call; - }; - Op.prototype.compileNode = function(o) { - var answer, isChain, lhs, message, ref3, rhs; - isChain = this.isChainable() && this.first.isChainable(); - if (!isChain) { - this.first.front = this.front; - } - if (this.operator === 'delete' && o.scope.check(this.first.unwrapAll().value)) { - this.error('delete operand may not be argument or var'); + compileUnary(o) { + var op, parts, plusMinus; + parts = []; + op = this.operator; + parts.push([this.makeCode(op)]); + if (op === '!' && this.first instanceof Existence) { + this.first.negated = !this.first.negated; + return this.first.compileToFragments(o); + } + if (o.level >= LEVEL_ACCESS) { + return (new Parens(this)).compileToFragments(o); + } + plusMinus = op === '+' || op === '-'; + if ((op === 'new' || op === 'typeof' || op === 'delete') || plusMinus && this.first instanceof Op && this.first.operator === op) { + parts.push([this.makeCode(' ')]); + } + if ((plusMinus && this.first instanceof Op) || (op === 'new' && this.first.isStatement(o))) { + this.first = new Parens(this.first); + } + parts.push(this.first.compileToFragments(o, LEVEL_OP)); + if (this.flip) { + parts.reverse(); + } + return this.joinFragmentArrays(parts, ''); } - if ((ref3 = this.operator) === '--' || ref3 === '++') { - message = isUnassignable(this.first.unwrapAll().value); - if (message) { - this.first.error(message); - } - } - if (this.isYield() || this.isAwait()) { - return this.compileContinuation(o); - } - if (this.isUnary()) { - return this.compileUnary(o); - } - if (isChain) { - return this.compileChain(o); - } - switch (this.operator) { - case '?': - return this.compileExistence(o); - case '**': - return this.compilePower(o); - case '//': - return this.compileFloorDivision(o); - case '%%': - return this.compileModulo(o); - default: - lhs = this.first.compileToFragments(o, LEVEL_OP); - rhs = this.second.compileToFragments(o, LEVEL_OP); - answer = [].concat(lhs, this.makeCode(` ${this.operator} `), rhs); - if (o.level <= LEVEL_OP) { - return answer; - } else { - return this.wrapInBraces(answer); + + compileContinuation(o) { + var op, parts, ref3, ref4; + parts = []; + op = this.operator; + if (o.scope.parent == null) { + this.error(`${this.operator} can only occur inside functions`); + } + if (((ref3 = o.scope.method) != null ? ref3.bound : void 0) && o.scope.method.isGenerator) { + this.error('yield cannot occur inside bound (fat arrow) functions'); + } + if (indexOf.call(Object.keys(this.first), 'expression') >= 0 && !(this.first instanceof Throw)) { + if (this.first.expression != null) { + parts.push(this.first.expression.compileToFragments(o, LEVEL_OP)); } + } else { + if (o.level >= LEVEL_PAREN) { + parts.push([this.makeCode("(")]); + } + parts.push([this.makeCode(op)]); + if (((ref4 = this.first.base) != null ? ref4.value : void 0) !== '') { + parts.push([this.makeCode(" ")]); + } + parts.push(this.first.compileToFragments(o, LEVEL_OP)); + if (o.level >= LEVEL_PAREN) { + parts.push([this.makeCode(")")]); + } + } + return this.joinFragmentArrays(parts, ''); } - }; - - Op.prototype.compileChain = function(o) { - var fragments, fst, ref3, shared; - ref3 = this.first.second.cache(o), this.first.second = ref3[0], shared = ref3[1]; - fst = this.first.compileToFragments(o, LEVEL_OP); - fragments = fst.concat(this.makeCode(` ${(this.invert ? '&&' : '||')} `), shared.compileToFragments(o), this.makeCode(` ${this.operator} `), this.second.compileToFragments(o, LEVEL_OP)); - return this.wrapInBraces(fragments); - }; - Op.prototype.compileExistence = function(o) { - var fst, ref; - if (this.first.isComplex()) { - ref = new IdentifierLiteral(o.scope.freeVariable('ref')); - fst = new Parens(new Assign(ref, this.first)); - } else { - fst = this.first; - ref = fst; - } - return new If(new Existence(fst), ref, { - type: 'if' - }).addElse(this.second).compileToFragments(o); - }; - - Op.prototype.compileUnary = function(o) { - var op, parts, plusMinus; - parts = []; - op = this.operator; - parts.push([this.makeCode(op)]); - if (op === '!' && this.first instanceof Existence) { - this.first.negated = !this.first.negated; - return this.first.compileToFragments(o); - } - if (o.level >= LEVEL_ACCESS) { - return (new Parens(this)).compileToFragments(o); - } - plusMinus = op === '+' || op === '-'; - if ((op === 'new' || op === 'typeof' || op === 'delete') || plusMinus && this.first instanceof Op && this.first.operator === op) { - parts.push([this.makeCode(' ')]); + compilePower(o) { + var pow; + pow = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('pow'))]); + return new Call(pow, [this.first, this.second]).compileToFragments(o); } - if ((plusMinus && this.first instanceof Op) || (op === 'new' && this.first.isStatement(o))) { - this.first = new Parens(this.first); - } - parts.push(this.first.compileToFragments(o, LEVEL_OP)); - if (this.flip) { - parts.reverse(); - } - return this.joinFragmentArrays(parts, ''); - }; - Op.prototype.compileContinuation = function(o) { - var op, parts, ref3, ref4; - parts = []; - op = this.operator; - if (o.scope.parent == null) { - this.error(`${this.operator} can only occur inside functions`); + compileFloorDivision(o) { + var div, floor, second; + floor = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('floor'))]); + second = this.second.isComplex() ? new Parens(this.second) : this.second; + div = new Op('/', this.first, second); + return new Call(floor, [div]).compileToFragments(o); } - if (((ref3 = o.scope.method) != null ? ref3.bound : void 0) && o.scope.method.isGenerator) { - this.error('yield cannot occur inside bound (fat arrow) functions'); + + compileModulo(o) { + var mod; + mod = new Value(new Literal(utility('modulo', o))); + return new Call(mod, [this.first, this.second]).compileToFragments(o); } - if (indexOf.call(Object.keys(this.first), 'expression') >= 0 && !(this.first instanceof Throw)) { - if (this.first.expression != null) { - parts.push(this.first.expression.compileToFragments(o, LEVEL_OP)); - } - } else { - if (o.level >= LEVEL_PAREN) { - parts.push([this.makeCode("(")]); - } - parts.push([this.makeCode(op)]); - if (((ref4 = this.first.base) != null ? ref4.value : void 0) !== '') { - parts.push([this.makeCode(" ")]); - } - parts.push(this.first.compileToFragments(o, LEVEL_OP)); - if (o.level >= LEVEL_PAREN) { - parts.push([this.makeCode(")")]); - } + + toString(idt) { + return Op.__super__.toString.call(this, idt, this.constructor.name + ' ' + this.operator); } - return this.joinFragmentArrays(parts, ''); - }; - Op.prototype.compilePower = function(o) { - var pow; - pow = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('pow'))]); - return new Call(pow, [this.first, this.second]).compileToFragments(o); }; - Op.prototype.compileFloorDivision = function(o) { - var div, floor, second; - floor = new Value(new IdentifierLiteral('Math'), [new Access(new PropertyName('floor'))]); - second = this.second.isComplex() ? new Parens(this.second) : this.second; - div = new Op('/', this.first, second); - return new Call(floor, [div]).compileToFragments(o); - }; + Op.__super__ = superClass.prototype; - Op.prototype.compileModulo = function(o) { - var mod; - mod = new Value(new Literal(utility('modulo', o))); - return new Call(mod, [this.first, this.second]).compileToFragments(o); + CONVERSIONS = { + '==': '===', + '!=': '!==', + 'of': 'in', + 'yieldfrom': 'yield*' }; - Op.prototype.toString = function(idt) { - return Op.__super__.toString.call(this, idt, this.constructor.name + ' ' + this.operator); + INVERSIONS = { + '!==': '===', + '===': '!==' }; + Op.prototype.children = ['first', 'second']; + return Op; })(Base); - exports.In = In = (function(superClass1) { - extend1(In, superClass1); - - function In(object, array) { - this.object = object; - this.array = array; - } - - In.prototype.children = ['object', 'array']; - - In.prototype.invert = NEGATE; + exports.In = In = (function(superClass) { + class In extends superClass { + constructor(object, array) { + super(...arguments); + this.object = object; + this.array = array; + } + + compileNode(o) { + var hasSplat, j, len1, obj, ref3; + if (this.array instanceof Value && this.array.isArray() && this.array.base.objects.length) { + ref3 = this.array.base.objects; + for (j = 0, len1 = ref3.length; j < len1; j++) { + obj = ref3[j]; + if (!(obj instanceof Splat)) { + continue; + } + hasSplat = true; + break; + } + if (!hasSplat) { + return this.compileOrTest(o); + } + } + return this.compileLoopTest(o); + } - In.prototype.compileNode = function(o) { - var hasSplat, j, len1, obj, ref3; - if (this.array instanceof Value && this.array.isArray() && this.array.base.objects.length) { - ref3 = this.array.base.objects; - for (j = 0, len1 = ref3.length; j < len1; j++) { - obj = ref3[j]; - if (!(obj instanceof Splat)) { - continue; + compileOrTest(o) { + var cmp, cnj, i, item, j, len1, ref, ref3, ref4, ref5, sub, tests; + ref3 = this.object.cache(o, LEVEL_OP), sub = ref3[0], ref = ref3[1]; + ref4 = this.negated ? [' !== ', ' && '] : [' === ', ' || '], cmp = ref4[0], cnj = ref4[1]; + tests = []; + ref5 = this.array.base.objects; + for (i = j = 0, len1 = ref5.length; j < len1; i = ++j) { + item = ref5[i]; + if (i) { + tests.push(this.makeCode(cnj)); } - hasSplat = true; - break; + tests = tests.concat((i ? ref : sub), this.makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)); } - if (!hasSplat) { - return this.compileOrTest(o); + if (o.level < LEVEL_OP) { + return tests; + } else { + return this.wrapInBraces(tests); } } - return this.compileLoopTest(o); - }; - In.prototype.compileOrTest = function(o) { - var cmp, cnj, i, item, j, len1, ref, ref3, ref4, ref5, sub, tests; - ref3 = this.object.cache(o, LEVEL_OP), sub = ref3[0], ref = ref3[1]; - ref4 = this.negated ? [' !== ', ' && '] : [' === ', ' || '], cmp = ref4[0], cnj = ref4[1]; - tests = []; - ref5 = this.array.base.objects; - for (i = j = 0, len1 = ref5.length; j < len1; i = ++j) { - item = ref5[i]; - if (i) { - tests.push(this.makeCode(cnj)); + compileLoopTest(o) { + var fragments, ref, ref3, sub; + ref3 = this.object.cache(o, LEVEL_LIST), sub = ref3[0], ref = ref3[1]; + fragments = [].concat(this.makeCode(utility('indexOf', o) + ".call("), this.array.compileToFragments(o, LEVEL_LIST), this.makeCode(", "), ref, this.makeCode(") " + (this.negated ? '< 0' : '>= 0'))); + if (fragmentsToText(sub) === fragmentsToText(ref)) { + return fragments; + } + fragments = sub.concat(this.makeCode(', '), fragments); + if (o.level < LEVEL_LIST) { + return fragments; + } else { + return this.wrapInBraces(fragments); } - tests = tests.concat((i ? ref : sub), this.makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)); - } - if (o.level < LEVEL_OP) { - return tests; - } else { - return this.wrapInBraces(tests); } - }; - In.prototype.compileLoopTest = function(o) { - var fragments, ref, ref3, sub; - ref3 = this.object.cache(o, LEVEL_LIST), sub = ref3[0], ref = ref3[1]; - fragments = [].concat(this.makeCode(utility('indexOf', o) + ".call("), this.array.compileToFragments(o, LEVEL_LIST), this.makeCode(", "), ref, this.makeCode(") " + (this.negated ? '< 0' : '>= 0'))); - if (fragmentsToText(sub) === fragmentsToText(ref)) { - return fragments; + toString(idt) { + return In.__super__.toString.call(this, idt, this.constructor.name + (this.negated ? '!' : '')); } - fragments = sub.concat(this.makeCode(', '), fragments); - if (o.level < LEVEL_LIST) { - return fragments; - } else { - return this.wrapInBraces(fragments); - } - }; - In.prototype.toString = function(idt) { - return In.__super__.toString.call(this, idt, this.constructor.name + (this.negated ? '!' : '')); }; - return In; + In.__super__ = superClass.prototype; - })(Base); + In.prototype.children = ['object', 'array']; - exports.Try = Try = (function(superClass1) { - extend1(Try, superClass1); + In.prototype.invert = NEGATE; - function Try(attempt, errorVariable, recovery, ensure) { - this.attempt = attempt; - this.errorVariable = errorVariable; - this.recovery = recovery; - this.ensure = ensure; - } + return In; - Try.prototype.children = ['attempt', 'recovery', 'ensure']; + })(Base); - Try.prototype.isStatement = YES; + exports.Try = Try = (function(superClass) { + class Try extends superClass { + constructor(attempt, errorVariable, recovery, ensure) { + super(...arguments); + this.attempt = attempt; + this.errorVariable = errorVariable; + this.recovery = recovery; + this.ensure = ensure; + } - Try.prototype.jumps = function(o) { - var ref3; - return this.attempt.jumps(o) || ((ref3 = this.recovery) != null ? ref3.jumps(o) : void 0); - }; + jumps(o) { + var ref3; + return this.attempt.jumps(o) || ((ref3 = this.recovery) != null ? ref3.jumps(o) : void 0); + } - Try.prototype.makeReturn = function(res) { - if (this.attempt) { - this.attempt = this.attempt.makeReturn(res); + makeReturn(res) { + if (this.attempt) { + this.attempt = this.attempt.makeReturn(res); + } + if (this.recovery) { + this.recovery = this.recovery.makeReturn(res); + } + return this; } - if (this.recovery) { - this.recovery = this.recovery.makeReturn(res); + + compileNode(o) { + var catchPart, ensurePart, generatedErrorVariableName, message, placeholder, tryPart; + o.indent += TAB; + tryPart = this.attempt.compileToFragments(o, LEVEL_TOP); + catchPart = this.recovery ? (generatedErrorVariableName = o.scope.freeVariable('error', { + reserve: false + }), placeholder = new IdentifierLiteral(generatedErrorVariableName), this.errorVariable ? (message = isUnassignable(this.errorVariable.unwrapAll().value), message ? this.errorVariable.error(message) : void 0, this.recovery.unshift(new Assign(this.errorVariable, placeholder))) : void 0, [].concat(this.makeCode(" catch ("), placeholder.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode(`\n${this.tab}}`))) : !(this.ensure || this.recovery) ? (generatedErrorVariableName = o.scope.freeVariable('error', { + reserve: false + }), [this.makeCode(` catch (${generatedErrorVariableName}) {}`)]) : []; + ensurePart = this.ensure ? [].concat(this.makeCode(" finally {\n"), this.ensure.compileToFragments(o, LEVEL_TOP), this.makeCode(`\n${this.tab}}`)) : []; + return [].concat(this.makeCode(`${this.tab}try {\n`), tryPart, this.makeCode(`\n${this.tab}}`), catchPart, ensurePart); } - return this; - }; - Try.prototype.compileNode = function(o) { - var catchPart, ensurePart, generatedErrorVariableName, message, placeholder, tryPart; - o.indent += TAB; - tryPart = this.attempt.compileToFragments(o, LEVEL_TOP); - catchPart = this.recovery ? (generatedErrorVariableName = o.scope.freeVariable('error', { - reserve: false - }), placeholder = new IdentifierLiteral(generatedErrorVariableName), this.errorVariable ? (message = isUnassignable(this.errorVariable.unwrapAll().value), message ? this.errorVariable.error(message) : void 0, this.recovery.unshift(new Assign(this.errorVariable, placeholder))) : void 0, [].concat(this.makeCode(" catch ("), placeholder.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode(`\n${this.tab}}`))) : !(this.ensure || this.recovery) ? (generatedErrorVariableName = o.scope.freeVariable('error', { - reserve: false - }), [this.makeCode(` catch (${generatedErrorVariableName}) {}`)]) : []; - ensurePart = this.ensure ? [].concat(this.makeCode(" finally {\n"), this.ensure.compileToFragments(o, LEVEL_TOP), this.makeCode(`\n${this.tab}}`)) : []; - return [].concat(this.makeCode(`${this.tab}try {\n`), tryPart, this.makeCode(`\n${this.tab}}`), catchPart, ensurePart); }; + Try.__super__ = superClass.prototype; + + Try.prototype.children = ['attempt', 'recovery', 'ensure']; + + Try.prototype.isStatement = YES; + return Try; })(Base); - exports.Throw = Throw = (function(superClass1) { - extend1(Throw, superClass1); + exports.Throw = Throw = (function(superClass) { + class Throw extends superClass { + constructor(expression1) { + super(...arguments); + this.expression = expression1; + } - function Throw(expression) { - this.expression = expression; - } + compileNode(o) { + return [].concat(this.makeCode(this.tab + "throw "), this.expression.compileToFragments(o), this.makeCode(";")); + } + + }; + + Throw.__super__ = superClass.prototype; Throw.prototype.children = ['expression']; @@ -3189,535 +3581,555 @@ Throw.prototype.makeReturn = THIS; - Throw.prototype.compileNode = function(o) { - return [].concat(this.makeCode(this.tab + "throw "), this.expression.compileToFragments(o), this.makeCode(";")); - }; - return Throw; })(Base); - exports.Existence = Existence = (function(superClass1) { - extend1(Existence, superClass1); + exports.Existence = Existence = (function(superClass) { + class Existence extends superClass { + constructor(expression1) { + super(...arguments); + this.expression = expression1; + } - function Existence(expression) { - this.expression = expression; - } + compileNode(o) { + var cmp, cnj, code, ref3; + this.expression.front = this.front; + code = this.expression.compile(o, LEVEL_OP); + if (this.expression.unwrap() instanceof IdentifierLiteral && !o.scope.check(code)) { + ref3 = this.negated ? ['===', '||'] : ['!==', '&&'], cmp = ref3[0], cnj = ref3[1]; + code = `typeof ${code} ${cmp} \"undefined\" ${cnj} ${code} ${cmp} null`; + } else { + code = `${code} ${(this.negated ? '==' : '!=')} null`; + } + return [this.makeCode(o.level <= LEVEL_COND ? code : `(${code})`)]; + } + + }; + + Existence.__super__ = superClass.prototype; Existence.prototype.children = ['expression']; Existence.prototype.invert = NEGATE; - Existence.prototype.compileNode = function(o) { - var cmp, cnj, code, ref3; - this.expression.front = this.front; - code = this.expression.compile(o, LEVEL_OP); - if (this.expression.unwrap() instanceof IdentifierLiteral && !o.scope.check(code)) { - ref3 = this.negated ? ['===', '||'] : ['!==', '&&'], cmp = ref3[0], cnj = ref3[1]; - code = `typeof ${code} ${cmp} \"undefined\" ${cnj} ${code} ${cmp} null`; - } else { - code = `${code} ${(this.negated ? '==' : '!=')} null`; - } - return [this.makeCode(o.level <= LEVEL_COND ? code : `(${code})`)]; - }; - return Existence; })(Base); - exports.Parens = Parens = (function(superClass1) { - extend1(Parens, superClass1); + exports.Parens = Parens = (function(superClass) { + class Parens extends superClass { + constructor(body1) { + super(...arguments); + this.body = body1; + } - function Parens(body1) { - this.body = body1; - } + unwrap() { + return this.body; + } - Parens.prototype.children = ['body']; + isComplex() { + return this.body.isComplex(); + } - Parens.prototype.unwrap = function() { - return this.body; - }; + compileNode(o) { + var bare, expr, fragments; + expr = this.body.unwrap(); + if (expr instanceof Value && expr.isAtomic()) { + expr.front = this.front; + return expr.compileToFragments(o); + } + fragments = expr.compileToFragments(o, LEVEL_PAREN); + bare = o.level < LEVEL_OP && (expr instanceof Op || expr instanceof Call || (expr instanceof For && expr.returns)); + if (bare) { + return fragments; + } else { + return this.wrapInBraces(fragments); + } + } - Parens.prototype.isComplex = function() { - return this.body.isComplex(); }; - Parens.prototype.compileNode = function(o) { - var bare, expr, fragments; - expr = this.body.unwrap(); - if (expr instanceof Value && expr.isAtomic()) { - expr.front = this.front; - return expr.compileToFragments(o); - } - fragments = expr.compileToFragments(o, LEVEL_PAREN); - bare = o.level < LEVEL_OP && (expr instanceof Op || expr instanceof Call || (expr instanceof For && expr.returns)); - if (bare) { - return fragments; - } else { - return this.wrapInBraces(fragments); - } - }; + Parens.__super__ = superClass.prototype; + + Parens.prototype.children = ['body']; return Parens; })(Base); - exports.StringWithInterpolations = StringWithInterpolations = (function(superClass1) { - extend1(StringWithInterpolations, superClass1); - - function StringWithInterpolations(body1) { - this.body = body1; - } - - StringWithInterpolations.prototype.children = ['body']; + exports.StringWithInterpolations = StringWithInterpolations = (function(superClass) { + class StringWithInterpolations extends superClass { + constructor(body1) { + super(...arguments); + this.body = body1; + } - StringWithInterpolations.prototype.unwrap = function() { - return this; - }; + unwrap() { + return this; + } - StringWithInterpolations.prototype.isComplex = function() { - return this.body.isComplex(); - }; + isComplex() { + return this.body.isComplex(); + } - StringWithInterpolations.prototype.compileNode = function(o) { - var element, elements, expr, fragments, j, len1, value; - expr = this.body.unwrap(); - elements = []; - expr.traverseChildren(false, function(node) { - if (node instanceof StringLiteral) { - elements.push(node); + compileNode(o) { + var element, elements, expr, fragments, j, len1, value; + expr = this.body.unwrap(); + elements = []; + expr.traverseChildren(false, function(node) { + if (node instanceof StringLiteral) { + elements.push(node); + return true; + } else if (node instanceof Parens) { + elements.push(node); + return false; + } return true; - } else if (node instanceof Parens) { - elements.push(node); - return false; - } - return true; - }); - fragments = []; - fragments.push(this.makeCode('`')); - for (j = 0, len1 = elements.length; j < len1; j++) { - element = elements[j]; - if (element instanceof StringLiteral) { - value = element.value.slice(1, -1); - value = value.replace(/(\\*)(`|\$\{)/g, function(match, backslashes, toBeEscaped) { - if (backslashes.length % 2 === 0) { - return `${backslashes}\\${toBeEscaped}`; - } else { - return match; - } - }); - fragments.push(this.makeCode(value)); - } else { - fragments.push(this.makeCode('${')); - fragments.push(...element.compileToFragments(o, LEVEL_PAREN)); - fragments.push(this.makeCode('}')); + }); + fragments = []; + fragments.push(this.makeCode('`')); + for (j = 0, len1 = elements.length; j < len1; j++) { + element = elements[j]; + if (element instanceof StringLiteral) { + value = element.value.slice(1, -1); + value = value.replace(/(\\*)(`|\$\{)/g, function(match, backslashes, toBeEscaped) { + if (backslashes.length % 2 === 0) { + return `${backslashes}\\${toBeEscaped}`; + } else { + return match; + } + }); + fragments.push(this.makeCode(value)); + } else { + fragments.push(this.makeCode('${')); + fragments.push(...element.compileToFragments(o, LEVEL_PAREN)); + fragments.push(this.makeCode('}')); + } } + fragments.push(this.makeCode('`')); + return fragments; } - fragments.push(this.makeCode('`')); - return fragments; - }; - return StringWithInterpolations; + }; - })(Base); + StringWithInterpolations.__super__ = superClass.prototype; - exports.For = For = (function(superClass1) { - extend1(For, superClass1); + StringWithInterpolations.prototype.children = ['body']; - function For(body, source) { - var ref3; - this.source = source.source, this.guard = source.guard, this.step = source.step, this.name = source.name, this.index = source.index; - this.body = Block.wrap([body]); - this.own = !!source.own; - this.object = !!source.object; - this.from = !!source.from; - if (this.from && this.index) { - this.index.error('cannot use index with for-from'); - } - if (this.own && !this.object) { - source.ownTag.error(`cannot use own with for-${(this.from ? 'from' : 'in')}`); - } - if (this.object) { - ref3 = [this.index, this.name], this.name = ref3[0], this.index = ref3[1]; - } - if (this.index instanceof Value) { - this.index.error('index cannot be a pattern matching expression'); - } - this.range = this.source instanceof Value && this.source.base instanceof Range && !this.source.properties.length && !this.from; - this.pattern = this.name instanceof Value; - if (this.range && this.index) { - this.index.error('indexes do not apply to range loops'); - } - if (this.range && this.pattern) { - this.name.error('cannot pattern match over range loops'); - } - this.returns = false; - } + return StringWithInterpolations; - For.prototype.children = ['body', 'source', 'guard', 'step']; + })(Base); - For.prototype.compileNode = function(o) { - var body, bodyFragments, compare, compareDown, declare, declareDown, defPart, defPartFragments, down, forPartFragments, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, last, lvar, name, namePart, ref, ref3, ref4, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart; - body = Block.wrap([this.body]); - ref3 = body.expressions, last = ref3[ref3.length - 1]; - if ((last != null ? last.jumps() : void 0) instanceof Return) { + exports.For = For = (function(superClass) { + class For extends superClass { + constructor(body, source) { + var ref3; + super(...arguments); + this.source = source.source, this.guard = source.guard, this.step = source.step, this.name = source.name, this.index = source.index; + this.body = Block.wrap([body]); + this.own = !!source.own; + this.object = !!source.object; + this.from = !!source.from; + if (this.from && this.index) { + this.index.error('cannot use index with for-from'); + } + if (this.own && !this.object) { + source.ownTag.error(`cannot use own with for-${(this.from ? 'from' : 'in')}`); + } + if (this.object) { + ref3 = [this.index, this.name], this.name = ref3[0], this.index = ref3[1]; + } + if (this.index instanceof Value) { + this.index.error('index cannot be a pattern matching expression'); + } + this.range = this.source instanceof Value && this.source.base instanceof Range && !this.source.properties.length && !this.from; + this.pattern = this.name instanceof Value; + if (this.range && this.index) { + this.index.error('indexes do not apply to range loops'); + } + if (this.range && this.pattern) { + this.name.error('cannot pattern match over range loops'); + } this.returns = false; } - source = this.range ? this.source.base : this.source; - scope = o.scope; - if (!this.pattern) { - name = this.name && (this.name.compile(o, LEVEL_LIST)); - } - index = this.index && (this.index.compile(o, LEVEL_LIST)); - if (name && !this.pattern) { - scope.find(name); - } - if (index) { - scope.find(index); - } - if (this.returns) { - rvar = scope.freeVariable('results'); - } - if (this.from) { - if (this.pattern) { - ivar = scope.freeVariable('x', { + + compileNode(o) { + var body, bodyFragments, compare, compareDown, declare, declareDown, defPart, defPartFragments, down, forPartFragments, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, last, lvar, name, namePart, ref, ref3, ref4, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart; + body = Block.wrap([this.body]); + ref3 = body.expressions, last = ref3[ref3.length - 1]; + if ((last != null ? last.jumps() : void 0) instanceof Return) { + this.returns = false; + } + source = this.range ? this.source.base : this.source; + scope = o.scope; + if (!this.pattern) { + name = this.name && (this.name.compile(o, LEVEL_LIST)); + } + index = this.index && (this.index.compile(o, LEVEL_LIST)); + if (name && !this.pattern) { + scope.find(name); + } + if (index) { + scope.find(index); + } + if (this.returns) { + rvar = scope.freeVariable('results'); + } + if (this.from) { + if (this.pattern) { + ivar = scope.freeVariable('x', { + single: true + }); + } + } else { + ivar = (this.object && index) || scope.freeVariable('i', { single: true }); } - } else { - ivar = (this.object && index) || scope.freeVariable('i', { - single: true - }); - } - kvar = ((this.range || this.from) && name) || index || ivar; - kvarAssign = kvar !== ivar ? `${kvar} = ` : ""; - if (this.step && !this.range) { - ref4 = this.cacheToCodeFragments(this.step.cache(o, LEVEL_LIST, isComplexOrAssignable)), step = ref4[0], stepVar = ref4[1]; - if (this.step.isNumber()) { - stepNum = Number(stepVar); - } - } - if (this.pattern) { - name = ivar; - } - varPart = ''; - guardPart = ''; - defPart = ''; - idt1 = this.tab + TAB; - if (this.range) { - forPartFragments = source.compileToFragments(merge(o, { - index: ivar, - name: name, - step: this.step, - isComplex: isComplexOrAssignable - })); - } else { - svar = this.source.compile(o, LEVEL_LIST); - if ((name || this.own) && !(this.source.unwrap() instanceof IdentifierLiteral)) { - defPart += `${this.tab}${(ref = scope.freeVariable('ref'))} = ${svar};\n`; - svar = ref; - } - if (name && !this.pattern && !this.from) { - namePart = `${name} = ${svar}[${kvar}]`; - } - if (!this.object && !this.from) { - if (step !== stepVar) { - defPart += `${this.tab}${step};\n`; - } - down = stepNum < 0; - if (!(this.step && (stepNum != null) && down)) { - lvar = scope.freeVariable('len'); - } - declare = `${kvarAssign}${ivar} = 0, ${lvar} = ${svar}.length`; - declareDown = `${kvarAssign}${ivar} = ${svar}.length - 1`; - compare = `${ivar} < ${lvar}`; - compareDown = `${ivar} >= 0`; - if (this.step) { - if (stepNum != null) { - if (down) { - compare = compareDown; - declare = declareDown; + kvar = ((this.range || this.from) && name) || index || ivar; + kvarAssign = kvar !== ivar ? `${kvar} = ` : ""; + if (this.step && !this.range) { + ref4 = this.cacheToCodeFragments(this.step.cache(o, LEVEL_LIST, isComplexOrAssignable)), step = ref4[0], stepVar = ref4[1]; + if (this.step.isNumber()) { + stepNum = Number(stepVar); + } + } + if (this.pattern) { + name = ivar; + } + varPart = ''; + guardPart = ''; + defPart = ''; + idt1 = this.tab + TAB; + if (this.range) { + forPartFragments = source.compileToFragments(merge(o, { + index: ivar, + name: name, + step: this.step, + isComplex: isComplexOrAssignable + })); + } else { + svar = this.source.compile(o, LEVEL_LIST); + if ((name || this.own) && !(this.source.unwrap() instanceof IdentifierLiteral)) { + defPart += `${this.tab}${(ref = scope.freeVariable('ref'))} = ${svar};\n`; + svar = ref; + } + if (name && !this.pattern && !this.from) { + namePart = `${name} = ${svar}[${kvar}]`; + } + if (!this.object && !this.from) { + if (step !== stepVar) { + defPart += `${this.tab}${step};\n`; + } + down = stepNum < 0; + if (!(this.step && (stepNum != null) && down)) { + lvar = scope.freeVariable('len'); + } + declare = `${kvarAssign}${ivar} = 0, ${lvar} = ${svar}.length`; + declareDown = `${kvarAssign}${ivar} = ${svar}.length - 1`; + compare = `${ivar} < ${lvar}`; + compareDown = `${ivar} >= 0`; + if (this.step) { + if (stepNum != null) { + if (down) { + compare = compareDown; + declare = declareDown; + } + } else { + compare = `${stepVar} > 0 ? ${compare} : ${compareDown}`; + declare = `(${stepVar} > 0 ? (${declare}) : ${declareDown})`; } + increment = `${ivar} += ${stepVar}`; } else { - compare = `${stepVar} > 0 ? ${compare} : ${compareDown}`; - declare = `(${stepVar} > 0 ? (${declare}) : ${declareDown})`; + increment = `${(kvar !== ivar ? `++${ivar}` : `${ivar}++`)}`; } - increment = `${ivar} += ${stepVar}`; + forPartFragments = [this.makeCode(`${declare}; ${compare}; ${kvarAssign}${increment}`)]; + } + } + if (this.returns) { + resultPart = `${this.tab}${rvar} = [];\n`; + returnResult = `\n${this.tab}return ${rvar};`; + body.makeReturn(rvar); + } + if (this.guard) { + if (body.expressions.length > 1) { + body.expressions.unshift(new If((new Parens(this.guard)).invert(), new StatementLiteral("continue"))); } else { - increment = `${(kvar !== ivar ? `++${ivar}` : `${ivar}++`)}`; + if (this.guard) { + body = Block.wrap([new If(this.guard, body)]); + } } - forPartFragments = [this.makeCode(`${declare}; ${compare}; ${kvarAssign}${increment}`)]; } - } - if (this.returns) { - resultPart = `${this.tab}${rvar} = [];\n`; - returnResult = `\n${this.tab}return ${rvar};`; - body.makeReturn(rvar); - } - if (this.guard) { - if (body.expressions.length > 1) { - body.expressions.unshift(new If((new Parens(this.guard)).invert(), new StatementLiteral("continue"))); - } else { - if (this.guard) { - body = Block.wrap([new If(this.guard, body)]); + if (this.pattern) { + body.expressions.unshift(new Assign(this.name, this.from ? new IdentifierLiteral(kvar) : new Literal(`${svar}[${kvar}]`))); + } + defPartFragments = [].concat(this.makeCode(defPart), this.pluckDirectCall(o, body)); + if (namePart) { + varPart = `\n${idt1}${namePart};`; + } + if (this.object) { + forPartFragments = [this.makeCode(`${kvar} in ${svar}`)]; + if (this.own) { + guardPart = `\n${idt1}if (!${utility('hasProp', o)}.call(${svar}, ${kvar})) continue;`; } + } else if (this.from) { + forPartFragments = [this.makeCode(`${kvar} of ${svar}`)]; } - } - if (this.pattern) { - body.expressions.unshift(new Assign(this.name, this.from ? new IdentifierLiteral(kvar) : new Literal(`${svar}[${kvar}]`))); - } - defPartFragments = [].concat(this.makeCode(defPart), this.pluckDirectCall(o, body)); - if (namePart) { - varPart = `\n${idt1}${namePart};`; - } - if (this.object) { - forPartFragments = [this.makeCode(`${kvar} in ${svar}`)]; - if (this.own) { - guardPart = `\n${idt1}if (!${utility('hasProp', o)}.call(${svar}, ${kvar})) continue;`; + bodyFragments = body.compileToFragments(merge(o, { + indent: idt1 + }), LEVEL_TOP); + if (bodyFragments && bodyFragments.length > 0) { + bodyFragments = [].concat(this.makeCode("\n"), bodyFragments, this.makeCode("\n")); } - } else if (this.from) { - forPartFragments = [this.makeCode(`${kvar} of ${svar}`)]; + return [].concat(defPartFragments, this.makeCode(`${resultPart || ''}${this.tab}for (`), forPartFragments, this.makeCode(`) {${guardPart}${varPart}`), bodyFragments, this.makeCode(`${this.tab}}${returnResult || ''}`)); } - bodyFragments = body.compileToFragments(merge(o, { - indent: idt1 - }), LEVEL_TOP); - if (bodyFragments && bodyFragments.length > 0) { - bodyFragments = [].concat(this.makeCode("\n"), bodyFragments, this.makeCode("\n")); - } - return [].concat(defPartFragments, this.makeCode(`${resultPart || ''}${this.tab}for (`), forPartFragments, this.makeCode(`) {${guardPart}${varPart}`), bodyFragments, this.makeCode(`${this.tab}}${returnResult || ''}`)); - }; - For.prototype.pluckDirectCall = function(o, body) { - var base, defs, expr, fn, idx, j, len1, ref, ref3, ref4, ref5, ref6, ref7, ref8, ref9, val; - defs = []; - ref3 = body.expressions; - for (idx = j = 0, len1 = ref3.length; j < len1; idx = ++j) { - expr = ref3[idx]; - expr = expr.unwrapAll(); - if (!(expr instanceof Call)) { - continue; - } - val = (ref4 = expr.variable) != null ? ref4.unwrapAll() : void 0; - if (!((val instanceof Code) || (val instanceof Value && ((ref5 = val.base) != null ? ref5.unwrapAll() : void 0) instanceof Code && val.properties.length === 1 && ((ref6 = (ref7 = val.properties[0].name) != null ? ref7.value : void 0) === 'call' || ref6 === 'apply')))) { - continue; - } - fn = ((ref8 = val.base) != null ? ref8.unwrapAll() : void 0) || val; - ref = new IdentifierLiteral(o.scope.freeVariable('fn')); - base = new Value(ref); - if (val.base) { - ref9 = [base, val], val.base = ref9[0], base = ref9[1]; + pluckDirectCall(o, body) { + var base, defs, expr, fn, idx, j, len1, ref, ref3, ref4, ref5, ref6, ref7, ref8, ref9, val; + defs = []; + ref3 = body.expressions; + for (idx = j = 0, len1 = ref3.length; j < len1; idx = ++j) { + expr = ref3[idx]; + expr = expr.unwrapAll(); + if (!(expr instanceof Call)) { + continue; + } + val = (ref4 = expr.variable) != null ? ref4.unwrapAll() : void 0; + if (!((val instanceof Code) || (val instanceof Value && ((ref5 = val.base) != null ? ref5.unwrapAll() : void 0) instanceof Code && val.properties.length === 1 && ((ref6 = (ref7 = val.properties[0].name) != null ? ref7.value : void 0) === 'call' || ref6 === 'apply')))) { + continue; + } + fn = ((ref8 = val.base) != null ? ref8.unwrapAll() : void 0) || val; + ref = new IdentifierLiteral(o.scope.freeVariable('fn')); + base = new Value(ref); + if (val.base) { + ref9 = [base, val], val.base = ref9[0], base = ref9[1]; + } + body.expressions[idx] = new Call(base, expr.args); + defs = defs.concat(this.makeCode(this.tab), new Assign(ref, fn).compileToFragments(o, LEVEL_TOP), this.makeCode(';\n')); } - body.expressions[idx] = new Call(base, expr.args); - defs = defs.concat(this.makeCode(this.tab), new Assign(ref, fn).compileToFragments(o, LEVEL_TOP), this.makeCode(';\n')); + return defs; } - return defs; - }; - return For; + }; - })(While); + For.__super__ = superClass.prototype; - exports.Switch = Switch = (function(superClass1) { - extend1(Switch, superClass1); + For.prototype.children = ['body', 'source', 'guard', 'step']; - function Switch(subject, cases, otherwise) { - this.subject = subject; - this.cases = cases; - this.otherwise = otherwise; - } + return For; - Switch.prototype.children = ['subject', 'cases', 'otherwise']; + })(While); - Switch.prototype.isStatement = YES; + exports.Switch = Switch = (function(superClass) { + class Switch extends superClass { + constructor(subject, cases, otherwise) { + super(...arguments); + this.subject = subject; + this.cases = cases; + this.otherwise = otherwise; + } - Switch.prototype.jumps = function(o = { - block: true - }) { - var block, conds, j, jumpNode, len1, ref3, ref4, ref5; - ref3 = this.cases; - for (j = 0, len1 = ref3.length; j < len1; j++) { - ref4 = ref3[j], conds = ref4[0], block = ref4[1]; - if (jumpNode = block.jumps(o)) { - return jumpNode; + jumps(o = { + block: true + }) { + var block, conds, j, jumpNode, len1, ref3, ref4, ref5; + ref3 = this.cases; + for (j = 0, len1 = ref3.length; j < len1; j++) { + ref4 = ref3[j], conds = ref4[0], block = ref4[1]; + if (jumpNode = block.jumps(o)) { + return jumpNode; + } } + return (ref5 = this.otherwise) != null ? ref5.jumps(o) : void 0; } - return (ref5 = this.otherwise) != null ? ref5.jumps(o) : void 0; - }; - Switch.prototype.makeReturn = function(res) { - var j, len1, pair, ref3, ref4; - ref3 = this.cases; - for (j = 0, len1 = ref3.length; j < len1; j++) { - pair = ref3[j]; - pair[1].makeReturn(res); - } - if (res) { - this.otherwise || (this.otherwise = new Block([new Literal('void 0')])); - } - if ((ref4 = this.otherwise) != null) { - ref4.makeReturn(res); + makeReturn(res) { + var j, len1, pair, ref3, ref4; + ref3 = this.cases; + for (j = 0, len1 = ref3.length; j < len1; j++) { + pair = ref3[j]; + pair[1].makeReturn(res); + } + if (res) { + this.otherwise || (this.otherwise = new Block([new Literal('void 0')])); + } + if ((ref4 = this.otherwise) != null) { + ref4.makeReturn(res); + } + return this; } - return this; - }; - Switch.prototype.compileNode = function(o) { - var block, body, cond, conditions, expr, fragments, i, idt1, idt2, j, k, len1, len2, ref3, ref4, ref5; - idt1 = o.indent + TAB; - idt2 = o.indent = idt1 + TAB; - fragments = [].concat(this.makeCode(this.tab + "switch ("), (this.subject ? this.subject.compileToFragments(o, LEVEL_PAREN) : this.makeCode("false")), this.makeCode(") {\n")); - ref3 = this.cases; - for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { - ref4 = ref3[i], conditions = ref4[0], block = ref4[1]; - ref5 = flatten([conditions]); - for (k = 0, len2 = ref5.length; k < len2; k++) { - cond = ref5[k]; - if (!this.subject) { - cond = cond.invert(); - } - fragments = fragments.concat(this.makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), this.makeCode(":\n")); - } - if ((body = block.compileToFragments(o, LEVEL_TOP)).length > 0) { - fragments = fragments.concat(body, this.makeCode('\n')); - } - if (i === this.cases.length - 1 && !this.otherwise) { - break; - } - expr = this.lastNonComment(block.expressions); - if (expr instanceof Return || (expr instanceof Literal && expr.jumps() && expr.value !== 'debugger')) { - continue; + compileNode(o) { + var block, body, cond, conditions, expr, fragments, i, idt1, idt2, j, k, len1, len2, ref3, ref4, ref5; + idt1 = o.indent + TAB; + idt2 = o.indent = idt1 + TAB; + fragments = [].concat(this.makeCode(this.tab + "switch ("), (this.subject ? this.subject.compileToFragments(o, LEVEL_PAREN) : this.makeCode("false")), this.makeCode(") {\n")); + ref3 = this.cases; + for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { + ref4 = ref3[i], conditions = ref4[0], block = ref4[1]; + ref5 = flatten([conditions]); + for (k = 0, len2 = ref5.length; k < len2; k++) { + cond = ref5[k]; + if (!this.subject) { + cond = cond.invert(); + } + fragments = fragments.concat(this.makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), this.makeCode(":\n")); + } + if ((body = block.compileToFragments(o, LEVEL_TOP)).length > 0) { + fragments = fragments.concat(body, this.makeCode('\n')); + } + if (i === this.cases.length - 1 && !this.otherwise) { + break; + } + expr = this.lastNonComment(block.expressions); + if (expr instanceof Return || (expr instanceof Literal && expr.jumps() && expr.value !== 'debugger')) { + continue; + } + fragments.push(cond.makeCode(idt2 + 'break;\n')); } - fragments.push(cond.makeCode(idt2 + 'break;\n')); - } - if (this.otherwise && this.otherwise.expressions.length) { - fragments.push(this.makeCode(idt1 + "default:\n"), ...this.otherwise.compileToFragments(o, LEVEL_TOP), this.makeCode("\n")); + if (this.otherwise && this.otherwise.expressions.length) { + fragments.push(this.makeCode(idt1 + "default:\n"), ...this.otherwise.compileToFragments(o, LEVEL_TOP), this.makeCode("\n")); + } + fragments.push(this.makeCode(this.tab + '}')); + return fragments; } - fragments.push(this.makeCode(this.tab + '}')); - return fragments; + }; - return Switch; + Switch.__super__ = superClass.prototype; - })(Base); + Switch.prototype.children = ['subject', 'cases', 'otherwise']; - exports.If = If = (function(superClass1) { - extend1(If, superClass1); + Switch.prototype.isStatement = YES; - function If(condition, body1, options = {}) { - this.body = body1; - this.condition = options.type === 'unless' ? condition.invert() : condition; - this.elseBody = null; - this.isChain = false; - this.soak = options.soak; - } + return Switch; - If.prototype.children = ['condition', 'body', 'elseBody']; + })(Base); - If.prototype.bodyNode = function() { - var ref3; - return (ref3 = this.body) != null ? ref3.unwrap() : void 0; - }; + exports.If = If = (function(superClass) { + class If extends superClass { + constructor(condition, body1, options = {}) { + super(...arguments); + this.body = body1; + this.condition = options.type === 'unless' ? condition.invert() : condition; + this.elseBody = null; + this.isChain = false; + this.soak = options.soak; + } - If.prototype.elseBodyNode = function() { - var ref3; - return (ref3 = this.elseBody) != null ? ref3.unwrap() : void 0; - }; + bodyNode() { + var ref3; + return (ref3 = this.body) != null ? ref3.unwrap() : void 0; + } - If.prototype.addElse = function(elseBody) { - if (this.isChain) { - this.elseBodyNode().addElse(elseBody); - } else { - this.isChain = elseBody instanceof If; - this.elseBody = this.ensureBlock(elseBody); - this.elseBody.updateLocationDataIfMissing(elseBody.locationData); + elseBodyNode() { + var ref3; + return (ref3 = this.elseBody) != null ? ref3.unwrap() : void 0; } - return this; - }; - If.prototype.isStatement = function(o) { - var ref3; - return (o != null ? o.level : void 0) === LEVEL_TOP || this.bodyNode().isStatement(o) || ((ref3 = this.elseBodyNode()) != null ? ref3.isStatement(o) : void 0); - }; + addElse(elseBody) { + if (this.isChain) { + this.elseBodyNode().addElse(elseBody); + } else { + this.isChain = elseBody instanceof If; + this.elseBody = this.ensureBlock(elseBody); + this.elseBody.updateLocationDataIfMissing(elseBody.locationData); + } + return this; + } - If.prototype.jumps = function(o) { - var ref3; - return this.body.jumps(o) || ((ref3 = this.elseBody) != null ? ref3.jumps(o) : void 0); - }; + isStatement(o) { + var ref3; + return (o != null ? o.level : void 0) === LEVEL_TOP || this.bodyNode().isStatement(o) || ((ref3 = this.elseBodyNode()) != null ? ref3.isStatement(o) : void 0); + } - If.prototype.compileNode = function(o) { - if (this.isStatement(o)) { - return this.compileStatement(o); - } else { - return this.compileExpression(o); + jumps(o) { + var ref3; + return this.body.jumps(o) || ((ref3 = this.elseBody) != null ? ref3.jumps(o) : void 0); } - }; - If.prototype.makeReturn = function(res) { - if (res) { - this.elseBody || (this.elseBody = new Block([new Literal('void 0')])); + compileNode(o) { + if (this.isStatement(o)) { + return this.compileStatement(o); + } else { + return this.compileExpression(o); + } } - this.body && (this.body = new Block([this.body.makeReturn(res)])); - this.elseBody && (this.elseBody = new Block([this.elseBody.makeReturn(res)])); - return this; - }; - If.prototype.ensureBlock = function(node) { - if (node instanceof Block) { - return node; - } else { - return new Block([node]); + makeReturn(res) { + if (res) { + this.elseBody || (this.elseBody = new Block([new Literal('void 0')])); + } + this.body && (this.body = new Block([this.body.makeReturn(res)])); + this.elseBody && (this.elseBody = new Block([this.elseBody.makeReturn(res)])); + return this; } - }; - If.prototype.compileStatement = function(o) { - var answer, body, child, cond, exeq, ifPart, indent; - child = del(o, 'chainChild'); - exeq = del(o, 'isExistentialEquals'); - if (exeq) { - return new If(this.condition.invert(), this.elseBodyNode(), { - type: 'if' - }).compileToFragments(o); - } - indent = o.indent + TAB; - cond = this.condition.compileToFragments(o, LEVEL_PAREN); - body = this.ensureBlock(this.body).compileToFragments(merge(o, { - indent: indent - })); - ifPart = [].concat(this.makeCode("if ("), cond, this.makeCode(") {\n"), body, this.makeCode(`\n${this.tab}}`)); - if (!child) { - ifPart.unshift(this.makeCode(this.tab)); - } - if (!this.elseBody) { - return ifPart; - } - answer = ifPart.concat(this.makeCode(' else ')); - if (this.isChain) { - o.chainChild = true; - answer = answer.concat(this.elseBody.unwrap().compileToFragments(o, LEVEL_TOP)); - } else { - answer = answer.concat(this.makeCode("{\n"), this.elseBody.compileToFragments(merge(o, { + ensureBlock(node) { + if (node instanceof Block) { + return node; + } else { + return new Block([node]); + } + } + + compileStatement(o) { + var answer, body, child, cond, exeq, ifPart, indent; + child = del(o, 'chainChild'); + exeq = del(o, 'isExistentialEquals'); + if (exeq) { + return new If(this.condition.invert(), this.elseBodyNode(), { + type: 'if' + }).compileToFragments(o); + } + indent = o.indent + TAB; + cond = this.condition.compileToFragments(o, LEVEL_PAREN); + body = this.ensureBlock(this.body).compileToFragments(merge(o, { indent: indent - }), LEVEL_TOP), this.makeCode(`\n${this.tab}}`)); + })); + ifPart = [].concat(this.makeCode("if ("), cond, this.makeCode(") {\n"), body, this.makeCode(`\n${this.tab}}`)); + if (!child) { + ifPart.unshift(this.makeCode(this.tab)); + } + if (!this.elseBody) { + return ifPart; + } + answer = ifPart.concat(this.makeCode(' else ')); + if (this.isChain) { + o.chainChild = true; + answer = answer.concat(this.elseBody.unwrap().compileToFragments(o, LEVEL_TOP)); + } else { + answer = answer.concat(this.makeCode("{\n"), this.elseBody.compileToFragments(merge(o, { + indent: indent + }), LEVEL_TOP), this.makeCode(`\n${this.tab}}`)); + } + return answer; } - return answer; - }; - If.prototype.compileExpression = function(o) { - var alt, body, cond, fragments; - cond = this.condition.compileToFragments(o, LEVEL_COND); - body = this.bodyNode().compileToFragments(o, LEVEL_LIST); - alt = this.elseBodyNode() ? this.elseBodyNode().compileToFragments(o, LEVEL_LIST) : [this.makeCode('void 0')]; - fragments = cond.concat(this.makeCode(" ? "), body, this.makeCode(" : "), alt); - if (o.level >= LEVEL_COND) { - return this.wrapInBraces(fragments); - } else { - return fragments; + compileExpression(o) { + var alt, body, cond, fragments; + cond = this.condition.compileToFragments(o, LEVEL_COND); + body = this.bodyNode().compileToFragments(o, LEVEL_LIST); + alt = this.elseBodyNode() ? this.elseBodyNode().compileToFragments(o, LEVEL_LIST) : [this.makeCode('void 0')]; + fragments = cond.concat(this.makeCode(" ? "), body, this.makeCode(" : "), alt); + if (o.level >= LEVEL_COND) { + return this.wrapInBraces(fragments); + } else { + return fragments; + } + } + + unfoldSoak() { + return this.soak && this; } - }; - If.prototype.unfoldSoak = function() { - return this.soak && this; }; + If.__super__ = superClass.prototype; + + If.prototype.children = ['condition', 'body', 'elseBody']; + return If; })(Base); diff --git a/lib/coffee-script/optparse.js b/lib/coffee-script/optparse.js index 5e1abad3bc..cefe05c8af 100644 --- a/lib/coffee-script/optparse.js +++ b/lib/coffee-script/optparse.js @@ -4,13 +4,13 @@ repeat = require('./helpers').repeat; - exports.OptionParser = OptionParser = (function() { - function OptionParser(rules, banner) { + exports.OptionParser = OptionParser = class OptionParser { + constructor(rules, banner) { this.banner = banner; this.rules = buildRules(rules); } - OptionParser.prototype.parse = function(args) { + parse(args) { var arg, i, isOption, j, k, len, len1, matchedRule, options, originalArgs, pos, ref, rule, seenNonOptionArg, skippingArgument, value; options = { "arguments": [] @@ -56,9 +56,9 @@ } } return options; - }; + } - OptionParser.prototype.help = function() { + help() { var j, len, letPart, lines, ref, rule, spaces; lines = []; if (this.banner) { @@ -73,11 +73,9 @@ lines.push(' ' + letPart + rule.longFlag + spaces + rule.description); } return `\n${lines.join('\n')}\n`; - }; - - return OptionParser; + } - })(); + }; LONG_FLAG = /^(--\w[\w\-]*)/; diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index 02f5de8882..b903991d84 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -14,470 +14,471 @@ }; exports.Rewriter = (function() { - function Rewriter() {} - - Rewriter.prototype.rewrite = function(tokens1) { - this.tokens = tokens1; - this.removeLeadingNewlines(); - this.closeOpenCalls(); - this.closeOpenIndexes(); - this.normalizeLines(); - this.tagPostfixConditionals(); - this.addImplicitBracesAndParens(); - this.addLocationDataToGeneratedTokens(); - this.fixOutdentLocationData(); - return this.tokens; - }; - - Rewriter.prototype.scanTokens = function(block) { - var i, token, tokens; - tokens = this.tokens; - i = 0; - while (token = tokens[i]) { - i += block.call(this, token, i, tokens); + class Rewriter { + rewrite(tokens1) { + this.tokens = tokens1; + this.removeLeadingNewlines(); + this.closeOpenCalls(); + this.closeOpenIndexes(); + this.normalizeLines(); + this.tagPostfixConditionals(); + this.addImplicitBracesAndParens(); + this.addLocationDataToGeneratedTokens(); + this.fixOutdentLocationData(); + return this.tokens; } - return true; - }; - Rewriter.prototype.detectEnd = function(i, condition, action) { - var levels, ref, ref1, token, tokens; - tokens = this.tokens; - levels = 0; - while (token = tokens[i]) { - if (levels === 0 && condition.call(this, token, i)) { - return action.call(this, token, i); - } - if (!token || levels < 0) { - return action.call(this, token, i - 1); - } - if (ref = token[0], indexOf.call(EXPRESSION_START, ref) >= 0) { - levels += 1; - } else if (ref1 = token[0], indexOf.call(EXPRESSION_END, ref1) >= 0) { - levels -= 1; + scanTokens(block) { + var i, token, tokens; + tokens = this.tokens; + i = 0; + while (token = tokens[i]) { + i += block.call(this, token, i, tokens); } - i += 1; + return true; } - return i - 1; - }; - Rewriter.prototype.removeLeadingNewlines = function() { - var i, k, len, ref, tag; - ref = this.tokens; - for (i = k = 0, len = ref.length; k < len; i = ++k) { - tag = ref[i][0]; - if (tag !== 'TERMINATOR') { - break; + detectEnd(i, condition, action) { + var levels, ref, ref1, token, tokens; + tokens = this.tokens; + levels = 0; + while (token = tokens[i]) { + if (levels === 0 && condition.call(this, token, i)) { + return action.call(this, token, i); + } + if (!token || levels < 0) { + return action.call(this, token, i - 1); + } + if (ref = token[0], indexOf.call(EXPRESSION_START, ref) >= 0) { + levels += 1; + } else if (ref1 = token[0], indexOf.call(EXPRESSION_END, ref1) >= 0) { + levels -= 1; + } + i += 1; } + return i - 1; } - if (i) { - return this.tokens.splice(0, i); - } - }; - - Rewriter.prototype.closeOpenCalls = function() { - var action, condition; - condition = function(token, i) { - var ref; - return ((ref = token[0]) === ')' || ref === 'CALL_END') || token[0] === 'OUTDENT' && this.tag(i - 1) === ')'; - }; - action = function(token, i) { - return this.tokens[token[0] === 'OUTDENT' ? i - 1 : i][0] = 'CALL_END'; - }; - return this.scanTokens(function(token, i) { - if (token[0] === 'CALL_START') { - this.detectEnd(i + 1, condition, action); - } - return 1; - }); - }; - - Rewriter.prototype.closeOpenIndexes = function() { - var action, condition; - condition = function(token, i) { - var ref; - return (ref = token[0]) === ']' || ref === 'INDEX_END'; - }; - action = function(token, i) { - return token[0] = 'INDEX_END'; - }; - return this.scanTokens(function(token, i) { - if (token[0] === 'INDEX_START') { - this.detectEnd(i + 1, condition, action); - } - return 1; - }); - }; - Rewriter.prototype.indexOfTag = function(i, ...pattern) { - var fuzz, j, k, ref, ref1; - fuzz = 0; - for (j = k = 0, ref = pattern.length; 0 <= ref ? k < ref : k > ref; j = 0 <= ref ? ++k : --k) { - while (this.tag(i + j + fuzz) === 'HERECOMMENT') { - fuzz += 2; - } - if (pattern[j] == null) { - continue; - } - if (typeof pattern[j] === 'string') { - pattern[j] = [pattern[j]]; + removeLeadingNewlines() { + var i, k, len, ref, tag; + ref = this.tokens; + for (i = k = 0, len = ref.length; k < len; i = ++k) { + tag = ref[i][0]; + if (tag !== 'TERMINATOR') { + break; + } } - if (ref1 = this.tag(i + j + fuzz), indexOf.call(pattern[j], ref1) < 0) { - return -1; + if (i) { + return this.tokens.splice(0, i); } } - return i + j + fuzz - 1; - }; - Rewriter.prototype.looksObjectish = function(j) { - var end, index; - if (this.indexOfTag(j, '@', null, ':') > -1 || this.indexOfTag(j, null, ':') > -1) { - return true; + closeOpenCalls() { + var action, condition; + condition = function(token, i) { + var ref; + return ((ref = token[0]) === ')' || ref === 'CALL_END') || token[0] === 'OUTDENT' && this.tag(i - 1) === ')'; + }; + action = function(token, i) { + return this.tokens[token[0] === 'OUTDENT' ? i - 1 : i][0] = 'CALL_END'; + }; + return this.scanTokens(function(token, i) { + if (token[0] === 'CALL_START') { + this.detectEnd(i + 1, condition, action); + } + return 1; + }); } - index = this.indexOfTag(j, EXPRESSION_START); - if (index > -1) { - end = null; - this.detectEnd(index + 1, (function(token) { + + closeOpenIndexes() { + var action, condition; + condition = function(token, i) { var ref; - return ref = token[0], indexOf.call(EXPRESSION_END, ref) >= 0; - }), (function(token, i) { - return end = i; - })); - if (this.tag(end + 1) === ':') { - return true; + return (ref = token[0]) === ']' || ref === 'INDEX_END'; + }; + action = function(token, i) { + return token[0] = 'INDEX_END'; + }; + return this.scanTokens(function(token, i) { + if (token[0] === 'INDEX_START') { + this.detectEnd(i + 1, condition, action); + } + return 1; + }); + } + + indexOfTag(i, ...pattern) { + var fuzz, j, k, ref, ref1; + fuzz = 0; + for (j = k = 0, ref = pattern.length; 0 <= ref ? k < ref : k > ref; j = 0 <= ref ? ++k : --k) { + while (this.tag(i + j + fuzz) === 'HERECOMMENT') { + fuzz += 2; + } + if (pattern[j] == null) { + continue; + } + if (typeof pattern[j] === 'string') { + pattern[j] = [pattern[j]]; + } + if (ref1 = this.tag(i + j + fuzz), indexOf.call(pattern[j], ref1) < 0) { + return -1; + } } + return i + j + fuzz - 1; } - return false; - }; - Rewriter.prototype.findTagsBackwards = function(i, tags) { - var backStack, ref, ref1, ref2, ref3, ref4, ref5; - backStack = []; - while (i >= 0 && (backStack.length || (ref2 = this.tag(i), indexOf.call(tags, ref2) < 0) && ((ref3 = this.tag(i), indexOf.call(EXPRESSION_START, ref3) < 0) || this.tokens[i].generated) && (ref4 = this.tag(i), indexOf.call(LINEBREAKS, ref4) < 0))) { - if (ref = this.tag(i), indexOf.call(EXPRESSION_END, ref) >= 0) { - backStack.push(this.tag(i)); + looksObjectish(j) { + var end, index; + if (this.indexOfTag(j, '@', null, ':') > -1 || this.indexOfTag(j, null, ':') > -1) { + return true; } - if ((ref1 = this.tag(i), indexOf.call(EXPRESSION_START, ref1) >= 0) && backStack.length) { - backStack.pop(); + index = this.indexOfTag(j, EXPRESSION_START); + if (index > -1) { + end = null; + this.detectEnd(index + 1, (function(token) { + var ref; + return ref = token[0], indexOf.call(EXPRESSION_END, ref) >= 0; + }), (function(token, i) { + return end = i; + })); + if (this.tag(end + 1) === ':') { + return true; + } } - i -= 1; + return false; } - return ref5 = this.tag(i), indexOf.call(tags, ref5) >= 0; - }; - Rewriter.prototype.addImplicitBracesAndParens = function() { - var stack, start; - stack = []; - start = null; - return this.scanTokens(function(token, i, tokens) { - var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, newLine, nextTag, offset, prevTag, prevToken, ref, ref1, ref2, ref3, ref4, ref5, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag; - tag = token[0]; - prevTag = (prevToken = i > 0 ? tokens[i - 1] : [])[0]; - nextTag = (i < tokens.length - 1 ? tokens[i + 1] : [])[0]; - stackTop = function() { - return stack[stack.length - 1]; - }; - startIdx = i; - forward = function(n) { - return i - startIdx + n; - }; - inImplicit = function() { - var ref, ref1; - return (ref = stackTop()) != null ? (ref1 = ref[2]) != null ? ref1.ours : void 0 : void 0; - }; - inImplicitCall = function() { - var ref; - return inImplicit() && ((ref = stackTop()) != null ? ref[0] : void 0) === '('; - }; - inImplicitObject = function() { - var ref; - return inImplicit() && ((ref = stackTop()) != null ? ref[0] : void 0) === '{'; - }; - inImplicitControl = function() { - var ref; - return inImplicit && ((ref = stackTop()) != null ? ref[0] : void 0) === 'CONTROL'; - }; - startImplicitCall = function(j) { - var idx; - idx = j != null ? j : i; - stack.push([ - '(', idx, { - ours: true + findTagsBackwards(i, tags) { + var backStack, ref, ref1, ref2, ref3, ref4, ref5; + backStack = []; + while (i >= 0 && (backStack.length || (ref2 = this.tag(i), indexOf.call(tags, ref2) < 0) && ((ref3 = this.tag(i), indexOf.call(EXPRESSION_START, ref3) < 0) || this.tokens[i].generated) && (ref4 = this.tag(i), indexOf.call(LINEBREAKS, ref4) < 0))) { + if (ref = this.tag(i), indexOf.call(EXPRESSION_END, ref) >= 0) { + backStack.push(this.tag(i)); + } + if ((ref1 = this.tag(i), indexOf.call(EXPRESSION_START, ref1) >= 0) && backStack.length) { + backStack.pop(); + } + i -= 1; + } + return ref5 = this.tag(i), indexOf.call(tags, ref5) >= 0; + } + + addImplicitBracesAndParens() { + var stack, start; + stack = []; + start = null; + return this.scanTokens(function(token, i, tokens) { + var endImplicitCall, endImplicitObject, forward, inImplicit, inImplicitCall, inImplicitControl, inImplicitObject, newLine, nextTag, offset, prevTag, prevToken, ref, ref1, ref2, ref3, ref4, ref5, s, sameLine, stackIdx, stackTag, stackTop, startIdx, startImplicitCall, startImplicitObject, startsLine, tag; + tag = token[0]; + prevTag = (prevToken = i > 0 ? tokens[i - 1] : [])[0]; + nextTag = (i < tokens.length - 1 ? tokens[i + 1] : [])[0]; + stackTop = function() { + return stack[stack.length - 1]; + }; + startIdx = i; + forward = function(n) { + return i - startIdx + n; + }; + inImplicit = function() { + var ref, ref1; + return (ref = stackTop()) != null ? (ref1 = ref[2]) != null ? ref1.ours : void 0 : void 0; + }; + inImplicitCall = function() { + var ref; + return inImplicit() && ((ref = stackTop()) != null ? ref[0] : void 0) === '('; + }; + inImplicitObject = function() { + var ref; + return inImplicit() && ((ref = stackTop()) != null ? ref[0] : void 0) === '{'; + }; + inImplicitControl = function() { + var ref; + return inImplicit && ((ref = stackTop()) != null ? ref[0] : void 0) === 'CONTROL'; + }; + startImplicitCall = function(j) { + var idx; + idx = j != null ? j : i; + stack.push([ + '(', idx, { + ours: true + } + ]); + tokens.splice(idx, 0, generate('CALL_START', '(')); + if (j == null) { + return i += 1; } - ]); - tokens.splice(idx, 0, generate('CALL_START', '(')); - if (j == null) { + }; + endImplicitCall = function() { + stack.pop(); + tokens.splice(i, 0, generate('CALL_END', ')', ['', 'end of input', token[2]])); return i += 1; - } - }; - endImplicitCall = function() { - stack.pop(); - tokens.splice(i, 0, generate('CALL_END', ')', ['', 'end of input', token[2]])); - return i += 1; - }; - startImplicitObject = function(j, startsLine = true) { - var idx, val; - idx = j != null ? j : i; - stack.push([ - '{', idx, { - sameLine: true, - startsLine: startsLine, - ours: true + }; + startImplicitObject = function(j, startsLine = true) { + var idx, val; + idx = j != null ? j : i; + stack.push([ + '{', idx, { + sameLine: true, + startsLine: startsLine, + ours: true + } + ]); + val = new String('{'); + val.generated = true; + tokens.splice(idx, 0, generate('{', val, token)); + if (j == null) { + return i += 1; } - ]); - val = new String('{'); - val.generated = true; - tokens.splice(idx, 0, generate('{', val, token)); - if (j == null) { + }; + endImplicitObject = function(j) { + j = j != null ? j : i; + stack.pop(); + tokens.splice(j, 0, generate('}', '}', token)); return i += 1; + }; + if (inImplicitCall() && (tag === 'IF' || tag === 'TRY' || tag === 'FINALLY' || tag === 'CATCH' || tag === 'CLASS' || tag === 'SWITCH')) { + stack.push([ + 'CONTROL', i, { + ours: true + } + ]); + return forward(1); } - }; - endImplicitObject = function(j) { - j = j != null ? j : i; - stack.pop(); - tokens.splice(j, 0, generate('}', '}', token)); - return i += 1; - }; - if (inImplicitCall() && (tag === 'IF' || tag === 'TRY' || tag === 'FINALLY' || tag === 'CATCH' || tag === 'CLASS' || tag === 'SWITCH')) { - stack.push([ - 'CONTROL', i, { - ours: true + if (tag === 'INDENT' && inImplicit()) { + if (prevTag !== '=>' && prevTag !== '->' && prevTag !== '[' && prevTag !== '(' && prevTag !== ',' && prevTag !== '{' && prevTag !== 'TRY' && prevTag !== 'ELSE' && prevTag !== '=') { + while (inImplicitCall()) { + endImplicitCall(); + } } - ]); - return forward(1); - } - if (tag === 'INDENT' && inImplicit()) { - if (prevTag !== '=>' && prevTag !== '->' && prevTag !== '[' && prevTag !== '(' && prevTag !== ',' && prevTag !== '{' && prevTag !== 'TRY' && prevTag !== 'ELSE' && prevTag !== '=') { - while (inImplicitCall()) { - endImplicitCall(); + if (inImplicitControl()) { + stack.pop(); } + stack.push([tag, i]); + return forward(1); } - if (inImplicitControl()) { - stack.pop(); + if (indexOf.call(EXPRESSION_START, tag) >= 0) { + stack.push([tag, i]); + return forward(1); } - stack.push([tag, i]); - return forward(1); - } - if (indexOf.call(EXPRESSION_START, tag) >= 0) { - stack.push([tag, i]); - return forward(1); - } - if (indexOf.call(EXPRESSION_END, tag) >= 0) { - while (inImplicit()) { - if (inImplicitCall()) { - endImplicitCall(); - } else if (inImplicitObject()) { - endImplicitObject(); - } else { - stack.pop(); + if (indexOf.call(EXPRESSION_END, tag) >= 0) { + while (inImplicit()) { + if (inImplicitCall()) { + endImplicitCall(); + } else if (inImplicitObject()) { + endImplicitObject(); + } else { + stack.pop(); + } } + start = stack.pop(); } - start = stack.pop(); - } - if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !((ref = tokens[i + 1]) != null ? ref.spaced : void 0) && !((ref1 = tokens[i + 1]) != null ? ref1.newLine : void 0))) { - if (tag === '?') { - tag = token[0] = 'FUNC_EXIST'; + if ((indexOf.call(IMPLICIT_FUNC, tag) >= 0 && token.spaced || tag === '?' && i > 0 && !tokens[i - 1].spaced) && (indexOf.call(IMPLICIT_CALL, nextTag) >= 0 || indexOf.call(IMPLICIT_UNSPACED_CALL, nextTag) >= 0 && !((ref = tokens[i + 1]) != null ? ref.spaced : void 0) && !((ref1 = tokens[i + 1]) != null ? ref1.newLine : void 0))) { + if (tag === '?') { + tag = token[0] = 'FUNC_EXIST'; + } + startImplicitCall(i + 1); + return forward(2); } - startImplicitCall(i + 1); - return forward(2); - } - if (indexOf.call(IMPLICIT_FUNC, tag) >= 0 && this.indexOfTag(i + 1, 'INDENT') > -1 && this.looksObjectish(i + 2) && !this.findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])) { - startImplicitCall(i + 1); - stack.push(['INDENT', i + 2]); - return forward(3); - } - if (tag === ':') { - s = (function() { - var ref2; - switch (false) { - case ref2 = this.tag(i - 1), indexOf.call(EXPRESSION_END, ref2) < 0: - return start[1]; - case this.tag(i - 2) !== '@': - return i - 2; - default: - return i - 1; + if (indexOf.call(IMPLICIT_FUNC, tag) >= 0 && this.indexOfTag(i + 1, 'INDENT') > -1 && this.looksObjectish(i + 2) && !this.findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH', 'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])) { + startImplicitCall(i + 1); + stack.push(['INDENT', i + 2]); + return forward(3); + } + if (tag === ':') { + s = (function() { + var ref2; + switch (false) { + case ref2 = this.tag(i - 1), indexOf.call(EXPRESSION_END, ref2) < 0: + return start[1]; + case this.tag(i - 2) !== '@': + return i - 2; + default: + return i - 1; + } + }).call(this); + while (this.tag(s - 2) === 'HERECOMMENT') { + s -= 2; } - }).call(this); - while (this.tag(s - 2) === 'HERECOMMENT') { - s -= 2; - } - this.insideForDeclaration = nextTag === 'FOR'; - startsLine = s === 0 || (ref2 = this.tag(s - 1), indexOf.call(LINEBREAKS, ref2) >= 0) || tokens[s - 1].newLine; - if (stackTop()) { - ref3 = stackTop(), stackTag = ref3[0], stackIdx = ref3[1]; - if ((stackTag === '{' || stackTag === 'INDENT' && this.tag(stackIdx - 1) === '{') && (startsLine || this.tag(s - 1) === ',' || this.tag(s - 1) === '{')) { - return forward(1); + this.insideForDeclaration = nextTag === 'FOR'; + startsLine = s === 0 || (ref2 = this.tag(s - 1), indexOf.call(LINEBREAKS, ref2) >= 0) || tokens[s - 1].newLine; + if (stackTop()) { + ref3 = stackTop(), stackTag = ref3[0], stackIdx = ref3[1]; + if ((stackTag === '{' || stackTag === 'INDENT' && this.tag(stackIdx - 1) === '{') && (startsLine || this.tag(s - 1) === ',' || this.tag(s - 1) === '{')) { + return forward(1); + } } + startImplicitObject(s, !!startsLine); + return forward(2); } - startImplicitObject(s, !!startsLine); - return forward(2); - } - if (inImplicitObject() && indexOf.call(LINEBREAKS, tag) >= 0) { - stackTop()[2].sameLine = false; - } - newLine = prevTag === 'OUTDENT' || prevToken.newLine; - if (indexOf.call(IMPLICIT_END, tag) >= 0 || indexOf.call(CALL_CLOSERS, tag) >= 0 && newLine) { - while (inImplicit()) { - ref4 = stackTop(), stackTag = ref4[0], stackIdx = ref4[1], (ref5 = ref4[2], sameLine = ref5.sameLine, startsLine = ref5.startsLine); - if (inImplicitCall() && prevTag !== ',') { - endImplicitCall(); - } else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':') { - endImplicitObject(); - } else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) { - if (nextTag === 'HERECOMMENT') { - return forward(1); + if (inImplicitObject() && indexOf.call(LINEBREAKS, tag) >= 0) { + stackTop()[2].sameLine = false; + } + newLine = prevTag === 'OUTDENT' || prevToken.newLine; + if (indexOf.call(IMPLICIT_END, tag) >= 0 || indexOf.call(CALL_CLOSERS, tag) >= 0 && newLine) { + while (inImplicit()) { + ref4 = stackTop(), stackTag = ref4[0], stackIdx = ref4[1], (ref5 = ref4[2], sameLine = ref5.sameLine, startsLine = ref5.startsLine); + if (inImplicitCall() && prevTag !== ',') { + endImplicitCall(); + } else if (inImplicitObject() && !this.insideForDeclaration && sameLine && tag !== 'TERMINATOR' && prevTag !== ':') { + endImplicitObject(); + } else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) { + if (nextTag === 'HERECOMMENT') { + return forward(1); + } + endImplicitObject(); + } else { + break; } - endImplicitObject(); - } else { - break; } } - } - if (tag === ',' && !this.looksObjectish(i + 1) && inImplicitObject() && !this.insideForDeclaration && (nextTag !== 'TERMINATOR' || !this.looksObjectish(i + 2))) { - offset = nextTag === 'OUTDENT' ? 1 : 0; - while (inImplicitObject()) { - endImplicitObject(i + offset); + if (tag === ',' && !this.looksObjectish(i + 1) && inImplicitObject() && !this.insideForDeclaration && (nextTag !== 'TERMINATOR' || !this.looksObjectish(i + 2))) { + offset = nextTag === 'OUTDENT' ? 1 : 0; + while (inImplicitObject()) { + endImplicitObject(i + offset); + } } - } - return forward(1); - }); - }; + return forward(1); + }); + } - Rewriter.prototype.addLocationDataToGeneratedTokens = function() { - return this.scanTokens(function(token, i, tokens) { - var column, line, nextLocation, prevLocation, ref, ref1; - if (token[2]) { - return 1; - } - if (!(token.generated || token.explicit)) { + addLocationDataToGeneratedTokens() { + return this.scanTokens(function(token, i, tokens) { + var column, line, nextLocation, prevLocation, ref, ref1; + if (token[2]) { + return 1; + } + if (!(token.generated || token.explicit)) { + return 1; + } + if (token[0] === '{' && (nextLocation = (ref = tokens[i + 1]) != null ? ref[2] : void 0)) { + line = nextLocation.first_line, column = nextLocation.first_column; + } else if (prevLocation = (ref1 = tokens[i - 1]) != null ? ref1[2] : void 0) { + line = prevLocation.last_line, column = prevLocation.last_column; + } else { + line = column = 0; + } + token[2] = { + first_line: line, + first_column: column, + last_line: line, + last_column: column + }; return 1; - } - if (token[0] === '{' && (nextLocation = (ref = tokens[i + 1]) != null ? ref[2] : void 0)) { - line = nextLocation.first_line, column = nextLocation.first_column; - } else if (prevLocation = (ref1 = tokens[i - 1]) != null ? ref1[2] : void 0) { - line = prevLocation.last_line, column = prevLocation.last_column; - } else { - line = column = 0; - } - token[2] = { - first_line: line, - first_column: column, - last_line: line, - last_column: column - }; - return 1; - }); - }; + }); + } - Rewriter.prototype.fixOutdentLocationData = function() { - return this.scanTokens(function(token, i, tokens) { - var prevLocationData; - if (!(token[0] === 'OUTDENT' || (token.generated && token[0] === 'CALL_END') || (token.generated && token[0] === '}'))) { + fixOutdentLocationData() { + return this.scanTokens(function(token, i, tokens) { + var prevLocationData; + if (!(token[0] === 'OUTDENT' || (token.generated && token[0] === 'CALL_END') || (token.generated && token[0] === '}'))) { + return 1; + } + prevLocationData = tokens[i - 1][2]; + token[2] = { + first_line: prevLocationData.last_line, + first_column: prevLocationData.last_column, + last_line: prevLocationData.last_line, + last_column: prevLocationData.last_column + }; return 1; - } - prevLocationData = tokens[i - 1][2]; - token[2] = { - first_line: prevLocationData.last_line, - first_column: prevLocationData.last_column, - last_line: prevLocationData.last_line, - last_column: prevLocationData.last_column - }; - return 1; - }); - }; + }); + } - Rewriter.prototype.normalizeLines = function() { - var action, condition, indent, outdent, starter; - starter = indent = outdent = null; - condition = function(token, i) { - var ref, ref1, ref2, ref3; - return token[1] !== ';' && (ref = token[0], indexOf.call(SINGLE_CLOSERS, ref) >= 0) && !(token[0] === 'TERMINATOR' && (ref1 = this.tag(i + 1), indexOf.call(EXPRESSION_CLOSE, ref1) >= 0)) && !(token[0] === 'ELSE' && starter !== 'THEN') && !(((ref2 = token[0]) === 'CATCH' || ref2 === 'FINALLY') && (starter === '->' || starter === '=>')) || (ref3 = token[0], indexOf.call(CALL_CLOSERS, ref3) >= 0) && this.tokens[i - 1].newLine; - }; - action = function(token, i) { - return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent); - }; - return this.scanTokens(function(token, i, tokens) { - var j, k, ref, ref1, ref2, tag; - tag = token[0]; - if (tag === 'TERMINATOR') { - if (this.tag(i + 1) === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') { - tokens.splice(i, 1, ...this.indentation()); - return 1; + normalizeLines() { + var action, condition, indent, outdent, starter; + starter = indent = outdent = null; + condition = function(token, i) { + var ref, ref1, ref2, ref3; + return token[1] !== ';' && (ref = token[0], indexOf.call(SINGLE_CLOSERS, ref) >= 0) && !(token[0] === 'TERMINATOR' && (ref1 = this.tag(i + 1), indexOf.call(EXPRESSION_CLOSE, ref1) >= 0)) && !(token[0] === 'ELSE' && starter !== 'THEN') && !(((ref2 = token[0]) === 'CATCH' || ref2 === 'FINALLY') && (starter === '->' || starter === '=>')) || (ref3 = token[0], indexOf.call(CALL_CLOSERS, ref3) >= 0) && this.tokens[i - 1].newLine; + }; + action = function(token, i) { + return this.tokens.splice((this.tag(i - 1) === ',' ? i - 1 : i), 0, outdent); + }; + return this.scanTokens(function(token, i, tokens) { + var j, k, ref, ref1, ref2, tag; + tag = token[0]; + if (tag === 'TERMINATOR') { + if (this.tag(i + 1) === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') { + tokens.splice(i, 1, ...this.indentation()); + return 1; + } + if (ref = this.tag(i + 1), indexOf.call(EXPRESSION_CLOSE, ref) >= 0) { + tokens.splice(i, 1); + return 0; + } } - if (ref = this.tag(i + 1), indexOf.call(EXPRESSION_CLOSE, ref) >= 0) { - tokens.splice(i, 1); - return 0; + if (tag === 'CATCH') { + for (j = k = 1; k <= 2; j = ++k) { + if (!((ref1 = this.tag(i + j)) === 'OUTDENT' || ref1 === 'TERMINATOR' || ref1 === 'FINALLY')) { + continue; + } + tokens.splice(i + j, 0, ...this.indentation()); + return 2 + j; + } } - } - if (tag === 'CATCH') { - for (j = k = 1; k <= 2; j = ++k) { - if (!((ref1 = this.tag(i + j)) === 'OUTDENT' || ref1 === 'TERMINATOR' || ref1 === 'FINALLY')) { - continue; + if (indexOf.call(SINGLE_LINERS, tag) >= 0 && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) { + starter = tag; + ref2 = this.indentation(tokens[i]), indent = ref2[0], outdent = ref2[1]; + if (starter === 'THEN') { + indent.fromThen = true; } - tokens.splice(i + j, 0, ...this.indentation()); - return 2 + j; + tokens.splice(i + 1, 0, indent); + this.detectEnd(i + 2, condition, action); + if (tag === 'THEN') { + tokens.splice(i, 1); + } + return 1; } - } - if (indexOf.call(SINGLE_LINERS, tag) >= 0 && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) { - starter = tag; - ref2 = this.indentation(tokens[i]), indent = ref2[0], outdent = ref2[1]; - if (starter === 'THEN') { - indent.fromThen = true; + return 1; + }); + } + + tagPostfixConditionals() { + var action, condition, original; + original = null; + condition = function(token, i) { + var prevTag, tag; + tag = token[0]; + prevTag = this.tokens[i - 1][0]; + return tag === 'TERMINATOR' || (tag === 'INDENT' && indexOf.call(SINGLE_LINERS, prevTag) < 0); + }; + action = function(token, i) { + if (token[0] !== 'INDENT' || (token.generated && !token.fromThen)) { + return original[0] = 'POST_' + original[0]; } - tokens.splice(i + 1, 0, indent); - this.detectEnd(i + 2, condition, action); - if (tag === 'THEN') { - tokens.splice(i, 1); + }; + return this.scanTokens(function(token, i) { + if (token[0] !== 'IF') { + return 1; } + original = token; + this.detectEnd(i + 1, condition, action); return 1; - } - return 1; - }); - }; + }); + } - Rewriter.prototype.tagPostfixConditionals = function() { - var action, condition, original; - original = null; - condition = function(token, i) { - var prevTag, tag; - tag = token[0]; - prevTag = this.tokens[i - 1][0]; - return tag === 'TERMINATOR' || (tag === 'INDENT' && indexOf.call(SINGLE_LINERS, prevTag) < 0); - }; - action = function(token, i) { - if (token[0] !== 'INDENT' || (token.generated && !token.fromThen)) { - return original[0] = 'POST_' + original[0]; - } - }; - return this.scanTokens(function(token, i) { - if (token[0] !== 'IF') { - return 1; + indentation(origin) { + var indent, outdent; + indent = ['INDENT', 2]; + outdent = ['OUTDENT', 2]; + if (origin) { + indent.generated = outdent.generated = true; + indent.origin = outdent.origin = origin; + } else { + indent.explicit = outdent.explicit = true; } - original = token; - this.detectEnd(i + 1, condition, action); - return 1; - }); - }; + return [indent, outdent]; + } - Rewriter.prototype.indentation = function(origin) { - var indent, outdent; - indent = ['INDENT', 2]; - outdent = ['OUTDENT', 2]; - if (origin) { - indent.generated = outdent.generated = true; - indent.origin = outdent.origin = origin; - } else { - indent.explicit = outdent.explicit = true; + tag(i) { + var ref; + return (ref = this.tokens[i]) != null ? ref[0] : void 0; } - return [indent, outdent]; + }; Rewriter.prototype.generate = generate; - Rewriter.prototype.tag = function(i) { - var ref; - return (ref = this.tokens[i]) != null ? ref[0] : void 0; - }; - return Rewriter; })(); diff --git a/lib/coffee-script/scope.js b/lib/coffee-script/scope.js index 99e1d4a239..2d2030b899 100644 --- a/lib/coffee-script/scope.js +++ b/lib/coffee-script/scope.js @@ -3,8 +3,8 @@ var Scope, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; - exports.Scope = Scope = (function() { - function Scope(parent, expressions, method, referencedVars) { + exports.Scope = Scope = class Scope { + constructor(parent, expressions, method, referencedVars) { var ref, ref1; this.parent = parent; this.expressions = expressions; @@ -23,7 +23,7 @@ this.root = (ref = (ref1 = this.parent) != null ? ref1.root : void 0) != null ? ref : this; } - Scope.prototype.add = function(name, type, immediate) { + add(name, type, immediate) { if (this.shared && !immediate) { return this.parent.add(name, type, immediate); } @@ -35,37 +35,37 @@ type: type }) - 1; } - }; + } - Scope.prototype.namedMethod = function() { + namedMethod() { var ref; if (((ref = this.method) != null ? ref.name : void 0) || !this.parent) { return this.method; } return this.parent.namedMethod(); - }; + } - Scope.prototype.find = function(name) { + find(name) { if (this.check(name)) { return true; } this.add(name, 'var'); return false; - }; + } - Scope.prototype.parameter = function(name) { + parameter(name) { if (this.shared && this.parent.check(name, true)) { return; } return this.add(name, 'param'); - }; + } - Scope.prototype.check = function(name) { + check(name) { var ref; return !!(this.type(name) || ((ref = this.parent) != null ? ref.check(name) : void 0)); - }; + } - Scope.prototype.temporary = function(name, index, single = false) { + temporary(name, index, single = false) { var diff, endCode, letter, newCode, num, startCode; if (single) { startCode = name.charCodeAt(0); @@ -78,9 +78,9 @@ } else { return `${name}${index || ''}`; } - }; + } - Scope.prototype.type = function(name) { + type(name) { var i, len, ref, v; ref = this.variables; for (i = 0, len = ref.length; i < len; i++) { @@ -90,9 +90,9 @@ } } return null; - }; + } - Scope.prototype.freeVariable = function(name, options = {}) { + freeVariable(name, options = {}) { var index, ref, temp; index = 0; while (true) { @@ -106,21 +106,21 @@ this.add(temp, 'var', true); } return temp; - }; + } - Scope.prototype.assign = function(name, value) { + assign(name, value) { this.add(name, { value: value, assigned: true }, true); return this.hasAssignments = true; - }; + } - Scope.prototype.hasDeclarations = function() { + hasDeclarations() { return !!this.declaredVariables().length; - }; + } - Scope.prototype.declaredVariables = function() { + declaredVariables() { var v; return ((function() { var i, len, ref, results; @@ -134,9 +134,9 @@ } return results; }).call(this)).sort(); - }; + } - Scope.prototype.assignedVariables = function() { + assignedVariables() { var i, len, ref, results, v; ref = this.variables; results = []; @@ -147,10 +147,8 @@ } } return results; - }; - - return Scope; + } - })(); + }; }).call(this); diff --git a/lib/coffee-script/sourcemap.js b/lib/coffee-script/sourcemap.js index 2bbbf82c78..9bce3aae36 100644 --- a/lib/coffee-script/sourcemap.js +++ b/lib/coffee-script/sourcemap.js @@ -2,13 +2,13 @@ (function() { var LineMap, SourceMap; - LineMap = (function() { - function LineMap(line1) { + LineMap = class LineMap { + constructor(line1) { this.line = line1; this.columns = []; } - LineMap.prototype.add = function(column, arg, options = {}) { + add(column, arg, options = {}) { var sourceColumn, sourceLine; sourceLine = arg[0], sourceColumn = arg[1]; if (this.columns[column] && options.noReplace) { @@ -20,94 +20,117 @@ sourceLine: sourceLine, sourceColumn: sourceColumn }; - }; + } - LineMap.prototype.sourceLocation = function(column) { + sourceLocation(column) { var mapping; while (!((mapping = this.columns[column]) || (column <= 0))) { column--; } return mapping && [mapping.sourceLine, mapping.sourceColumn]; - }; - - return LineMap; + } - })(); + }; SourceMap = (function() { var BASE64_CHARS, VLQ_CONTINUATION_BIT, VLQ_SHIFT, VLQ_VALUE_MASK; - function SourceMap() { - this.lines = []; - } + class SourceMap { + constructor() { + this.lines = []; + } - SourceMap.prototype.add = function(sourceLocation, generatedLocation, options = {}) { - var base, column, line, lineMap; - line = generatedLocation[0], column = generatedLocation[1]; - lineMap = ((base = this.lines)[line] || (base[line] = new LineMap(line))); - return lineMap.add(column, sourceLocation, options); - }; + add(sourceLocation, generatedLocation, options = {}) { + var base, column, line, lineMap; + line = generatedLocation[0], column = generatedLocation[1]; + lineMap = ((base = this.lines)[line] || (base[line] = new LineMap(line))); + return lineMap.add(column, sourceLocation, options); + } - SourceMap.prototype.sourceLocation = function(arg) { - var column, line, lineMap; - line = arg[0], column = arg[1]; - while (!((lineMap = this.lines[line]) || (line <= 0))) { - line--; + sourceLocation(arg) { + var column, line, lineMap; + line = arg[0], column = arg[1]; + while (!((lineMap = this.lines[line]) || (line <= 0))) { + line--; + } + return lineMap && lineMap.sourceLocation(column); } - return lineMap && lineMap.sourceLocation(column); - }; - SourceMap.prototype.generate = function(options = {}, code = null) { - var buffer, i, j, lastColumn, lastSourceColumn, lastSourceLine, len, len1, lineMap, lineNumber, mapping, needComma, ref, ref1, v3, writingline; - writingline = 0; - lastColumn = 0; - lastSourceLine = 0; - lastSourceColumn = 0; - needComma = false; - buffer = ""; - ref = this.lines; - for (lineNumber = i = 0, len = ref.length; i < len; lineNumber = ++i) { - lineMap = ref[lineNumber]; - if (lineMap) { - ref1 = lineMap.columns; - for (j = 0, len1 = ref1.length; j < len1; j++) { - mapping = ref1[j]; - if (!(mapping)) { - continue; - } - while (writingline < mapping.line) { - lastColumn = 0; - needComma = false; - buffer += ";"; - writingline++; - } - if (needComma) { - buffer += ","; - needComma = false; + generate(options = {}, code = null) { + var buffer, i, j, lastColumn, lastSourceColumn, lastSourceLine, len, len1, lineMap, lineNumber, mapping, needComma, ref, ref1, v3, writingline; + writingline = 0; + lastColumn = 0; + lastSourceLine = 0; + lastSourceColumn = 0; + needComma = false; + buffer = ""; + ref = this.lines; + for (lineNumber = i = 0, len = ref.length; i < len; lineNumber = ++i) { + lineMap = ref[lineNumber]; + if (lineMap) { + ref1 = lineMap.columns; + for (j = 0, len1 = ref1.length; j < len1; j++) { + mapping = ref1[j]; + if (!(mapping)) { + continue; + } + while (writingline < mapping.line) { + lastColumn = 0; + needComma = false; + buffer += ";"; + writingline++; + } + if (needComma) { + buffer += ","; + needComma = false; + } + buffer += this.encodeVlq(mapping.column - lastColumn); + lastColumn = mapping.column; + buffer += this.encodeVlq(0); + buffer += this.encodeVlq(mapping.sourceLine - lastSourceLine); + lastSourceLine = mapping.sourceLine; + buffer += this.encodeVlq(mapping.sourceColumn - lastSourceColumn); + lastSourceColumn = mapping.sourceColumn; + needComma = true; } - buffer += this.encodeVlq(mapping.column - lastColumn); - lastColumn = mapping.column; - buffer += this.encodeVlq(0); - buffer += this.encodeVlq(mapping.sourceLine - lastSourceLine); - lastSourceLine = mapping.sourceLine; - buffer += this.encodeVlq(mapping.sourceColumn - lastSourceColumn); - lastSourceColumn = mapping.sourceColumn; - needComma = true; } } + v3 = { + version: 3, + file: options.generatedFile || '', + sourceRoot: options.sourceRoot || '', + sources: options.sourceFiles || [''], + names: [], + mappings: buffer + }; + if (options.inlineMap) { + v3.sourcesContent = [code]; + } + return v3; } - v3 = { - version: 3, - file: options.generatedFile || '', - sourceRoot: options.sourceRoot || '', - sources: options.sourceFiles || [''], - names: [], - mappings: buffer - }; - if (options.inlineMap) { - v3.sourcesContent = [code]; + + encodeVlq(value) { + var answer, nextChunk, signBit, valueToEncode; + answer = ''; + signBit = value < 0 ? 1 : 0; + valueToEncode = (Math.abs(value) << 1) + signBit; + while (valueToEncode || !answer) { + nextChunk = valueToEncode & VLQ_VALUE_MASK; + valueToEncode = valueToEncode >> VLQ_SHIFT; + if (valueToEncode) { + nextChunk |= VLQ_CONTINUATION_BIT; + } + answer += this.encodeBase64(nextChunk); + } + return answer; + } + + encodeBase64(value) { + return BASE64_CHARS[value] || (function() { + throw new Error(`Cannot Base64 encode value: ${value}`); + })(); } - return v3; + }; VLQ_SHIFT = 5; @@ -116,30 +139,8 @@ VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - 1; - SourceMap.prototype.encodeVlq = function(value) { - var answer, nextChunk, signBit, valueToEncode; - answer = ''; - signBit = value < 0 ? 1 : 0; - valueToEncode = (Math.abs(value) << 1) + signBit; - while (valueToEncode || !answer) { - nextChunk = valueToEncode & VLQ_VALUE_MASK; - valueToEncode = valueToEncode >> VLQ_SHIFT; - if (valueToEncode) { - nextChunk |= VLQ_CONTINUATION_BIT; - } - answer += this.encodeBase64(nextChunk); - } - return answer; - }; - BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - SourceMap.prototype.encodeBase64 = function(value) { - return BASE64_CHARS[value] || (function() { - throw new Error(`Cannot Base64 encode value: ${value}`); - })(); - }; - return SourceMap; })(); diff --git a/src/nodes.coffee b/src/nodes.coffee index 73fadbce4d..012bd8a2b6 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -117,6 +117,29 @@ exports.Base = class Base ref = if level then @compileToFragments o, level else this [ref, ref] + # Occasionally it may be useful to make an expression behave as if it was 'hoisted', whereby the + # result of the expression is available before its location in the source, but the expression's + # variable scope corresponds the source position. This is used extensively to deal with executable + # class bodies in classes. + # + # Calling this method mutates the node, proxying the `compileNode` and `compileToFragments` + # methods to store their result for later replacing the `target` node, which is returned by the + # call. + hoist: -> + @hoisted = yes + target = new HoistTarget @ + + compileNode = @compileNode + compileToFragments = @compileToFragments + + @compileNode = (o) -> + target.update compileNode, o + + @compileToFragments = (o) -> + target.update compileToFragments, o + + target + cacheToCodeFragments: (cacheValues) -> [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])] @@ -223,6 +246,45 @@ exports.Base = class Base answer = answer.concat fragments answer +#### HoistTarget + +# A **HoistTargetNode** represents the output location in the node tree for a hoisted node. +# See Base#hoist. +exports.HoistTarget = class HoistTarget extends Base + # Expands hoisted fragments in the given array + @expand = (fragments) -> + for fragment, i in fragments by -1 when fragment.fragments + fragments[i..i] = fragment.fragments + fragments + + constructor: (@source) -> + # Holds presentational options to apply when the source node is compiled + @options = {} + + # Placeholder fragments to be replaced by the source node's compilation + @targetFragments = { fragments: [] } + + isStatement: (o) -> + @source.isStatement o + + # Update the target fragments with the result of compiling the source. + # Calls the given compile function with the node and options (overriden with the target + # presentational options). + update: (compile, o) -> + @targetFragments.fragments = compile.call @source, merge o, @options + + # Copies the target indent and level, and returns the placeholder fragments + compileToFragments: (o, level) -> + @options.indent = o.indent + @options.level = level ? o.level + [ @targetFragments ] + + compileNode: (o) -> + @compileToFragments o + + compileClosure: (o) -> + @compileToFragments o + #### Block # The block is the list of expressions that forms the body of an @@ -299,6 +361,9 @@ exports.Block = class Block extends Base # it in a new scope; we just compile the statements in this block along with # our own compiledNodes.push node.compileNode o + else if node.hoisted + # This is a hoisted expression. We want to compile this and ignore the result. + node.compileToFragments o else if top node.front = true fragments = node.compileToFragments o @@ -343,6 +408,7 @@ exports.Block = class Block extends Base prelude.push @makeCode "\n" @expressions = rest fragments = @compileWithDeclarations o + HoistTarget.expand fragments return fragments if o.bare [].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n") @@ -562,8 +628,8 @@ exports.Value = class Value extends Base lastProp instanceof Slice looksStatic: (className) -> - @base.value is className and @properties.length is 1 and - @properties[0].name?.value isnt 'prototype' + (@this or @base instanceof ThisLiteral or @base.value is className) and + @properties.length is 1 and @properties[0].name?.value isnt 'prototype' # The value can be unwrapped as its inner node, if there are no attached # properties. @@ -697,8 +763,12 @@ exports.Call = class Call extends Base fragments = [] if this instanceof SuperCall - preface = @superReference(o) + ".call(#{@superThis(o)}" - if compiledArgs.length then preface += ", " + preface = @superReference o + if preface is 'super' + preface += '(' + else + preface += ".call(#{@superThis(o)}" + if compiledArgs.length then preface += ", " fragments.push @makeCode preface else if @isNew then fragments.push @makeCode 'new ' @@ -722,7 +792,9 @@ exports.SuperCall = class SuperCall extends Call # method. superReference: (o) -> method = o.scope.namedMethod() - if method?.klass + if method?.ctor + 'super' + else if method?.klass {klass, name, variable} = method if klass.isComplex() bref = new IdentifierLiteral o.scope.parent.freeVariable 'base' @@ -731,15 +803,11 @@ exports.SuperCall = class SuperCall extends Call variable.properties.splice 0, klass.properties.length if name.isComplex() or (name instanceof Index and name.index.isAssignable()) nref = new IdentifierLiteral o.scope.parent.freeVariable 'name' - name = new Index new Assign nref, name.index - variable.properties.pop() - variable.properties.push name + name.index = new Assign nref, name.index accesses = [new Access new PropertyName '__super__'] accesses.push new Access new PropertyName 'constructor' if method.static accesses.push if nref? then new Index nref else name (new Value bref ? klass, accesses).compile o - else if method?.ctor - "#{method.name}.__super__.constructor" else @error 'cannot call super outside of an instance method.' @@ -924,7 +992,6 @@ exports.Slice = class Slice extends Base children: ['range'] constructor: (@range) -> - super() # We have to be careful when trying to slice through the end of the array, # `9e9` is used because not all implementations respect `undefined` or `1/0`. @@ -1030,18 +1097,69 @@ exports.Arr = class Arr extends Base #### Class # The CoffeeScript class definition. -# Initialize a **Class** with its name, an optional superclass, and a -# list of prototype property assignments. +# Initialize a **Class** with its name, an optional superclass, and a body. exports.Class = class Class extends Base + children: [ 'variable', 'parent', 'body' ] + + defaultClassVariableName: '_Class' + constructor: (@variable, @parent, @body = new Block) -> - @boundFuncs = [] - @body.classBody = yes + @properties = [] - children: ['variable', 'parent', 'body'] + compileNode: (o) -> + if jumpNode = @body.jumps() + jumpNode.error 'Class bodies cannot contain pure statements' + if argumentsNode = @body.contains isLiteralArguments + argumentsNode.error "Class bodies shouldn't reference arguments" - defaultClassVariableName: '_Class' + name = @determineName() + + [ directives, properties ] = @walkBody name + @setContext name + + # TODO Once ES super is being used throughout, the check for `@parent`, and the assignment + # below, can be removed. + emptyBody = not @parent and not @externalCtor and directives.length is 0 and + (@body.isEmpty() or @body.expressions.length <= properties.length) + + if emptyBody + klass = new ClassDeclaration name, @parent, new Block properties + else + ident = new IdentifierLiteral name + params = [] + args = [] + wrapper = new Code params, @body + klass = new Call wrapper, args + + @body.spaced = true - # Figure out the appropriate name for the constructor function of this class. + o.classScope = wrapper.makeScope o.scope + + declaration = new ClassDeclaration name + declaration.body.push property.hoist() for property in properties + + if @externalCtor + externalCtor = new IdentifierLiteral o.classScope.freeVariable 'ctor', reserve: no + declaration.externalCtor = externalCtor + @externalCtor.variable.base = externalCtor + + if @parent + parent = new IdentifierLiteral o.classScope.freeVariable 'superClass', reserve: no + declaration.parent = parent + params.push new Param parent + args.push @parent + @body.unshift new Literal "#{name}.__super__ = #{parent.value}.prototype" + + @body.expressions.unshift directives..., declaration + @body.push ident + + if @variable + new Assign(@variable, klass, null, { @moduleDeclaration }).compileToFragments o + else + klass.compileToFragments o + + # Figure out the appropriate name for this class + # TODO ES classes can be anonymous, so we shouldn't need a default name determineName: -> return @defaultClassVariableName unless @variable [..., tail] = @variable.properties @@ -1057,130 +1175,193 @@ exports.Class = class Class extends Base @variable.error message if message if name in JS_FORBIDDEN then "_#{name}" else name - # For all `this`-references and bound functions in the class definition, - # `this` is the Class being constructed. + # Traverse the class's children and: + # - Hoist valid ES properties into `@properties` + # - Hoist static assignments into `@properties` + # - Convert invalid ES properties into class or prototype assignments + walkBody: (name) -> + directives = [] + properties = [] + + while expr = @body.expressions[0] + break unless expr instanceof Comment or expr instanceof Value and expr.isString() + directives.push @body.expressions.shift() + + comment = null + for expr, i in @body.expressions.slice() + if expr instanceof Value and expr.isObject true + for assign, i in expr.base.properties + if @validInitializerMethod name, assign + expr.base.properties[i] = method = @addInitializerMethod name, assign + properties.push comment if comment + properties.push method + comment = if assign instanceof Comment then assign else null + else if expr instanceof Assign and expr.variable.looksStatic name + if @validInitializerMethod name, expr + @body.expressions[i] = method = @addInitializerMethod name, expr + properties.push comment if comment + properties.push method + + comment = if expr instanceof Comment then expr else null + + @traverseChildren false, (child) => + cont = true + return false if child instanceof Class + if child instanceof Block + for node, i in child.expressions + if node instanceof Value and node.isObject(true) + cont = false + child.expressions[i] = @addProperties node.base.properties + else if node instanceof Assign and node.variable.looksStatic name + node.value.static = yes + child.expressions = flatten child.expressions + cont and child not instanceof Class + + [ directives, properties ] + setContext: (name) -> @body.traverseChildren false, (node) -> return false if node.classBody if node instanceof ThisLiteral - node.value = name + node.value = name else if node instanceof Code - node.context = name if node.bound - - # Ensure that all functions bound to the instance are proxied in the - # constructor. - addBoundFunctions: (o) -> - for bvar in @boundFuncs - lhs = (new Value (new ThisLiteral), [new Access bvar]).compile o - @ctor.body.unshift new Literal "#{lhs} = #{utility 'bind', o}(#{lhs}, this)" - return + node.context = name if node.bound + + # Checks if the given node is a valid ES class initializer method. + validInitializerMethod: (name, node) -> + return false unless node instanceof Assign and node.value instanceof Code + return true unless node.variable.hasProperties() + return node.variable.looksStatic name + + # Add a method to the class initializer + addInitializerMethod: (name, assign) -> + variable = assign.variable + method = assign.value + method.isMethod = yes + method.static = variable.looksStatic name + method.klass = new IdentifierLiteral name + method.variable = variable + + if method.static + method.name = variable.properties[0] + else + methodName = variable.base + method.ctor = methodName.value is 'constructor' + method.name = new (if methodName.isComplex() then Index else Access) methodName + method.error 'Cannot define a constructor as a bound function' if method.bound and method.ctor + + method + + # Make class/prototype assignments for invalid ES properties + addProperties: (assigns) -> + result = for assign in assigns + variable = assign.variable + base = variable?.base + value = assign.value + delete assign.context + + if assign instanceof Comment or assign.isMethod + # Passthrough + else if base.value is 'constructor' + if value instanceof Code + base.error "constructors must be defined at the top level of a class body" + + # The class scope is not available yet, so return the assignment to update later + assign = @externalCtor = new Assign new Value, value + else if not assign.variable.this + name = new (if base.isComplex() then Index else Access) base + prototype = new Access new PropertyName 'prototype' + variable = new Value new ThisLiteral(), [ prototype, name ] + + assign.variable = variable - # Merge the properties from a top-level object as prototypal properties - # on the class. - addProperties: (node, name, o) -> - props = node.base.properties[..] - exprs = while assign = props.shift() - if assign instanceof Assign - base = assign.variable.base - delete assign.context - func = assign.value - if base.value is 'constructor' - if @ctor - assign.error 'cannot define more than one constructor in a class' - if func.bound - assign.error 'cannot define a constructor as a bound function' - if func instanceof Code - assign = @ctor = func - else - @externalCtor = o.classScope.freeVariable 'ctor' - assign = new Assign new IdentifierLiteral(@externalCtor), func - else - if assign.variable.this - func.static = yes - else - acc = if base.isComplex() then new Index base else new Access base - assign.variable = new Value(new IdentifierLiteral(name), [(new Access new PropertyName 'prototype'), acc]) - if func instanceof Code and func.bound - @boundFuncs.push base - func.bound = no assign - compact exprs + compact result - # Walk the body of the class, looking for prototype properties to be converted - # and tagging static assignments. - walkBody: (name, o) -> - @traverseChildren false, (child) => - cont = true - return false if child instanceof Class - if child instanceof Block - for node, i in exps = child.expressions - if node instanceof Assign and node.variable.looksStatic name - node.value.static = yes - else if node instanceof Value and node.isObject(true) - cont = false - exps[i] = @addProperties node, name, o - child.expressions = exps = flatten exps - cont and child not instanceof Class +exports.ClassDeclaration = class ClassDeclaration extends Base + children: ['parent', 'body'] + + constructor: (@name = null, @parent = null, @body = new Block) -> - # `use strict` (and other directives) must be the first expression statement(s) - # of a function body. This method ensures the prologue is correctly positioned - # above the `constructor`. - hoistDirectivePrologue: -> - index = 0 - {expressions} = @body - ++index while (node = expressions[index]) and node instanceof Comment or - node instanceof Value and node.isString() - @directives = expressions.splice 0, index - - # Make sure that a constructor is defined for the class, and properly - # configured. - ensureConstructor: (name) -> - if not @ctor - @ctor = new Code - if @externalCtor - @ctor.body.push new Literal "#{@externalCtor}.apply(this, arguments)" - else if @parent - @ctor.body.push new Literal "#{name}.__super__.constructor.apply(this, arguments)" - @ctor.body.makeReturn() - @body.expressions.unshift @ctor - @ctor.ctor = @ctor.name = name - @ctor.klass = null - @ctor.noReturn = yes - - # Instead of generating the JavaScript string directly, we build up the - # equivalent syntax tree and compile that, in pieces. You can see the - # constructor, property assignments, and inheritance getting built out below. compileNode: (o) -> - if jumpNode = @body.jumps() - jumpNode.error 'Class bodies cannot contain pure statements' - if argumentsNode = @body.contains isLiteralArguments - argumentsNode.error "Class bodies shouldn't reference arguments" + [ ctor, boundMethods ] = @walkBody() - name = @determineName() - lname = new IdentifierLiteral name - func = new Code [], Block.wrap [@body] - args = [] - o.classScope = func.makeScope o.scope + ctor ?= @makeDefaultConstructor() if @externalCtor or boundMethods.length + ctor?.noReturn = true - @hoistDirectivePrologue() - @setContext name - @walkBody name, o - @ensureConstructor name - @addBoundFunctions o - @body.spaced = yes - @body.expressions.push lname + @proxyBoundMethods o, ctor, boundMethods + @ensureConstructorSuperCall ctor, boundMethods if @parent - if @parent - superClass = new IdentifierLiteral o.classScope.freeVariable 'superClass', reserve: no - @body.expressions.unshift new Extends lname, superClass - func.params.push new Param superClass - args.push @parent + o.indent += TAB + + result = [] + result.push @makeCode "class " + result.push @makeCode "#{@name} " if @name + result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent + result.push @makeCode "{#{if @body.isEmpty() then '' else '\n'}" + + @body.spaced = true + result.push @body.compileToFragments(o, LEVEL_TOP)... unless @body.isEmpty() + + result.push @makeCode "#{if @body.isEmpty() then '' else "\n#{@tab}"}}" + result + + walkBody: -> + ctor = null + boundMethods = [] + + for expression in @body.expressions + method = expression.source ? expression + continue if method instanceof Comment + method.error 'A class declaration can only contain functions' unless method instanceof Code + method.error 'Cannot define more than one constructor in a class' if ctor and method.ctor - @body.expressions.unshift @directives... + if method.bound and not method.static + boundMethods.push method.name + method.bound = false + ctor = method if method.ctor - klass = new Parens new Call func, args - klass = new Assign @variable, klass, null, { @moduleDeclaration } if @variable - klass.compileToFragments o + [ ctor, boundMethods ] + + makeDefaultConstructor: -> + ctor = new Code + ctor.ctor = true + ctor.name = new Access new PropertyName 'constructor' + ctor.isMethod = yes + @body.unshift ctor + + if @externalCtor + applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ] + applyArgs = [ new ThisLiteral, new IdentifierLiteral 'arguments' ] + ctor.body.push new Call applyCtor, applyArgs + ctor.body.makeReturn() + + ctor + + proxyBoundMethods: (o, ctor, boundMethods) -> + return if not ctor or boundMethods.length is 0 + + for name in boundMethods by -1 + name = new Value(new ThisLiteral, [ name ]).compile o + ctor.body.unshift new Literal "#{name} = #{utility 'bind', o}(#{name}, this)" + + null + + ensureConstructorSuperCall: (ctor, boundMethods) -> + return if not ctor + + hasThisParam = no + hasThisParam = yes for param in ctor.params when param.name.this + + superCall = ctor.superCall() + + if hasThisParam and superCall + superCall.error 'super not allowed with `@` parameters in derived constructors' + + if boundMethods.length and superCall + superCall.error 'super not allowed with bound functions in derived constructors' + + ctor.body.unshift new SuperCall unless superCall #### Import and Export @@ -1249,7 +1430,6 @@ exports.ExportDeclaration = class ExportDeclaration extends ModuleDeclaration if @clause instanceof Class and not @clause.variable @clause.error 'anonymous classes cannot be exported' - # When the ES2015 `class` keyword is supported, don’t add a `var` here code.push @makeCode 'var ' @clause.moduleDeclaration = 'export' @@ -1569,6 +1749,7 @@ exports.Code = class Code extends Base @bound = tag is 'boundfunc' @isGenerator = no @isAsync = no + @isMethod = no @body.traverseChildren no, (node) => if (node instanceof Op and node.isYield()) or node instanceof YieldReturn @@ -1580,7 +1761,7 @@ exports.Code = class Code extends Base children: ['params', 'body'] - isStatement: -> !!@ctor + isStatement: -> @isMethod jumps: NO @@ -1606,12 +1787,19 @@ exports.Code = class Code extends Base exprs = [] paramsAfterSplat = [] haveSplatParam = no + haveThisParam = no # Check for duplicate parameters. paramNames = [] @eachParamName (name, node) => node.error "multiple parameters named '#{name}'" if name in paramNames paramNames.push name + haveThisParam = yes if node.this + + # If there's a super call as the first expression in a constructor, it needs to stay above any + # `this` assignments + if @ctor and haveThisParam and @body.expressions[0] instanceof SuperCall + exprs.push @body.expressions.shift() # Parse the parameters, adding them to the list of parameters to put in the # function definition; and dealing with splats or expansions, including @@ -1695,23 +1883,36 @@ exports.Code = class Code extends Base @body.makeReturn() unless wasEmpty or @noReturn # Assemble the output - code = '' - code += 'async ' if @isAsync - unless @bound - code += 'function' - code += '*' if @isGenerator # Arrow functions can’t be generators - code += ' ' + @name if @ctor - code += '(' - answer = [@makeCode(code)] + modifiers = [] + modifiers.push 'static' if @static + modifiers.push 'async' if @isAsync + modifiers.push 'function' if not @isMethod and not @bound + modifiers.push '*' if @isGenerator + + signature = [@makeCode '('] for param, i in params - answer.push @makeCode ', ' if i - answer.push @makeCode '...' if haveSplatParam and i is params.length - 1 # Rest syntax is always on the last parameter - answer.push param.compileToFragments(o)... - answer.push @makeCode unless @bound then ') {' else ') => {' - answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty() + signature.push @makeCode ', ' if i + signature.push @makeCode '...' if haveSplatParam and i is params.length - 1 + signature.push param.compileToFragments(o)... + signature.push @makeCode ')' + + body = @body.compileWithDeclarations o unless @body.isEmpty() + + # We need to compile the body before method names to ensure super references are handled + if @isMethod + name = @name.compileToFragments o + name.shift() if name[0].code is '.' + + answer = @joinFragmentArrays (@makeCode m for m in modifiers), ' ' + answer.push @makeCode ' ' if modifiers.length and name + answer.push name... if name + answer.push signature... + answer.push @makeCode ' =>' if @bound and not @isMethod + answer.push @makeCode ' {' + answer.push @makeCode('\n'), body..., @makeCode("\n#{@tab}") if body?.length answer.push @makeCode '}' - return [@makeCode(@tab), answer...] if @ctor + return [@makeCode(@tab), answer...] if @isMethod if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer eachParamName: (iterator) -> @@ -1722,6 +1923,16 @@ exports.Code = class Code extends Base traverseChildren: (crossScope, func) -> super(crossScope, func) if crossScope + # Find a super call in this function + superCall: -> + superCall = null + @traverseChildren true, (child) -> + superCall = child if child instanceof SuperCall + + # `super` has the same target in bound (arrow) functions, so check them too + not superCall and (child not instanceof Code or child.bound) + superCall + #### Param # A parameter in a function definition. Beyond a typical JavaScript parameter, diff --git a/test/assignment.coffee b/test/assignment.coffee index a5add30852..aff5371a1d 100644 --- a/test/assignment.coffee +++ b/test/assignment.coffee @@ -449,7 +449,7 @@ test "#1591, #1101: splatted expressions in destructuring assignment must be ass test "#1643: splatted accesses in destructuring assignments should not be declared as variables", -> nonce = {} - accesses = ['o.a', 'o["a"]', '(o.a)', '(o.a).a', '@o.a', 'C::a', 'C::', 'f().a', 'o?.a', 'o?.a.b', 'f?().a'] + accesses = ['o.a', 'o["a"]', '(o.a)', '(o.a).a', '@o.a', 'C::a', 'f().a', 'o?.a', 'o?.a.b', 'f?().a'] for access in accesses for i,j in [1,2,3] #position can matter code = diff --git a/test/classes.coffee b/test/classes.coffee index 1a92f5186d..cb3471d51c 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -66,25 +66,6 @@ test "constructors with inheritance and super", -> ok (new SubClass).prop is 'top-super-sub' -test "Overriding the static property new doesn't clobber Function::new", -> - - class OneClass - @new: 'new' - function: 'function' - constructor: (name) -> @name = name - - class TwoClass extends OneClass - delete TwoClass.new - - Function.prototype.new = -> new this arguments... - - ok (TwoClass.new('three')).name is 'three' - ok (new OneClass).function is 'function' - ok OneClass.new is 'new' - - delete Function.prototype.new - - test "basic classes, again, but in the manual prototype style", -> Base = -> @@ -464,7 +445,7 @@ test "ensure that constructors invoked with splats return a new object", -> # Ensure that constructors invoked with splats cache the function. called = 0 get = -> if called++ then false else class Type - new get() args... + new (get()) args... test "`new` shouldn't add extra parens", -> @@ -480,6 +461,7 @@ test "`new` works against bare function", -> test "#1182: a subclass should be able to set its constructor to an external function", -> ctor = -> @val = 1 + return class A class B extends A constructor: ctor @@ -738,21 +720,21 @@ test "#2599: other typed constructors should be inherited", -> ok (new Derived) not instanceof Base ok (new Base) not instanceof Base -test "#2359: extending native objects that use other typed constructors requires defining a constructor", -> - class BrokenArray extends Array - method: -> 'no one will call me' +test "extending native objects works with and without defining a constructor", -> + class MyArray extends Array + method: -> 'yes!' - brokenArray = new BrokenArray - ok brokenArray not instanceof BrokenArray - ok typeof brokenArray.method is 'undefined' + myArray = new MyArray + ok myArray instanceof MyArray + ok 'yes!', myArray.method() - class WorkingArray extends Array + class OverrideArray extends Array constructor: -> super method: -> 'yes!' - workingArray = new WorkingArray - ok workingArray instanceof WorkingArray - eq 'yes!', workingArray.method() + overrideArray = new OverrideArray + ok overrideArray instanceof OverrideArray + eq 'yes!', overrideArray.method() test "#2782: non-alphanumeric-named bound functions", -> @@ -855,7 +837,7 @@ test "#1392 calling `super` in methods defined on namespaced classes", -> eq 1, count class C - @a: -> + @a: (->) @a extends Base @a::m = -> super eq 5, (new C.a).m() diff --git a/test/comments.coffee b/test/comments.coffee index 563cd47524..e6489b2467 100644 --- a/test/comments.coffee +++ b/test/comments.coffee @@ -378,11 +378,15 @@ test "#3132: Place block-comments nicely", -> var DummyClass; DummyClass = (function() { + class DummyClass { - /** - * @constructor - */ - function DummyClass() {} + /** + * @constructor + */ + + constructor() {} + + }; /** diff --git a/test/modules.coffee b/test/modules.coffee index a7753bbad3..81994ac232 100644 --- a/test/modules.coffee +++ b/test/modules.coffee @@ -457,7 +457,7 @@ test "export class", -> baz: -> console.log 'hello, world!'""" output = toJS input - ok /^export (class foo|var foo = \(function)/.test toJS input + ok /^export (var foo = class foo|var foo = \(function)/.test toJS input test "export class that extends", -> input = """ From e4acb2aa33f1ab31295b923936cbdbfbe94daf27 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Fri, 11 Nov 2016 23:36:22 -0800 Subject: [PATCH 02/32] Add tests to verify class interoperability with ES --- test/classes.coffee | 95 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/test/classes.coffee b/test/classes.coffee index cb3471d51c..66d448f0da 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -4,6 +4,7 @@ # * Class Definition # * Class Instantiation # * Inheritance and Super +# * ES2015+ Class Interoperability test "classes with a four-level inheritance chain", -> @@ -880,3 +881,97 @@ test "dynamic method names and super", -> class C extends B m: -> super eq 5, (new C).m() + +# ES2015+ class interoperability +# Based on https://github.com/balupton/es6-javascript-class-interop +# Helper functions to generate true ES classes to extend: +getBasicClass = -> + ``` + class BasicClass { + constructor (greeting) { + this.greeting = greeting || 'hi' + } + } + ``` + BasicClass + +getExtendedClass = (BaseClass) -> + ``` + class ExtendedClass extends BaseClass { + constructor (greeting, name) { + super(greeting || 'hello') + this.name = name + } + } + ``` + ExtendedClass + +test "can instantiate a basic ES class", -> + BasicClass = getBasicClass() + i = new BasicClass 'howdy!' + eq i.greeting, 'howdy!' + +test "can instantiate an extended ES class", -> + BasicClass = getBasicClass() + ExtendedClass = getExtendedClass BasicClass + i = new ExtendedClass 'yo', 'buddy' + eq i.greeting, 'yo' + eq i.name, 'buddy' + +test "can extend a basic ES class", -> + BasicClass = getBasicClass() + class ExtendedClass extends BasicClass + constructor: (@name) -> + i = new ExtendedClass 'dude' + eq i.name, 'dude' + +test "can extend an extended ES class", -> + BasicClass = getBasicClass() + ExtendedClass = getExtendedClass BasicClass + + class ExtendedExtendedClass extends ExtendedClass + constructor: (@value) -> + getDoubledValue: -> + @value * 2 + + i = new ExtendedExtendedClass 7 + eq i.getDoubledValue(), 14 + +test "CoffeeScript class can be extended in ES", -> + class CoffeeClass + constructor: (@favoriteDrink = 'latte', @size = 'grande') -> + getDrinkOrder: -> + "#{@size} #{@favoriteDrink}" + + ``` + class ECMAScriptClass extends CoffeeClass { + constructor (favoriteDrink) { + super(favoriteDrink); + this.favoriteDrink = this.favoriteDrink + ' with a dash of semicolons'; + } + } + ``` + + e = new ECMAScriptClass 'coffee' + eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons' + +test "extended CoffeeScript class can be extended in ES", -> + class CoffeeClass + constructor: (@favoriteDrink = 'latte') -> + + class CoffeeClassWithDrinkOrder extends CoffeeClass + constructor: (@favoriteDrink, @size = 'grande') -> + getDrinkOrder: -> + "#{@size} #{@favoriteDrink}" + + ``` + class ECMAScriptClass extends CoffeeClassWithDrinkOrder { + constructor (favoriteDrink) { + super(favoriteDrink); + this.favoriteDrink = this.favoriteDrink + ' with a dash of semicolons'; + } + } + ``` + + e = new ECMAScriptClass 'coffee' + eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons' From f703e0c050119de447a0aa5e272b9f890f8e2beb Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Tue, 13 Dec 2016 10:28:42 +0000 Subject: [PATCH 03/32] Refactor class nodes to separate executable body logic Logic has been redistributed amongst the class nodes so that: - `Class` contains the logic necessary to compile an ES class declaration. - `ExecutableClassBody` contains the logic necessary to compile CS' class extensions that require an executable class body. `Class` still necessarily contains logic to determine whether an expression is valid in an ES class initializer or not. If any invalid expressions are found then `Class` will wrap itself in an `ExecutableClassBody` when compiling. --- lib/coffee-script/nodes.js | 535 ++++++++++++++++++++----------------- src/nodes.coffee | 407 +++++++++++++++------------- 2 files changed, 514 insertions(+), 428 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index b5d70ebecf..b3458459f4 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript 2.0.0-alpha (function() { - var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Class, ClassDeclaration, Code, CodeFragment, Comment, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility, + var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, slice = [].slice; @@ -1688,62 +1688,59 @@ this.variable = variable1; this.parent = parent1; this.body = body1 != null ? body1 : new Block; - this.properties = []; } compileNode(o) { - var args, argumentsNode, declaration, directives, emptyBody, externalCtor, ident, j, jumpNode, klass, len1, name, params, parent, properties, property, ref3, wrapper; - if (jumpNode = this.body.jumps()) { - jumpNode.error('Class bodies cannot contain pure statements'); - } - if (argumentsNode = this.body.contains(isLiteralArguments)) { - argumentsNode.error("Class bodies shouldn't reference arguments"); - } - name = this.determineName(); - ref3 = this.walkBody(name), directives = ref3[0], properties = ref3[1]; - this.setContext(name); - emptyBody = !this.parent && !this.externalCtor && directives.length === 0 && (this.body.isEmpty() || this.body.expressions.length <= properties.length); - if (emptyBody) { - klass = new ClassDeclaration(name, this.parent, new Block(properties)); + var assign, executableBody, result; + this.name = this.determineName(); + executableBody = this.walkBody(); + if (this.parent || executableBody) { + this.compileNode = this.compileClassDeclaration; + result = new ExecutableClassBody(this, executableBody).compileToFragments(o); + this.compileNode = this.constructor.prototype.compileNode; } else { - ident = new IdentifierLiteral(name); - params = []; - args = []; - wrapper = new Code(params, this.body); - klass = new Call(wrapper, args); - this.body.spaced = true; - o.classScope = wrapper.makeScope(o.scope); - declaration = new ClassDeclaration(name); - for (j = 0, len1 = properties.length; j < len1; j++) { - property = properties[j]; - declaration.body.push(property.hoist()); - } - if (this.externalCtor) { - externalCtor = new IdentifierLiteral(o.classScope.freeVariable('ctor', { - reserve: false - })); - declaration.externalCtor = externalCtor; - this.externalCtor.variable.base = externalCtor; - } - if (this.parent) { - parent = new IdentifierLiteral(o.classScope.freeVariable('superClass', { - reserve: false - })); - declaration.parent = parent; - params.push(new Param(parent)); - args.push(this.parent); - this.body.unshift(new Literal(`${name}.__super__ = ${parent.value}.prototype`)); - } - this.body.expressions.unshift(...directives, declaration); - this.body.push(ident); + result = this.compileClassDeclaration(o); } if (this.variable) { - return new Assign(this.variable, klass, null, { + assign = new Assign(this.variable, new Literal(''), null, { moduleDeclaration: this.moduleDeclaration - }).compileToFragments(o); + }); + return [...assign.compileToFragments(o), ...result]; } else { - return klass.compileToFragments(o); + return result; + } + } + + compileClassDeclaration(o) { + var ref3, result; + if (this.externalCtor || this.boundMethods.length) { + if (this.ctor == null) { + this.ctor = this.makeDefaultConstructor(); + } + } + if ((ref3 = this.ctor) != null) { + ref3.noReturn = true; + } + this.proxyBoundMethods(o); + this.ensureConstructorSuperCall(); + o.indent += TAB; + result = []; + result.push(this.makeCode("class ")); + if (this.name) { + result.push(this.makeCode(`${this.name} `)); + } + if (this.parent) { + result.push(this.makeCode('extends '), ...this.parent.compileToFragments(o), this.makeCode(' ')); + } + result.push(this.makeCode('{')); + if (!this.body.isEmpty()) { + this.body.spaced = true; + result.push(this.makeCode('\n')); + result.push(...this.body.compileToFragments(o, LEVEL_TOP)); + result.push(this.makeCode(`\n${this.tab}`)); } + result.push(this.makeCode('}')); + return result; } determineName() { @@ -1770,100 +1767,111 @@ } } - walkBody(name) { - var assign, comment, directives, expr, i, j, k, len1, len2, method, properties, ref3, ref4; - directives = []; - properties = []; - while (expr = this.body.expressions[0]) { - if (!(expr instanceof Comment || expr instanceof Value && expr.isString())) { - break; - } - directives.push(this.body.expressions.shift()); - } - comment = null; - ref3 = this.body.expressions.slice(); - for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { - expr = ref3[i]; - if (expr instanceof Value && expr.isObject(true)) { - ref4 = expr.base.properties; - for (i = k = 0, len2 = ref4.length; k < len2; i = ++k) { - assign = ref4[i]; - if (this.validInitializerMethod(name, assign)) { - expr.base.properties[i] = method = this.addInitializerMethod(name, assign); - if (comment) { - properties.push(comment); - } - properties.push(method); + walkBody() { + var assign, end, executableBody, expression, expressions, exprs, i, initializer, initializerExpression, j, k, len1, len2, method, properties, pushSlice, ref3, start; + this.ctor = null; + this.boundMethods = []; + executableBody = null; + initializer = []; + expressions = this.body.expressions; + i = 0; + ref3 = expressions.slice(); + for (j = 0, len1 = ref3.length; j < len1; j++) { + expression = ref3[j]; + if (expression instanceof Value && expression.isObject(true)) { + properties = expression.base.properties; + exprs = []; + end = 0; + start = 0; + pushSlice = function() { + if (end > start) { + return exprs.push(new Value(new Obj(properties.slice(start, end), true))); } - comment = assign instanceof Comment ? assign : null; - } - } else if (expr instanceof Assign && expr.variable.looksStatic(name)) { - if (this.validInitializerMethod(name, expr)) { - this.body.expressions[i] = method = this.addInitializerMethod(name, expr); - if (comment) { - properties.push(comment); + }; + while (assign = properties[end]) { + if (initializerExpression = this.addInitializerExpression(assign)) { + pushSlice(); + exprs.push(initializerExpression); + initializer.push(initializerExpression); + start = end + 1; + } else if (initializer[initializer.length - 1] instanceof Comment) { + exprs.pop(); + initializer.pop(); + start--; } - properties.push(method); + end++; + } + pushSlice(); + [].splice.apply(expressions, [i, i - i + 1].concat(exprs)), exprs; + i += exprs.length; + } else { + if (initializerExpression = this.addInitializerExpression(expression)) { + initializer.push(initializerExpression); + expressions[i] = initializerExpression; + } else if (initializer[initializer.length - 1] instanceof Comment) { + initializer.pop(); } + i += 1; } - comment = expr instanceof Comment ? expr : null; } - this.traverseChildren(false, (child) => { - var cont, l, len3, node, ref5; - cont = true; - if (child instanceof Class) { - return false; - } - if (child instanceof Block) { - ref5 = child.expressions; - for (i = l = 0, len3 = ref5.length; l < len3; i = ++l) { - node = ref5[i]; - if (node instanceof Value && node.isObject(true)) { - cont = false; - child.expressions[i] = this.addProperties(node.base.properties); - } else if (node instanceof Assign && node.variable.looksStatic(name)) { - node.value["static"] = true; + for (k = 0, len2 = initializer.length; k < len2; k++) { + method = initializer[k]; + if (method instanceof Code) { + if (method.ctor) { + if (this.ctor) { + method.error('Cannot define more than one constructor in a class'); } + this.ctor = method; + } else if (method.bound && method["static"]) { + method.context = this.name; + } else if (method.bound) { + this.boundMethods.push(method.name); + method.bound = false; } - child.expressions = flatten(child.expressions); } - return cont && !(child instanceof Class); - }); - return [directives, properties]; + } + if (initializer.length !== expressions.length) { + this.body.expressions = (function() { + var l, len3, results; + results = []; + for (l = 0, len3 = initializer.length; l < len3; l++) { + expression = initializer[l]; + results.push(expression.hoist()); + } + return results; + })(); + return new Block(expressions); + } } - setContext(name) { - return this.body.traverseChildren(false, function(node) { - if (node.classBody) { - return false; - } - if (node instanceof ThisLiteral) { - return node.value = name; - } else if (node instanceof Code) { - if (node.bound) { - return node.context = name; - } - } - }); + addInitializerExpression(node) { + switch (false) { + case !(node instanceof Comment): + return node; + case !this.validInitializerMethod(node): + return this.addInitializerMethod(node); + default: + return null; + } } - validInitializerMethod(name, node) { + validInitializerMethod(node) { if (!(node instanceof Assign && node.value instanceof Code)) { return false; } - if (!node.variable.hasProperties()) { + if (node.context === 'object' && !node.variable.hasProperties()) { return true; } - return node.variable.looksStatic(name); + return node.variable.looksStatic(this.name); } - addInitializerMethod(name, assign) { + addInitializerMethod(assign) { var method, methodName, variable; variable = assign.variable; method = assign.value; method.isMethod = true; - method["static"] = variable.looksStatic(name); - method.klass = new IdentifierLiteral(name); + method["static"] = variable.looksStatic(this.name); + method.klass = new IdentifierLiteral(this.name); method.variable = variable; if (method["static"]) { method.name = variable.properties[0]; @@ -1878,120 +1886,6 @@ return method; } - addProperties(assigns) { - var assign, base, name, prototype, result, value, variable; - result = (function() { - var j, len1, results; - results = []; - for (j = 0, len1 = assigns.length; j < len1; j++) { - assign = assigns[j]; - variable = assign.variable; - base = variable != null ? variable.base : void 0; - value = assign.value; - delete assign.context; - if (assign instanceof Comment || assign.isMethod) { - - } else if (base.value === 'constructor') { - if (value instanceof Code) { - base.error("constructors must be defined at the top level of a class body"); - } - assign = this.externalCtor = new Assign(new Value, value); - } else if (!assign.variable["this"]) { - name = new (base.isComplex() ? Index : Access)(base); - prototype = new Access(new PropertyName('prototype')); - variable = new Value(new ThisLiteral(), [prototype, name]); - assign.variable = variable; - } - results.push(assign); - } - return results; - }).call(this); - return compact(result); - } - - }; - - Class.__super__ = superClass.prototype; - - Class.prototype.children = ['variable', 'parent', 'body']; - - Class.prototype.defaultClassVariableName = '_Class'; - - return Class; - - })(Base); - - exports.ClassDeclaration = ClassDeclaration = (function(superClass) { - class ClassDeclaration extends superClass { - constructor(name1, parent1, body1) { - var body1, name1, parent1; - super(...arguments); - this.name = name1 != null ? name1 : null; - this.parent = parent1 != null ? parent1 : null; - this.body = body1 != null ? body1 : new Block; - } - - compileNode(o) { - var boundMethods, ctor, ref3, result; - ref3 = this.walkBody(), ctor = ref3[0], boundMethods = ref3[1]; - if (this.externalCtor || boundMethods.length) { - if (ctor == null) { - ctor = this.makeDefaultConstructor(); - } - } - if (ctor != null) { - ctor.noReturn = true; - } - this.proxyBoundMethods(o, ctor, boundMethods); - if (this.parent) { - this.ensureConstructorSuperCall(ctor, boundMethods); - } - o.indent += TAB; - result = []; - result.push(this.makeCode("class ")); - if (this.name) { - result.push(this.makeCode(`${this.name} `)); - } - if (this.parent) { - result.push(this.makeCode('extends '), ...this.parent.compileToFragments(o), this.makeCode(' ')); - } - result.push(this.makeCode(`{${(this.body.isEmpty() ? '' : '\n')}`)); - this.body.spaced = true; - if (!this.body.isEmpty()) { - result.push(...this.body.compileToFragments(o, LEVEL_TOP)); - } - result.push(this.makeCode(`${(this.body.isEmpty() ? '' : `\n${this.tab}`)}}`)); - return result; - } - - walkBody() { - var boundMethods, ctor, expression, j, len1, method, ref3, ref4; - ctor = null; - boundMethods = []; - ref3 = this.body.expressions; - for (j = 0, len1 = ref3.length; j < len1; j++) { - expression = ref3[j]; - method = (ref4 = expression.source) != null ? ref4 : expression; - if (method instanceof Comment) { - continue; - } - if (!(method instanceof Code)) { - method.error('A class declaration can only contain functions'); - } - if (ctor && method.ctor) { - method.error('Cannot define more than one constructor in a class'); - } - if (method.bound && !method["static"]) { - boundMethods.push(method.name); - method.bound = false; - } - if (method.ctor) { - ctor = method; - } - } - return [ctor, boundMethods]; - } - makeDefaultConstructor() { var applyArgs, applyCtor, ctor; ctor = new Code; @@ -2008,51 +1902,196 @@ return ctor; } - proxyBoundMethods(o, ctor, boundMethods) { - var j, name; - if (!ctor || boundMethods.length === 0) { + proxyBoundMethods(o) { + var j, name, ref3; + if (!this.boundMethods.length) { return; } - for (j = boundMethods.length - 1; j >= 0; j += -1) { - name = boundMethods[j]; + ref3 = this.boundMethods; + for (j = ref3.length - 1; j >= 0; j += -1) { + name = ref3[j]; name = new Value(new ThisLiteral, [name]).compile(o); - ctor.body.unshift(new Literal(`${name} = ${utility('bind', o)}(${name}, this)`)); + this.ctor.body.unshift(new Literal(`${name} = ${utility('bind', o)}(${name}, this)`)); } return null; } - ensureConstructorSuperCall(ctor, boundMethods) { + ensureConstructorSuperCall() { var hasThisParam, j, len1, param, ref3, superCall; - if (!ctor) { + if (!(this.parent && this.ctor)) { return; } hasThisParam = false; - ref3 = ctor.params; + ref3 = this.ctor.params; for (j = 0, len1 = ref3.length; j < len1; j++) { param = ref3[j]; if (param.name["this"]) { hasThisParam = true; } } - superCall = ctor.superCall(); + superCall = this.ctor.superCall(); if (hasThisParam && superCall) { superCall.error('super not allowed with `@` parameters in derived constructors'); } - if (boundMethods.length && superCall) { + if (this.boundMethods.length && superCall) { superCall.error('super not allowed with bound functions in derived constructors'); } if (!superCall) { - return ctor.body.unshift(new SuperCall); + return this.ctor.body.unshift(new SuperCall); + } + } + + }; + + Class.__super__ = superClass.prototype; + + Class.prototype.children = ['variable', 'parent', 'body']; + + Class.prototype.defaultClassVariableName = '_Class'; + + return Class; + + })(Base); + + exports.ExecutableClassBody = ExecutableClassBody = (function(superClass) { + class ExecutableClassBody extends superClass { + constructor(_class, body1) { + var body1; + super(...arguments); + this["class"] = _class; + this.body = body1 != null ? body1 : new Block; + } + + compileNode(o) { + var args, argumentsNode, directives, externalCtor, ident, jumpNode, klass, params, parent, wrapper; + if (jumpNode = this.body.jumps()) { + jumpNode.error('Class bodies cannot contain pure statements'); + } + if (argumentsNode = this.body.contains(isLiteralArguments)) { + argumentsNode.error("Class bodies shouldn't reference arguments"); + } + this.name = this["class"].name; + directives = this.walkBody(); + this.setContext(); + ident = new IdentifierLiteral(this.name); + params = []; + args = []; + wrapper = new Code(params, this.body); + klass = new Call(wrapper, args); + this.body.spaced = true; + o.classScope = wrapper.makeScope(o.scope); + if (this.externalCtor) { + externalCtor = new IdentifierLiteral(o.classScope.freeVariable('ctor', { + reserve: false + })); + this["class"].externalCtor = externalCtor; + this.externalCtor.variable.base = externalCtor; + } + if (this["class"].parent) { + parent = new IdentifierLiteral(o.classScope.freeVariable('superClass', { + reserve: false + })); + params.push(new Param(parent)); + args.push(this["class"].parent); + this["class"].parent = parent; + this.body.unshift(new Literal(`${this.name}.__super__ = ${parent.value}.prototype`)); + } + if (this.name !== this["class"].name) { + this.body.expressions.unshift(new Assign(new IdentifierLiteral(this.name), this["class"])); + } else { + this.body.expressions.unshift(this["class"]); + } + this.body.expressions.unshift(...directives); + this.body.push(ident); + return klass.compileToFragments(o); + } + + walkBody() { + var directives, expr, index; + directives = []; + index = 0; + while (expr = this.body.expressions[index]) { + if (!(expr instanceof Comment || expr instanceof Value && expr.isString())) { + break; + } + if (expr.hoisted) { + index++; + } else { + directives.push(...this.body.expressions.splice(index, 1)); + } } + this.traverseChildren(false, (child) => { + var cont, i, j, len1, node, ref3; + if (child instanceof Class || child instanceof HoistTarget) { + return false; + } + cont = true; + if (child instanceof Block) { + ref3 = child.expressions; + for (i = j = 0, len1 = ref3.length; j < len1; i = ++j) { + node = ref3[i]; + if (node instanceof Value && node.isObject(true)) { + cont = false; + child.expressions[i] = this.addProperties(node.base.properties); + } else if (node instanceof Assign && node.variable.looksStatic(this.name)) { + node.value["static"] = true; + } + } + child.expressions = flatten(child.expressions); + } + return cont; + }); + return directives; + } + + setContext() { + return this.body.traverseChildren(false, (node) => { + if (node instanceof ThisLiteral) { + return node.value = this.name; + } else if (node instanceof Code && node.bound) { + return node.context = this.name; + } + }); + } + + addProperties(assigns) { + var assign, base, name, prototype, result, value, variable; + result = (function() { + var j, len1, results; + results = []; + for (j = 0, len1 = assigns.length; j < len1; j++) { + assign = assigns[j]; + variable = assign.variable; + base = variable != null ? variable.base : void 0; + value = assign.value; + delete assign.context; + if (assign instanceof Comment) { + + } else if (base.value === 'constructor') { + if (value instanceof Code) { + base.error('constructors must be defined at the top level of a class body'); + } + assign = this.externalCtor = new Assign(new Value, value); + } else if (!assign.variable["this"]) { + name = new (base.isComplex() ? Index : Access)(base); + prototype = new Access(new PropertyName('prototype')); + variable = new Value(new ThisLiteral(), [prototype, name]); + assign.variable = variable; + } + results.push(assign); + } + return results; + }).call(this); + return compact(result); } }; - ClassDeclaration.__super__ = superClass.prototype; + ExecutableClassBody.__super__ = superClass.prototype; - ClassDeclaration.prototype.children = ['parent', 'body']; + ExecutableClassBody.prototype.children = ['class', 'body']; - return ClassDeclaration; + return ExecutableClassBody; })(Base); diff --git a/src/nodes.coffee b/src/nodes.coffee index 012bd8a2b6..fd1a4d5160 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1098,65 +1098,55 @@ exports.Arr = class Arr extends Base # The CoffeeScript class definition. # Initialize a **Class** with its name, an optional superclass, and a body. + exports.Class = class Class extends Base - children: [ 'variable', 'parent', 'body' ] + children: ['variable', 'parent', 'body'] defaultClassVariableName: '_Class' constructor: (@variable, @parent, @body = new Block) -> - @properties = [] compileNode: (o) -> - if jumpNode = @body.jumps() - jumpNode.error 'Class bodies cannot contain pure statements' - if argumentsNode = @body.contains isLiteralArguments - argumentsNode.error "Class bodies shouldn't reference arguments" - - name = @determineName() - - [ directives, properties ] = @walkBody name - @setContext name - - # TODO Once ES super is being used throughout, the check for `@parent`, and the assignment - # below, can be removed. - emptyBody = not @parent and not @externalCtor and directives.length is 0 and - (@body.isEmpty() or @body.expressions.length <= properties.length) - - if emptyBody - klass = new ClassDeclaration name, @parent, new Block properties + @name = @determineName() + executableBody = @walkBody() + + # TODO Once `super` has been changed over to ES, the check for @parent can be remomved + if @parent or executableBody + @compileNode = @compileClassDeclaration + result = new ExecutableClassBody(@, executableBody).compileToFragments o + @compileNode = @constructor::compileNode else - ident = new IdentifierLiteral name - params = [] - args = [] - wrapper = new Code params, @body - klass = new Call wrapper, args + result = @compileClassDeclaration o - @body.spaced = true + if @variable + assign = new Assign @variable, new Literal(''), null, { @moduleDeclaration } + [ assign.compileToFragments(o)..., result... ] + else + result - o.classScope = wrapper.makeScope o.scope + compileClassDeclaration: (o) -> + @ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length + @ctor?.noReturn = true - declaration = new ClassDeclaration name - declaration.body.push property.hoist() for property in properties + @proxyBoundMethods o + @ensureConstructorSuperCall() - if @externalCtor - externalCtor = new IdentifierLiteral o.classScope.freeVariable 'ctor', reserve: no - declaration.externalCtor = externalCtor - @externalCtor.variable.base = externalCtor + o.indent += TAB - if @parent - parent = new IdentifierLiteral o.classScope.freeVariable 'superClass', reserve: no - declaration.parent = parent - params.push new Param parent - args.push @parent - @body.unshift new Literal "#{name}.__super__ = #{parent.value}.prototype" + result = [] + result.push @makeCode "class " + result.push @makeCode "#{@name} " if @name + result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent - @body.expressions.unshift directives..., declaration - @body.push ident + result.push @makeCode '{' + unless @body.isEmpty() + @body.spaced = true + result.push @makeCode '\n' + result.push @body.compileToFragments(o, LEVEL_TOP)... + result.push @makeCode "\n#{@tab}" + result.push @makeCode '}' - if @variable - new Assign(@variable, klass, null, { @moduleDeclaration }).compileToFragments o - else - klass.compileToFragments o + result # Figure out the appropriate name for this class # TODO ES classes can be anonymous, so we shouldn't need a default name @@ -1175,71 +1165,88 @@ exports.Class = class Class extends Base @variable.error message if message if name in JS_FORBIDDEN then "_#{name}" else name - # Traverse the class's children and: - # - Hoist valid ES properties into `@properties` - # - Hoist static assignments into `@properties` - # - Convert invalid ES properties into class or prototype assignments - walkBody: (name) -> - directives = [] - properties = [] - - while expr = @body.expressions[0] - break unless expr instanceof Comment or expr instanceof Value and expr.isString() - directives.push @body.expressions.shift() - - comment = null - for expr, i in @body.expressions.slice() - if expr instanceof Value and expr.isObject true - for assign, i in expr.base.properties - if @validInitializerMethod name, assign - expr.base.properties[i] = method = @addInitializerMethod name, assign - properties.push comment if comment - properties.push method - comment = if assign instanceof Comment then assign else null - else if expr instanceof Assign and expr.variable.looksStatic name - if @validInitializerMethod name, expr - @body.expressions[i] = method = @addInitializerMethod name, expr - properties.push comment if comment - properties.push method - - comment = if expr instanceof Comment then expr else null - - @traverseChildren false, (child) => - cont = true - return false if child instanceof Class - if child instanceof Block - for node, i in child.expressions - if node instanceof Value and node.isObject(true) - cont = false - child.expressions[i] = @addProperties node.base.properties - else if node instanceof Assign and node.variable.looksStatic name - node.value.static = yes - child.expressions = flatten child.expressions - cont and child not instanceof Class + walkBody: -> + @ctor = null + @boundMethods = [] + executableBody = null + + initializer = [] + { expressions } = @body + + i = 0 + for expression in expressions.slice() + if expression instanceof Value and expression.isObject true + { properties } = expression.base + exprs = [] + end = 0 + start = 0 + pushSlice = -> exprs.push new Value new Obj properties[start...end], true if end > start + + while assign = properties[end] + if initializerExpression = @addInitializerExpression assign + pushSlice() + exprs.push initializerExpression + initializer.push initializerExpression + start = end + 1 + else if initializer[initializer.length - 1] instanceof Comment + # Try to keep comments with their subsequent assign + exprs.pop() + initializer.pop() + start-- + end++ + pushSlice() + + expressions[i..i] = exprs + i += exprs.length + else + if initializerExpression = @addInitializerExpression expression + initializer.push initializerExpression + expressions[i] = initializerExpression + else if initializer[initializer.length - 1] instanceof Comment + # Try to keep comments with their subsequent assign + initializer.pop() + i += 1 + + for method in initializer when method instanceof Code + if method.ctor + method.error 'Cannot define more than one constructor in a class' if @ctor + @ctor = method + else if method.bound and method.static + method.context = @name + else if method.bound + @boundMethods.push method.name + method.bound = false - [ directives, properties ] + if initializer.length != expressions.length + @body.expressions = (expression.hoist() for expression in initializer) + new Block expressions - setContext: (name) -> - @body.traverseChildren false, (node) -> - return false if node.classBody - if node instanceof ThisLiteral - node.value = name - else if node instanceof Code - node.context = name if node.bound + # Add an expression to the class initializer + # + # NOTE Currently, only comments, methods and static methods are valid in ES class initializers. + # When additional expressions become valid, this method should be updated to handle them. + addInitializerExpression: (node) -> + switch + when node instanceof Comment + node + when @validInitializerMethod node + @addInitializerMethod node + else + null # Checks if the given node is a valid ES class initializer method. - validInitializerMethod: (name, node) -> + validInitializerMethod: (node) -> return false unless node instanceof Assign and node.value instanceof Code - return true unless node.variable.hasProperties() - return node.variable.looksStatic name + return true if node.context is 'object' and not node.variable.hasProperties() + return node.variable.looksStatic @name - # Add a method to the class initializer - addInitializerMethod: (name, assign) -> + # Returns a configured class initializer method + addInitializerMethod: (assign) -> variable = assign.variable method = assign.value method.isMethod = yes - method.static = variable.looksStatic name - method.klass = new IdentifierLiteral name + method.static = variable.looksStatic @name + method.klass = new IdentifierLiteral @name method.variable = variable if method.static @@ -1252,116 +1259,156 @@ exports.Class = class Class extends Base method - # Make class/prototype assignments for invalid ES properties - addProperties: (assigns) -> - result = for assign in assigns - variable = assign.variable - base = variable?.base - value = assign.value - delete assign.context + makeDefaultConstructor: -> + ctor = new Code + ctor.ctor = true + ctor.name = new Access new PropertyName 'constructor' + ctor.isMethod = yes + @body.unshift ctor - if assign instanceof Comment or assign.isMethod - # Passthrough - else if base.value is 'constructor' - if value instanceof Code - base.error "constructors must be defined at the top level of a class body" + if @externalCtor + applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ] + applyArgs = [ new ThisLiteral, new IdentifierLiteral 'arguments' ] + ctor.body.push new Call applyCtor, applyArgs + ctor.body.makeReturn() - # The class scope is not available yet, so return the assignment to update later - assign = @externalCtor = new Assign new Value, value - else if not assign.variable.this - name = new (if base.isComplex() then Index else Access) base - prototype = new Access new PropertyName 'prototype' - variable = new Value new ThisLiteral(), [ prototype, name ] + ctor - assign.variable = variable + proxyBoundMethods: (o) -> + return unless @boundMethods.length - assign - compact result + for name in @boundMethods by -1 + name = new Value(new ThisLiteral, [ name ]).compile o + @ctor.body.unshift new Literal "#{name} = #{utility 'bind', o}(#{name}, this)" -exports.ClassDeclaration = class ClassDeclaration extends Base - children: ['parent', 'body'] + null - constructor: (@name = null, @parent = null, @body = new Block) -> + ensureConstructorSuperCall: -> + return unless @parent and @ctor - compileNode: (o) -> - [ ctor, boundMethods ] = @walkBody() + hasThisParam = no + hasThisParam = yes for param in @ctor.params when param.name.this - ctor ?= @makeDefaultConstructor() if @externalCtor or boundMethods.length - ctor?.noReturn = true + superCall = @ctor.superCall() - @proxyBoundMethods o, ctor, boundMethods - @ensureConstructorSuperCall ctor, boundMethods if @parent + if hasThisParam and superCall + superCall.error 'super not allowed with `@` parameters in derived constructors' - o.indent += TAB + if @boundMethods.length and superCall + superCall.error 'super not allowed with bound functions in derived constructors' - result = [] - result.push @makeCode "class " - result.push @makeCode "#{@name} " if @name - result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent - result.push @makeCode "{#{if @body.isEmpty() then '' else '\n'}" + @ctor.body.unshift new SuperCall unless superCall - @body.spaced = true - result.push @body.compileToFragments(o, LEVEL_TOP)... unless @body.isEmpty() +exports.ExecutableClassBody = class ExecutableClassBody extends Base + children: [ 'class', 'body' ] - result.push @makeCode "#{if @body.isEmpty() then '' else "\n#{@tab}"}}" - result + constructor: (@class, @body = new Block) -> - walkBody: -> - ctor = null - boundMethods = [] + compileNode: (o) -> + if jumpNode = @body.jumps() + jumpNode.error 'Class bodies cannot contain pure statements' + if argumentsNode = @body.contains isLiteralArguments + argumentsNode.error "Class bodies shouldn't reference arguments" - for expression in @body.expressions - method = expression.source ? expression - continue if method instanceof Comment - method.error 'A class declaration can only contain functions' unless method instanceof Code - method.error 'Cannot define more than one constructor in a class' if ctor and method.ctor + @name = @class.name + directives = @walkBody() + @setContext() - if method.bound and not method.static - boundMethods.push method.name - method.bound = false - ctor = method if method.ctor + ident = new IdentifierLiteral @name + params = [] + args = [] + wrapper = new Code params, @body + klass = new Call wrapper, args - [ ctor, boundMethods ] + @body.spaced = true - makeDefaultConstructor: -> - ctor = new Code - ctor.ctor = true - ctor.name = new Access new PropertyName 'constructor' - ctor.isMethod = yes - @body.unshift ctor + o.classScope = wrapper.makeScope o.scope if @externalCtor - applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ] - applyArgs = [ new ThisLiteral, new IdentifierLiteral 'arguments' ] - ctor.body.push new Call applyCtor, applyArgs - ctor.body.makeReturn() + externalCtor = new IdentifierLiteral o.classScope.freeVariable 'ctor', reserve: no + @class.externalCtor = externalCtor + @externalCtor.variable.base = externalCtor - ctor + if @class.parent + parent = new IdentifierLiteral o.classScope.freeVariable 'superClass', reserve: no + params.push new Param parent + args.push @class.parent - proxyBoundMethods: (o, ctor, boundMethods) -> - return if not ctor or boundMethods.length is 0 + @class.parent = parent + @body.unshift new Literal "#{@name}.__super__ = #{parent.value}.prototype" - for name in boundMethods by -1 - name = new Value(new ThisLiteral, [ name ]).compile o - ctor.body.unshift new Literal "#{name} = #{utility 'bind', o}(#{name}, this)" + if @name != @class.name + @body.expressions.unshift new Assign (new IdentifierLiteral @name), @class + else + @body.expressions.unshift @class + @body.expressions.unshift directives... + @body.push ident - null + klass.compileToFragments o - ensureConstructorSuperCall: (ctor, boundMethods) -> - return if not ctor + # Traverse the class's children and: + # - Hoist valid ES properties into `@properties` + # - Hoist static assignments into `@properties` + # - Convert invalid ES properties into class or prototype assignments + walkBody: -> + directives = [] - hasThisParam = no - hasThisParam = yes for param in ctor.params when param.name.this + index = 0 + while expr = @body.expressions[index] + break unless expr instanceof Comment or expr instanceof Value and expr.isString() + if expr.hoisted + index++ + else + directives.push @body.expressions.splice(index, 1)... - superCall = ctor.superCall() + @traverseChildren false, (child) => + return false if child instanceof Class or child instanceof HoistTarget - if hasThisParam and superCall - superCall.error 'super not allowed with `@` parameters in derived constructors' + cont = true + if child instanceof Block + for node, i in child.expressions + if node instanceof Value and node.isObject(true) + cont = false + child.expressions[i] = @addProperties node.base.properties + else if node instanceof Assign and node.variable.looksStatic @name + node.value.static = yes + child.expressions = flatten child.expressions + cont - if boundMethods.length and superCall - superCall.error 'super not allowed with bound functions in derived constructors' + directives + + setContext: -> + @body.traverseChildren false, (node) => + if node instanceof ThisLiteral + node.value = @name + else if node instanceof Code and node.bound + node.context = @name - ctor.body.unshift new SuperCall unless superCall + # Make class/prototype assignments for invalid ES properties + addProperties: (assigns) -> + result = for assign in assigns + variable = assign.variable + base = variable?.base + value = assign.value + delete assign.context + + if assign instanceof Comment + # Passthrough + else if base.value is 'constructor' + if value instanceof Code + base.error 'constructors must be defined at the top level of a class body' + + # The class scope is not available yet, so return the assignment to update later + assign = @externalCtor = new Assign new Value, value + else if not assign.variable.this + name = new (if base.isComplex() then Index else Access) base + prototype = new Access new PropertyName 'prototype' + variable = new Value new ThisLiteral(), [ prototype, name ] + + assign.variable = variable + + assign + compact result #### Import and Export From 39b2f696af3972bc9c87dd74e89f1f66da9170b7 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Thu, 15 Dec 2016 02:27:10 +0000 Subject: [PATCH 04/32] Rename `Code#static` to `Code#isStatic` This naming is more consistent with other `Code` flags. --- lib/coffee-script/nodes.js | 14 +++++++------- src/nodes.coffee | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index b3458459f4..d68649e33b 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1250,7 +1250,7 @@ name.index = new Assign(nref, name.index); } accesses = [new Access(new PropertyName('__super__'))]; - if (method["static"]) { + if (method.isStatic) { accesses.push(new Access(new PropertyName('constructor'))); } accesses.push(nref != null ? new Index(nref) : name); @@ -1822,7 +1822,7 @@ method.error('Cannot define more than one constructor in a class'); } this.ctor = method; - } else if (method.bound && method["static"]) { + } else if (method.bound && method.isStatic) { method.context = this.name; } else if (method.bound) { this.boundMethods.push(method.name); @@ -1870,10 +1870,10 @@ variable = assign.variable; method = assign.value; method.isMethod = true; - method["static"] = variable.looksStatic(this.name); + method.isStatic = variable.looksStatic(this.name); method.klass = new IdentifierLiteral(this.name); method.variable = variable; - if (method["static"]) { + if (method.isStatic) { method.name = variable.properties[0]; } else { methodName = variable.base; @@ -2034,7 +2034,7 @@ cont = false; child.expressions[i] = this.addProperties(node.base.properties); } else if (node instanceof Assign && node.variable.looksStatic(this.name)) { - node.value["static"] = true; + node.value.isStatic = true; } } child.expressions = flatten(child.expressions); @@ -2455,7 +2455,7 @@ } } if (this.value instanceof Code) { - if (this.value["static"]) { + if (this.value.isStatic) { this.value.klass = this.variable.base; this.value.name = this.variable.properties[0]; this.value.variable = this.variable; @@ -2849,7 +2849,7 @@ this.body.makeReturn(); } modifiers = []; - if (this["static"]) { + if (this.isStatic) { modifiers.push('static'); } if (this.isAsync) { diff --git a/src/nodes.coffee b/src/nodes.coffee index fd1a4d5160..726219fa6b 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -805,7 +805,7 @@ exports.SuperCall = class SuperCall extends Call nref = new IdentifierLiteral o.scope.parent.freeVariable 'name' name.index = new Assign nref, name.index accesses = [new Access new PropertyName '__super__'] - accesses.push new Access new PropertyName 'constructor' if method.static + accesses.push new Access new PropertyName 'constructor' if method.isStatic accesses.push if nref? then new Index nref else name (new Value bref ? klass, accesses).compile o else @@ -1211,7 +1211,7 @@ exports.Class = class Class extends Base if method.ctor method.error 'Cannot define more than one constructor in a class' if @ctor @ctor = method - else if method.bound and method.static + else if method.bound and method.isStatic method.context = @name else if method.bound @boundMethods.push method.name @@ -1245,11 +1245,11 @@ exports.Class = class Class extends Base variable = assign.variable method = assign.value method.isMethod = yes - method.static = variable.looksStatic @name + method.isStatic = variable.looksStatic @name method.klass = new IdentifierLiteral @name method.variable = variable - if method.static + if method.isStatic method.name = variable.properties[0] else methodName = variable.base @@ -1371,7 +1371,7 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base cont = false child.expressions[i] = @addProperties node.base.properties else if node instanceof Assign and node.variable.looksStatic @name - node.value.static = yes + node.value.isStatic = yes child.expressions = flatten child.expressions cont @@ -1589,7 +1589,7 @@ exports.Assign = class Assign extends Base return @compileConditional o if @context in ['||=', '&&=', '?='] return @compileSpecialMath o if @context in ['**=', '//=', '%%='] if @value instanceof Code - if @value.static + if @value.isStatic @value.klass = @variable.base @value.name = @variable.properties[0] @value.variable = @variable @@ -1931,7 +1931,7 @@ exports.Code = class Code extends Base # Assemble the output modifiers = [] - modifiers.push 'static' if @static + modifiers.push 'static' if @isStatic modifiers.push 'async' if @isAsync modifiers.push 'function' if not @isMethod and not @bound modifiers.push '*' if @isGenerator From 3000908451c93a446fb4077ee303fb530e78138e Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Thu, 15 Dec 2016 02:40:40 +0000 Subject: [PATCH 05/32] Output anonymous classes when possible Anonymous classes can be output when: - The class has no parent. The current super compilation needs a class variable to reference. This condition will go away when ES2015 super is in use. - The class contains no bound static methods. Bound static methods have their context set to the class name. --- lib/coffee-script/nodes.js | 18 ++++++++++++------ src/nodes.coffee | 16 ++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index d68649e33b..b622a34424 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1744,14 +1744,15 @@ } determineName() { - var message, name, node, ref3, tail; + var defaultClassName, message, name, node, ref3, tail; + defaultClassName = this.parent ? this.defaultClassName : null; if (!this.variable) { - return this.defaultClassVariableName; + return defaultClassName; } ref3 = this.variable.properties, tail = ref3[ref3.length - 1]; node = tail ? tail instanceof Access && tail.name : this.variable.base; if (!(node instanceof IdentifierLiteral || node instanceof PropertyName)) { - return this.defaultClassVariableName; + return defaultClassName; } name = node.value; if (!tail) { @@ -1823,6 +1824,9 @@ } this.ctor = method; } else if (method.bound && method.isStatic) { + if (this.name == null) { + this.name = this.defaultClassName; + } method.context = this.name; } else if (method.bound) { this.boundMethods.push(method.name); @@ -1947,7 +1951,7 @@ Class.prototype.children = ['variable', 'parent', 'body']; - Class.prototype.defaultClassVariableName = '_Class'; + Class.prototype.defaultClassName = '_Class'; return Class; @@ -1963,14 +1967,14 @@ } compileNode(o) { - var args, argumentsNode, directives, externalCtor, ident, jumpNode, klass, params, parent, wrapper; + var args, argumentsNode, directives, externalCtor, ident, jumpNode, klass, params, parent, ref3, wrapper; if (jumpNode = this.body.jumps()) { jumpNode.error('Class bodies cannot contain pure statements'); } if (argumentsNode = this.body.contains(isLiteralArguments)) { argumentsNode.error("Class bodies shouldn't reference arguments"); } - this.name = this["class"].name; + this.name = (ref3 = this["class"].name) != null ? ref3 : this.defaultClassVariableName; directives = this.walkBody(); this.setContext(); ident = new IdentifierLiteral(this.name); @@ -2091,6 +2095,8 @@ ExecutableClassBody.prototype.children = ['class', 'body']; + ExecutableClassBody.prototype.defaultClassVariableName = '_Class'; + return ExecutableClassBody; })(Base); diff --git a/src/nodes.coffee b/src/nodes.coffee index 726219fa6b..b40caabe7a 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1102,7 +1102,7 @@ exports.Arr = class Arr extends Base exports.Class = class Class extends Base children: ['variable', 'parent', 'body'] - defaultClassVariableName: '_Class' + defaultClassName: '_Class' constructor: (@variable, @parent, @body = new Block) -> @@ -1110,7 +1110,7 @@ exports.Class = class Class extends Base @name = @determineName() executableBody = @walkBody() - # TODO Once `super` has been changed over to ES, the check for @parent can be remomved + # TODO Once `super` has been changed over to ES, the check for @parent can be removed if @parent or executableBody @compileNode = @compileClassDeclaration result = new ExecutableClassBody(@, executableBody).compileToFragments o @@ -1149,16 +1149,17 @@ exports.Class = class Class extends Base result # Figure out the appropriate name for this class - # TODO ES classes can be anonymous, so we shouldn't need a default name determineName: -> - return @defaultClassVariableName unless @variable + # TODO Once `super` has been changed over to ES, the check for @parent can be removed + defaultClassName = if @parent then @defaultClassName else null + return defaultClassName unless @variable [..., tail] = @variable.properties node = if tail tail instanceof Access and tail.name else @variable.base unless node instanceof IdentifierLiteral or node instanceof PropertyName - return @defaultClassVariableName + return defaultClassName name = node.value unless tail message = isUnassignable name @@ -1212,6 +1213,7 @@ exports.Class = class Class extends Base method.error 'Cannot define more than one constructor in a class' if @ctor @ctor = method else if method.bound and method.isStatic + @name ?= @defaultClassName method.context = @name else if method.bound @boundMethods.push method.name @@ -1302,6 +1304,8 @@ exports.Class = class Class extends Base exports.ExecutableClassBody = class ExecutableClassBody extends Base children: [ 'class', 'body' ] + defaultClassVariableName: '_Class' + constructor: (@class, @body = new Block) -> compileNode: (o) -> @@ -1310,7 +1314,7 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base if argumentsNode = @body.contains isLiteralArguments argumentsNode.error "Class bodies shouldn't reference arguments" - @name = @class.name + @name = @class.name ? @defaultClassVariableName directives = @walkBody() @setContext() From 2a7687ca0d8495e49108b266e526ed537716e76b Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Thu, 15 Dec 2016 03:04:18 +0000 Subject: [PATCH 06/32] Throw errors at compile time for async or generator constructors --- lib/coffee-script/nodes.js | 12 ++++++++++-- src/nodes.coffee | 12 ++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index b622a34424..5146bfdcb5 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2779,8 +2779,16 @@ return haveThisParam = true; } }); - if (this.ctor && haveThisParam && this.body.expressions[0] instanceof SuperCall) { - exprs.push(this.body.expressions.shift()); + if (this.ctor) { + if (this.isAsync) { + this.variable.error('Class constructor may not be async'); + } + if (this.isGenerator) { + this.variable.error('Class constructor may not be a generator'); + } + if (haveThisParam && this.body.expressions[0] instanceof SuperCall) { + exprs.push(this.body.expressions.shift()); + } } ref4 = this.params; for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { diff --git a/src/nodes.coffee b/src/nodes.coffee index b40caabe7a..dfd57c823d 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1847,10 +1847,14 @@ exports.Code = class Code extends Base paramNames.push name haveThisParam = yes if node.this - # If there's a super call as the first expression in a constructor, it needs to stay above any - # `this` assignments - if @ctor and haveThisParam and @body.expressions[0] instanceof SuperCall - exprs.push @body.expressions.shift() + if @ctor + @variable.error 'Class constructor may not be async' if @isAsync + @variable.error 'Class constructor may not be a generator' if @isGenerator + + # If there's a super call as the first expression in a constructor, it needs to stay above any + # `this` assignments + if haveThisParam and @body.expressions[0] instanceof SuperCall + exprs.push @body.expressions.shift() # Parse the parameters, adding them to the list of parameters to put in the # function definition; and dealing with splats or expansions, including From cc808cc8de844aba84bb2febf28f7c1c0c64cc9d Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Thu, 15 Dec 2016 04:09:08 +0000 Subject: [PATCH 07/32] Improve handling of anonymous classes Anonymous classes are now always anonymous. If a name is required (e.g. for bound static methods or derived classes) then the class is compiled in an `ExecutableClassBody` which will give the anonymous class a stable reference. --- lib/coffee-script/nodes.js | 24 +++++++++++------------- src/nodes.coffee | 23 +++++++++++------------ 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 5146bfdcb5..a8d087735d 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1694,7 +1694,7 @@ var assign, executableBody, result; this.name = this.determineName(); executableBody = this.walkBody(); - if (this.parent || executableBody) { + if (executableBody) { this.compileNode = this.compileClassDeclaration; result = new ExecutableClassBody(this, executableBody).compileToFragments(o); this.compileNode = this.constructor.prototype.compileNode; @@ -1744,15 +1744,14 @@ } determineName() { - var defaultClassName, message, name, node, ref3, tail; - defaultClassName = this.parent ? this.defaultClassName : null; + var message, name, node, ref3, tail; if (!this.variable) { - return defaultClassName; + return null; } ref3 = this.variable.properties, tail = ref3[ref3.length - 1]; node = tail ? tail instanceof Access && tail.name : this.variable.base; if (!(node instanceof IdentifierLiteral || node instanceof PropertyName)) { - return defaultClassName; + return null; } name = node.value; if (!tail) { @@ -1824,9 +1823,6 @@ } this.ctor = method; } else if (method.bound && method.isStatic) { - if (this.name == null) { - this.name = this.defaultClassName; - } method.context = this.name; } else if (method.bound) { this.boundMethods.push(method.name); @@ -1834,7 +1830,7 @@ } } } - if (initializer.length !== expressions.length) { + if (this.parent || initializer.length !== expressions.length) { this.body.expressions = (function() { var l, len3, results; results = []; @@ -1866,7 +1862,7 @@ if (node.context === 'object' && !node.variable.hasProperties()) { return true; } - return node.variable.looksStatic(this.name); + return node.variable.looksStatic(this.name) && (this.name || !node.value.bound); } addInitializerMethod(assign) { @@ -1951,8 +1947,6 @@ Class.prototype.children = ['variable', 'parent', 'body']; - Class.prototype.defaultClassName = '_Class'; - return Class; })(Base); @@ -2039,6 +2033,8 @@ child.expressions[i] = this.addProperties(node.base.properties); } else if (node instanceof Assign && node.variable.looksStatic(this.name)) { node.value.isStatic = true; + } else if (node instanceof Code && node.isMethod) { + node.klass = new IdentifierLiteral(this.name); } } child.expressions = flatten(child.expressions); @@ -2081,6 +2077,8 @@ prototype = new Access(new PropertyName('prototype')); variable = new Value(new ThisLiteral(), [prototype, name]); assign.variable = variable; + } else if (assign.value instanceof Code) { + assign.value.isStatic = true; } results.push(assign); } @@ -2863,7 +2861,7 @@ this.body.makeReturn(); } modifiers = []; - if (this.isStatic) { + if (this.isMethod && this.isStatic) { modifiers.push('static'); } if (this.isAsync) { diff --git a/src/nodes.coffee b/src/nodes.coffee index dfd57c823d..9210004967 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1102,16 +1102,13 @@ exports.Arr = class Arr extends Base exports.Class = class Class extends Base children: ['variable', 'parent', 'body'] - defaultClassName: '_Class' - constructor: (@variable, @parent, @body = new Block) -> compileNode: (o) -> @name = @determineName() executableBody = @walkBody() - # TODO Once `super` has been changed over to ES, the check for @parent can be removed - if @parent or executableBody + if executableBody @compileNode = @compileClassDeclaration result = new ExecutableClassBody(@, executableBody).compileToFragments o @compileNode = @constructor::compileNode @@ -1150,16 +1147,14 @@ exports.Class = class Class extends Base # Figure out the appropriate name for this class determineName: -> - # TODO Once `super` has been changed over to ES, the check for @parent can be removed - defaultClassName = if @parent then @defaultClassName else null - return defaultClassName unless @variable + return null unless @variable [..., tail] = @variable.properties node = if tail tail instanceof Access and tail.name else @variable.base unless node instanceof IdentifierLiteral or node instanceof PropertyName - return defaultClassName + return null name = node.value unless tail message = isUnassignable name @@ -1213,13 +1208,13 @@ exports.Class = class Class extends Base method.error 'Cannot define more than one constructor in a class' if @ctor @ctor = method else if method.bound and method.isStatic - @name ?= @defaultClassName method.context = @name else if method.bound @boundMethods.push method.name method.bound = false - if initializer.length != expressions.length + # TODO Once `super` has been changed over to ES, the check for @parent can be removed + if @parent or initializer.length != expressions.length @body.expressions = (expression.hoist() for expression in initializer) new Block expressions @@ -1240,7 +1235,7 @@ exports.Class = class Class extends Base validInitializerMethod: (node) -> return false unless node instanceof Assign and node.value instanceof Code return true if node.context is 'object' and not node.variable.hasProperties() - return node.variable.looksStatic @name + return node.variable.looksStatic(@name) and (@name or not node.value.bound) # Returns a configured class initializer method addInitializerMethod: (assign) -> @@ -1376,6 +1371,8 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base child.expressions[i] = @addProperties node.base.properties else if node instanceof Assign and node.variable.looksStatic @name node.value.isStatic = yes + else if node instanceof Code and node.isMethod + node.klass = new IdentifierLiteral @name child.expressions = flatten child.expressions cont @@ -1410,6 +1407,8 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base variable = new Value new ThisLiteral(), [ prototype, name ] assign.variable = variable + else if assign.value instanceof Code + assign.value.isStatic = true assign compact result @@ -1939,7 +1938,7 @@ exports.Code = class Code extends Base # Assemble the output modifiers = [] - modifiers.push 'static' if @isStatic + modifiers.push 'static' if @isMethod and @isStatic modifiers.push 'async' if @isAsync modifiers.push 'function' if not @isMethod and not @bound modifiers.push '*' if @isGenerator From 6c5b86ee4c7af1353ede4173e632709d4ef0cf7f Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Mon, 19 Dec 2016 11:59:36 +0000 Subject: [PATCH 08/32] Add a `replaceInContext` method to `Node` `replaceInContext` will traverse children looking for a node for which `match` returns true. Once found, the matching node will be replaced by the result of calling `replacement`. --- lib/coffee-script/nodes.js | 41 ++++++++++++++++++++++++++++++++++++++ src/nodes.coffee | 26 ++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index a8d087735d..3dc3bf35d4 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -224,6 +224,39 @@ }); } + replaceInContext(match, replacement) { + var attr, child, children, i, j, k, len1, len2, ref3, ref4; + if (!this.children) { + return false; + } + ref3 = this.children; + for (j = 0, len1 = ref3.length; j < len1; j++) { + attr = ref3[j]; + if (children = this[attr]) { + if (Array.isArray(children)) { + for (i = k = 0, len2 = children.length; k < len2; i = ++k) { + child = children[i]; + if (match(child)) { + [].splice.apply(children, [i, i - i + 1].concat(ref4 = replacement(child, this))), ref4; + return true; + } else { + if (child.replaceInContext(match, replacement)) { + return true; + } + } + } + } else if (match(children)) { + this[attr] = replacement(children, this); + return true; + } else { + if (children.replaceInContext(match, replacement)) { + return true; + } + } + } + } + } + invert() { return new Op('!', this); } @@ -2945,6 +2978,14 @@ } } + replaceInContext(child, replacement) { + if (this.bound) { + return Code.__super__.replaceInContext.call(this, child, replacement); + } else { + return false; + } + } + superCall() { var superCall; superCall = null; diff --git a/src/nodes.coffee b/src/nodes.coffee index 9210004967..ff4e7485ab 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -192,6 +192,24 @@ exports.Base = class Base recur = func(child) child.traverseChildren(crossScope, func) unless recur is no + # `replaceInContext` will traverse children looking for a node for which `match` returns + # true. Once found, the matching node will be replaced by the result of calling `replacement`. + replaceInContext: (match, replacement) -> + return false unless @children + for attr in @children when children = @[attr] + if Array.isArray children + for child, i in children + if match child + children[i..i] = replacement child, @ + return true + else + return true if child.replaceInContext match, replacement + else if match children + @[attr] = replacement children, @ + return true + else + return true if children.replaceInContext match, replacement + invert: -> new Op '!', this @@ -1977,6 +1995,14 @@ exports.Code = class Code extends Base traverseChildren: (crossScope, func) -> super(crossScope, func) if crossScope + # Short-circuit `replaceInContext` method to prevent it from crossing context boundaries. Bound + # functions have the same context. + replaceInContext: (child, replacement) -> + if @bound + super child, replacement + else + false + # Find a super call in this function superCall: -> superCall = null From 4369e44eb3e75b26f6b9c08489eae40b1b2acfac Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Mon, 19 Dec 2016 12:30:51 +0000 Subject: [PATCH 09/32] Separate `this` assignments from function parameters This change has been made to simplify two future changes: 1. Outputting `@`-param assignments after a `super` call. In this case it is necessary that non-`@` parameters are available before `super` is called, so destructuring has to happen before `this` assignment. 2. Compiling destructured assignment to ES6 In this case also destructuring has to happen before `this`, as destructuring can happen in the arguments list, but `this` assignment can not. A bonus side-effect is that default values for `@` params are now output as ES6 default parameters, e.g. (@a = 1) -> becomes function a (a = 1) { this.a = a; } --- lib/coffee-script/nodes.js | 68 +++++++++++++++++++++++++++----------- src/nodes.coffee | 53 ++++++++++++++++++++--------- 2 files changed, 85 insertions(+), 36 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 3dc3bf35d4..db29ee19e6 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1137,11 +1137,10 @@ exports.Call = Call = (function(superClass) { class Call extends superClass { - constructor(variable1, args1, soak1) { - var args1; + constructor(variable1, args1 = [], soak1) { super(...arguments); this.variable = variable1; - this.args = args1 != null ? args1 : []; + this.args = args1; this.soak = soak1; this.isNew = false; if (this.variable instanceof Value && this.variable.isNotCallable()) { @@ -1562,10 +1561,9 @@ exports.Obj = Obj = (function(superClass) { class Obj extends superClass { - constructor(props, generated) { - var generated; + constructor(props, generated = false) { super(...arguments); - this.generated = generated != null ? generated : false; + this.generated = generated; this.objects = this.properties = props || []; } @@ -1715,12 +1713,11 @@ exports.Class = Class = (function(superClass) { class Class extends superClass { - constructor(variable1, parent1, body1) { - var body1; + constructor(variable1, parent1, body1 = new Block) { super(...arguments); this.variable = variable1; this.parent = parent1; - this.body = body1 != null ? body1 : new Block; + this.body = body1; } compileNode(o) { @@ -1986,11 +1983,10 @@ exports.ExecutableClassBody = ExecutableClassBody = (function(superClass) { class ExecutableClassBody extends superClass { - constructor(_class, body1) { - var body1; + constructor(_class, body1 = new Block) { super(...arguments); this["class"] = _class; - this.body = body1 != null ? body1 : new Block; + this.body = body1; } compileNode(o) { @@ -2781,7 +2777,7 @@ } compileNode(o) { - var answer, body, condition, exprs, haveSplatParam, haveThisParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, signature, splatParamName, val, wasEmpty; + var answer, body, condition, exprs, haveSplatParam, haveThisParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, signature, splatParamName, thisAssignments, val, wasEmpty; if (this.bound) { if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; @@ -2801,13 +2797,22 @@ haveSplatParam = false; haveThisParam = false; paramNames = []; - this.eachParamName((name, node) => { + thisAssignments = []; + this.eachParamName(function(name, node, param) { + var target; if (indexOf.call(paramNames, name) >= 0) { node.error(`multiple parameters named '${name}'`); } paramNames.push(name); if (node["this"]) { - return haveThisParam = true; + haveThisParam = true; + name = node.properties[0].name.value; + if (indexOf.call(JS_FORBIDDEN, name) >= 0) { + name = `_${name}`; + } + target = new IdentifierLiteral(o.scope.freeVariable(name)); + param.renameParam(node, target); + return thisAssignments.push(new Assign(node, target)); } }); if (this.ctor) { @@ -2887,6 +2892,9 @@ ])), new Value(new IdentifierLiteral(splatParamName)))); } wasEmpty = this.body.isEmpty(); + if (thisAssignments.length) { + this.body.expressions.unshift(...thisAssignments); + } if (exprs.length) { this.body.expressions.unshift(...exprs); } @@ -3058,11 +3066,11 @@ eachName(iterator, name = this.name) { var atParam, j, len1, node, obj, ref3, ref4; - atParam = function(obj) { - return iterator(`@${obj.properties[0].name.value}`, obj); + atParam = (obj) => { + return iterator(`@${obj.properties[0].name.value}`, obj, this); }; if (name instanceof Literal) { - return iterator(name.value, name); + return iterator(name.value, name, this); } if (name instanceof Value) { return atParam(name); @@ -3080,14 +3088,14 @@ this.eachName(iterator, obj.value.unwrap()); } else if (obj instanceof Splat) { node = obj.name.unwrap(); - iterator(node.value, node); + iterator(node.value, node, this); } else if (obj instanceof Value) { if (obj.isArray() || obj.isObject()) { this.eachName(iterator, obj.base); } else if (obj["this"]) { atParam(obj); } else { - iterator(obj.base.value, obj.base); + iterator(obj.base.value, obj.base, this); } } else if (!(obj instanceof Expansion)) { obj.error(`illegal parameter ${obj.compile()}`); @@ -3095,6 +3103,26 @@ } } + renameParam(node, newNode) { + var isNode, replacement; + isNode = function(candidate) { + return candidate === node; + }; + replacement = (node, parent) => { + var key; + if (parent instanceof Obj) { + key = node; + if (node["this"]) { + key = node.properties[0].name; + } + return new Assign(new Value(key), newNode, 'object'); + } else { + return newNode; + } + }; + return this.replaceInContext(isNode, replacement); + } + }; Param.__super__ = superClass.prototype; diff --git a/src/nodes.coffee b/src/nodes.coffee index ff4e7485ab..c481556b5f 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1857,12 +1857,20 @@ exports.Code = class Code extends Base haveSplatParam = no haveThisParam = no - # Check for duplicate parameters. + # Check for duplicate parameters and separate `this` assignments paramNames = [] - @eachParamName (name, node) => + thisAssignments = [] + @eachParamName (name, node, param) -> node.error "multiple parameters named '#{name}'" if name in paramNames paramNames.push name - haveThisParam = yes if node.this + + if node.this + haveThisParam = yes + name = node.properties[0].name.value + name = "_#{name}" if name in JS_FORBIDDEN + target = new IdentifierLiteral o.scope.freeVariable name + param.renameParam node, target + thisAssignments.push new Assign node, target if @ctor @variable.error 'Class constructor may not be async' if @isAsync @@ -1891,12 +1899,11 @@ exports.Code = class Code extends Base if param.splat params.push ref = param.asReference o splatParamName = fragmentsToText ref.compileNode o - if param.isComplex() # Parameter is destructured or attached to `this` + if param.isComplex() # Parameter is destructured exprs.push new Assign new Value(param.name), ref, '=', param: yes - # TODO: output destrucutred parameters as is, *unless* they contain - # `this` parameters; and fix destructuring of objects with default - # values to work in this context (see Obj.compileNode - # `if prop.context isnt 'object'`) + # TODO: output destructured parameters as is, and fix destructuring + # of objects with default values to work in this context (see + # Obj.compileNode `if prop.context isnt 'object'`) else # `param` is an Expansion splatParamName = o.scope.freeVariable 'args' @@ -1909,10 +1916,9 @@ exports.Code = class Code extends Base # the function definition. else if param.isComplex() - # This parameter is attached to `this`, which ES doesn’t allow; - # or it’s destructured. So add a statement to the function body - # assigning it, e.g. `(a) => { this.a = a; }` or with a default - # value if it has one. + # This parameter is destructured. So add a statement to the function + # body assigning it, e.g. `(arg) => { var a = arg.a; }` or with a + # default value if it has one. val = ref = param.asReference o val = new Op '?', ref, param.value if param.value exprs.push new Assign new Value(param.name), val, '=', param: yes @@ -1951,6 +1957,7 @@ exports.Code = class Code extends Base # Add new expressions to the function body wasEmpty = @body.isEmpty() + @body.expressions.unshift thisAssignments... if thisAssignments.length @body.expressions.unshift exprs... if exprs.length @body.makeReturn() unless wasEmpty or @noReturn @@ -2054,9 +2061,9 @@ exports.Param = class Param extends Base # `name` is the name of the parameter and `node` is the AST node corresponding # to that name. eachName: (iterator, name = @name) -> - atParam = (obj) -> iterator "@#{obj.properties[0].name.value}", obj + atParam = (obj) => iterator "@#{obj.properties[0].name.value}", obj, @ # * simple literals `foo` - return iterator name.value, name if name instanceof Literal + return iterator name.value, name, @ if name instanceof Literal # * at-params `@foo` return atParam name if name instanceof Value for obj in name.objects ? [] @@ -2072,7 +2079,7 @@ exports.Param = class Param extends Base # * splats within destructured parameters `[xs...]` else if obj instanceof Splat node = obj.name.unwrap() - iterator node.value, node + iterator node.value, node, @ else if obj instanceof Value # * destructured parameters within destructured parameters `[{a}]` if obj.isArray() or obj.isObject() @@ -2081,11 +2088,25 @@ exports.Param = class Param extends Base else if obj.this atParam obj # * simple destructured parameters {foo} - else iterator obj.base.value, obj.base + else iterator obj.base.value, obj.base, @ else if obj not instanceof Expansion obj.error "illegal parameter #{obj.compile()}" return + # Rename a param by replacing the given AST node for a name with a new node. + # This needs to ensure that the the source for object destructuring does not change. + renameParam: (node, newNode) -> + isNode = (candidate) -> candidate is node + replacement = (node, parent) => + if parent instanceof Obj + key = node + key = node.properties[0].name if node.this + new Assign new Value(key), newNode, 'object' + else + newNode + + @replaceInContext isNode, replacement + #### Splat # A splat, either as a parameter to a function, an argument to a call, From 45c72a7e8f3f7043926203ff8ae2aa2e0616444a Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Mon, 19 Dec 2016 15:00:47 +0000 Subject: [PATCH 10/32] Change `super` handling in class constructors Inside an ES derived constructor (a constructor for a class that extends another class), it is impossible to access `this` until `super` has been called. This conflicts with CoffeeScript's `@`-param and bound method features, which compile to `this` references at the top of a function body. For example: class B extends A constructor: (@param) -> super method: => This would compile to something like: class B extends A { constructor (param) { this.param = param; this.method = bind(this.method, this); super(...arguments); } } This would break in an ES-compliant runtime as there are `this` references before the call to `super`. Before this commit we were dealing with this by injecting an implicit `super` call into derived constructors that do not already have an explicit `super` call. Furthermore, we would disallow explicit `super` calls in derived constructors that used bound methods or `@`-params, meaning the above example would need to be rewritten as: class B extends A constructor: (@param) -> method: => This would result in a call to `super(...arguments)` being generated as the first expression in `B#constructor`. Whilst this approach seems to work pretty well, and is arguably more convenient than having to manually call `super` when you don't particularly care about the arguments, it does introduce some 'magic' and separation from ES, and would likely be a pain point in a project that made use of significant constructor overriding. This commit introduces a mechanism through which `super` in constructors is 'expanded' to include any generated `this` assignments, whilst retaining the same semantics of a super call. The first example above now compiles to something like: class B extends A { constructor (param) { var ref ref = super(...arguments), this.param = param, this.method = bind(this.method, this), ref; } } --- lib/coffee-script/nodes.js | 193 +++++++++++++++++-------------------- src/nodes.coffee | 126 +++++++++++++++--------- test/classes.coffee | 7 +- test/repl.coffee | 2 + 4 files changed, 181 insertions(+), 147 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index db29ee19e6..a2d4b012cd 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -345,7 +345,7 @@ } constructor(source1) { - super(...arguments); + super(); this.source = source1; this.options = {}; this.targetFragments = { @@ -386,7 +386,7 @@ exports.Block = Block = (function(superClass) { class Block extends superClass { constructor(nodes) { - super(...arguments); + super(); this.expressions = compact(flatten(nodes || [])); } @@ -620,7 +620,7 @@ exports.Literal = Literal = (function(superClass) { class Literal extends superClass { constructor(value1) { - super(...arguments); + super(); this.value = value1; } @@ -833,7 +833,7 @@ exports.Return = Return = (function(superClass) { class Return extends superClass { constructor(expression1) { - super(...arguments); + super(); this.expression = expression1; } @@ -911,10 +911,10 @@ exports.Value = Value = (function(superClass) { class Value extends superClass { constructor(base, props, tag) { - super(...arguments); if (!props && base instanceof Value) { return base; } + super(); this.base = base; this.properties = props || []; if (tag) { @@ -1109,7 +1109,7 @@ exports.Comment = Comment = (function(superClass) { class Comment extends superClass { constructor(comment1) { - super(...arguments); + super(); this.comment = comment1; } @@ -1138,7 +1138,7 @@ exports.Call = Call = (function(superClass) { class Call extends superClass { constructor(variable1, args1 = [], soak1) { - super(...arguments); + super(); this.variable = variable1; this.args = args1; this.soak = soak1; @@ -1344,7 +1344,7 @@ exports.Extends = Extends = (function(superClass) { class Extends extends superClass { constructor(child1, parent1) { - super(...arguments); + super(); this.child = child1; this.parent = parent1; } @@ -1366,7 +1366,7 @@ exports.Access = Access = (function(superClass) { class Access extends superClass { constructor(name1, tag) { - super(...arguments); + super(); this.name = name1; this.soak = tag === 'soak'; } @@ -1401,7 +1401,7 @@ exports.Index = Index = (function(superClass) { class Index extends superClass { constructor(index1) { - super(...arguments); + super(); this.index = index1; } @@ -1426,7 +1426,7 @@ exports.Range = Range = (function(superClass) { class Range extends superClass { constructor(from1, to1, tag) { - super(...arguments); + super(); this.from = from1; this.to = to1; this.exclusive = tag === 'exclusive'; @@ -1531,7 +1531,7 @@ exports.Slice = Slice = (function(superClass) { class Slice extends superClass { constructor(range1) { - super(...arguments); + super(); this.range = range1; } @@ -1562,7 +1562,7 @@ exports.Obj = Obj = (function(superClass) { class Obj extends superClass { constructor(props, generated = false) { - super(...arguments); + super(); this.generated = generated; this.objects = this.properties = props || []; } @@ -1651,7 +1651,7 @@ exports.Arr = Arr = (function(superClass) { class Arr extends superClass { constructor(objs) { - super(...arguments); + super(); this.objects = objs || []; } @@ -1714,7 +1714,7 @@ exports.Class = Class = (function(superClass) { class Class extends superClass { constructor(variable1, parent1, body1 = new Block) { - super(...arguments); + super(); this.variable = variable1; this.parent = parent1; this.body = body1; @@ -1751,8 +1751,9 @@ if ((ref3 = this.ctor) != null) { ref3.noReturn = true; } - this.proxyBoundMethods(o); - this.ensureConstructorSuperCall(); + if (this.boundMethods.length) { + this.proxyBoundMethods(o); + } o.indent += TAB; result = []; result.push(this.makeCode("class ")); @@ -1923,6 +1924,9 @@ ctor.name = new Access(new PropertyName('constructor')); ctor.isMethod = true; this.body.unshift(ctor); + if (this.parent) { + ctor.body.push(new SuperCall); + } if (this.externalCtor) { applyCtor = new Value(this.externalCtor, [new Access(new PropertyName('apply'))]); applyArgs = [new ThisLiteral, new IdentifierLiteral('arguments')]; @@ -1933,42 +1937,19 @@ } proxyBoundMethods(o) { - var j, name, ref3; - if (!this.boundMethods.length) { - return; - } - ref3 = this.boundMethods; - for (j = ref3.length - 1; j >= 0; j += -1) { - name = ref3[j]; - name = new Value(new ThisLiteral, [name]).compile(o); - this.ctor.body.unshift(new Literal(`${name} = ${utility('bind', o)}(${name}, this)`)); - } - return null; - } - - ensureConstructorSuperCall() { - var hasThisParam, j, len1, param, ref3, superCall; - if (!(this.parent && this.ctor)) { - return; - } - hasThisParam = false; - ref3 = this.ctor.params; - for (j = 0, len1 = ref3.length; j < len1; j++) { - param = ref3[j]; - if (param.name["this"]) { - hasThisParam = true; + var name; + this.ctor.thisAssignments = (function() { + var j, ref3, results; + ref3 = this.boundMethods; + results = []; + for (j = ref3.length - 1; j >= 0; j += -1) { + name = ref3[j]; + name = new Value(new ThisLiteral, [name]).compile(o); + results.push(new Literal(`${name} = ${utility('bind', o)}(${name}, this)`)); } - } - superCall = this.ctor.superCall(); - if (hasThisParam && superCall) { - superCall.error('super not allowed with `@` parameters in derived constructors'); - } - if (this.boundMethods.length && superCall) { - superCall.error('super not allowed with bound functions in derived constructors'); - } - if (!superCall) { - return this.ctor.body.unshift(new SuperCall); - } + return results; + }).call(this); + return null; } }; @@ -1984,7 +1965,7 @@ exports.ExecutableClassBody = ExecutableClassBody = (function(superClass) { class ExecutableClassBody extends superClass { constructor(_class, body1 = new Block) { - super(...arguments); + super(); this["class"] = _class; this.body = body1; } @@ -2131,7 +2112,7 @@ exports.ModuleDeclaration = ModuleDeclaration = (function(superClass) { class ModuleDeclaration extends superClass { constructor(clause, source1) { - super(...arguments); + super(); this.clause = clause; this.source = source1; this.checkSource(); @@ -2197,7 +2178,7 @@ exports.ImportClause = ImportClause = (function(superClass) { class ImportClause extends superClass { constructor(defaultBinding, namedImports) { - super(...arguments); + super(); this.defaultBinding = defaultBinding; this.namedImports = namedImports; } @@ -2294,7 +2275,7 @@ exports.ModuleSpecifierList = ModuleSpecifierList = (function(superClass) { class ModuleSpecifierList extends superClass { constructor(specifiers) { - super(...arguments); + super(); this.specifiers = specifiers; } @@ -2359,7 +2340,7 @@ exports.ModuleSpecifier = ModuleSpecifier = (function(superClass) { class ModuleSpecifier extends superClass { constructor(original, alias, moduleDeclarationType1) { - super(...arguments); + super(); this.original = original; this.alias = alias; this.moduleDeclarationType = moduleDeclarationType1; @@ -2446,7 +2427,7 @@ exports.Assign = Assign = (function(superClass) { class Assign extends superClass { constructor(variable1, value1, context, options = {}) { - super(...arguments); + super(); this.variable = variable1; this.value = value1; this.context = context; @@ -2748,7 +2729,7 @@ exports.Code = Code = (function(superClass) { class Code extends superClass { constructor(params, body, tag) { - super(...arguments); + super(); this.params = params || []; this.body = body || new Block; this.bound = tag === 'boundfunc'; @@ -2777,7 +2758,15 @@ } compileNode(o) { - var answer, body, condition, exprs, haveSplatParam, haveThisParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, signature, splatParamName, thisAssignments, val, wasEmpty; + var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, thisAssignments, val, wasEmpty; + if (this.ctor) { + if (this.isAsync) { + this.variable.error('Class constructor may not be async'); + } + if (this.isGenerator) { + this.variable.error('Class constructor may not be a generator'); + } + } if (this.bound) { if ((ref3 = o.scope.method) != null ? ref3.bound : void 0) { this.context = o.scope.method.context; @@ -2793,11 +2782,10 @@ delete o.isExistentialEquals; params = []; exprs = []; + thisAssignments = (ref4 = (ref5 = this.thisAssignments) != null ? ref5.slice() : void 0) != null ? ref4 : []; paramsAfterSplat = []; haveSplatParam = false; - haveThisParam = false; paramNames = []; - thisAssignments = []; this.eachParamName(function(name, node, param) { var target; if (indexOf.call(paramNames, name) >= 0) { @@ -2805,7 +2793,6 @@ } paramNames.push(name); if (node["this"]) { - haveThisParam = true; name = node.properties[0].name.value; if (indexOf.call(JS_FORBIDDEN, name) >= 0) { name = `_${name}`; @@ -2815,20 +2802,9 @@ return thisAssignments.push(new Assign(node, target)); } }); - if (this.ctor) { - if (this.isAsync) { - this.variable.error('Class constructor may not be async'); - } - if (this.isGenerator) { - this.variable.error('Class constructor may not be a generator'); - } - if (haveThisParam && this.body.expressions[0] instanceof SuperCall) { - exprs.push(this.body.expressions.shift()); - } - } - ref4 = this.params; - for (i = j = 0, len1 = ref4.length; j < len1; i = ++j) { - param = ref4[i]; + ref6 = this.params; + for (i = j = 0, len1 = ref6.length; j < len1; i = ++j) { + param = ref6[i]; if (param.splat || param instanceof Expansion) { if (haveSplatParam) { param.error('only one splat or expansion parameter is allowed per function definition'); @@ -2872,7 +2848,7 @@ ifTrue = new Assign(new Value(param.name), param.value, '='); exprs.push(new If(condition, ifTrue)); } - if (((ref5 = param.name) != null ? ref5.value : void 0) != null) { + if (((ref7 = param.name) != null ? ref7.value : void 0) != null) { o.scope.add(param.name.value, 'var', true); } } @@ -2892,7 +2868,9 @@ ])), new Value(new IdentifierLiteral(splatParamName)))); } wasEmpty = this.body.isEmpty(); - if (thisAssignments.length) { + if (this.ctor) { + this.expandCtorSuperCall(o, thisAssignments); + } else if (thisAssignments.length) { this.body.expressions.unshift(...thisAssignments); } if (exprs.length) { @@ -2994,16 +2972,27 @@ } } - superCall() { - var superCall; - superCall = null; - this.traverseChildren(true, function(child) { - if (child instanceof SuperCall) { - superCall = child; + expandCtorSuperCall(o, thisAssignments) { + var isSuper, replacement; + isSuper = function(child) { + return child instanceof SuperCall; + }; + replacement = function(child, parent) { + var assign, ref, ref3; + replacement = new Block(thisAssignments); + if (parent instanceof Block) { + replacement.unshift(child); + } else { + ref3 = child.cache(o), assign = ref3[0], ref = ref3[1]; + replacement.unshift(assign); + replacement.push(ref); } - return !superCall && (!(child instanceof Code) || child.bound); - }); - return superCall; + return replacement; + }; + if (!this.body.replaceInContext(isSuper, replacement)) { + this.body.expressions.unshift(...thisAssignments); + } + return null; } }; @@ -3022,7 +3011,7 @@ class Param extends superClass { constructor(name1, value1, splat) { var message, token; - super(...arguments); + super(); this.name = name1; this.value = value1; this.splat = splat; @@ -3136,7 +3125,7 @@ exports.Splat = Splat = (function(superClass) { class Splat extends superClass { constructor(name) { - super(...arguments); + super(); this.name = name.compile ? name : new Literal(name); } @@ -3189,7 +3178,7 @@ exports.While = While = (function(superClass) { class While extends superClass { constructor(condition, options) { - super(...arguments); + super(); this.condition = (options != null ? options.invert : void 0) ? condition.invert() : condition; this.guard = options != null ? options.guard : void 0; } @@ -3274,12 +3263,11 @@ class Op extends superClass { constructor(op, first, second, flip) { - super(...arguments); if (op === 'in') { return new In(first, second); } if (op === 'do') { - return this.generateDo(first); + return Op.prototype.generateDo(first); } if (op === 'new') { if (first instanceof Call && !first["do"] && !first.isNew) { @@ -3289,6 +3277,7 @@ first = new Parens(first); } } + super(); this.operator = CONVERSIONS[op] || op; this.first = first; this.second = second; @@ -3553,7 +3542,7 @@ exports.In = In = (function(superClass) { class In extends superClass { constructor(object, array) { - super(...arguments); + super(); this.object = object; this.array = array; } @@ -3631,7 +3620,7 @@ exports.Try = Try = (function(superClass) { class Try extends superClass { constructor(attempt, errorVariable, recovery, ensure) { - super(...arguments); + super(); this.attempt = attempt; this.errorVariable = errorVariable; this.recovery = recovery; @@ -3681,7 +3670,7 @@ exports.Throw = Throw = (function(superClass) { class Throw extends superClass { constructor(expression1) { - super(...arguments); + super(); this.expression = expression1; } @@ -3708,7 +3697,7 @@ exports.Existence = Existence = (function(superClass) { class Existence extends superClass { constructor(expression1) { - super(...arguments); + super(); this.expression = expression1; } @@ -3740,7 +3729,7 @@ exports.Parens = Parens = (function(superClass) { class Parens extends superClass { constructor(body1) { - super(...arguments); + super(); this.body = body1; } @@ -3781,7 +3770,7 @@ exports.StringWithInterpolations = StringWithInterpolations = (function(superClass) { class StringWithInterpolations extends superClass { constructor(body1) { - super(...arguments); + super(); this.body = body1; } @@ -3845,7 +3834,7 @@ class For extends superClass { constructor(body, source) { var ref3; - super(...arguments); + super(); this.source = source.source, this.guard = source.guard, this.step = source.step, this.name = source.name, this.index = source.index; this.body = Block.wrap([body]); this.own = !!source.own; @@ -4044,7 +4033,7 @@ exports.Switch = Switch = (function(superClass) { class Switch extends superClass { constructor(subject, cases, otherwise) { - super(...arguments); + super(); this.subject = subject; this.cases = cases; this.otherwise = otherwise; @@ -4130,7 +4119,7 @@ exports.If = If = (function(superClass) { class If extends superClass { constructor(condition, body1, options = {}) { - super(...arguments); + super(); this.body = body1; this.condition = options.type === 'unless' ? condition.invert() : condition; this.elseBody = null; diff --git a/src/nodes.coffee b/src/nodes.coffee index c481556b5f..1d50f3f73d 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -276,6 +276,8 @@ exports.HoistTarget = class HoistTarget extends Base fragments constructor: (@source) -> + super() + # Holds presentational options to apply when the source node is compiled @options = {} @@ -310,6 +312,8 @@ exports.HoistTarget = class HoistTarget extends Base # `if`, `switch`, or `try`, and so on... exports.Block = class Block extends Base constructor: (nodes) -> + super() + @expressions = compact flatten nodes or [] children: ['expressions'] @@ -475,6 +479,7 @@ exports.Block = class Block extends Base # `true`, `false`, `null`... exports.Literal = class Literal extends Base constructor: (@value) -> + super() isComplex: NO @@ -552,6 +557,7 @@ exports.BooleanLiteral = class BooleanLiteral extends Literal # make sense. exports.Return = class Return extends Base constructor: (@expression) -> + super() children: ['expression'] @@ -595,6 +601,9 @@ exports.AwaitReturn = class AwaitReturn extends Return exports.Value = class Value extends Base constructor: (base, props, tag) -> return base if not props and base instanceof Value + + super() + @base = base @properties = props or [] @[tag] = true if tag @@ -709,6 +718,7 @@ exports.Value = class Value extends Base # at the same position. exports.Comment = class Comment extends Base constructor: (@comment) -> + super() isStatement: YES makeReturn: THIS @@ -724,6 +734,8 @@ exports.Comment = class Comment extends Base # Node for a function invocation. exports.Call = class Call extends Base constructor: (@variable, @args = [], @soak) -> + super() + @isNew = false if @variable instanceof Value and @variable.isNotCallable() @variable.error "literal is not a function" @@ -859,6 +871,7 @@ exports.TaggedTemplateCall = class TaggedTemplateCall extends Call # [Closure Library](https://github.com/google/closure-library/blob/master/closure/goog/base.js). exports.Extends = class Extends extends Base constructor: (@child, @parent) -> + super() children: ['child', 'parent'] @@ -872,6 +885,7 @@ exports.Extends = class Extends extends Base # an access into the object's prototype. exports.Access = class Access extends Base constructor: (@name, tag) -> + super() @soak = tag is 'soak' children: ['name'] @@ -894,6 +908,7 @@ exports.Access = class Access extends Base # A `[ ... ]` indexed access into an array or object. exports.Index = class Index extends Base constructor: (@index) -> + super() children: ['index'] @@ -913,6 +928,8 @@ exports.Range = class Range extends Base children: ['from', 'to'] constructor: (@from, @to, tag) -> + super() + @exclusive = tag is 'exclusive' @equals = if @exclusive then '' else '=' @@ -1010,6 +1027,7 @@ exports.Slice = class Slice extends Base children: ['range'] constructor: (@range) -> + super() # We have to be careful when trying to slice through the end of the array, # `9e9` is used because not all implementations respect `undefined` or `1/0`. @@ -1036,6 +1054,8 @@ exports.Slice = class Slice extends Base # An object literal, nothing fancy. exports.Obj = class Obj extends Base constructor: (props, @generated = false) -> + super() + @objects = @properties = props or [] children: ['properties'] @@ -1086,6 +1106,8 @@ exports.Obj = class Obj extends Base # An array literal. exports.Arr = class Arr extends Base constructor: (objs) -> + super() + @objects = objs or [] children: ['objects'] @@ -1121,6 +1143,7 @@ exports.Class = class Class extends Base children: ['variable', 'parent', 'body'] constructor: (@variable, @parent, @body = new Block) -> + super() compileNode: (o) -> @name = @determineName() @@ -1143,8 +1166,7 @@ exports.Class = class Class extends Base @ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length @ctor?.noReturn = true - @proxyBoundMethods o - @ensureConstructorSuperCall() + @proxyBoundMethods o if @boundMethods.length o.indent += TAB @@ -1281,6 +1303,8 @@ exports.Class = class Class extends Base ctor.isMethod = yes @body.unshift ctor + ctor.body.push new SuperCall if @parent + if @externalCtor applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ] applyArgs = [ new ThisLiteral, new IdentifierLiteral 'arguments' ] @@ -1290,36 +1314,19 @@ exports.Class = class Class extends Base ctor proxyBoundMethods: (o) -> - return unless @boundMethods.length - - for name in @boundMethods by -1 + @ctor.thisAssignments = for name in @boundMethods by -1 name = new Value(new ThisLiteral, [ name ]).compile o - @ctor.body.unshift new Literal "#{name} = #{utility 'bind', o}(#{name}, this)" + new Literal "#{name} = #{utility 'bind', o}(#{name}, this)" null - ensureConstructorSuperCall: -> - return unless @parent and @ctor - - hasThisParam = no - hasThisParam = yes for param in @ctor.params when param.name.this - - superCall = @ctor.superCall() - - if hasThisParam and superCall - superCall.error 'super not allowed with `@` parameters in derived constructors' - - if @boundMethods.length and superCall - superCall.error 'super not allowed with bound functions in derived constructors' - - @ctor.body.unshift new SuperCall unless superCall - exports.ExecutableClassBody = class ExecutableClassBody extends Base children: [ 'class', 'body' ] defaultClassVariableName: '_Class' constructor: (@class, @body = new Block) -> + super() compileNode: (o) -> if jumpNode = @body.jumps() @@ -1435,6 +1442,7 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base exports.ModuleDeclaration = class ModuleDeclaration extends Base constructor: (@clause, @source) -> + super() @checkSource() children: ['clause', 'source'] @@ -1469,6 +1477,7 @@ exports.ImportDeclaration = class ImportDeclaration extends ModuleDeclaration exports.ImportClause = class ImportClause extends Base constructor: (@defaultBinding, @namedImports) -> + super() children: ['defaultBinding', 'namedImports'] @@ -1518,6 +1527,7 @@ exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclarat exports.ModuleSpecifierList = class ModuleSpecifierList extends Base constructor: (@specifiers) -> + super() children: ['specifiers'] @@ -1542,6 +1552,8 @@ exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierL exports.ModuleSpecifier = class ModuleSpecifier extends Base constructor: (@original, @alias, @moduleDeclarationType) -> + super() + # The name of the variable entering the local scope @identifier = if @alias? then @alias.value else @original.value @@ -1581,6 +1593,7 @@ exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier # property of an object -- including within object literals. exports.Assign = class Assign extends Base constructor: (@variable, @value, @context, options = {}) -> + super() {@param, @subpattern, @operatorToken, @moduleDeclaration} = options children: ['variable', 'value'] @@ -1812,6 +1825,8 @@ exports.Assign = class Assign extends Base # has no *children* -- they're within the inner scope. exports.Code = class Code extends Base constructor: (params, body, tag) -> + super() + @params = params or [] @body = body or new Block @bound = tag is 'boundfunc' @@ -1842,6 +1857,10 @@ exports.Code = class Code extends Base # parameters after the splat, they are declared via expressions in the # function body. compileNode: (o) -> + if @ctor + @variable.error 'Class constructor may not be async' if @isAsync + @variable.error 'Class constructor may not be a generator' if @isGenerator + if @bound @context = o.scope.method.context if o.scope.method?.bound @context = 'this' unless @context @@ -1853,34 +1872,23 @@ exports.Code = class Code extends Base delete o.isExistentialEquals params = [] exprs = [] + thisAssignments = @thisAssignments?.slice() ? [] paramsAfterSplat = [] haveSplatParam = no - haveThisParam = no # Check for duplicate parameters and separate `this` assignments paramNames = [] - thisAssignments = [] @eachParamName (name, node, param) -> node.error "multiple parameters named '#{name}'" if name in paramNames paramNames.push name if node.this - haveThisParam = yes name = node.properties[0].name.value name = "_#{name}" if name in JS_FORBIDDEN target = new IdentifierLiteral o.scope.freeVariable name param.renameParam node, target thisAssignments.push new Assign node, target - if @ctor - @variable.error 'Class constructor may not be async' if @isAsync - @variable.error 'Class constructor may not be a generator' if @isGenerator - - # If there's a super call as the first expression in a constructor, it needs to stay above any - # `this` assignments - if haveThisParam and @body.expressions[0] instanceof SuperCall - exprs.push @body.expressions.shift() - # Parse the parameters, adding them to the list of parameters to put in the # function definition; and dealing with splats or expansions, including # adding expressions to the function body to declare all parameter @@ -1955,9 +1963,13 @@ exports.Code = class Code extends Base new Arr [new Splat(new IdentifierLiteral(splatParamName)), (param.asReference o for param in paramsAfterSplat)...] ), new Value new IdentifierLiteral splatParamName + # Add new expressions to the function body wasEmpty = @body.isEmpty() - @body.expressions.unshift thisAssignments... if thisAssignments.length + if @ctor + @expandCtorSuperCall o, thisAssignments + else if thisAssignments.length + @body.expressions.unshift thisAssignments... @body.expressions.unshift exprs... if exprs.length @body.makeReturn() unless wasEmpty or @noReturn @@ -2010,15 +2022,22 @@ exports.Code = class Code extends Base else false - # Find a super call in this function - superCall: -> - superCall = null - @traverseChildren true, (child) -> - superCall = child if child instanceof SuperCall + # Replace the first `super` call with a `Block` containing `thisAssignments` + # If there is no `super` call, `thisAssignments` are prepended to the body. + expandCtorSuperCall: (o, thisAssignments) -> + isSuper = (child) -> child instanceof SuperCall + replacement = (child, parent) -> + replacement = new Block thisAssignments + if parent instanceof Block + replacement.unshift child + else + [assign, ref] = child.cache o + replacement.unshift assign + replacement.push ref + replacement - # `super` has the same target in bound (arrow) functions, so check them too - not superCall and (child not instanceof Code or child.bound) - superCall + @body.expressions.unshift thisAssignments... unless @body.replaceInContext isSuper, replacement + null #### Param @@ -2027,6 +2046,8 @@ exports.Code = class Code extends Base # as well as be a splat, gathering up a group of parameters into an array. exports.Param = class Param extends Base constructor: (@name, @value, @splat) -> + super() + message = isUnassignable @name.unwrapAll().value @name.error message if message if @name instanceof Obj and @name.generated @@ -2118,6 +2139,7 @@ exports.Splat = class Splat extends Base isAssignable: YES constructor: (name) -> + super() @name = if name.compile then name else new Literal name assigns: (name) -> @@ -2152,6 +2174,8 @@ exports.Expansion = class Expansion extends Base # flexibility or more speed than a comprehension can provide. exports.While = class While extends Base constructor: (condition, options) -> + super() + @condition = if options?.invert then condition.invert() else condition @guard = options?.guard @@ -2209,10 +2233,13 @@ exports.Op = class Op extends Base constructor: (op, first, second, flip ) -> return new In first, second if op is 'in' if op is 'do' - return @generateDo first + return Op::generateDo first if op is 'new' return first.newInstance() if first instanceof Call and not first.do and not first.isNew first = new Parens first if first instanceof Code and first.bound or first.do + + super() + @operator = CONVERSIONS[op] or op @first = first @second = second @@ -2403,6 +2430,7 @@ exports.Op = class Op extends Base #### In exports.In = class In extends Base constructor: (@object, @array) -> + super() children: ['object', 'array'] @@ -2442,6 +2470,7 @@ exports.In = class In extends Base # A classic *try/catch/finally* block. exports.Try = class Try extends Base constructor: (@attempt, @errorVariable, @recovery, @ensure) -> + super() children: ['attempt', 'recovery', 'ensure'] @@ -2487,6 +2516,7 @@ exports.Try = class Try extends Base # Simple node to throw an exception. exports.Throw = class Throw extends Base constructor: (@expression) -> + super() children: ['expression'] @@ -2506,6 +2536,7 @@ exports.Throw = class Throw extends Base # table. exports.Existence = class Existence extends Base constructor: (@expression) -> + super() children: ['expression'] @@ -2531,6 +2562,7 @@ exports.Existence = class Existence extends Base # Parentheses are a good way to force any statement to become an expression. exports.Parens = class Parens extends Base constructor: (@body) -> + super() children: ['body'] @@ -2551,6 +2583,7 @@ exports.Parens = class Parens extends Base exports.StringWithInterpolations = class StringWithInterpolations extends Base constructor: (@body) -> + super() children: ['body'] @@ -2606,6 +2639,8 @@ exports.StringWithInterpolations = class StringWithInterpolations extends Base # you can map and filter in a single pass. exports.For = class For extends While constructor: (body, source) -> + super() + {@source, @guard, @step, @name, @index} = source @body = Block.wrap [body] @own = !!source.own @@ -2732,6 +2767,7 @@ exports.For = class For extends While # A JavaScript *switch* statement. Converts into a returnable expression on-demand. exports.Switch = class Switch extends Base constructor: (@subject, @cases, @otherwise) -> + super() children: ['subject', 'cases', 'otherwise'] @@ -2777,6 +2813,8 @@ exports.Switch = class Switch extends Base # because ternaries are already proper expressions, and don't need conversion. exports.If = class If extends Base constructor: (condition, @body, options = {}) -> + super() + @condition = if options.type is 'unless' then condition.invert() else condition @elseBody = null @isChain = false diff --git a/test/classes.coffee b/test/classes.coffee index 66d448f0da..8f6428dce6 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -27,7 +27,9 @@ test "classes with a four-level inheritance chain", -> @array = [1, 2, 3] class ThirdChild extends SecondChild - constructor: -> thirdCtor.call this + constructor: -> + super() + thirdCtor.call this # Gratuitous comment for testing. func: (string) -> @@ -922,6 +924,7 @@ test "can extend a basic ES class", -> BasicClass = getBasicClass() class ExtendedClass extends BasicClass constructor: (@name) -> + super() i = new ExtendedClass 'dude' eq i.name, 'dude' @@ -931,6 +934,7 @@ test "can extend an extended ES class", -> class ExtendedExtendedClass extends ExtendedClass constructor: (@value) -> + super() getDoubledValue: -> @value * 2 @@ -961,6 +965,7 @@ test "extended CoffeeScript class can be extended in ES", -> class CoffeeClassWithDrinkOrder extends CoffeeClass constructor: (@favoriteDrink, @size = 'grande') -> + super() getDrinkOrder: -> "#{@size} #{@favoriteDrink}" diff --git a/test/repl.coffee b/test/repl.coffee index d9926ef495..4bf2c60d0d 100644 --- a/test/repl.coffee +++ b/test/repl.coffee @@ -8,6 +8,7 @@ Stream = require 'stream' class MockInputStream extends Stream constructor: -> + super() @readable = true resume: -> @@ -17,6 +18,7 @@ class MockInputStream extends Stream class MockOutputStream extends Stream constructor: -> + super() @writable = true @written = [] From 41070d5e66e73ff00d71cbdb9992c9289552b72f Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Mon, 19 Dec 2016 16:47:42 +0000 Subject: [PATCH 11/32] Improve `super` handling in constructors Rather than functions expanding their `super` calls, the `SuperCall` node can now be given a list of `thisAssignments` to apply when it is compiled. This allows us to use the normal compiler machinery to determine whether the `super` result needs to be cached, whether it appears inline or not, etc. --- lib/coffee-script/nodes.js | 61 ++++++++++++++++++++++++-------------- src/nodes.coffee | 45 +++++++++++++++++----------- 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index a2d4b012cd..ea37273c89 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1264,6 +1264,28 @@ this.isBare = args != null; } + isStatement(o) { + var ref3; + return ((ref3 = this.thisAssignments) != null ? ref3.length : void 0) && o.level === LEVEL_TOP; + } + + compileNode(o) { + var call, ref, ref3, ref4, replacement, superCall; + if (!((ref3 = this.thisAssignments) != null ? ref3.length : void 0)) { + return SuperCall.__super__.compileNode.call(this, ...arguments); + } + superCall = new Literal(fragmentsToText(SuperCall.__super__.compileNode.call(this, ...arguments))); + replacement = new Block(this.thisAssignments.slice()); + if (o.level === LEVEL_TOP) { + replacement.unshift(superCall); + } else { + ref4 = superCall.cache(o, null, YES), call = ref4[0], ref = ref4[1]; + replacement.unshift(call); + replacement.push(ref); + } + return replacement.compileToFragments(o); + } + superReference(o) { var accesses, base, bref, klass, method, name, nref, variable; method = o.scope.namedMethod(); @@ -1302,6 +1324,8 @@ SuperCall.__super__ = superClass.prototype; + SuperCall.prototype.children = ['thisAssignments']; + return SuperCall; })(Call); @@ -2758,7 +2782,7 @@ } compileNode(o) { - var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, thisAssignments, val, wasEmpty; + var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, superCall, thisAssignments, val, wasEmpty; if (this.ctor) { if (this.isAsync) { this.variable.error('Class constructor may not be async'); @@ -2868,8 +2892,8 @@ ])), new Value(new IdentifierLiteral(splatParamName)))); } wasEmpty = this.body.isEmpty(); - if (this.ctor) { - this.expandCtorSuperCall(o, thisAssignments); + if (this.ctor && (superCall = this.superCall())) { + superCall.thisAssignments = thisAssignments; } else if (thisAssignments.length) { this.body.expressions.unshift(...thisAssignments); } @@ -2972,27 +2996,18 @@ } } - expandCtorSuperCall(o, thisAssignments) { - var isSuper, replacement; - isSuper = function(child) { - return child instanceof SuperCall; - }; - replacement = function(child, parent) { - var assign, ref, ref3; - replacement = new Block(thisAssignments); - if (parent instanceof Block) { - replacement.unshift(child); - } else { - ref3 = child.cache(o), assign = ref3[0], ref = ref3[1]; - replacement.unshift(assign); - replacement.push(ref); + superCall() { + var superCall; + superCall = null; + this.traverseChildren(true, function(child) { + if (child instanceof SuperCall) { + if (superCall == null) { + superCall = child; + } } - return replacement; - }; - if (!this.body.replaceInContext(isSuper, replacement)) { - this.body.expressions.unshift(...thisAssignments); - } - return null; + return !superCall && (!(child instanceof Code) || child.bound); + }); + return superCall; } }; diff --git a/src/nodes.coffee b/src/nodes.coffee index 1d50f3f73d..71651e3849 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -813,11 +813,29 @@ exports.Call = class Call extends Base # Takes care of converting `super()` calls into calls against the prototype's # function of the same name. exports.SuperCall = class SuperCall extends Call + children: ['thisAssignments'] + constructor: (args) -> super null, args ? [new Splat new IdentifierLiteral 'arguments'] # Allow to recognize a bare `super` call without parentheses and arguments. @isBare = args? + isStatement: (o) -> + @thisAssignments?.length and o.level is LEVEL_TOP + + compileNode: (o) -> + return super unless @thisAssignments?.length + + superCall = new Literal fragmentsToText super + replacement = new Block @thisAssignments.slice() + if o.level is LEVEL_TOP + replacement.unshift superCall + else + [call, ref] = superCall.cache o, null, YES + replacement.unshift call + replacement.push ref + replacement.compileToFragments o + # Grab the reference to the superclass's implementation of the current # method. superReference: (o) -> @@ -1966,8 +1984,8 @@ exports.Code = class Code extends Base # Add new expressions to the function body wasEmpty = @body.isEmpty() - if @ctor - @expandCtorSuperCall o, thisAssignments + if @ctor and superCall = @superCall() + superCall.thisAssignments = thisAssignments else if thisAssignments.length @body.expressions.unshift thisAssignments... @body.expressions.unshift exprs... if exprs.length @@ -2022,22 +2040,15 @@ exports.Code = class Code extends Base else false - # Replace the first `super` call with a `Block` containing `thisAssignments` - # If there is no `super` call, `thisAssignments` are prepended to the body. - expandCtorSuperCall: (o, thisAssignments) -> - isSuper = (child) -> child instanceof SuperCall - replacement = (child, parent) -> - replacement = new Block thisAssignments - if parent instanceof Block - replacement.unshift child - else - [assign, ref] = child.cache o - replacement.unshift assign - replacement.push ref - replacement + # Find a super call in this function + superCall: -> + superCall = null + @traverseChildren true, (child) -> + superCall ?= child if child instanceof SuperCall - @body.expressions.unshift thisAssignments... unless @body.replaceInContext isSuper, replacement - null + # `super` has the same target in bound (arrow) functions, so check them too + not superCall and (child not instanceof Code or child.bound) + superCall #### Param From 19e7a96f1791820ed3b961ab62308e9daee522aa Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Mon, 19 Dec 2016 17:04:37 +0000 Subject: [PATCH 12/32] Fix anonymous classes at the top level Anonymous classes in ES are only valid within expressions. If an anonymous class is at the top level it will now be wrapped in parenthses to force it into an expression. --- lib/coffee-script/nodes.js | 3 +++ src/nodes.coffee | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index ea37273c89..0bb396bd6b 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1754,6 +1754,9 @@ this.compileNode = this.constructor.prototype.compileNode; } else { result = this.compileClassDeclaration(o); + if ((this.name == null) && o.level === LEVEL_TOP) { + result = this.wrapInBraces(result); + } } if (this.variable) { assign = new Assign(this.variable, new Literal(''), null, { diff --git a/src/nodes.coffee b/src/nodes.coffee index 71651e3849..fbcb3d500a 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1174,6 +1174,9 @@ exports.Class = class Class extends Base else result = @compileClassDeclaration o + # Anonymous classes are only valid in expressions + result = @wrapInBraces result if not @name? and o.level is LEVEL_TOP + if @variable assign = new Assign @variable, new Literal(''), null, { @moduleDeclaration } [ assign.compileToFragments(o)..., result... ] From 13431569c4311daa8f4ab42a28fc6b84674221de Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Mon, 19 Dec 2016 18:02:47 +0000 Subject: [PATCH 13/32] Re-add Parens wrapper around executable class bodies This was lost in the refactoring, but it necessary to ensure `new class ...` works as expected when there's an executable body. --- lib/coffee-script/nodes.js | 2 +- src/nodes.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 0bb396bd6b..d5866d15ae 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2012,7 +2012,7 @@ params = []; args = []; wrapper = new Code(params, this.body); - klass = new Call(wrapper, args); + klass = new Parens(new Call(wrapper, args)); this.body.spaced = true; o.classScope = wrapper.makeScope(o.scope); if (this.externalCtor) { diff --git a/src/nodes.coffee b/src/nodes.coffee index fbcb3d500a..d3a0ea5e8f 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1363,7 +1363,7 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base params = [] args = [] wrapper = new Code params, @body - klass = new Call wrapper, args + klass = new Parens new Call wrapper, args @body.spaced = true From acb97c6dd350d12af094200c51af12eb21efced2 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Mon, 19 Dec 2016 21:17:05 +0000 Subject: [PATCH 14/32] Throw compiler errors for badly configured derived constructors Rather than letting them become runtime errors, the following checks are now performed when compiling a derived constructor: - The constructor **must** include a call to `super`. - The constructor **must not** reference `this` in the function body before `super` has been called. --- lib/coffee-script/nodes.js | 27 +++++++++++++++++---------- src/nodes.coffee | 20 +++++++++++--------- test/classes.coffee | 2 +- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index d5866d15ae..884f450d40 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1935,8 +1935,10 @@ method.name = variable.properties[0]; } else { methodName = variable.base; - method.ctor = methodName.value === 'constructor'; method.name = new (methodName.isComplex() ? Index : Access)(methodName); + if (methodName.value === 'constructor') { + method.ctor = (this.parent ? 'derived' : 'base'); + } if (method.bound && method.ctor) { method.error('Cannot define a constructor as a bound function'); } @@ -1946,10 +1948,7 @@ makeDefaultConstructor() { var applyArgs, applyCtor, ctor; - ctor = new Code; - ctor.ctor = true; - ctor.name = new Access(new PropertyName('constructor')); - ctor.isMethod = true; + ctor = this.addInitializerMethod(new Assign(new Value(new PropertyName('constructor')), new Code)); this.body.unshift(ctor); if (this.parent) { ctor.body.push(new SuperCall); @@ -2785,7 +2784,7 @@ } compileNode(o) { - var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, superCall, thisAssignments, val, wasEmpty; + var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, thisAssignments, val, wasEmpty; if (this.ctor) { if (this.isAsync) { this.variable.error('Class constructor may not be async'); @@ -2895,8 +2894,8 @@ ])), new Value(new IdentifierLiteral(splatParamName)))); } wasEmpty = this.body.isEmpty(); - if (this.ctor && (superCall = this.superCall())) { - superCall.thisAssignments = thisAssignments; + if (this.ctor === 'derived') { + this.derivedCtorSuperCall().thisAssignments = thisAssignments; } else if (thisAssignments.length) { this.body.expressions.unshift(...thisAssignments); } @@ -2999,17 +2998,25 @@ } } - superCall() { + derivedCtorSuperCall() { var superCall; superCall = null; - this.traverseChildren(true, function(child) { + this.traverseChildren(true, (child) => { if (child instanceof SuperCall) { if (superCall == null) { superCall = child; } } + if (child instanceof ThisLiteral) { + if (superCall == null) { + child.error("Can't reference 'this' before calling super"); + } + } return !superCall && (!(child instanceof Code) || child.bound); }); + if (superCall == null) { + this.error('Derived class constructors must include a call to super'); + } return superCall; } diff --git a/src/nodes.coffee b/src/nodes.coffee index d3a0ea5e8f..b83131f023 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1311,17 +1311,14 @@ exports.Class = class Class extends Base method.name = variable.properties[0] else methodName = variable.base - method.ctor = methodName.value is 'constructor' method.name = new (if methodName.isComplex() then Index else Access) methodName + method.ctor = (if @parent then 'derived' else 'base') if methodName.value is 'constructor' method.error 'Cannot define a constructor as a bound function' if method.bound and method.ctor method makeDefaultConstructor: -> - ctor = new Code - ctor.ctor = true - ctor.name = new Access new PropertyName 'constructor' - ctor.isMethod = yes + ctor = @addInitializerMethod new Assign (new Value new PropertyName 'constructor'), new Code @body.unshift ctor ctor.body.push new SuperCall if @parent @@ -1987,8 +1984,8 @@ exports.Code = class Code extends Base # Add new expressions to the function body wasEmpty = @body.isEmpty() - if @ctor and superCall = @superCall() - superCall.thisAssignments = thisAssignments + if @ctor is 'derived' + @derivedCtorSuperCall().thisAssignments = thisAssignments else if thisAssignments.length @body.expressions.unshift thisAssignments... @body.expressions.unshift exprs... if exprs.length @@ -2044,13 +2041,18 @@ exports.Code = class Code extends Base false # Find a super call in this function - superCall: -> + derivedCtorSuperCall: -> superCall = null - @traverseChildren true, (child) -> + @traverseChildren true, (child) => superCall ?= child if child instanceof SuperCall + if child instanceof ThisLiteral + child.error "Can't reference 'this' before calling super" if not superCall? + # `super` has the same target in bound (arrow) functions, so check them too not superCall and (child not instanceof Code or child.bound) + + @error 'Derived class constructors must include a call to super' if not superCall? superCall #### Param diff --git a/test/classes.coffee b/test/classes.coffee index 8f6428dce6..0e5e3b2c92 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -483,7 +483,7 @@ test "#1313: misplaced __extends", -> class A class B extends A prop: nonce - constructor: -> + constructor: -> super eq nonce, B::prop test "#1182: execution order needs to be considered as well", -> From 6d34521a4e0698f1e7f9867e2d661301ed0808dd Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Mon, 19 Dec 2016 21:56:03 +0000 Subject: [PATCH 15/32] Add some tests exercising new class behaviour - async methods in classes - `this` access after `super` in extended classes - constructor super in arrow functions - constructor functions can't be async - constructor functions can't be generators - derived constructors must call super - derived constructors can't reference `this` before calling super - generator methods in classes - 'new' target --- test/async.coffee | 17 +++++++++++++++++ test/classes.coffee | 25 +++++++++++++++++++++++++ test/error_messages.coffee | 28 ++++++++++++++++++++++++++++ test/generators.coffee | 17 +++++++++++++++++ test/operators.coffee | 24 ++++++++++++++++++++++++ 5 files changed, 111 insertions(+) diff --git a/test/async.coffee b/test/async.coffee index 7f39d2aa4c..ebc028674e 100644 --- a/test/async.coffee +++ b/test/async.coffee @@ -191,3 +191,20 @@ test "implicit call with `await`", -> a = addOne await 3 eq a, 4 + +test "async methods in classes", -> + class Base + @static: -> + await 1 + method: -> + await 2 + + eq await Base.static(), 1 + eq await new Base().method(), 2 + + class Child extends Base + @static: -> super + method: -> super + + eq await Child.static(), 1 + eq await new Child().method(), 2 diff --git a/test/classes.coffee b/test/classes.coffee index 0e5e3b2c92..1fd732d2aa 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -980,3 +980,28 @@ test "extended CoffeeScript class can be extended in ES", -> e = new ECMAScriptClass 'coffee' eq e.getDrinkOrder(), 'grande coffee with a dash of semicolons' + +test "`this` access after `super` in extended classes", -> + class Base + + class Test extends Base + constructor: (param, @param) -> + eq param, nonce + + result = { super: super(), @param, @method } + eq result.super, this + eq result.param, @param + eq result.method, @method + + method: => + + nonce = {} + new Test nonce, {} + +test "constructor super in arrow functions", -> + class Test extends (class) + constructor: (@param) -> + do => super + eq @param, nonce + + new Test nonce = {} diff --git a/test/error_messages.coffee b/test/error_messages.coffee index 8034e4efeb..e1693e1bff 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1205,3 +1205,31 @@ test "tagged template literals must be called by an identifier", -> 1"""#{b}""" ^ ''' + +test "constructor functions can't be async", -> + assertErrorFormat 'class then constructor: -> await x', ''' + [stdin]:1:12: error: Class constructor may not be async + class then constructor: -> await x + ^^^^^^^^^^^ + ''' + +test "constructor functions can't be generators", -> + assertErrorFormat 'class then constructor: -> yield', ''' + [stdin]:1:12: error: Class constructor may not be a generator + class then constructor: -> yield + ^^^^^^^^^^^ + ''' + +test "derived constructors must call super", -> + assertErrorFormat 'class extends A then constructor: ->', ''' + [stdin]:1:35: error: Derived class constructors must include a call to super + class extends A then constructor: -> + ^^ + ''' + +test "derived constructors can't reference `this` before calling super", -> + assertErrorFormat 'class extends A then constructor: -> @', ''' + [stdin]:1:38: error: Can't reference 'this' before calling super + class extends A then constructor: -> @ + ^ + ''' diff --git a/test/generators.coffee b/test/generators.coffee index c00d685003..3d02c4db8f 100644 --- a/test/generators.coffee +++ b/test/generators.coffee @@ -325,3 +325,20 @@ test "from as a destructured array variable name in a for loop declaration", -> for [from, to] from a b.push from arrayEq b, [1, 3] + +test "generator methods in classes", -> + class Base + @static: -> + yield 1 + method: -> + yield 2 + + arrayEq [1], Array.from Base.static() + arrayEq [2], Array.from new Base().method() + + class Child extends Base + @static: -> super + method: -> super + + arrayEq [1], Array.from Child.static() + arrayEq [2], Array.from new Child().method() diff --git a/test/operators.coffee b/test/operators.coffee index 9c683aaefb..0b94e30ee9 100644 --- a/test/operators.coffee +++ b/test/operators.coffee @@ -436,3 +436,27 @@ test "#3598: Unary + and - coerce the operand once when it is an identifier", -> ok ~a in [0, -2] assertOneCoercion (a) -> ok a / 2 in [0, 0.5] + +test "'new' target", -> + nonce = {} + ctor = -> nonce + + eq (new ctor), nonce + eq (new ctor()), nonce + + ok new class + + ctor = class + ok (new ctor) instanceof ctor + ok (new ctor()) instanceof ctor + + # Force an executable class body + ctor = class then a = 1 + ok (new ctor) instanceof ctor + + get = -> ctor + ok (new get()) not instanceof ctor + ok (new (get())()) instanceof ctor + + # classes must be called with `new`. In this case `new` applies to `get` only + throws -> new get()() From a5f897592d0002485d03a31e23994fb970ec61e7 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Tue, 20 Dec 2016 08:42:00 +0000 Subject: [PATCH 16/32] Improve constructor `super` errors Add a check for `super` in non-extended class constructors, and explicitly mention derived constructors in the "can't reference this before super" error. --- lib/coffee-script/nodes.js | 18 +++++++++--------- src/nodes.coffee | 16 ++++++++++------ test/error_messages.coffee | 9 ++++++++- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 884f450d40..dca853dd40 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2784,7 +2784,7 @@ } compileNode(o) { - var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, thisAssignments, val, wasEmpty; + var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, superCall, thisAssignments, val, wasEmpty; if (this.ctor) { if (this.isAsync) { this.variable.error('Class constructor may not be async'); @@ -2894,8 +2894,8 @@ ])), new Value(new IdentifierLiteral(splatParamName)))); } wasEmpty = this.body.isEmpty(); - if (this.ctor === 'derived') { - this.derivedCtorSuperCall().thisAssignments = thisAssignments; + if (this.ctor && (superCall = this.ctorSuperCall())) { + superCall.thisAssignments = thisAssignments; } else if (thisAssignments.length) { this.body.expressions.unshift(...thisAssignments); } @@ -2998,7 +2998,7 @@ } } - derivedCtorSuperCall() { + ctorSuperCall() { var superCall; superCall = null; this.traverseChildren(true, (child) => { @@ -3007,15 +3007,15 @@ superCall = child; } } - if (child instanceof ThisLiteral) { - if (superCall == null) { - child.error("Can't reference 'this' before calling super"); - } + if (this.ctor === 'derived' && child instanceof ThisLiteral && (superCall == null)) { + child.error("Can't reference 'this' before calling super in derived class constructors"); } return !superCall && (!(child instanceof Code) || child.bound); }); - if (superCall == null) { + if (this.ctor === 'derived' && (superCall == null)) { this.error('Derived class constructors must include a call to super'); + } else if (this.ctor === 'base' && (superCall != null)) { + superCall.error("'super' is only allowed in derived class constructors"); } return superCall; } diff --git a/src/nodes.coffee b/src/nodes.coffee index b83131f023..77d856f5e4 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1984,8 +1984,8 @@ exports.Code = class Code extends Base # Add new expressions to the function body wasEmpty = @body.isEmpty() - if @ctor is 'derived' - @derivedCtorSuperCall().thisAssignments = thisAssignments + if @ctor and superCall = @ctorSuperCall() + superCall.thisAssignments = thisAssignments else if thisAssignments.length @body.expressions.unshift thisAssignments... @body.expressions.unshift exprs... if exprs.length @@ -2041,18 +2041,22 @@ exports.Code = class Code extends Base false # Find a super call in this function - derivedCtorSuperCall: -> + ctorSuperCall: -> superCall = null @traverseChildren true, (child) => superCall ?= child if child instanceof SuperCall - if child instanceof ThisLiteral - child.error "Can't reference 'this' before calling super" if not superCall? + if @ctor is 'derived' and child instanceof ThisLiteral and not superCall? + child.error "Can't reference 'this' before calling super in derived class constructors" # `super` has the same target in bound (arrow) functions, so check them too not superCall and (child not instanceof Code or child.bound) - @error 'Derived class constructors must include a call to super' if not superCall? + if @ctor is 'derived' and not superCall? + @error 'Derived class constructors must include a call to super' + else if @ctor is 'base' and superCall? + superCall.error "'super' is only allowed in derived class constructors" + superCall #### Param diff --git a/test/error_messages.coffee b/test/error_messages.coffee index e1693e1bff..36f9938df7 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1220,6 +1220,13 @@ test "constructor functions can't be generators", -> ^^^^^^^^^^^ ''' +test "non-derived constructors can't call super", -> + assertErrorFormat 'class then constructor: -> super', ''' + [stdin]:1:28: error: 'super' is only allowed in derived class constructors + class then constructor: -> super + ^^^^^ + ''' + test "derived constructors must call super", -> assertErrorFormat 'class extends A then constructor: ->', ''' [stdin]:1:35: error: Derived class constructors must include a call to super @@ -1229,7 +1236,7 @@ test "derived constructors must call super", -> test "derived constructors can't reference `this` before calling super", -> assertErrorFormat 'class extends A then constructor: -> @', ''' - [stdin]:1:38: error: Can't reference 'this' before calling super + [stdin]:1:38: error: Can't reference 'this' before calling super in derived class constructors class extends A then constructor: -> @ ^ ''' From 02b9f12bbab263a01687394312410618cdade87f Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Tue, 20 Dec 2016 12:58:10 +0000 Subject: [PATCH 17/32] Fix compilation of multiple `super` paths in derived constructors `super` can only be called once, but it can be called conditionally from multiple locations. The chosen fix is to add the `this` assignments to every super call. --- lib/coffee-script/nodes.js | 68 +++++++++++++++++++------------------- src/nodes.coffee | 54 ++++++++++++++++-------------- test/classes.coffee | 49 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 58 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index dca853dd40..3916a452f2 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1266,24 +1266,22 @@ isStatement(o) { var ref3; - return ((ref3 = this.thisAssignments) != null ? ref3.length : void 0) && o.level === LEVEL_TOP; + return ((ref3 = this.expressions) != null ? ref3.length : void 0) && o.level === LEVEL_TOP; } compileNode(o) { - var call, ref, ref3, ref4, replacement, superCall; - if (!((ref3 = this.thisAssignments) != null ? ref3.length : void 0)) { + var ref, ref3, ref4, replacement, superCall; + if (!((ref3 = this.expressions) != null ? ref3.length : void 0)) { return SuperCall.__super__.compileNode.call(this, ...arguments); } superCall = new Literal(fragmentsToText(SuperCall.__super__.compileNode.call(this, ...arguments))); - replacement = new Block(this.thisAssignments.slice()); - if (o.level === LEVEL_TOP) { - replacement.unshift(superCall); - } else { - ref4 = superCall.cache(o, null, YES), call = ref4[0], ref = ref4[1]; - replacement.unshift(call); + replacement = new Block(this.expressions.slice()); + if (o.level > LEVEL_TOP) { + ref4 = superCall.cache(o, null, YES), superCall = ref4[0], ref = ref4[1]; replacement.push(ref); } - return replacement.compileToFragments(o); + replacement.unshift(superCall); + return replacement.compileToFragments(o, o.level === LEVEL_TOP ? o.level : LEVEL_LIST); } superReference(o) { @@ -1324,7 +1322,7 @@ SuperCall.__super__ = superClass.prototype; - SuperCall.prototype.children = ['thisAssignments']; + SuperCall.prototype.children = ['expressions']; return SuperCall; @@ -2784,7 +2782,7 @@ } compileNode(o) { - var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, superCall, thisAssignments, val, wasEmpty; + var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, l, len1, len2, len3, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, superCall, superCalls, thisAssignments, val, wasEmpty; if (this.ctor) { if (this.isAsync) { this.variable.error('Class constructor may not be async'); @@ -2893,15 +2891,19 @@ })() ])), new Value(new IdentifierLiteral(splatParamName)))); } + if (this.ctor) { + superCalls = this.superCalls(); + } wasEmpty = this.body.isEmpty(); - if (this.ctor && (superCall = this.ctorSuperCall())) { - superCall.thisAssignments = thisAssignments; - } else if (thisAssignments.length) { + if (this.ctor === 'derived') { + for (k = 0, len2 = superCalls.length; k < len2; k++) { + superCall = superCalls[k]; + superCall.expressions = thisAssignments; + } + } else { this.body.expressions.unshift(...thisAssignments); } - if (exprs.length) { - this.body.expressions.unshift(...exprs); - } + this.body.expressions.unshift(...exprs); if (!(wasEmpty || this.noReturn)) { this.body.makeReturn(); } @@ -2919,7 +2921,7 @@ modifiers.push('*'); } signature = [this.makeCode('(')]; - for (i = k = 0, len2 = params.length; k < len2; i = ++k) { + for (i = l = 0, len3 = params.length; l < len3; i = ++l) { param = params[i]; if (i) { signature.push(this.makeCode(', ')); @@ -2940,10 +2942,10 @@ } } answer = this.joinFragmentArrays((function() { - var l, len3, results; + var len4, p, results; results = []; - for (l = 0, len3 = modifiers.length; l < len3; l++) { - m = modifiers[l]; + for (p = 0, len4 = modifiers.length; p < len4; p++) { + m = modifiers[p]; results.push(this.makeCode(m)); } return results; @@ -2998,26 +3000,24 @@ } } - ctorSuperCall() { - var superCall; - superCall = null; + superCalls() { + var superCalls; + superCalls = []; this.traverseChildren(true, (child) => { if (child instanceof SuperCall) { - if (superCall == null) { - superCall = child; - } + superCalls.push(child); } - if (this.ctor === 'derived' && child instanceof ThisLiteral && (superCall == null)) { + if (this.ctor === 'derived' && child instanceof ThisLiteral && !superCalls.length) { child.error("Can't reference 'this' before calling super in derived class constructors"); } - return !superCall && (!(child instanceof Code) || child.bound); + return !(child instanceof SuperCall) && (!(child instanceof Code) || child.bound); }); - if (this.ctor === 'derived' && (superCall == null)) { + if (this.ctor === 'derived' && !superCalls.length) { this.error('Derived class constructors must include a call to super'); - } else if (this.ctor === 'base' && (superCall != null)) { - superCall.error("'super' is only allowed in derived class constructors"); + } else if (this.ctor === 'base' && superCalls.length) { + superCalls[0].error("'super' is only allowed in derived class constructors"); } - return superCall; + return superCalls; } }; diff --git a/src/nodes.coffee b/src/nodes.coffee index 77d856f5e4..d75a8b9dea 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -812,8 +812,11 @@ exports.Call = class Call extends Base # Takes care of converting `super()` calls into calls against the prototype's # function of the same name. +# When `expressions` are set the call will be compiled in such a way that the +# expressions are evaluated without altering the return value of the `SuperCall` +# expression. exports.SuperCall = class SuperCall extends Call - children: ['thisAssignments'] + children: ['expressions'] constructor: (args) -> super null, args ? [new Splat new IdentifierLiteral 'arguments'] @@ -821,20 +824,21 @@ exports.SuperCall = class SuperCall extends Call @isBare = args? isStatement: (o) -> - @thisAssignments?.length and o.level is LEVEL_TOP + @expressions?.length and o.level is LEVEL_TOP compileNode: (o) -> - return super unless @thisAssignments?.length + return super unless @expressions?.length superCall = new Literal fragmentsToText super - replacement = new Block @thisAssignments.slice() - if o.level is LEVEL_TOP - replacement.unshift superCall - else - [call, ref] = superCall.cache o, null, YES - replacement.unshift call + replacement = new Block @expressions.slice() + + if o.level > LEVEL_TOP + # If we might be in an expression we need to cache and return the result + [superCall, ref] = superCall.cache o, null, YES replacement.push ref - replacement.compileToFragments o + + replacement.unshift superCall + replacement.compileToFragments o, if o.level is LEVEL_TOP then o.level else LEVEL_LIST # Grab the reference to the superclass's implementation of the current # method. @@ -1981,14 +1985,16 @@ exports.Code = class Code extends Base new Arr [new Splat(new IdentifierLiteral(splatParamName)), (param.asReference o for param in paramsAfterSplat)...] ), new Value new IdentifierLiteral splatParamName + # Find and validate constructor super calls + superCalls = @superCalls() if @ctor # Add new expressions to the function body wasEmpty = @body.isEmpty() - if @ctor and superCall = @ctorSuperCall() - superCall.thisAssignments = thisAssignments - else if thisAssignments.length + if @ctor is 'derived' + superCall.expressions = thisAssignments for superCall in superCalls + else @body.expressions.unshift thisAssignments... - @body.expressions.unshift exprs... if exprs.length + @body.expressions.unshift exprs... @body.makeReturn() unless wasEmpty or @noReturn # Assemble the output @@ -2040,24 +2046,24 @@ exports.Code = class Code extends Base else false - # Find a super call in this function - ctorSuperCall: -> - superCall = null + # Find all super calls in this function + superCalls: -> + superCalls = [] @traverseChildren true, (child) => - superCall ?= child if child instanceof SuperCall + superCalls.push child if child instanceof SuperCall - if @ctor is 'derived' and child instanceof ThisLiteral and not superCall? + if @ctor is 'derived' and child instanceof ThisLiteral and not superCalls.length child.error "Can't reference 'this' before calling super in derived class constructors" # `super` has the same target in bound (arrow) functions, so check them too - not superCall and (child not instanceof Code or child.bound) + child not instanceof SuperCall and (child not instanceof Code or child.bound) - if @ctor is 'derived' and not superCall? + if @ctor is 'derived' and not superCalls.length @error 'Derived class constructors must include a call to super' - else if @ctor is 'base' and superCall? - superCall.error "'super' is only allowed in derived class constructors" + else if @ctor is 'base' and superCalls.length + superCalls[0].error "'super' is only allowed in derived class constructors" - superCall + superCalls #### Param diff --git a/test/classes.coffee b/test/classes.coffee index 1fd732d2aa..bf16f9a3a6 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -992,12 +992,61 @@ test "`this` access after `super` in extended classes", -> eq result.super, this eq result.param, @param eq result.method, @method + ok result.method isnt Test::method method: => nonce = {} new Test nonce, {} +test "`@`-params and bound methods with multiple `super` paths (blocks)", -> + nonce = {} + + class Base + constructor: (@name) -> + + class Test extends Base + constructor: (param, @param) -> + if param + super 'param' + eq @name, 'param' + else + super 'not param' + eq @name, 'not param' + eq @param, nonce + ok @method isnt Test::method + method: => + new Test true, nonce + new Test false, nonce + + +test "`@`-params and bound methods with multiple `super` paths (expressions)", -> + nonce = {} + + class Base + constructor: (@name) -> + + class Test extends Base + constructor: (param, @param) -> + # Contrived example: force each path into an expression with inline assertions + if param + result = ( + eq (super 'param'), @; + eq @name, 'param'; + eq @param, nonce; + ok @method isnt Test::method + ) + else + result = ( + eq (super 'not param'), @; + eq @name, 'not param'; + eq @param, nonce; + ok @method isnt Test::method + ) + method: => + new Test true, nonce + new Test false, nonce + test "constructor super in arrow functions", -> class Test extends (class) constructor: (@param) -> From 7fa295ba13a73bad81ee2552717bebeb62d672de Mon Sep 17 00:00:00 2001 From: mrmowgli Date: Wed, 21 Dec 2016 03:20:47 -0800 Subject: [PATCH 18/32] Additional class tests, added as a separate file to simplify testing and merging. Some methods are commented out because they currently throw and I'm not sure how to test for compilation errors like those. There is also one test which I deliberately left without passing, `super` in an external prototype override. This test should 'pass' but is really a variation on the failing `super only allowed in an instance method` tests above it. --- test/classes-additional.coffee | 227 +++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 test/classes-additional.coffee diff --git a/test/classes-additional.coffee b/test/classes-additional.coffee new file mode 100644 index 0000000000..63654a168b --- /dev/null +++ b/test/classes-additional.coffee @@ -0,0 +1,227 @@ +# Classes +# ------- + +# * Class Instantiation +# * Inheritance and Super +# * Default parameters +# * Version Compatability +test "multiple super calls", -> + # class A + # constructor: (@drink) -> + # make: -> "Making a #{@drink}" + + # class MultiSuper extends A + # constructor: (drink) -> + # super(drink) + # super(drink) + # @newDrink = drink + # eq (new MultiSuper('Late')).make(), 'Making a Late' + +test "@ params", -> + class A + constructor: (@drink, @shots, @flavor) -> + make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" + + a = new A('Machiato', 2, 'chocolate') + eq a.make(), "Making a chocolate Machiato with 2 shot(s)" + + class B extends A + b = new B('Machiato', 2, 'chocolate') + eq b.make(), "Making a chocolate Machiato with 2 shot(s)" + +test "@ params with defaults", -> + class A + # Multiple @ params with defaults + constructor: (@drink = 'Americano', @shots = '1', @flavor = 'caramel') -> + make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" + + a = new A() + eq a.make(), "Making a caramel Americano with 1 shot(s)" + +test "@ params with class params", -> + class Beverage + drink: 'Americano' + shots: '1' + flavor: 'caramel' + + class A + # Class creation as a default param with `this` + constructor: (@drink = new Beverage()) -> + a = new A() + eq a.drink.drink, 'Americano' + + beverage = new Beverage + class B + # class costruction with a default external param + constructor: (@drink = beverage) -> + + b = new B() + eq b.drink.drink, 'Americano' + + class C + # Default constructor with anonymous empty class + constructor: (@meta = class) -> + c = new C() + ok c.meta instanceof Function + +test "@ params without super, including errors", -> + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" + a = new A('Machiato') + eq a.make(), "Making a Machiato" + + throwsB = """ + class B extends A + #implied super + constructor: (@drink) -> + if @drink is 'Machiato' + @cost = 'expensive' + make: -> "Making an #{@cost} #{@drink}" + b = new B('Machiato', 'Cafe Ole', 'Americano') + eq b.make(), "Making an expensive Machiato" + """ + throws -> CoffeeScript.run classA + throwsB, bare: yes + + throwsC = """ + class C extends A + # super with splats + constructor: (@params...) -> + super(@params[0]) + console.log @params + + c = new C('Machiato', 'Cafe Ole', 'Americano') + # eq c.make(), "Making a Machiato" + """ + throws -> CoffeeScript.run classA + throwsC, bare: yes + + +test "super and external constructors", -> + ctorA = (@drink) -> + class A + constructor: ctorA + make: -> "Making a #{@drink}" + + # # External constructor with super + # ctorB = (drink, flavor) -> + # super(drink) + # @flavor = flavor + + # class B extends A + # constructor: ctorB + # b = new B('Machiato') + # eq b.make(), "Making a Machiato" + +test "super and external overrides", -> + # class A + # constructor: (@drink) -> + # make: -> "Making a #{@drink}" + + # # External method and super + # makeB = (@flavor) -> super.make() + " with #{@flavor}" + + # class B extends A + # make: makeB + # b = new B('Machiato') + # eq b.make('caramel'), "Making a Machiato with caramel" + + # # External bound method and super + # makeC = (@flavor) => super.make() + " with #{@flavor}" + + # class C extends A + # make: makeC + # e = new C('Machiato') + # eq e.make('caramel'), "Making a Machiato with caramel" + + # class D extends A + # d = new D('Machiato') + # d.make = (@flavor) -> super.make() + " with #{@flavor}" + # eq d.make('caramel'), "Making a Machiato with caramel" + + # # class G extends A + # # g = new G('Machiato') + # # g.make = (@flavor) => super.make() + " with #{@flavor}" + # # eq g.make('caramel'), "Making a Machiato with caramel" + # # Bound function with @ + +test "super in external prototype", -> + ctorA = (drink) -> + @drink = drink + + class A + constructor: (drink) -> + make: -> "Making a #{@drink}" + + # Fails + class B extends A + B::make = (@flavor) -> super.make() + " with #{@flavor}" + b = new B('Machiato') + eq b.make('caramel'), "Making a Machiato with caramel" + + +test "bound functions without super", -> + # Bound function with @ + # This should throw on compile, since bound + # constructors are illegal + ctorA = (@drink) => + + class A + constructor: ctorA + make: => + "Making a #{@drink}" + ok (new A('Machiato')).make() isnt "Making a Machiato" + + + # extended bound function, extending fails too. + class B extends A + b = new B('Machiato') + ok (new B('Machiato')).make() isnt "Making a Machiato" + +test "super in a bound function", -> + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" + + class B extends A + make: (@flavor) => + super + " with #{@flavor}" + + b = new B('Machiato') + eq b.make('vanilla'), "Making a Machiato with vanilla" + + # super in a bound function in a bound function + class C extends A + make: (@flavor) => + func = () => + super + " with #{@flavor}" + func() + + c = new C('Machiato') + eq c.make('vanilla'), "Making a Machiato with vanilla" + +# duplicate +test "super in a try/catch", -> + classA = """ + class A + constructor: (param) -> + throw "" unless param + """ + + throwsB = """ + class B extends A + constructor: -> + try + super + """ + + throwsC = """ + ctor = -> + try + super + + class C extends A + constructor: ctor + """ + throws -> CoffeeScript.run classA + throwsB, bare: yes + throws -> CoffeeScript.run classA + throwsC, bare: yes + From 80ce0e89b1f0ab7d3d9ddee94fc3a984e5f1c395 Mon Sep 17 00:00:00 2001 From: mrmowgli Date: Wed, 21 Dec 2016 22:48:36 -0800 Subject: [PATCH 19/32] Changes to the tests. Found bug in super in prototype method. fixed. --- test/classes-additional.coffee | 80 ++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/test/classes-additional.coffee b/test/classes-additional.coffee index 63654a168b..5d11843de9 100644 --- a/test/classes-additional.coffee +++ b/test/classes-additional.coffee @@ -145,18 +145,15 @@ test "super and external overrides", -> # # Bound function with @ test "super in external prototype", -> - ctorA = (drink) -> - @drink = drink + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" - class A - constructor: (drink) -> - make: -> "Making a #{@drink}" - - # Fails - class B extends A - B::make = (@flavor) -> super.make() + " with #{@flavor}" - b = new B('Machiato') - eq b.make('caramel'), "Making a Machiato with caramel" + # Fails + class B extends A + B::make = (@flavor) -> super() + " with #{@flavor}" + b = new B('Machiato') + eq b.make('caramel'), "Making a Machiato with caramel" test "bound functions without super", -> @@ -175,7 +172,7 @@ test "bound functions without super", -> # extended bound function, extending fails too. class B extends A b = new B('Machiato') - ok (new B('Machiato')).make() isnt "Making a Machiato" + ok (new B('Machiato')).make() isnt "Making a Machiato" test "super in a bound function", -> class A @@ -190,6 +187,7 @@ test "super in a bound function", -> eq b.make('vanilla'), "Making a Machiato with vanilla" # super in a bound function in a bound function + # FAILS class C extends A make: (@flavor) => func = () => @@ -197,7 +195,17 @@ test "super in a bound function", -> func() c = new C('Machiato') - eq c.make('vanilla'), "Making a Machiato with vanilla" + eq c.make('vanilla'), "Making a Machiato with vanilla" + + # bound function in a constructor + class D extends A + constructor: (drink) -> + super(drink) + x = => + eq @drink, "Machiato" + x() + d = new D('Machiato') + eq d.make(), "Making a Machiato" # duplicate test "super in a try/catch", -> @@ -225,3 +233,49 @@ test "super in a try/catch", -> throws -> CoffeeScript.run classA + throwsB, bare: yes throws -> CoffeeScript.run classA + throwsC, bare: yes +test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> + # Extended test + # ES2015+ class interoperability + + # ``` + # class Base { + # constructor (greeting) { + # this.greeting = greeting || 'hii'; + + # } + # func (string) { + # return 'zero/' + string; + # } + # static staticFunc (string) { + # return 'static/' + string; + # } + # } + # ``` + # class FirstChild extends Base + # func: (string) -> + # super('one/') + string + + # ``` + # class SecondChild extends FirstChild { + # func (string) { + # super.func('two/' + string); + # } + # } + # ``` + + # thirdCtor = -> + # @array = [1, 2, 3] + + # class ThirdChild extends SecondChild + # constructor: -> + # super() + # thirdCtor.call this + + # # Gratuitous comment for testing. + # func: (string) -> + # super('three/') + string + + # result = (new ThirdChild).func 'four' + + # ok result is 'zero/one/two/three/four' + # ok Base.static('word') is 'static/word' From 31c6e788b545079be2d45ebba8caf5eeb61c26ef Mon Sep 17 00:00:00 2001 From: mrmowgli Date: Wed, 21 Dec 2016 22:55:15 -0800 Subject: [PATCH 20/32] Added failing test back in, dealing with bound functions in external prototype overrides. --- test/classes-additional.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/classes-additional.coffee b/test/classes-additional.coffee index 5d11843de9..afd639d930 100644 --- a/test/classes-additional.coffee +++ b/test/classes-additional.coffee @@ -149,12 +149,17 @@ test "super in external prototype", -> constructor: (@drink) -> make: -> "Making a #{@drink}" - # Fails class B extends A B::make = (@flavor) -> super() + " with #{@flavor}" b = new B('Machiato') eq b.make('caramel'), "Making a Machiato with caramel" + # Fails, not bound + class C extends A + C::make = (@flavor) => super() + " with #{@flavor}" + c = new C('Machiato') + ok c.make('caramel') isnt "Making a Machiato with caramel" + test "bound functions without super", -> # Bound function with @ From 16cf6ec88091bd138f99409a9e5dc3d8da1a5305 Mon Sep 17 00:00:00 2001 From: mrmowgli Date: Thu, 22 Dec 2016 00:18:47 -0800 Subject: [PATCH 21/32] Located a bug in the compiler relating to assertions and escaped ES6 classes. --- test/classes-additional.coffee | 105 +++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 45 deletions(-) diff --git a/test/classes-additional.coffee b/test/classes-additional.coffee index afd639d930..48cad37950 100644 --- a/test/classes-additional.coffee +++ b/test/classes-additional.coffee @@ -158,7 +158,23 @@ test "super in external prototype", -> class C extends A C::make = (@flavor) => super() + " with #{@flavor}" c = new C('Machiato') - ok c.make('caramel') isnt "Making a Machiato with caramel" + ok c.make('caramel') isnt "Making a Machiato with caramel" + + classA = """ + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" + """ + + # This throws since during compile super isn't in a known state. + # This incorrect syntax is technically valid, but is actually ES6 syntax. + # I suspect this will be a common error + throwsD = """ + class D extends A + D::make = (@flavor) -> super.make() + " with #{@flavor}" + d = new D('Machiato') + """ + throws -> CoffeeScript.run classA + throwsD, bare: yes test "bound functions without super", -> @@ -171,8 +187,7 @@ test "bound functions without super", -> constructor: ctorA make: => "Making a #{@drink}" - ok (new A('Machiato')).make() isnt "Making a Machiato" - + ok (new A('Machiato')).make() isnt "Making a Machiato" # extended bound function, extending fails too. class B extends A @@ -192,7 +207,6 @@ test "super in a bound function", -> eq b.make('vanilla'), "Making a Machiato with vanilla" # super in a bound function in a bound function - # FAILS class C extends A make: (@flavor) => func = () => @@ -242,45 +256,46 @@ test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> # Extended test # ES2015+ class interoperability - # ``` - # class Base { - # constructor (greeting) { - # this.greeting = greeting || 'hii'; - - # } - # func (string) { - # return 'zero/' + string; - # } - # static staticFunc (string) { - # return 'static/' + string; - # } - # } - # ``` - # class FirstChild extends Base - # func: (string) -> - # super('one/') + string - - # ``` - # class SecondChild extends FirstChild { - # func (string) { - # super.func('two/' + string); - # } - # } - # ``` - - # thirdCtor = -> - # @array = [1, 2, 3] - - # class ThirdChild extends SecondChild - # constructor: -> - # super() - # thirdCtor.call this - - # # Gratuitous comment for testing. - # func: (string) -> - # super('three/') + string - - # result = (new ThirdChild).func 'four' - + ``` + class Base { + constructor (greeting) { + this.greeting = greeting || 'hi'; + } + func (string) { + return 'zero/' + string; + } + static staticFunc (string) { + return 'static/' + string; + } + } + ``` + + class FirstChild extends Base + func: (string) -> + super('one/') + string + + + ``` + class SecondChild extends FirstChild { + func (string) { + super.func('two/' + string); + } + } + ``` + + thirdCtor = -> + @array = [1, 2, 3] + + class ThirdChild extends SecondChild + constructor: -> + super() + thirdCtor.call this + func: (string) -> + super('three/') + string + + result = (new ThirdChild).func 'four' + console.log "BUG: mixed ES6 and CS6 classes with a four-level inheritance chain in classes-extended.coffee" + + # Uncomment this line: # ok result is 'zero/one/two/three/four' - # ok Base.static('word') is 'static/word' + ok Base.staticFunc('word') is 'static/word' From 7a1821362b7c68d047fd0f06e1745379240d0604 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 26 Dec 2016 21:30:19 -0500 Subject: [PATCH 22/32] Move tests from classes-additional.coffee into classes.coffee; comment out console.log --- test/classes-additional.coffee | 301 --------------------------------- test/classes.coffee | 295 ++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+), 301 deletions(-) delete mode 100644 test/classes-additional.coffee diff --git a/test/classes-additional.coffee b/test/classes-additional.coffee deleted file mode 100644 index 48cad37950..0000000000 --- a/test/classes-additional.coffee +++ /dev/null @@ -1,301 +0,0 @@ -# Classes -# ------- - -# * Class Instantiation -# * Inheritance and Super -# * Default parameters -# * Version Compatability -test "multiple super calls", -> - # class A - # constructor: (@drink) -> - # make: -> "Making a #{@drink}" - - # class MultiSuper extends A - # constructor: (drink) -> - # super(drink) - # super(drink) - # @newDrink = drink - # eq (new MultiSuper('Late')).make(), 'Making a Late' - -test "@ params", -> - class A - constructor: (@drink, @shots, @flavor) -> - make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" - - a = new A('Machiato', 2, 'chocolate') - eq a.make(), "Making a chocolate Machiato with 2 shot(s)" - - class B extends A - b = new B('Machiato', 2, 'chocolate') - eq b.make(), "Making a chocolate Machiato with 2 shot(s)" - -test "@ params with defaults", -> - class A - # Multiple @ params with defaults - constructor: (@drink = 'Americano', @shots = '1', @flavor = 'caramel') -> - make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" - - a = new A() - eq a.make(), "Making a caramel Americano with 1 shot(s)" - -test "@ params with class params", -> - class Beverage - drink: 'Americano' - shots: '1' - flavor: 'caramel' - - class A - # Class creation as a default param with `this` - constructor: (@drink = new Beverage()) -> - a = new A() - eq a.drink.drink, 'Americano' - - beverage = new Beverage - class B - # class costruction with a default external param - constructor: (@drink = beverage) -> - - b = new B() - eq b.drink.drink, 'Americano' - - class C - # Default constructor with anonymous empty class - constructor: (@meta = class) -> - c = new C() - ok c.meta instanceof Function - -test "@ params without super, including errors", -> - class A - constructor: (@drink) -> - make: -> "Making a #{@drink}" - a = new A('Machiato') - eq a.make(), "Making a Machiato" - - throwsB = """ - class B extends A - #implied super - constructor: (@drink) -> - if @drink is 'Machiato' - @cost = 'expensive' - make: -> "Making an #{@cost} #{@drink}" - b = new B('Machiato', 'Cafe Ole', 'Americano') - eq b.make(), "Making an expensive Machiato" - """ - throws -> CoffeeScript.run classA + throwsB, bare: yes - - throwsC = """ - class C extends A - # super with splats - constructor: (@params...) -> - super(@params[0]) - console.log @params - - c = new C('Machiato', 'Cafe Ole', 'Americano') - # eq c.make(), "Making a Machiato" - """ - throws -> CoffeeScript.run classA + throwsC, bare: yes - - -test "super and external constructors", -> - ctorA = (@drink) -> - class A - constructor: ctorA - make: -> "Making a #{@drink}" - - # # External constructor with super - # ctorB = (drink, flavor) -> - # super(drink) - # @flavor = flavor - - # class B extends A - # constructor: ctorB - # b = new B('Machiato') - # eq b.make(), "Making a Machiato" - -test "super and external overrides", -> - # class A - # constructor: (@drink) -> - # make: -> "Making a #{@drink}" - - # # External method and super - # makeB = (@flavor) -> super.make() + " with #{@flavor}" - - # class B extends A - # make: makeB - # b = new B('Machiato') - # eq b.make('caramel'), "Making a Machiato with caramel" - - # # External bound method and super - # makeC = (@flavor) => super.make() + " with #{@flavor}" - - # class C extends A - # make: makeC - # e = new C('Machiato') - # eq e.make('caramel'), "Making a Machiato with caramel" - - # class D extends A - # d = new D('Machiato') - # d.make = (@flavor) -> super.make() + " with #{@flavor}" - # eq d.make('caramel'), "Making a Machiato with caramel" - - # # class G extends A - # # g = new G('Machiato') - # # g.make = (@flavor) => super.make() + " with #{@flavor}" - # # eq g.make('caramel'), "Making a Machiato with caramel" - # # Bound function with @ - -test "super in external prototype", -> - class A - constructor: (@drink) -> - make: -> "Making a #{@drink}" - - class B extends A - B::make = (@flavor) -> super() + " with #{@flavor}" - b = new B('Machiato') - eq b.make('caramel'), "Making a Machiato with caramel" - - # Fails, not bound - class C extends A - C::make = (@flavor) => super() + " with #{@flavor}" - c = new C('Machiato') - ok c.make('caramel') isnt "Making a Machiato with caramel" - - classA = """ - class A - constructor: (@drink) -> - make: -> "Making a #{@drink}" - """ - - # This throws since during compile super isn't in a known state. - # This incorrect syntax is technically valid, but is actually ES6 syntax. - # I suspect this will be a common error - throwsD = """ - class D extends A - D::make = (@flavor) -> super.make() + " with #{@flavor}" - d = new D('Machiato') - """ - throws -> CoffeeScript.run classA + throwsD, bare: yes - - -test "bound functions without super", -> - # Bound function with @ - # This should throw on compile, since bound - # constructors are illegal - ctorA = (@drink) => - - class A - constructor: ctorA - make: => - "Making a #{@drink}" - ok (new A('Machiato')).make() isnt "Making a Machiato" - - # extended bound function, extending fails too. - class B extends A - b = new B('Machiato') - ok (new B('Machiato')).make() isnt "Making a Machiato" - -test "super in a bound function", -> - class A - constructor: (@drink) -> - make: -> "Making a #{@drink}" - - class B extends A - make: (@flavor) => - super + " with #{@flavor}" - - b = new B('Machiato') - eq b.make('vanilla'), "Making a Machiato with vanilla" - - # super in a bound function in a bound function - class C extends A - make: (@flavor) => - func = () => - super + " with #{@flavor}" - func() - - c = new C('Machiato') - eq c.make('vanilla'), "Making a Machiato with vanilla" - - # bound function in a constructor - class D extends A - constructor: (drink) -> - super(drink) - x = => - eq @drink, "Machiato" - x() - d = new D('Machiato') - eq d.make(), "Making a Machiato" - -# duplicate -test "super in a try/catch", -> - classA = """ - class A - constructor: (param) -> - throw "" unless param - """ - - throwsB = """ - class B extends A - constructor: -> - try - super - """ - - throwsC = """ - ctor = -> - try - super - - class C extends A - constructor: ctor - """ - throws -> CoffeeScript.run classA + throwsB, bare: yes - throws -> CoffeeScript.run classA + throwsC, bare: yes - -test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> - # Extended test - # ES2015+ class interoperability - - ``` - class Base { - constructor (greeting) { - this.greeting = greeting || 'hi'; - } - func (string) { - return 'zero/' + string; - } - static staticFunc (string) { - return 'static/' + string; - } - } - ``` - - class FirstChild extends Base - func: (string) -> - super('one/') + string - - - ``` - class SecondChild extends FirstChild { - func (string) { - super.func('two/' + string); - } - } - ``` - - thirdCtor = -> - @array = [1, 2, 3] - - class ThirdChild extends SecondChild - constructor: -> - super() - thirdCtor.call this - func: (string) -> - super('three/') + string - - result = (new ThirdChild).func 'four' - console.log "BUG: mixed ES6 and CS6 classes with a four-level inheritance chain in classes-extended.coffee" - - # Uncomment this line: - # ok result is 'zero/one/two/three/four' - ok Base.staticFunc('word') is 'static/word' diff --git a/test/classes.coffee b/test/classes.coffee index bf16f9a3a6..4501fddc1f 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -1054,3 +1054,298 @@ test "constructor super in arrow functions", -> eq @param, nonce new Test nonce = {} + +test "multiple super calls", -> + # class A + # constructor: (@drink) -> + # make: -> "Making a #{@drink}" + + # class MultiSuper extends A + # constructor: (drink) -> + # super(drink) + # super(drink) + # @newDrink = drink + # eq (new MultiSuper('Late')).make(), 'Making a Late' + +test "@ params", -> + class A + constructor: (@drink, @shots, @flavor) -> + make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" + + a = new A('Machiato', 2, 'chocolate') + eq a.make(), "Making a chocolate Machiato with 2 shot(s)" + + class B extends A + b = new B('Machiato', 2, 'chocolate') + eq b.make(), "Making a chocolate Machiato with 2 shot(s)" + +test "@ params with defaults", -> + class A + # Multiple @ params with defaults + constructor: (@drink = 'Americano', @shots = '1', @flavor = 'caramel') -> + make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" + + a = new A() + eq a.make(), "Making a caramel Americano with 1 shot(s)" + +test "@ params with class params", -> + class Beverage + drink: 'Americano' + shots: '1' + flavor: 'caramel' + + class A + # Class creation as a default param with `this` + constructor: (@drink = new Beverage()) -> + a = new A() + eq a.drink.drink, 'Americano' + + beverage = new Beverage + class B + # class costruction with a default external param + constructor: (@drink = beverage) -> + + b = new B() + eq b.drink.drink, 'Americano' + + class C + # Default constructor with anonymous empty class + constructor: (@meta = class) -> + c = new C() + ok c.meta instanceof Function + +test "@ params without super, including errors", -> + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" + a = new A('Machiato') + eq a.make(), "Making a Machiato" + + throwsB = """ + class B extends A + #implied super + constructor: (@drink) -> + if @drink is 'Machiato' + @cost = 'expensive' + make: -> "Making an #{@cost} #{@drink}" + b = new B('Machiato', 'Cafe Ole', 'Americano') + eq b.make(), "Making an expensive Machiato" + """ + throws -> CoffeeScript.run classA + throwsB, bare: yes + + throwsC = """ + class C extends A + # super with splats + constructor: (@params...) -> + super(@params[0]) + console.log @params + + c = new C('Machiato', 'Cafe Ole', 'Americano') + # eq c.make(), "Making a Machiato" + """ + throws -> CoffeeScript.run classA + throwsC, bare: yes + + +test "super and external constructors", -> + ctorA = (@drink) -> + class A + constructor: ctorA + make: -> "Making a #{@drink}" + + # # External constructor with super + # ctorB = (drink, flavor) -> + # super(drink) + # @flavor = flavor + + # class B extends A + # constructor: ctorB + # b = new B('Machiato') + # eq b.make(), "Making a Machiato" + +test "super and external overrides", -> + # class A + # constructor: (@drink) -> + # make: -> "Making a #{@drink}" + + # # External method and super + # makeB = (@flavor) -> super.make() + " with #{@flavor}" + + # class B extends A + # make: makeB + # b = new B('Machiato') + # eq b.make('caramel'), "Making a Machiato with caramel" + + # # External bound method and super + # makeC = (@flavor) => super.make() + " with #{@flavor}" + + # class C extends A + # make: makeC + # e = new C('Machiato') + # eq e.make('caramel'), "Making a Machiato with caramel" + + # class D extends A + # d = new D('Machiato') + # d.make = (@flavor) -> super.make() + " with #{@flavor}" + # eq d.make('caramel'), "Making a Machiato with caramel" + + # # class G extends A + # # g = new G('Machiato') + # # g.make = (@flavor) => super.make() + " with #{@flavor}" + # # eq g.make('caramel'), "Making a Machiato with caramel" + # # Bound function with @ + +test "super in external prototype", -> + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" + + class B extends A + B::make = (@flavor) -> super() + " with #{@flavor}" + b = new B('Machiato') + eq b.make('caramel'), "Making a Machiato with caramel" + + # Fails, not bound + class C extends A + C::make = (@flavor) => super() + " with #{@flavor}" + c = new C('Machiato') + ok c.make('caramel') isnt "Making a Machiato with caramel" + + classA = """ + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" + """ + + # This throws since during compile super isn't in a known state. + # This incorrect syntax is technically valid, but is actually ES6 syntax. + # I suspect this will be a common error + throwsD = """ + class D extends A + D::make = (@flavor) -> super.make() + " with #{@flavor}" + d = new D('Machiato') + """ + throws -> CoffeeScript.run classA + throwsD, bare: yes + + +test "bound functions without super", -> + # Bound function with @ + # This should throw on compile, since bound + # constructors are illegal + ctorA = (@drink) => + + class A + constructor: ctorA + make: => + "Making a #{@drink}" + ok (new A('Machiato')).make() isnt "Making a Machiato" + + # extended bound function, extending fails too. + class B extends A + b = new B('Machiato') + ok (new B('Machiato')).make() isnt "Making a Machiato" + +test "super in a bound function", -> + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" + + class B extends A + make: (@flavor) => + super + " with #{@flavor}" + + b = new B('Machiato') + eq b.make('vanilla'), "Making a Machiato with vanilla" + + # super in a bound function in a bound function + class C extends A + make: (@flavor) => + func = () => + super + " with #{@flavor}" + func() + + c = new C('Machiato') + eq c.make('vanilla'), "Making a Machiato with vanilla" + + # bound function in a constructor + class D extends A + constructor: (drink) -> + super(drink) + x = => + eq @drink, "Machiato" + x() + d = new D('Machiato') + eq d.make(), "Making a Machiato" + +# duplicate +test "super in a try/catch", -> + classA = """ + class A + constructor: (param) -> + throw "" unless param + """ + + throwsB = """ + class B extends A + constructor: -> + try + super + """ + + throwsC = """ + ctor = -> + try + super + + class C extends A + constructor: ctor + """ + throws -> CoffeeScript.run classA + throwsB, bare: yes + throws -> CoffeeScript.run classA + throwsC, bare: yes + +test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> + # Extended test + # ES2015+ class interoperability + + ``` + class Base { + constructor (greeting) { + this.greeting = greeting || 'hi'; + } + func (string) { + return 'zero/' + string; + } + static staticFunc (string) { + return 'static/' + string; + } + } + ``` + + class FirstChild extends Base + func: (string) -> + super('one/') + string + + + ``` + class SecondChild extends FirstChild { + func (string) { + super.func('two/' + string); + } + } + ``` + + thirdCtor = -> + @array = [1, 2, 3] + + class ThirdChild extends SecondChild + constructor: -> + super() + thirdCtor.call this + func: (string) -> + super('three/') + string + + result = (new ThirdChild).func 'four' + # console.log "BUG: mixed ES6 and CS6 classes with a four-level inheritance chain in classes-extended.coffee" + + # Uncomment this line: + # ok result is 'zero/one/two/three/four' + ok Base.staticFunc('word') is 'static/word' From 87ba4db29341c8d8bc9d0a4adcf0598e5e6d3b8d Mon Sep 17 00:00:00 2001 From: mrmowgli Date: Wed, 28 Dec 2016 17:37:31 -0800 Subject: [PATCH 23/32] Cleaned up tests and made changes based on feedback. Test at the end still has issues, but it's commented out for now. --- test/classes.coffee | 263 +++++++++++++++++++++++++------------------- 1 file changed, 148 insertions(+), 115 deletions(-) diff --git a/test/classes.coffee b/test/classes.coffee index 4501fddc1f..e0a12a0805 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -1055,20 +1055,28 @@ test "constructor super in arrow functions", -> new Test nonce = {} +# Ensure that we always throw if we experience more than one super() +# call in a constructor. This ends up being a runtime error. +# Should be caught at compile time. test "multiple super calls", -> - # class A - # constructor: (@drink) -> - # make: -> "Making a #{@drink}" + throwsA = """ + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" - # class MultiSuper extends A - # constructor: (drink) -> - # super(drink) - # super(drink) - # @newDrink = drink - # eq (new MultiSuper('Late')).make(), 'Making a Late' + class MultiSuper extends A + constructor: (drink) -> + super(drink) + super(drink) + @newDrink = drink + new MultiSuper('Late').make() + """ + throws -> CoffeeScript.run throwsA, bare: yes +# Basic test to ensure we can pass @params in a constuctor and +# inheritance works correctly test "@ params", -> - class A + class A constructor: (@drink, @shots, @flavor) -> make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" @@ -1079,8 +1087,9 @@ test "@ params", -> b = new B('Machiato', 2, 'chocolate') eq b.make(), "Making a chocolate Machiato with 2 shot(s)" -test "@ params with defaults", -> - class A +# Ensure we can accept @params with default parameters in a constructor +test "@ params with defaults in a constructor", -> + class A # Multiple @ params with defaults constructor: (@drink = 'Americano', @shots = '1', @flavor = 'caramel') -> make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" @@ -1088,173 +1097,172 @@ test "@ params with defaults", -> a = new A() eq a.make(), "Making a caramel Americano with 1 shot(s)" +# Ensure we can handle default constructors with class params test "@ params with class params", -> class Beverage drink: 'Americano' shots: '1' flavor: 'caramel' - class A + class A # Class creation as a default param with `this` constructor: (@drink = new Beverage()) -> a = new A() eq a.drink.drink, 'Americano' beverage = new Beverage - class B + class B # class costruction with a default external param constructor: (@drink = beverage) -> b = new B() eq b.drink.drink, 'Americano' - class C + class C # Default constructor with anonymous empty class constructor: (@meta = class) -> c = new C() ok c.meta instanceof Function test "@ params without super, including errors", -> - class A + classA = """ + class A constructor: (@drink) -> make: -> "Making a #{@drink}" a = new A('Machiato') - eq a.make(), "Making a Machiato" + """ throwsB = """ class B extends A #implied super constructor: (@drink) -> - if @drink is 'Machiato' - @cost = 'expensive' - make: -> "Making an #{@cost} #{@drink}" - b = new B('Machiato', 'Cafe Ole', 'Americano') - eq b.make(), "Making an expensive Machiato" + b = new B('Machiato') """ - throws -> CoffeeScript.run classA + throwsB, bare: yes + throws -> CoffeeScript.compile classA + throwsB, bare: yes +test "@ params super race condition", -> + classA = """ + class A + constructor: (@drink) -> + make: -> "Making a #{@drink}" + """ + + throwsB = """ + class B extends A + constructor: (@params) -> + + b = new B('Machiato') + """ + throws -> CoffeeScript.compile classA + throwsB, bare: yes + + # Race condition with @ and super throwsC = """ class C extends A - # super with splats - constructor: (@params...) -> - super(@params[0]) - console.log @params + constructor: (@params) -> + super(@params) - c = new C('Machiato', 'Cafe Ole', 'Americano') - # eq c.make(), "Making a Machiato" + c = new C('Machiato') """ - throws -> CoffeeScript.run classA + throwsC, bare: yes - + throws -> CoffeeScript.compile classA + throwsC, bare: yes -test "super and external constructors", -> - ctorA = (@drink) -> - class A - constructor: ctorA + +test "@ with super call", -> + class D make: -> "Making a #{@drink}" - # # External constructor with super - # ctorB = (drink, flavor) -> - # super(drink) - # @flavor = flavor + class E extends D + constructor: (@drink) -> + super() + + e = new E('Machiato') + eq e.make(), "Making a Machiato" - # class B extends A - # constructor: ctorB - # b = new B('Machiato') - # eq b.make(), "Making a Machiato" +test "@ with splats and super call", -> + class A + make: -> "Making a #{@drink}" -test "super and external overrides", -> - # class A - # constructor: (@drink) -> - # make: -> "Making a #{@drink}" + class B extends A + constructor: (@drink...) -> + super() + + B = new B('Machiato') + eq B.make(), "Making a Machiato" - # # External method and super - # makeB = (@flavor) -> super.make() + " with #{@flavor}" - # class B extends A - # make: makeB - # b = new B('Machiato') - # eq b.make('caramel'), "Making a Machiato with caramel" +test "super and external constructors", -> + # external constructor with @ param is allowed + ctorA = (@drink) -> + class A + constructor: ctorA + make: -> "Making a #{@drink}" + a = new A('Machiato') + eq a.make(), "Making a Machiato" - # # External bound method and super - # makeC = (@flavor) => super.make() + " with #{@flavor}" + # External constructor with super + throwsC = """ + class B + constructor: (@drink) -> + make: -> "Making a #{@drink}" - # class C extends A - # make: makeC - # e = new C('Machiato') - # eq e.make('caramel'), "Making a Machiato with caramel" + ctorC = (drink) -> + super(drink) - # class D extends A - # d = new D('Machiato') - # d.make = (@flavor) -> super.make() + " with #{@flavor}" - # eq d.make('caramel'), "Making a Machiato with caramel" + class C extends B + constructor: ctorC + c = new C('Machiato') + """ + throws -> CoffeeScript.compile throwsC, bare: yes - # # class G extends A - # # g = new G('Machiato') - # # g.make = (@flavor) => super.make() + " with #{@flavor}" - # # eq g.make('caramel'), "Making a Machiato with caramel" - # # Bound function with @ test "super in external prototype", -> - class A + class A constructor: (@drink) -> make: -> "Making a #{@drink}" - class B extends A + class B extends A B::make = (@flavor) -> super() + " with #{@flavor}" b = new B('Machiato') eq b.make('caramel'), "Making a Machiato with caramel" - # Fails, not bound - class C extends A + # Fails, bound + # TODO: Could this throw a compile error? + class C extends A C::make = (@flavor) => super() + " with #{@flavor}" c = new C('Machiato') ok c.make('caramel') isnt "Making a Machiato with caramel" - classA = """ - class A - constructor: (@drink) -> - make: -> "Making a #{@drink}" - """ - - # This throws since during compile super isn't in a known state. - # This incorrect syntax is technically valid, but is actually ES6 syntax. - # I suspect this will be a common error - throwsD = """ - class D extends A - D::make = (@flavor) -> super.make() + " with #{@flavor}" - d = new D('Machiato') - """ - throws -> CoffeeScript.run classA + throwsD, bare: yes - test "bound functions without super", -> # Bound function with @ - # This should throw on compile, since bound + # Throw on compile, since bound # constructors are illegal - ctorA = (@drink) => + throwsA = """ + class A + constructor: (drink) => + @drink = drink - class A - constructor: ctorA - make: => - "Making a #{@drink}" - ok (new A('Machiato')).make() isnt "Making a Machiato" + """ + throws -> CoffeeScript.compile throwsA, bare: yes - # extended bound function, extending fails too. +test "super in a bound function in a constructor", -> + throwsB = """ + class A class B extends A - b = new B('Machiato') - ok (new B('Machiato')).make() isnt "Making a Machiato" + constructor: do => super + """ + throws -> CoffeeScript.compile throwsB, bare: yes test "super in a bound function", -> - class A + class A constructor: (@drink) -> make: -> "Making a #{@drink}" - + class B extends A make: (@flavor) => super + " with #{@flavor}" b = new B('Machiato') - eq b.make('vanilla'), "Making a Machiato with vanilla" + eq b.make('vanilla'), "Making a Machiato with vanilla" # super in a bound function in a bound function class C extends A @@ -1264,22 +1272,22 @@ test "super in a bound function", -> func() c = new C('Machiato') - eq c.make('vanilla'), "Making a Machiato with vanilla" + eq c.make('vanilla'), "Making a Machiato with vanilla" # bound function in a constructor class D extends A constructor: (drink) -> super(drink) x = => - eq @drink, "Machiato" + eq @drink, "Machiato" x() d = new D('Machiato') - eq d.make(), "Making a Machiato" + eq d.make(), "Making a Machiato" -# duplicate +# duplicate test "super in a try/catch", -> classA = """ - class A + class A constructor: (param) -> throw "" unless param """ @@ -1295,15 +1303,15 @@ test "super in a try/catch", -> ctor = -> try super - + class C extends A constructor: ctor """ throws -> CoffeeScript.run classA + throwsB, bare: yes throws -> CoffeeScript.run classA + throwsC, bare: yes - + test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> - # Extended test + # Extended test # ES2015+ class interoperability ``` @@ -1328,7 +1336,7 @@ test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> ``` class SecondChild extends FirstChild { func (string) { - super.func('two/' + string); + return super.func('two/' + string); } } ``` @@ -1337,15 +1345,40 @@ test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> @array = [1, 2, 3] class ThirdChild extends SecondChild - constructor: -> + constructor: -> super() thirdCtor.call this func: (string) -> super('three/') + string result = (new ThirdChild).func 'four' - # console.log "BUG: mixed ES6 and CS6 classes with a four-level inheritance chain in classes-extended.coffee" - - # Uncomment this line: - # ok result is 'zero/one/two/three/four' + ok result is 'zero/one/two/three/four' ok Base.staticFunc('word') is 'static/word' + +# TODO: This test has compile errors, and needs resolving. +# # exercise extends in a nested class +# test "nested classes with super", -> +# class Outer +# constructor: -> +# @label = 'outer' + +# class @Inner +# constructor: -> +# @label = 'inner' + +# class @ExtendedInner extends @Inner +# constructor: -> +# console.log super() +# @label = super().label + ' extended' + +# extender: () -> +# class @ExtendedSelf extends @ +# constructor: -> +# super() +# @label = super + ' from this' +# new ExtendedSelf + +# # eq (new Outer).label, 'outer' +# # eq (new Outer.Inner).label, 'inner' +# # eq (new Outer.ExtendedInner).label, 'inner extended' +# # eq (new Outer.extender()).label, 'inner from this' \ No newline at end of file From 0bf07700d31dbb788e8ce1e5529bb876c99ddb42 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Thu, 29 Dec 2016 11:19:46 +0000 Subject: [PATCH 24/32] Make HoistTarget.expand recursive It's possible that a hoisted node may itself contain hoisted nodes (e.g. a class method inside a class method). For this to work the hoisted fragments need to be expanded recursively. --- lib/coffee-script/nodes.js | 2 +- src/nodes.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 3916a452f2..e4789248c4 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -338,7 +338,7 @@ for (i = j = fragments.length - 1; j >= 0; i = j += -1) { fragment = fragments[i]; if (fragment.fragments) { - [].splice.apply(fragments, [i, i - i + 1].concat(ref3 = fragment.fragments)), ref3; + [].splice.apply(fragments, [i, i - i + 1].concat(ref3 = this.expand(fragment.fragments))), ref3; } } return fragments; diff --git a/src/nodes.coffee b/src/nodes.coffee index d75a8b9dea..08b0b4edc4 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -272,7 +272,7 @@ exports.HoistTarget = class HoistTarget extends Base # Expands hoisted fragments in the given array @expand = (fragments) -> for fragment, i in fragments by -1 when fragment.fragments - fragments[i..i] = fragment.fragments + fragments[i..i] = @expand fragment.fragments fragments constructor: (@source) -> From aa5656b974d9310639cd9f9fe271a50fc89c1eb9 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Thu, 29 Dec 2016 11:25:34 +0000 Subject: [PATCH 25/32] Uncomment final test in classes.coffee The test case now compiles, however another issue is affecting the test due to the error for `this` before `super` triggering based on source order rather than execution order. These have been commented out for now. --- test/classes.coffee | 123 ++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/test/classes.coffee b/test/classes.coffee index e0a12a0805..caab8ec678 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -1060,7 +1060,7 @@ test "constructor super in arrow functions", -> # Should be caught at compile time. test "multiple super calls", -> throwsA = """ - class A + class A constructor: (@drink) -> make: -> "Making a #{@drink}" @@ -1076,7 +1076,7 @@ test "multiple super calls", -> # Basic test to ensure we can pass @params in a constuctor and # inheritance works correctly test "@ params", -> - class A + class A constructor: (@drink, @shots, @flavor) -> make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" @@ -1089,7 +1089,7 @@ test "@ params", -> # Ensure we can accept @params with default parameters in a constructor test "@ params with defaults in a constructor", -> - class A + class A # Multiple @ params with defaults constructor: (@drink = 'Americano', @shots = '1', @flavor = 'caramel') -> make: -> "Making a #{@flavor} #{@drink} with #{@shots} shot(s)" @@ -1104,21 +1104,21 @@ test "@ params with class params", -> shots: '1' flavor: 'caramel' - class A + class A # Class creation as a default param with `this` constructor: (@drink = new Beverage()) -> a = new A() eq a.drink.drink, 'Americano' beverage = new Beverage - class B + class B # class costruction with a default external param constructor: (@drink = beverage) -> b = new B() eq b.drink.drink, 'Americano' - class C + class C # Default constructor with anonymous empty class constructor: (@meta = class) -> c = new C() @@ -1126,7 +1126,7 @@ test "@ params with class params", -> test "@ params without super, including errors", -> classA = """ - class A + class A constructor: (@drink) -> make: -> "Making a #{@drink}" a = new A('Machiato') @@ -1142,7 +1142,7 @@ test "@ params without super, including errors", -> test "@ params super race condition", -> classA = """ - class A + class A constructor: (@drink) -> make: -> "Making a #{@drink}" """ @@ -1165,34 +1165,34 @@ test "@ params super race condition", -> """ throws -> CoffeeScript.compile classA + throwsC, bare: yes - -test "@ with super call", -> + +test "@ with super call", -> class D make: -> "Making a #{@drink}" class E extends D constructor: (@drink) -> super() - + e = new E('Machiato') - eq e.make(), "Making a Machiato" + eq e.make(), "Making a Machiato" -test "@ with splats and super call", -> +test "@ with splats and super call", -> class A make: -> "Making a #{@drink}" class B extends A constructor: (@drink...) -> super() - + B = new B('Machiato') - eq B.make(), "Making a Machiato" + eq B.make(), "Making a Machiato" test "super and external constructors", -> # external constructor with @ param is allowed ctorA = (@drink) -> - class A + class A constructor: ctorA make: -> "Making a #{@drink}" a = new A('Machiato') @@ -1215,18 +1215,18 @@ test "super and external constructors", -> test "super in external prototype", -> - class A + class A constructor: (@drink) -> make: -> "Making a #{@drink}" - class B extends A + class B extends A B::make = (@flavor) -> super() + " with #{@flavor}" b = new B('Machiato') eq b.make('caramel'), "Making a Machiato with caramel" # Fails, bound # TODO: Could this throw a compile error? - class C extends A + class C extends A C::make = (@flavor) => super() + " with #{@flavor}" c = new C('Machiato') ok c.make('caramel') isnt "Making a Machiato with caramel" @@ -1234,11 +1234,11 @@ test "super in external prototype", -> test "bound functions without super", -> # Bound function with @ - # Throw on compile, since bound + # Throw on compile, since bound # constructors are illegal throwsA = """ - class A - constructor: (drink) => + class A + constructor: (drink) => @drink = drink """ @@ -1253,16 +1253,16 @@ test "super in a bound function in a constructor", -> throws -> CoffeeScript.compile throwsB, bare: yes test "super in a bound function", -> - class A + class A constructor: (@drink) -> make: -> "Making a #{@drink}" - + class B extends A make: (@flavor) => super + " with #{@flavor}" b = new B('Machiato') - eq b.make('vanilla'), "Making a Machiato with vanilla" + eq b.make('vanilla'), "Making a Machiato with vanilla" # super in a bound function in a bound function class C extends A @@ -1272,22 +1272,22 @@ test "super in a bound function", -> func() c = new C('Machiato') - eq c.make('vanilla'), "Making a Machiato with vanilla" + eq c.make('vanilla'), "Making a Machiato with vanilla" # bound function in a constructor class D extends A constructor: (drink) -> super(drink) x = => - eq @drink, "Machiato" + eq @drink, "Machiato" x() d = new D('Machiato') - eq d.make(), "Making a Machiato" + eq d.make(), "Making a Machiato" -# duplicate +# duplicate test "super in a try/catch", -> classA = """ - class A + class A constructor: (param) -> throw "" unless param """ @@ -1303,15 +1303,15 @@ test "super in a try/catch", -> ctor = -> try super - + class C extends A constructor: ctor """ throws -> CoffeeScript.run classA + throwsB, bare: yes throws -> CoffeeScript.run classA + throwsC, bare: yes - + test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> - # Extended test + # Extended test # ES2015+ class interoperability ``` @@ -1345,7 +1345,7 @@ test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> @array = [1, 2, 3] class ThirdChild extends SecondChild - constructor: -> + constructor: -> super() thirdCtor.call this func: (string) -> @@ -1355,30 +1355,31 @@ test "mixed ES6 and CS6 classes with a four-level inheritance chain", -> ok result is 'zero/one/two/three/four' ok Base.staticFunc('word') is 'static/word' -# TODO: This test has compile errors, and needs resolving. -# # exercise extends in a nested class -# test "nested classes with super", -> -# class Outer -# constructor: -> -# @label = 'outer' - -# class @Inner -# constructor: -> -# @label = 'inner' - -# class @ExtendedInner extends @Inner -# constructor: -> -# console.log super() -# @label = super().label + ' extended' - -# extender: () -> -# class @ExtendedSelf extends @ -# constructor: -> -# super() -# @label = super + ' from this' -# new ExtendedSelf - -# # eq (new Outer).label, 'outer' -# # eq (new Outer.Inner).label, 'inner' -# # eq (new Outer.ExtendedInner).label, 'inner extended' -# # eq (new Outer.extender()).label, 'inner from this' \ No newline at end of file +# exercise extends in a nested class +test "nested classes with super", -> + class Outer + constructor: -> + @label = 'outer' + + class @Inner + constructor: -> + @label = 'inner' + + class @ExtendedInner extends @Inner + constructor: -> + super() + # TODO this should be valid since the `super` will happen before the assignment + # @label = super().label + ' extended' + + extender: () -> + class @ExtendedSelf extends @ + constructor: -> + super() + # TODO this should be valid since the `super` will happen before the assignment + # @label = super + ' from this' + new ExtendedSelf + + eq (new Outer).label, 'outer' + eq (new Outer.Inner).label, 'inner' + # eq (new Outer.ExtendedInner).label, 'inner extended' + # eq (new Outer.extender()).label, 'inner from this' From a7d3520ee321d3e7a162a0e8c74dde55a343a0ae Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Thu, 29 Dec 2016 11:44:28 +0000 Subject: [PATCH 26/32] Fixed last test TODOs in test/classes.coffee Turns out an expression like `this.foo = super()` won't run in JS as it attempts to lookup `this` before evaluating `super` (i.e. throws "this is not defined"). --- test/classes.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/classes.coffee b/test/classes.coffee index caab8ec678..539a2b4634 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -1368,14 +1368,14 @@ test "nested classes with super", -> class @ExtendedInner extends @Inner constructor: -> super() - # TODO this should be valid since the `super` will happen before the assignment + # TODO this is invalid since `this` is accessed before `super` # @label = super().label + ' extended' extender: () -> class @ExtendedSelf extends @ constructor: -> super() - # TODO this should be valid since the `super` will happen before the assignment + # TODO this is invalid since `this` is accessed before `super` # @label = super + ' from this' new ExtendedSelf From fc1fd82790439981131caa0646eb84d9e99c274a Mon Sep 17 00:00:00 2001 From: mrmowgli Date: Fri, 30 Dec 2016 07:45:28 -0800 Subject: [PATCH 27/32] Added more tests for compatability checks, statics, prototypes and ES6 expectations. Cleaned test "nested classes with super". --- test/classes.coffee | 331 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 321 insertions(+), 10 deletions(-) diff --git a/test/classes.coffee b/test/classes.coffee index 539a2b4634..c0b9199e6c 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -1367,19 +1367,330 @@ test "nested classes with super", -> class @ExtendedInner extends @Inner constructor: -> - super() - # TODO this is invalid since `this` is accessed before `super` - # @label = super().label + ' extended' + tmp = super() + @label = tmp.label + ' extended' - extender: () -> - class @ExtendedSelf extends @ + @extender: () => + class ExtendedSelf extends @ constructor: -> - super() - # TODO this is invalid since `this` is accessed before `super` - # @label = super + ' from this' + tmp = super() + @label = tmp.label + ' from this' new ExtendedSelf eq (new Outer).label, 'outer' eq (new Outer.Inner).label, 'inner' - # eq (new Outer.ExtendedInner).label, 'inner extended' - # eq (new Outer.extender()).label, 'inner from this' + eq (new Outer.ExtendedInner).label, 'inner extended' + eq (Outer.extender()).label, 'outer from this' + +# TODO: Currently this test fails. +test "Static methods generate 'static' keywords", -> + compile = """ + class CheckStatic + constructor: (@drink) -> + @className: 'CheckStatic' + + c = new CheckStatic('Machiato') + """ + result = CoffeeScript.compile compile, bare: yes + ok (result && result.match(' static ') && true) isnt true + +test "Static methods in nested classes", -> + class Outer + @name: -> 'Outer' + + class @Inner + @name: -> 'Inner' + + eq Outer.name(), 'Outer' + eq Outer.Inner.name(), 'Inner' + + +test "mixed constructors with inheritance and ES6 super", -> + identity = (f) -> f + + class TopClass + constructor: (arg) -> + @prop = 'top-' + arg + + ``` + class SuperClass extends TopClass { + constructor (arg) { + identity(super('super-' + arg)); + } + } + ``` + class SubClass extends SuperClass + constructor: -> + identity super 'sub' + + ok (new SubClass).prop is 'top-super-sub' + +test "ES6 static class methods can be overriden", -> + class A + @name: -> 'A' + + class B extends A + @name: -> 'B' + + eq A.name(), 'A' + eq B.name(), 'B' + +# If creating static by direct assignment rather than ES6 static keyword +test "ES6 Static methods should set `this` to undefined // ES6 ", -> + class A + @test: -> + eq this, undefined + +# Ensure that our object prototypes work with ES6 +test "ES6 prototypes can be overriden", -> + class A + className: 'classA' + + ``` + class B { + test () {return "B";}; + } + ``` + b = new B + a = new A + eq a.className, 'classA' + eq b.test(), 'B' + Object.setPrototypeOf(b, a) + eq b.className, 'classA' + # This shouldn't throw, + # as we only change inheritance not object construction + # This may be an issue with ES, rather than CS construction? + #eq b.test(), 'B' + + class D extends B + B::test = () -> 'D' + eq (new D).test(), 'D' + + +test "ES6 conformance to extending non-classes", -> + A = (@title) -> + 'Title: ' + @ + + class B extends A + b = new B('caffeinated') + eq b.title, 'caffeinated' + + # Check inheritance chain + A::getTitle = () -> @title + eq b.getTitle(), 'caffeinated' + + throwsC = """ + C = {title: 'invalid'} + class D extends {} + """ + # TODO: This should catch on compile and message should be "class can only extend classes and functions." + throws -> CoffeeScript.run throwsC, bare: yes + +# TODO: Either restrict valid statements in class bodies OR insert 'use strict'? +test "Class function environment should be in `strict mode`, ie as if 'use strict' was in use", -> + class A + # this might be a meaningless test, since these are likely to be runtime errors and different + # for every browser. Thoughts? + constructor: () -> + # Ivalid: prop reassignment + @state = {prop: [1], prop: {a: 'a'}} + # eval reassignment + @badEval = eval; + + # Should throw, but doesn't + a = new A + +# TODO: new.target needs support +test "ES6 support for new.target (functions and constructors)", -> + throwsA = """ + class A + constructor: () -> + a = new.target.name + """ + throws -> CoffeeScript.compile throwsA, bare: yes + +test "only one method named constructor allowed", -> + throwsA = """ + class A + constructor: (@first) -> + constructor: (@last) -> + """ + throws -> CoffeeScript.compile throwsA, bare: yes + +# TODO: We can't expect super to be enforced for all constructors. If it's mandetory, +# the following is invalid +test "If the constructor of a child class does not call super, the constructor is expected to return an object. ", -> + throwsA = """ + class A + class B extends A + constructor: () -> + return {} + """ + throws -> CoffeeScript.compile throwsA, bare: yes + + +test "super can only exist in extended classes", -> + throwsA = """ + class A + constructor: (@name) -> + super() + """ + throws -> CoffeeScript.compile throwsA, bare: yes + +# --- CS1 classes compatability breaks --- +test "CS6 Class extends a CS1 compiled class", -> + ``` + // Generated by CoffeeScript 1.11.1 + var BaseCS1, ExtendedCS1, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + BaseCS1 = (function() { + function BaseCS1(drink) { + this.drink = drink; + } + + BaseCS1.prototype.make = function() { + return "making a " + this.drink; + }; + + BaseCS1.className = function() { + return 'BaseCS1'; + }; + + return BaseCS1; + + })(); + + ExtendedCS1 = (function(superClass) { + extend(ExtendedCS1, superClass); + + function ExtendedCS1(flavor) { + this.flavor = flavor; + ExtendedCS1.__super__.constructor.call(this, 'cafe ole'); + } + + ExtendedCS1.prototype.make = function() { + return "making a " + this.drink + " with " + this.flavor; + }; + + ExtendedCS1.className = function() { + return 'ExtendedCS1'; + }; + + return ExtendedCS1; + + })(BaseCS1); + + ``` + class B extends BaseCS1 + eq B.className(), 'BaseCS1' + b = new B('machiato') + eq b.make(), "making a machiato" + + +test "CS6 Class extends an extended CS1 compiled class", -> + ``` + // Generated by CoffeeScript 1.11.1 + var BaseCS1, ExtendedCS1, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + BaseCS1 = (function() { + function BaseCS1(drink) { + this.drink = drink; + } + + BaseCS1.prototype.make = function() { + return "making a " + this.drink; + }; + + BaseCS1.className = function() { + return 'BaseCS1'; + }; + + return BaseCS1; + + })(); + + ExtendedCS1 = (function(superClass) { + extend(ExtendedCS1, superClass); + + function ExtendedCS1(flavor) { + this.flavor = flavor; + ExtendedCS1.__super__.constructor.call(this, 'cafe ole'); + } + + ExtendedCS1.prototype.make = function() { + return "making a " + this.drink + " with " + this.flavor; + }; + + ExtendedCS1.className = function() { + return 'ExtendedCS1'; + }; + + return ExtendedCS1; + + })(BaseCS1); + + ``` + class B extends ExtendedCS1 + eq B.className(), 'ExtendedCS1' + b = new B('vanilla') + eq b.make(), "making a cafe ole with vanilla" + +test "CS6 Class extends a CS1 compiled class with super()", -> + ``` + // Generated by CoffeeScript 1.11.1 + var BaseCS1, ExtendedCS1, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + BaseCS1 = (function() { + function BaseCS1(drink) { + this.drink = drink; + } + + BaseCS1.prototype.make = function() { + return "making a " + this.drink; + }; + + BaseCS1.className = function() { + return 'BaseCS1'; + }; + + return BaseCS1; + + })(); + + ExtendedCS1 = (function(superClass) { + extend(ExtendedCS1, superClass); + + function ExtendedCS1(flavor) { + this.flavor = flavor; + ExtendedCS1.__super__.constructor.call(this, 'cafe ole'); + } + + ExtendedCS1.prototype.make = function() { + return "making a " + this.drink + " with " + this.flavor; + }; + + ExtendedCS1.className = function() { + return 'ExtendedCS1'; + }; + + return ExtendedCS1; + + })(BaseCS1); + + ``` + class B extends ExtendedCS1 + constructor: (@shots) -> + super('caramel') + make: () -> + super + " and #{@shots} shots of espresso" + + eq B.className(), 'ExtendedCS1' + b = new B('three') + eq b.make(), "making a cafe ole with caramel and three shots of espresso" + From e9d12dd6c8804eac3a36a56b1e53ac8596ac2a49 Mon Sep 17 00:00:00 2001 From: mrmowgli Date: Sat, 7 Jan 2017 05:12:53 -0500 Subject: [PATCH 28/32] Changes to reflect feedback and to comment out issues that will be addressed seperately. --- test/classes.coffee | 94 ++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/test/classes.coffee b/test/classes.coffee index c0b9199e6c..f0e6d974c3 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -1371,7 +1371,7 @@ test "nested classes with super", -> @label = tmp.label + ' extended' @extender: () => - class ExtendedSelf extends @ + class ExtendedSelf extends @ constructor: -> tmp = super() @label = tmp.label + ' from this' @@ -1382,17 +1382,16 @@ test "nested classes with super", -> eq (new Outer.ExtendedInner).label, 'inner extended' eq (Outer.extender()).label, 'outer from this' -# TODO: Currently this test fails. test "Static methods generate 'static' keywords", -> compile = """ class CheckStatic constructor: (@drink) -> - @className: 'CheckStatic' + @className: -> 'CheckStatic' c = new CheckStatic('Machiato') """ result = CoffeeScript.compile compile, bare: yes - ok (result && result.match(' static ') && true) isnt true + ok (result && result.match(' static ') && true) test "Static methods in nested classes", -> class Outer @@ -1466,48 +1465,48 @@ test "ES6 prototypes can be overriden", -> B::test = () -> 'D' eq (new D).test(), 'D' - -test "ES6 conformance to extending non-classes", -> - A = (@title) -> - 'Title: ' + @ - - class B extends A - b = new B('caffeinated') - eq b.title, 'caffeinated' - - # Check inheritance chain - A::getTitle = () -> @title - eq b.getTitle(), 'caffeinated' - - throwsC = """ - C = {title: 'invalid'} - class D extends {} - """ - # TODO: This should catch on compile and message should be "class can only extend classes and functions." - throws -> CoffeeScript.run throwsC, bare: yes - -# TODO: Either restrict valid statements in class bodies OR insert 'use strict'? -test "Class function environment should be in `strict mode`, ie as if 'use strict' was in use", -> - class A - # this might be a meaningless test, since these are likely to be runtime errors and different - # for every browser. Thoughts? - constructor: () -> - # Ivalid: prop reassignment - @state = {prop: [1], prop: {a: 'a'}} - # eval reassignment - @badEval = eval; - - # Should throw, but doesn't - a = new A - -# TODO: new.target needs support -test "ES6 support for new.target (functions and constructors)", -> - throwsA = """ - class A - constructor: () -> - a = new.target.name - """ - throws -> CoffeeScript.compile throwsA, bare: yes +# TODO: implement this error check +# test "ES6 conformance to extending non-classes", -> +# A = (@title) -> +# 'Title: ' + @ + +# class B extends A +# b = new B('caffeinated') +# eq b.title, 'caffeinated' + +# # Check inheritance chain +# A::getTitle = () -> @title +# eq b.getTitle(), 'caffeinated' + +# throwsC = """ +# C = {title: 'invalid'} +# class D extends {} +# """ +# # This should catch on compile and message should be "class can only extend classes and functions." +# throws -> CoffeeScript.run throwsC, bare: yes + +# TODO: Evaluate future compliance with "strict mode"; +# test "Class function environment should be in `strict mode`, ie as if 'use strict' was in use", -> +# class A +# # this might be a meaningless test, since these are likely to be runtime errors and different +# # for every browser. Thoughts? +# constructor: () -> +# # Ivalid: prop reassignment +# @state = {prop: [1], prop: {a: 'a'}} +# # eval reassignment +# @badEval = eval; + +# # Should throw, but doesn't +# a = new A + +# TODO: new.target needs support Separate issue +# test "ES6 support for new.target (functions and constructors)", -> +# throwsA = """ +# class A +# constructor: () -> +# a = new.target.name +# """ +# throws -> CoffeeScript.compile throwsA, bare: yes test "only one method named constructor allowed", -> throwsA = """ @@ -1518,7 +1517,8 @@ test "only one method named constructor allowed", -> throws -> CoffeeScript.compile throwsA, bare: yes # TODO: We can't expect super to be enforced for all constructors. If it's mandetory, -# the following is invalid +# the following is invalid; Eg: return {prop: value, prop1: val1} vs. requiring super +# See: https://github.com/coffeescript6/discuss/issues/68 test "If the constructor of a child class does not call super, the constructor is expected to return an object. ", -> throwsA = """ class A From ca203b50cf3f1164bc7bcf0721b30ee4a7119ee1 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Sun, 8 Jan 2017 12:58:55 +0000 Subject: [PATCH 29/32] Clean up test/classes.coffee - Trim trailing whitespace. - Rephrase a condition to be more idiomatic. --- test/classes.coffee | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/test/classes.coffee b/test/classes.coffee index f0e6d974c3..246a5854e1 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -1391,7 +1391,7 @@ test "Static methods generate 'static' keywords", -> c = new CheckStatic('Machiato') """ result = CoffeeScript.compile compile, bare: yes - ok (result && result.match(' static ') && true) + ok result.match(' static ') test "Static methods in nested classes", -> class Outer @@ -1404,7 +1404,7 @@ test "Static methods in nested classes", -> eq Outer.Inner.name(), 'Inner' -test "mixed constructors with inheritance and ES6 super", -> +test "mixed constructors with inheritance and ES6 super", -> identity = (f) -> f class TopClass @@ -1437,7 +1437,7 @@ test "ES6 static class methods can be overriden", -> # If creating static by direct assignment rather than ES6 static keyword test "ES6 Static methods should set `this` to undefined // ES6 ", -> class A - @test: -> + @test: -> eq this, undefined # Ensure that our object prototypes work with ES6 @@ -1456,12 +1456,12 @@ test "ES6 prototypes can be overriden", -> eq b.test(), 'B' Object.setPrototypeOf(b, a) eq b.className, 'classA' - # This shouldn't throw, + # This shouldn't throw, # as we only change inheritance not object construction # This may be an issue with ES, rather than CS construction? #eq b.test(), 'B' - class D extends B + class D extends B B::test = () -> 'D' eq (new D).test(), 'D' @@ -1471,10 +1471,10 @@ test "ES6 prototypes can be overriden", -> # 'Title: ' + @ # class B extends A -# b = new B('caffeinated') +# b = new B('caffeinated') # eq b.title, 'caffeinated' -# # Check inheritance chain +# # Check inheritance chain # A::getTitle = () -> @title # eq b.getTitle(), 'caffeinated' @@ -1497,20 +1497,20 @@ test "ES6 prototypes can be overriden", -> # @badEval = eval; # # Should throw, but doesn't -# a = new A +# a = new A # TODO: new.target needs support Separate issue # test "ES6 support for new.target (functions and constructors)", -> # throwsA = """ -# class A +# class A # constructor: () -> -# a = new.target.name +# a = new.target.name # """ # throws -> CoffeeScript.compile throwsA, bare: yes test "only one method named constructor allowed", -> throwsA = """ - class A + class A constructor: (@first) -> constructor: (@last) -> """ @@ -1531,13 +1531,13 @@ test "If the constructor of a child class does not call super, the constructor i test "super can only exist in extended classes", -> throwsA = """ - class A + class A constructor: (@name) -> super() """ throws -> CoffeeScript.compile throwsA, bare: yes -# --- CS1 classes compatability breaks --- +# --- CS1 classes compatability breaks --- test "CS6 Class extends a CS1 compiled class", -> ``` // Generated by CoffeeScript 1.11.1 @@ -1569,7 +1569,7 @@ test "CS6 Class extends a CS1 compiled class", -> this.flavor = flavor; ExtendedCS1.__super__.constructor.call(this, 'cafe ole'); } - + ExtendedCS1.prototype.make = function() { return "making a " + this.drink + " with " + this.flavor; }; @@ -1584,7 +1584,7 @@ test "CS6 Class extends a CS1 compiled class", -> ``` class B extends BaseCS1 - eq B.className(), 'BaseCS1' + eq B.className(), 'BaseCS1' b = new B('machiato') eq b.make(), "making a machiato" @@ -1620,7 +1620,7 @@ test "CS6 Class extends an extended CS1 compiled class", -> this.flavor = flavor; ExtendedCS1.__super__.constructor.call(this, 'cafe ole'); } - + ExtendedCS1.prototype.make = function() { return "making a " + this.drink + " with " + this.flavor; }; @@ -1635,7 +1635,7 @@ test "CS6 Class extends an extended CS1 compiled class", -> ``` class B extends ExtendedCS1 - eq B.className(), 'ExtendedCS1' + eq B.className(), 'ExtendedCS1' b = new B('vanilla') eq b.make(), "making a cafe ole with vanilla" @@ -1670,7 +1670,7 @@ test "CS6 Class extends a CS1 compiled class with super()", -> this.flavor = flavor; ExtendedCS1.__super__.constructor.call(this, 'cafe ole'); } - + ExtendedCS1.prototype.make = function() { return "making a " + this.drink + " with " + this.flavor; }; @@ -1690,7 +1690,6 @@ test "CS6 Class extends a CS1 compiled class with super()", -> make: () -> super + " and #{@shots} shots of espresso" - eq B.className(), 'ExtendedCS1' + eq B.className(), 'ExtendedCS1' b = new B('three') eq b.make(), "making a cafe ole with caramel and three shots of espresso" - From 52063f341546038e5040bc7f0ff48c91cc4e2348 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Sun, 8 Jan 2017 13:02:45 +0000 Subject: [PATCH 30/32] Remove check for `super` in derived constructors In order to be usable at runtime, an extended ES class must call `super` OR return an alternative object. This check prevented the latter case, and checking for an alternative return can't be completed statically without control flow analysis. --- lib/coffee-script/nodes.js | 4 +--- src/nodes.coffee | 4 +--- test/classes.coffee | 16 +++++++--------- test/error_messages.coffee | 7 ------- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index e4789248c4..a8e8a815ae 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -3012,9 +3012,7 @@ } return !(child instanceof SuperCall) && (!(child instanceof Code) || child.bound); }); - if (this.ctor === 'derived' && !superCalls.length) { - this.error('Derived class constructors must include a call to super'); - } else if (this.ctor === 'base' && superCalls.length) { + if (this.ctor === 'base' && superCalls.length) { superCalls[0].error("'super' is only allowed in derived class constructors"); } return superCalls; diff --git a/src/nodes.coffee b/src/nodes.coffee index 08b0b4edc4..ac41ef55d9 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2058,9 +2058,7 @@ exports.Code = class Code extends Base # `super` has the same target in bound (arrow) functions, so check them too child not instanceof SuperCall and (child not instanceof Code or child.bound) - if @ctor is 'derived' and not superCalls.length - @error 'Derived class constructors must include a call to super' - else if @ctor is 'base' and superCalls.length + if @ctor is 'base' and superCalls.length superCalls[0].error "'super' is only allowed in derived class constructors" superCalls diff --git a/test/classes.coffee b/test/classes.coffee index 246a5854e1..beb3a45420 100644 --- a/test/classes.coffee +++ b/test/classes.coffee @@ -1516,17 +1516,15 @@ test "only one method named constructor allowed", -> """ throws -> CoffeeScript.compile throwsA, bare: yes -# TODO: We can't expect super to be enforced for all constructors. If it's mandetory, -# the following is invalid; Eg: return {prop: value, prop1: val1} vs. requiring super -# See: https://github.com/coffeescript6/discuss/issues/68 -test "If the constructor of a child class does not call super, the constructor is expected to return an object. ", -> - throwsA = """ +test "If the constructor of a child class does not call super,it should return an object.", -> + nonce = {} + class A class B extends A - constructor: () -> - return {} - """ - throws -> CoffeeScript.compile throwsA, bare: yes + constructor: -> + return nonce + + eq nonce, new B test "super can only exist in extended classes", -> diff --git a/test/error_messages.coffee b/test/error_messages.coffee index 36f9938df7..d82736a34b 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1227,13 +1227,6 @@ test "non-derived constructors can't call super", -> ^^^^^ ''' -test "derived constructors must call super", -> - assertErrorFormat 'class extends A then constructor: ->', ''' - [stdin]:1:35: error: Derived class constructors must include a call to super - class extends A then constructor: -> - ^^ - ''' - test "derived constructors can't reference `this` before calling super", -> assertErrorFormat 'class extends A then constructor: -> @', ''' [stdin]:1:38: error: Can't reference 'this' before calling super in derived class constructors From 9d7c1f748871e7fe5ebd57c9fedd9a5fda614dc1 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Tue, 10 Jan 2017 09:00:30 +0000 Subject: [PATCH 31/32] Disallow 'super' in constructor parameter defaults There are many edge cases when combining 'super' in parameter defaults with @-parameters and bound functions (and potentially property initializers in the future). Rather than attempting to resolve these edge cases, 'super' is now explicitly disallowed in constructor parameter defaults. --- lib/coffee-script/nodes.js | 60 +++++++++++++++++++++----------------- src/nodes.coffee | 40 ++++++++++++++----------- test/error_messages.coffee | 7 +++++ 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index a8e8a815ae..ff7b6fbc31 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2450,11 +2450,11 @@ exports.Assign = Assign = (function(superClass) { class Assign extends superClass { - constructor(variable1, value1, context, options = {}) { + constructor(variable1, value1, context1, options = {}) { super(); this.variable = variable1; this.value = value1; - this.context = context; + this.context = context1; this.param = options.param, this.subpattern = options.subpattern, this.operatorToken = options.operatorToken, this.moduleDeclaration = options.moduleDeclaration; } @@ -2782,7 +2782,7 @@ } compileNode(o) { - var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, l, len1, len2, len3, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, superCall, superCalls, thisAssignments, val, wasEmpty; + var answer, body, condition, exprs, haveSplatParam, i, ifTrue, j, k, len1, len2, m, modifiers, name, param, paramNames, params, paramsAfterSplat, ref, ref3, ref4, ref5, ref6, ref7, signature, splatParamName, thisAssignments, val, wasEmpty; if (this.ctor) { if (this.isAsync) { this.variable.error('Class constructor may not be async'); @@ -2891,16 +2891,8 @@ })() ])), new Value(new IdentifierLiteral(splatParamName)))); } - if (this.ctor) { - superCalls = this.superCalls(); - } wasEmpty = this.body.isEmpty(); - if (this.ctor === 'derived') { - for (k = 0, len2 = superCalls.length; k < len2; k++) { - superCall = superCalls[k]; - superCall.expressions = thisAssignments; - } - } else { + if (!this.expandCtorSuper(thisAssignments)) { this.body.expressions.unshift(...thisAssignments); } this.body.expressions.unshift(...exprs); @@ -2921,7 +2913,7 @@ modifiers.push('*'); } signature = [this.makeCode('(')]; - for (i = l = 0, len3 = params.length; l < len3; i = ++l) { + for (i = k = 0, len2 = params.length; k < len2; i = ++k) { param = params[i]; if (i) { signature.push(this.makeCode(', ')); @@ -2942,10 +2934,10 @@ } } answer = this.joinFragmentArrays((function() { - var len4, p, results; + var l, len3, results; results = []; - for (p = 0, len4 = modifiers.length; p < len4; p++) { - m = modifiers[p]; + for (l = 0, len3 = modifiers.length; l < len3; l++) { + m = modifiers[l]; results.push(this.makeCode(m)); } return results; @@ -3000,22 +2992,36 @@ } } - superCalls() { - var superCalls; - superCalls = []; - this.traverseChildren(true, (child) => { - if (child instanceof SuperCall) { - superCalls.push(child); + expandCtorSuper(thisAssignments) { + var seenSuper; + if (!this.ctor) { + return false; + } + this.eachSuperCall(Block.wrap(this.params), function(superCall) { + return superCall.error("'super' is not allowed in constructor parameter defaults"); + }); + seenSuper = this.eachSuperCall(this.body, (superCall) => { + if (this.ctor === 'base') { + superCall.error("'super' is only allowed in derived class constructors"); } - if (this.ctor === 'derived' && child instanceof ThisLiteral && !superCalls.length) { + return superCall.expressions = thisAssignments; + }); + return seenSuper; + } + + eachSuperCall(context, iterator) { + var seenSuper; + seenSuper = false; + context.traverseChildren(true, (child) => { + if (child instanceof SuperCall) { + seenSuper = true; + iterator(child); + } else if (child instanceof ThisLiteral && this.ctor === 'derived' && !seenSuper) { child.error("Can't reference 'this' before calling super in derived class constructors"); } return !(child instanceof SuperCall) && (!(child instanceof Code) || child.bound); }); - if (this.ctor === 'base' && superCalls.length) { - superCalls[0].error("'super' is only allowed in derived class constructors"); - } - return superCalls; + return seenSuper; } }; diff --git a/src/nodes.coffee b/src/nodes.coffee index ac41ef55d9..9d1ca074f0 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1985,15 +1985,9 @@ exports.Code = class Code extends Base new Arr [new Splat(new IdentifierLiteral(splatParamName)), (param.asReference o for param in paramsAfterSplat)...] ), new Value new IdentifierLiteral splatParamName - # Find and validate constructor super calls - superCalls = @superCalls() if @ctor - # Add new expressions to the function body wasEmpty = @body.isEmpty() - if @ctor is 'derived' - superCall.expressions = thisAssignments for superCall in superCalls - else - @body.expressions.unshift thisAssignments... + @body.expressions.unshift thisAssignments... unless @expandCtorSuper thisAssignments @body.expressions.unshift exprs... @body.makeReturn() unless wasEmpty or @noReturn @@ -2046,22 +2040,34 @@ exports.Code = class Code extends Base else false - # Find all super calls in this function - superCalls: -> - superCalls = [] - @traverseChildren true, (child) => - superCalls.push child if child instanceof SuperCall + expandCtorSuper: (thisAssignments) -> + return false unless @ctor + + @eachSuperCall Block.wrap(@params), (superCall) -> + superCall.error "'super' is not allowed in constructor parameter defaults" + + seenSuper = @eachSuperCall @body, (superCall) => + superCall.error "'super' is only allowed in derived class constructors" if @ctor is 'base' + superCall.expressions = thisAssignments - if @ctor is 'derived' and child instanceof ThisLiteral and not superCalls.length + seenSuper + + # Find all super calls in the given context node + # Returns `true` if `iterator` is called + eachSuperCall: (context, iterator) -> + seenSuper = no + + context.traverseChildren true, (child) => + if child instanceof SuperCall + seenSuper = yes + iterator child + else if child instanceof ThisLiteral and @ctor is 'derived' and not seenSuper child.error "Can't reference 'this' before calling super in derived class constructors" # `super` has the same target in bound (arrow) functions, so check them too child not instanceof SuperCall and (child not instanceof Code or child.bound) - if @ctor is 'base' and superCalls.length - superCalls[0].error "'super' is only allowed in derived class constructors" - - superCalls + seenSuper #### Param diff --git a/test/error_messages.coffee b/test/error_messages.coffee index d82736a34b..91c951f38b 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1233,3 +1233,10 @@ test "derived constructors can't reference `this` before calling super", -> class extends A then constructor: -> @ ^ ''' + +test "'super' is not allowed in constructor parameter defaults", -> + assertErrorFormat 'class extends A then constructor: (a = super) ->', ''' + [stdin]:1:40: error: 'super' is not allowed in constructor parameter defaults + class extends A then constructor: (a = super) -> + ^^^^^ + ''' From 57508593c4d367f0201605016e527ef013d20eb1 Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Tue, 10 Jan 2017 09:06:05 +0000 Subject: [PATCH 32/32] Disallow @-params in derived constructors without 'super' @-parameters can't be assigned unless 'super' is called. --- lib/coffee-script/nodes.js | 7 ++++++- src/nodes.coffee | 5 +++++ test/error_messages.coffee | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index ff7b6fbc31..60a3c8ab26 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -2993,7 +2993,7 @@ } expandCtorSuper(thisAssignments) { - var seenSuper; + var haveThisParam, param, ref3, seenSuper; if (!this.ctor) { return false; } @@ -3006,6 +3006,11 @@ } return superCall.expressions = thisAssignments; }); + haveThisParam = thisAssignments.length && thisAssignments.length !== ((ref3 = this.thisAssignments) != null ? ref3.length : void 0); + if (this.ctor === 'derived' && !seenSuper && haveThisParam) { + param = thisAssignments[0].variable; + param.error("Can't use @params in derived class constructors without calling super"); + } return seenSuper; } diff --git a/src/nodes.coffee b/src/nodes.coffee index 9d1ca074f0..9e722b0f6b 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2050,6 +2050,11 @@ exports.Code = class Code extends Base superCall.error "'super' is only allowed in derived class constructors" if @ctor is 'base' superCall.expressions = thisAssignments + haveThisParam = thisAssignments.length and thisAssignments.length != @thisAssignments?.length + if @ctor is 'derived' and not seenSuper and haveThisParam + param = thisAssignments[0].variable + param.error "Can't use @params in derived class constructors without calling super" + seenSuper # Find all super calls in the given context node diff --git a/test/error_messages.coffee b/test/error_messages.coffee index 91c951f38b..c98c4263a1 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1234,6 +1234,13 @@ test "derived constructors can't reference `this` before calling super", -> ^ ''' +test "derived constructors can't use @params without calling super", -> + assertErrorFormat 'class extends A then constructor: (@a) ->', ''' + [stdin]:1:36: error: Can't use @params in derived class constructors without calling super + class extends A then constructor: (@a) -> + ^^ + ''' + test "'super' is not allowed in constructor parameter defaults", -> assertErrorFormat 'class extends A then constructor: (a = super) ->', ''' [stdin]:1:40: error: 'super' is not allowed in constructor parameter defaults