Skip to content

Commit 6faa7f2

Browse files
Fix #4706: Flow generics (#4736)
* Fix #4706: Comments before a PARAM_START token stay before that token * Simplify nodes * Add function-in-function test * Fix #4706: Comments after class name should go after the identifier that's after `class`, not the variable assigned to * Fix #4706: Top-level identifiers with trailing comments get wrapped in parentheses (around the comment too) so that Flow doesn't interpret it as a JavaScript label * Cleanup * If the source has parentheses wrapping an identifier followed by a block comment, output those parentheses rather than optimizing them away; this is a requirement of Flow, to distinguish from JavaScript labels * More tests for Flow comments
1 parent 063c2d1 commit 6faa7f2

File tree

10 files changed

+168
-37
lines changed

10 files changed

+168
-37
lines changed

lib/coffeescript/grammar.js

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/nodes.js

Lines changed: 31 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/parser.js

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/rewriter.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/coffeescript/sourcemap.js

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/grammar.coffee

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ grammar =
262262
# The **Code** node is the function literal. It's defined by an indented block
263263
# of **Block** preceded by a function arrow, with an optional parameter list.
264264
Code: [
265-
o 'PARAM_START ParamList PARAM_END FuncGlyph Block', -> new Code $2, $5, $4
265+
o 'PARAM_START ParamList PARAM_END FuncGlyph Block', -> new Code $2, $5, $4, LOC(1)(new Literal $1)
266266
o 'FuncGlyph Block', -> new Code [], $2, $1
267267
]
268268

src/nodes.coffee

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -502,8 +502,6 @@ exports.Block = class Block extends Base
502502
# We want to compile this and ignore the result.
503503
node.compileToFragments o
504504
continue
505-
506-
node = node.unwrapAll()
507505
node = (node.unfoldSoak(o) or node)
508506
if node instanceof Block
509507
# This is a nested block. We don’t do anything special here like
@@ -1676,7 +1674,9 @@ exports.Class = class Class extends Base
16761674

16771675
result = []
16781676
result.push @makeCode "class "
1679-
result.push @makeCode "#{@name} " if @name
1677+
result.push @makeCode @name if @name
1678+
@compileCommentFragments o, @variable, result if @variable?.comments?
1679+
result.push @makeCode ' ' if @name
16801680
result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent
16811681

16821682
result.push @makeCode '{'
@@ -2486,7 +2486,7 @@ exports.FuncGlyph = class FuncGlyph extends Base
24862486
# When for the purposes of walking the contents of a function body, the Code
24872487
# has no *children* -- they're within the inner scope.
24882488
exports.Code = class Code extends Base
2489-
constructor: (params, body, @funcGlyph) ->
2489+
constructor: (params, body, @funcGlyph, @paramStart) ->
24902490
super()
24912491

24922492
@params = params or []
@@ -2686,6 +2686,10 @@ exports.Code = class Code extends Base
26862686
modifiers.push '*'
26872687

26882688
signature = [@makeCode '(']
2689+
# Block comments between a function name and `(` get output between
2690+
# `function` and `(`.
2691+
if @paramStart?.comments?
2692+
@compileCommentFragments o, @paramStart, signature
26892693
for param, i in params
26902694
signature.push @makeCode ', ' if i isnt 0
26912695
signature.push @makeCode '...' if haveSplatParam and i is params.length - 1
@@ -3339,13 +3343,21 @@ exports.Parens = class Parens extends Base
33393343

33403344
compileNode: (o) ->
33413345
expr = @body.unwrap()
3342-
if expr instanceof Value and expr.isAtomic() and not @csxAttribute
3346+
# If these parentheses are wrapping an `IdentifierLiteral` followed by a
3347+
# block comment, output the parentheses (or put another way, don’t optimize
3348+
# away these redundant parentheses). This is because Flow requires
3349+
# parentheses in certain circumstances to distinguish identifiers followed
3350+
# by comment-based type annotations from JavaScript labels.
3351+
shouldWrapComment = expr.comments?.some(
3352+
(comment) -> comment.here and not comment.unshift and not comment.newLine)
3353+
if expr instanceof Value and expr.isAtomic() and not @csxAttribute and not shouldWrapComment
33433354
expr.front = @front
33443355
return expr.compileToFragments o
33453356
fragments = expr.compileToFragments o, LEVEL_PAREN
3346-
bare = o.level < LEVEL_OP and (expr instanceof Op or expr.unwrap() instanceof Call or
3347-
(expr instanceof For and expr.returns)) and (o.level < LEVEL_COND or
3348-
fragments.length <= 3)
3357+
bare = o.level < LEVEL_OP and not shouldWrapComment and (
3358+
expr instanceof Op or expr.unwrap() instanceof Call or
3359+
(expr instanceof For and expr.returns)
3360+
) and (o.level < LEVEL_COND or fragments.length <= 3)
33493361
return @wrapInBraces fragments if @csxAttribute
33503362
if bare then fragments else @wrapInParentheses fragments
33513363

