1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

[Enhancement] CSX fragments syntax (#4802) (#4804)

* CSX fragments

* regex improvement; tests

* regex improvement

* regex improvement

* bug fix; regex

* Fix style

* Split fragment tests
This commit is contained in:
zdenko 2017-12-09 07:41:03 +01:00 committed by Geoffrey Booth
parent 5bc85b8f6d
commit 7f5ab7eb0d
4 changed files with 60 additions and 10 deletions

View file

@ -17,12 +17,10 @@
// from our rules and saves it into `lib/parser.js`. // from our rules and saves it into `lib/parser.js`.
// The only dependency is on the **Jison.Parser**. // The only dependency is on the **Jison.Parser**.
var Parser, alt, alternatives, grammar, log, name, o, operators, token, tokens, unwrap; var Parser, alt, alternatives, grammar, name, o, operators, token, tokens, unwrap;
({Parser} = require('jison')); ({Parser} = require('jison'));
log = console.log;
// Jison DSL // Jison DSL
// --------- // ---------

View file

@ -10,7 +10,7 @@
// where locationData is {first_line, first_column, last_line, last_column}, which is a // where locationData is {first_line, first_column, last_line, last_column}, which is a
// format that can be fed directly into [Jison](https://github.com/zaach/jison). These // format that can be fed directly into [Jison](https://github.com/zaach/jison). These
// are read by jison in the `parser.lexer` function defined in coffeescript.coffee. // are read by jison in the `parser.lexer` function defined in coffeescript.coffee.
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, CSX_ATTRIBUTE, CSX_IDENTIFIER, CSX_INTERPOLATION, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_CSX, INVERSES, JSTOKEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, UNICODE_CODE_POINT_ESCAPE, VALID_FLAGS, WHITESPACE, attachCommentsToNode, compact, count, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, repeat, starts, throwSyntaxError, var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, CSX_ATTRIBUTE, CSX_FRAGMENT_IDENTIFIER, CSX_IDENTIFIER, CSX_INTERPOLATION, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_CSX, INVERSES, JSTOKEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, UNICODE_CODE_POINT_ESCAPE, VALID_FLAGS, WHITESPACE, attachCommentsToNode, compact, count, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, repeat, starts, throwSyntaxError,
indexOf = [].indexOf; indexOf = [].indexOf;
({Rewriter, INVERSES} = require('./rewriter')); ({Rewriter, INVERSES} = require('./rewriter'));
@ -741,7 +741,7 @@
// Check the previous token to detect if attribute is spread. // Check the previous token to detect if attribute is spread.
prevChar = this.tokens.length > 0 ? this.tokens[this.tokens.length - 1][0] : ''; prevChar = this.tokens.length > 0 ? this.tokens[this.tokens.length - 1][0] : '';
if (firstChar === '<') { if (firstChar === '<') {
match = CSX_IDENTIFIER.exec(this.chunk.slice(1)); match = CSX_IDENTIFIER.exec(this.chunk.slice(1)) || CSX_FRAGMENT_IDENTIFIER.exec(this.chunk.slice(1));
// Not the right hand side of an unspaced comparison (i.e. `a<b`). // Not the right hand side of an unspaced comparison (i.e. `a<b`).
if (!(match && (this.csxDepth > 0 || !(prev = this.prev()) || prev.spaced || (ref = prev[0], indexOf.call(COMPARABLE_LEFT_SIDE, ref) < 0)))) { if (!(match && (this.csxDepth > 0 || !(prev = this.prev()) || prev.spaced || (ref = prev[0], indexOf.call(COMPARABLE_LEFT_SIDE, ref) < 0)))) {
return 0; return 0;
@ -793,8 +793,8 @@
delimiter: '>' delimiter: '>'
}); });
}); });
match = CSX_IDENTIFIER.exec(this.chunk.slice(end)); match = CSX_IDENTIFIER.exec(this.chunk.slice(end)) || CSX_FRAGMENT_IDENTIFIER.exec(this.chunk.slice(end));
if (!match || match[0] !== csxTag.name) { if (!match || match[1] !== csxTag.name) {
this.error(`expected corresponding CSX closing tag for ${csxTag.name}`, csxTag.origin[2]); this.error(`expected corresponding CSX closing tag for ${csxTag.name}`, csxTag.origin[2]);
} }
afterTag = end + csxTag.name.length; afterTag = end + csxTag.name.length;
@ -1541,6 +1541,9 @@
CSX_IDENTIFIER = /^(?![\d<])((?:(?!\s)[\.\-$\w\x7f-\uffff])+)/; // Must not start with `<`. CSX_IDENTIFIER = /^(?![\d<])((?:(?!\s)[\.\-$\w\x7f-\uffff])+)/; // Must not start with `<`.
// Like `IDENTIFIER`, but includes `-`s and `.`s. // Like `IDENTIFIER`, but includes `-`s and `.`s.
// Fragment: <></>
CSX_FRAGMENT_IDENTIFIER = /^()>/; // Ends immediately with `>`.
CSX_ATTRIBUTE = /^(?!\d)((?:(?!\s)[\-$\w\x7f-\uffff])+)([^\S]*=(?!=))?/; // Like `IDENTIFIER`, but includes `-`s. CSX_ATTRIBUTE = /^(?!\d)((?:(?!\s)[\-$\w\x7f-\uffff])+)([^\S]*=(?!=))?/; // Like `IDENTIFIER`, but includes `-`s.
// Is this an attribute with a value? // Is this an attribute with a value?

