From fe7377aed596fcf5cbdf5cfda89a39b1cb27a8a9 Mon Sep 17 00:00:00 2001 From: Julian Rosse Date: Sun, 14 Oct 2018 22:07:12 -0400 Subject: [PATCH] Call AST (#5117) * op ast * test expected errors * use new ast methods * remove unused abstraction * logical ? * fix rebase * follow convention * fixes from code review * test helper naming * always convert new to Call * new cases * don't mutate locationData * updated grammar * tests * Rebuild * always expose generated * todo for OptionalMemberExpression --- lib/coffeescript/grammar.js | 1 + lib/coffeescript/nodes.js | 28 +++ lib/coffeescript/parser.js | 30 +-- lib/coffeescript/rewriter.js | 2 +- package-lock.json | 8 +- src/grammar.coffee | 2 +- src/nodes.coffee | 14 ++ src/rewriter.coffee | 2 +- test/abstract_syntax_tree.coffee | 130 ++++++++---- .../abstract_syntax_tree_location_data.coffee | 189 +++++++++++++++--- 10 files changed, 328 insertions(+), 78 deletions(-) diff --git a/lib/coffeescript/grammar.js b/lib/coffeescript/grammar.js index 6220de92ef..10302703e8 100644 --- a/lib/coffeescript/grammar.js +++ b/lib/coffeescript/grammar.js @@ -1039,6 +1039,7 @@ }), o('CALL_START ArgList OptComma CALL_END', function() { + $2.implicit = $1.generated; return $2; }) ], diff --git a/lib/coffeescript/nodes.js b/lib/coffeescript/nodes.js index dce5a334f3..300744d819 100644 --- a/lib/coffeescript/nodes.js +++ b/lib/coffeescript/nodes.js @@ -1944,6 +1944,7 @@ this.args = args1; this.soak = soak1; this.token = token1; + this.implicit = this.args.implicit; this.isNew = false; if (this.variable instanceof Value && this.variable.isNotCallable()) { this.variable.error("literal is not a function"); @@ -2142,6 +2143,33 @@ return fragments; } + astType() { + if (this.isNew) { + return 'NewExpression'; + } else { + return 'CallExpression'; + } + } + + astProperties() { + var arg; + return { + callee: this.variable.ast(), + arguments: (function() { + var j, len1, ref1, results; + ref1 = this.args; + results = []; + for (j = 0, len1 = ref1.length; j < len1; j++) { + arg = ref1[j]; + results.push(arg.ast()); + } + return results; + }).call(this), + optional: !!this.soak, + implicit: !!this.implicit + }; + } + }; Call.prototype.children = ['variable', 'args']; diff --git a/lib/coffeescript/parser.js b/lib/coffeescript/parser.js index adfb6c1e04..78868d27ce 100644 --- a/lib/coffeescript/parser.js +++ b/lib/coffeescript/parser.js @@ -84,7 +84,7 @@ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* actio var $0 = $$.length - 1; switch (yystate) { case 1: -return this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Block); +return this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Block()); break; case 2: return this.$ = $$[$0]; @@ -121,7 +121,7 @@ this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])(new yy.Op($$[$0-2].concat($$[$0- $$[$0])); break; case 35: -this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.Block); +this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.Block()); break; case 36: case 90: case 146: this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])($$[$0-1]); @@ -172,7 +172,7 @@ case 47: this.$ = yy.addDataToNode(yy, _$[$0-4], _$[$0])(new yy.Interpolation($$[$0-2])); break; case 48: -this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.Interpolation); +this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.Interpolation()); break; case 49: case 275: this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])($$[$0]); @@ -274,7 +274,7 @@ case 78: case 121: this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.Splat($$[$0])); break; case 84: -this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.SuperCall(yy.addDataToNode(yy, _$[$0-1])(new yy.Super), +this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.SuperCall(yy.addDataToNode(yy, _$[$0-1])(new yy.Super()), $$[$0], false, $$[$0-1])); @@ -300,19 +300,19 @@ case 92: this.$ = yy.addDataToNode(yy, _$[$0-3], _$[$0])(new yy.Return(new yy.Value($$[$0-1]))); break; case 93: -this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Return); +this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Return()); break; case 94: this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])(new yy.YieldReturn($$[$0])); break; case 95: -this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.YieldReturn); +this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.YieldReturn()); break; case 96: this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])(new yy.AwaitReturn($$[$0])); break; case 97: -this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.AwaitReturn); +this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.AwaitReturn()); break; case 98: this.$ = yy.addDataToNode(yy, _$[$0-4], _$[$0])(new yy.Code($$[$0-3], @@ -369,7 +369,7 @@ this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])(new yy.Param($$[$0-2], $$[$0])); break; case 115: case 233: -this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Expansion); +this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Expansion()); break; case 123: this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])($$[$0-1].add($$[$0])); @@ -444,7 +444,7 @@ this.$ = yy.addDataToNode(yy, _$[$0-3], _$[$0])(new yy.Obj($$[$0-2], $$[$0-3].generated)); break; case 156: -this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Class); +this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Class()); break; case 157: this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])(new yy.Class(null, @@ -511,7 +511,7 @@ this.$ = yy.addDataToNode(yy, _$[$0-8], _$[$0])(new yy.ImportDeclaration(new yy. new yy.ImportSpecifierList($$[$0-4])), $$[$0])); break; -case 174: case 195: case 208: case 228: +case 174: case 195: case 228: this.$ = yy.addDataToNode(yy, _$[$0-3], _$[$0])($$[$0-2]); break; case 176: @@ -611,7 +611,7 @@ this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])(new yy.Call($$[$0-2], $$[$0-1])); break; case 204: -this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])(new yy.SuperCall(yy.addDataToNode(yy, _$[$0-2])(new yy.Super), +this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])(new yy.SuperCall(yy.addDataToNode(yy, _$[$0-2])(new yy.Super()), $$[$0], $$[$0-1], $$[$0-2])); @@ -625,6 +625,12 @@ break; case 207: this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])([]); break; +case 208: +this.$ = yy.addDataToNode(yy, _$[$0-3], _$[$0])((function() { + $$[$0-2].implicit = $$[$0-3].generated; + return $$[$0-2]; + }())); +break; case 209: case 210: this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Value(new yy.ThisLiteral($$[$0]))); break; @@ -690,7 +696,7 @@ case 242: this.$ = yy.addDataToNode(yy, _$[$0-1], _$[$0])([].concat($$[$0])); break; case 245: -this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Elision); +this.$ = yy.addDataToNode(yy, _$[$0], _$[$0])(new yy.Elision()); break; case 248: case 249: this.$ = yy.addDataToNode(yy, _$[$0-2], _$[$0])([].concat($$[$0-2], diff --git a/lib/coffeescript/rewriter.js b/lib/coffeescript/rewriter.js index d28bca7a5d..e4f7d38600 100644 --- a/lib/coffeescript/rewriter.js +++ b/lib/coffeescript/rewriter.js @@ -912,7 +912,7 @@ exposeTokenDataToGrammar() { return this.scanTokens(function(token, i) { var key, ref, ref1, val; - if (token.data && Object.keys(token.data).length || token[0] === 'JS' && token.generated) { + if (token.generated || (token.data && Object.keys(token.data).length !== 0)) { token[1] = new String(token[1]); ref1 = (ref = token.data) != null ? ref : {}; for (key in ref1) { diff --git a/package-lock.json b/package-lock.json index e2855c97e7..1a65783043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "coffeescript", - "version": "2.3.1", + "version": "2.3.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1803,7 +1803,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -3801,7 +3801,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -3846,7 +3846,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { diff --git a/src/grammar.coffee b/src/grammar.coffee index 275472094e..7445d0b1b0 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -520,7 +520,7 @@ grammar = # The list of arguments to a function call. Arguments: [ o 'CALL_START CALL_END', -> [] - o 'CALL_START ArgList OptComma CALL_END', -> $2 + o 'CALL_START ArgList OptComma CALL_END', -> $2.implicit = $1.generated; $2 ] # A reference to the *this* current object. diff --git a/src/nodes.coffee b/src/nodes.coffee index 46afab1b17..d8af113cbd 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1285,6 +1285,7 @@ exports.Call = class Call extends Base constructor: (@variable, @args = [], @soak, @token) -> super() + @implicit = @args.implicit @isNew = no if @variable instanceof Value and @variable.isNotCallable() @variable.error "literal is not a function" @@ -1426,6 +1427,19 @@ exports.Call = class Call extends Base fragments.push @makeCode(' />') fragments + astType: -> + if @isNew + 'NewExpression' + else + 'CallExpression' + + astProperties: -> + return + callee: @variable.ast() + arguments: arg.ast() for arg in @args + optional: !!@soak + implicit: !!@implicit + #### Super # Takes care of converting `super()` calls into calls against the prototype's diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 093f1968c1..2ff0e97c01 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -655,7 +655,7 @@ exports.Rewriter = class Rewriter # primitive string and separately passing any expected token data properties exposeTokenDataToGrammar: -> @scanTokens (token, i) -> - if token.data and Object.keys(token.data).length or token[0] is 'JS' and token.generated + if token.generated or (token.data and Object.keys(token.data).length isnt 0) token[1] = new String token[1] token[1][key] = val for own key, val of (token.data ? {}) token[1].generated = yes if token.generated diff --git a/test/abstract_syntax_tree.coffee b/test/abstract_syntax_tree.coffee index 4015c0c093..ee6fec3866 100644 --- a/test/abstract_syntax_tree.coffee +++ b/test/abstract_syntax_tree.coffee @@ -270,47 +270,98 @@ test "AST as expected for BooleanLiteral node", -> # # Comments aren’t nodes, so they shouldn’t appear in the AST. -# test "AST as expected for Call node", -> -# testExpression 'fn()', -# type: 'Call' -# variable: -# value: 'fn' +test "AST as expected for Call node", -> + testExpression 'fn()', + type: 'CallExpression' + callee: + type: 'Identifier' + name: 'fn' + arguments: [] + optional: no + implicit: no -# testExpression 'new Date()', -# type: 'Call' -# variable: -# value: 'Date' -# isNew: yes + testExpression 'new Date()', + type: 'NewExpression' + callee: + type: 'Identifier' + name: 'Date' + arguments: [] + optional: no + implicit: no -# testExpression 'new Old', -# type: 'NewExpression' -# callee: -# type: 'Identifier' -# name: 'Old' + testExpression 'new Date?()', + type: 'NewExpression' + callee: + type: 'Identifier' + name: 'Date' + arguments: [] + optional: yes + implicit: no -# testExpression 'maybe?()', -# type: 'Call' -# soak: yes + testExpression 'new Old', + type: 'NewExpression' + callee: + type: 'Identifier' + name: 'Old' + arguments: [] + optional: no + implicit: no -# testExpression 'goDo this, that', -# type: 'Call' -# args: [ -# {value: 'this'} -# {value: 'that'} -# ] + testExpression 'new Old(1)', + type: 'NewExpression' + callee: + type: 'Identifier' + name: 'Old' + arguments: [ + type: 'NumericLiteral' + value: 1 + ] + optional: no + implicit: no -# testExpression 'do ->', -# type: 'Call' -# do: yes -# variable: -# type: 'Code' + testExpression 'new Old 1', + type: 'NewExpression' + callee: + type: 'Identifier' + name: 'Old' + arguments: [ + type: 'NumericLiteral' + value: 1 + ] + optional: no + implicit: yes -# testExpression 'do fn', -# type: 'Call' -# do: yes -# variable: -# type: 'IdentifierLiteral' -# value: 'fn' + testExpression 'maybe?()', + type: 'CallExpression' + optional: yes + implicit: no + + testExpression 'maybe?(1 + 1)', + type: 'CallExpression' + arguments: [ + type: 'BinaryExpression' + ] + optional: yes + implicit: no + + testExpression 'maybe? 1 + 1', + type: 'CallExpression' + arguments: [ + type: 'BinaryExpression' + ] + optional: yes + implicit: yes + + testExpression 'goDo this, that', + type: 'CallExpression' + arguments: [ + type: 'ThisExpression' + , + type: 'Identifier' + name: 'that' + ] + implicit: yes + optional: no # test "AST as expected for SuperCall node", -> # testExpression 'class child extends parent then constructor: -> super()', @@ -372,6 +423,8 @@ test "AST as expected for Access node", -> shorthand: no testExpression 'obj?.prop', + # TODO: support Babel 7-style OptionalMemberExpression type + # type: 'OptionalMemberExpression' type: 'MemberExpression' object: type: 'Identifier' @@ -1076,6 +1129,13 @@ test "AST as expected for Op node", -> type: 'Identifier' name: 'x' + # testExpression 'do ->', + # type: 'UnaryExpression' + # operator: 'do' + # prefix: yes + # argument: + # type: 'FunctionExpression' + testExpression '!x', type: 'UnaryExpression' operator: '!' diff --git a/test/abstract_syntax_tree_location_data.coffee b/test/abstract_syntax_tree_location_data.coffee index 0e6bf9fc52..9bdb56c1a8 100644 --- a/test/abstract_syntax_tree_location_data.coffee +++ b/test/abstract_syntax_tree_location_data.coffee @@ -600,27 +600,168 @@ test "AST location data as expected for Op node", -> line: 1 column: 11 -# test "AST location data as expected for Call node", -> -# testAstLocationData 'new Old', -# type: 'NewExpression' -# callee: -# start: 4 -# end: 7 -# range: [4, 7] -# loc: -# start: -# line: 1 -# column: 4 -# end: -# line: 1 -# column: 7 -# start: 0 -# end: 7 -# range: [0, 7] -# loc: -# start: -# line: 1 -# column: 0 -# end: -# line: 1 -# column: 7 +test "AST location data as expected for Call node", -> + testAstLocationData 'fn()', + type: 'CallExpression' + start: 0 + end: 4 + range: [0, 4] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 4 + callee: + start: 0 + end: 2 + range: [0, 2] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 2 + + testAstLocationData 'new Date()', + type: 'NewExpression' + start: 0 + end: 10 + range: [0, 10] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 10 + callee: + start: 4 + end: 8 + range: [4, 8] + loc: + start: + line: 1 + column: 4 + end: + line: 1 + column: 8 + + testAstLocationData ''' + new Old( + 1 + ) + ''', + start: 0 + end: 14 + range: [0, 14] + loc: + start: + line: 1 + column: 0 + end: + line: 3 + column: 1 + type: 'NewExpression' + arguments: [ + start: 11 + end: 12 + range: [11, 12] + loc: + start: + line: 2 + column: 2 + end: + line: 2 + column: 3 + ] + + testAstLocationData 'maybe? 1 + 1', + type: 'CallExpression' + start: 0 + end: 12 + range: [0, 12] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 12 + arguments: [ + start: 7 + end: 12 + range: [7, 12] + loc: + start: + line: 1 + column: 7 + end: + line: 1 + column: 12 + ] + + testAstLocationData ''' + goDo(this, + that) + ''', + type: 'CallExpression' + start: 0 + end: 18 + range: [0, 18] + loc: + start: + line: 1 + column: 0 + end: + line: 2 + column: 7 + arguments: [ + start: 5 + end: 9 + range: [5, 9] + loc: + start: + line: 1 + column: 5 + end: + line: 1 + column: 9 + , + start: 13 + end: 17 + range: [13, 17] + loc: + start: + line: 2 + column: 2 + end: + line: 2 + column: 6 + ] + + testAstLocationData 'new Old', + type: 'NewExpression' + callee: + start: 4 + end: 7 + range: [4, 7] + loc: + start: + line: 1 + column: 4 + end: + line: 1 + column: 7 + start: 0 + end: 7 + range: [0, 7] + loc: + start: + line: 1 + column: 0 + end: + line: 1 + column: 7