src/rewriter.coffee

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,6 @@ CONTROL_IN_IMPLICIT = ['IF', 'TRY', 'FINALLY', 'CATCH', 'CLASS', 'SWITCH']
693693
DISCARDED = ['(', ')', '[', ']', '{', '}', '.', '..', '...', ',', '=', '++', '--', '?',
694694
'AS', 'AWAIT', 'CALL_START', 'CALL_END', 'DEFAULT', 'ELSE', 'EXTENDS', 'EXPORT',
695695
'FORIN', 'FOROF', 'FORFROM', 'IMPORT', 'INDENT', 'INDEX_SOAK', 'LEADING_WHEN',
696-
'OUTDENT', 'PARAM_START', 'PARAM_END', 'REGEX_START', 'REGEX_END', 'RETURN',
697-
'STRING_END', 'THROW', 'UNARY', 'YIELD'
696+
'OUTDENT', 'PARAM_END', 'REGEX_START', 'REGEX_END', 'RETURN', 'STRING_END', 'THROW',
697+
'UNARY', 'YIELD'
698698
].concat IMPLICIT_UNSPACED_CALL.concat IMPLICIT_END.concat CALL_CLOSERS.concat CONTROL_IN_IMPLICIT

test/comments.coffee

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,3 +973,103 @@ test "Flow comment-based syntax support", ->
973973
fn = function(str/*: string */, num/*: number */)/*: string */ {
974974
return str + num;
975975
};'''
976+
977+
test "#4706: Flow comments around function parameters", ->
978+
eqJS '''
979+
identity = ###::<T>### (value ###: T ###) ###: T ### ->
980+
value
981+
''', '''
982+
var identity;
983+
984+
identity = function/*::<T>*/(value/*: T */)/*: T */ {
985+
return value;
986+
};'''
987+
988+
test "#4706: Flow comments around function parameters", ->
989+
eqJS '''
990+
copy = arr.map(###:: <T> ###(item ###: T ###) ###: T ### => item)
991+
''', '''
992+
var copy;
993+
994+
copy = arr.map(/*:: <T> */(item/*: T */)/*: T */ => {
995+
return item;
996+
});'''
997+
998+
test "#4706: Flow comments after class name", ->
999+
eqJS '''
1000+
class Container ###::<T> ###
1001+
method: ###::<U> ### () -> true
1002+
''', '''
1003+
var Container;
1004+
1005+
Container = class Container/*::<T> */ {
1006+
method() {
1007+
return true;
1008+
}
1009+
1010+
};'''
1011+
1012+
test "#4706: Identifiers with comments wrapped in parentheses remain wrapped", ->
1013+
eqJS '(arr ###: Array<number> ###)', '(arr/*: Array<number> */);'
1014+
eqJS 'other = (arr ###: any ###)', '''
1015+
var other;
1016+
1017+
other = (arr/*: any */);'''
1018+
1019+
test "#4706: Flow comments before class methods", ->
1020+
eqJS '''
1021+
class Container
1022+
###::
1023+
method: (number) => string;
1024+
method: (string) => number;
1025+
###
1026+
method: -> true
1027+
''', '''
1028+
var Container;
1029+
1030+
Container = class Container {
1031+
/*::
1032+
method: (number) => string;
1033+
method: (string) => number;
1034+
*/
1035+
method() {
1036+
return true;
1037+
}
1038+
1039+
};'''
1040+
1041+
test "#4706: Flow comments for class method params", ->
1042+
eqJS '''
1043+
class Container
1044+
method: (param ###: string ###) -> true
1045+
''', '''
1046+
var Container;
1047+
1048+
Container = class Container {
1049+
method(param/*: string */) {
1050+
return true;
1051+
}
1052+
1053+
};'''
1054+
1055+
test "#4706: Flow comments for class method returns", ->
1056+
eqJS '''
1057+
class Container
1058+
method: () ###: string ### -> true
1059+
''', '''
1060+
var Container;
1061+
1062+
Container = class Container {
1063+
method()/*: string */ {
1064+
return true;
1065+
}
1066+
1067+
};'''
1068+
1069+
test "#4706: Flow comments for function spread", ->
1070+
eqJS '''
1071+
method = (...rest ###: Array<string> ###) =>
1072+
''', '''
1073+
var method;
1074+
1075+
method = (...rest/*: Array<string> */) => {};'''

test/modules.coffee

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ test "multiline simple import", ->
171171
bar as baz
172172
} from 'lib';"""
173173

174-
175174
test "multiline complex import", ->
176175
eqJS """
177176
import foo, {
@@ -474,7 +473,6 @@ test "export default named member, within an object", ->
474473
bar
475474
};"""
476475

477-
478476
# Import and export in the same statement
479477

480478
test "export an entire module's contents", ->

0 commit comments

Comments
 (0)