View file

@ -556,7 +556,7 @@ exports.Lexer = class Lexer
# Check the previous token to detect if attribute is spread. # Check the previous token to detect if attribute is spread.
prevChar = if @tokens.length > 0 then @tokens[@tokens.length - 1][0] else '' prevChar = if @tokens.length > 0 then @tokens[@tokens.length - 1][0] else ''
if firstChar is '<' if firstChar is '<'
match = CSX_IDENTIFIER.exec @chunk[1...] match = CSX_IDENTIFIER.exec(@chunk[1...]) or CSX_FRAGMENT_IDENTIFIER.exec(@chunk[1...])
return 0 unless match and ( return 0 unless match and (
@csxDepth > 0 or @csxDepth > 0 or
# Not the right hand side of an unspaced comparison (i.e. `a<b`). # Not the right hand side of an unspaced comparison (i.e. `a<b`).
@ -596,8 +596,8 @@ exports.Lexer = class Lexer
@matchWithInterpolations INSIDE_CSX, '>', '</', CSX_INTERPOLATION @matchWithInterpolations INSIDE_CSX, '>', '</', CSX_INTERPOLATION
@mergeInterpolationTokens tokens, {delimiter: '"'}, (value, i) => @mergeInterpolationTokens tokens, {delimiter: '"'}, (value, i) =>
@formatString value, delimiter: '>' @formatString value, delimiter: '>'
match = CSX_IDENTIFIER.exec @chunk[end...] match = CSX_IDENTIFIER.exec(@chunk[end...]) or CSX_FRAGMENT_IDENTIFIER.exec(@chunk[end...])
if not match or match[0] isnt csxTag.name if not match or match[1] isnt csxTag.name
@error "expected corresponding CSX closing tag for #{csxTag.name}", @error "expected corresponding CSX closing tag for #{csxTag.name}",
csxTag.origin[2] csxTag.origin[2]
afterTag = end + csxTag.name.length afterTag = end + csxTag.name.length
@ -1178,6 +1178,11 @@ CSX_IDENTIFIER = /// ^
( (?: (?!\s)[\.\-$\w\x7f-\uffff] )+ ) # Like `IDENTIFIER`, but includes `-`s and `.`s. ( (?: (?!\s)[\.\-$\w\x7f-\uffff] )+ ) # Like `IDENTIFIER`, but includes `-`s and `.`s.
/// ///
# Fragment: <></>
CSX_FRAGMENT_IDENTIFIER = /// ^
()> # Ends immediately with `>`.
///
CSX_ATTRIBUTE = /// ^ CSX_ATTRIBUTE = /// ^
(?!\d) (?!\d)
( (?: (?!\s)[\-$\w\x7f-\uffff] )+ ) # Like `IDENTIFIER`, but includes `-`s. ( (?: (?!\s)[\-$\w\x7f-\uffff] )+ ) # Like `IDENTIFIER`, but includes `-`s.

View file

@ -742,3 +742,47 @@ test '#4686: comments inside interpolations that also contain CSX attributes', -
</div>; </div>;
''' '''
# https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
test 'JSX fragments: empty fragment', ->
eqJS '''
<></>
''', '''
<></>;
'''
test 'JSX fragments: fragment with text nodes', ->
eqJS '''
<>
Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
</>
''', '''
<>
Some text.
<h2>A heading</h2>
More text.
<h2>Another heading</h2>
Even more text.
</>;
'''
test 'JSX fragments: fragment with component nodes', ->
eqJS '''
Component = (props) =>
<Fragment>
<OtherComponent />
<OtherComponent />
</Fragment>
''', '''
var Component;
Component = (props) => {
return <Fragment>
<OtherComponent />
<OtherComponent />
</Fragment>;
};
'''