if options.ast
+ nodes.allCommentTokens = helpers.extractAllCommentTokens tokens
+ sourceCodeNumberOfLines = (code.match(/\r?\n/g) or '').length + 1
+ sourceCodeLastLine = /.*$/.exec(code)[0] # `.*` matches all but line break characters.
+ ast = nodes.ast options
+ range = [0, code.length]
+ ast.start = ast.program.start = range[0]
+ ast.end = ast.program.end = range[1]
+ ast.range = ast.program.range = range
+ ast.loc.start = ast.program.loc.start = {line: 1, column: 0}
+ ast.loc.end.line = ast.program.loc.end.line = sourceCodeNumberOfLines
+ ast.loc.end.column = ast.program.loc.end.column = sourceCodeLastLine.length
+ ast.tokens = tokens
+ return ast
+
+ fragments = nodes.compileToFragments options
currentLine = 0
currentLine += 1 if options.header
@@ -409,11 +443,11 @@ the same name.
-
Update the sourcemap with data from each fragment.
@@ -424,11 +458,11 @@ the same name. -Do not include empty, whitespace, or semicolon-only fragments.
@@ -449,11 +483,11 @@ the same name. -Copy the code from each fragment into the final JavaScript.
@@ -474,11 +508,11 @@ the same name. -This only happens if run via the Node API and transpile
is set to
something other than an object.
Get the reference to Babel that we have been passed if this compiler is run via the CLI or Node API.
@@ -509,11 +543,11 @@ is run via the CLI or Node API. -See https://github.com/babel/babel/issues/827#issuecomment-77573107:
Babel can take a v3 source map object as input in inputSourceMap
@@ -548,11 +582,11 @@ and it will return an updated v3 source map object in its output.
Tokenize a string of CoffeeScript code, and return the array of tokens.
@@ -564,11 +598,11 @@ and it will return an updated v3 source map object in its output. -Parse a string of CoffeeScript code or an array of lexed tokens, and
return the AST. You can then compile it by calling .compile()
on the root,
@@ -577,19 +611,17 @@ or traverse it by using .traverseChildren()
with a callback.
exports.nodes = withPrettyErrors (source, options) ->
- if typeof source is 'string'
- parser.parse lexer.tokenize source, options
- else
- parser.parse source
This file used to export these methods; leave stubs that throw warnings
instead. These methods have been moved into index.coffee
to provide
@@ -605,11 +637,11 @@ environment.
Instantiate a Lexer for our use here.
@@ -620,11 +652,11 @@ environment. -The real Lexer produces a generic stream of tokens. This object provides a thin wrapper around it, compatible with the Jison API. We can then pass it @@ -633,6 +665,10 @@ directly as a “Jison lexer.”
parser.lexer =
+ yylloc:
+ range: []
+ options:
+ ranges: yes
lex: ->
token = parser.tokens[@pos++]
if token
@@ -650,11 +686,11 @@ directly as a “Jison lexer.”
-
Make all the AST nodes visible to the parser.
@@ -665,11 +701,11 @@ directly as a “Jison lexer.” -Override Jison’s default error handling function.
@@ -680,11 +716,11 @@ directly as a “Jison lexer.” -Disregard Jison’s message, it contains redundant line number information. Disregard the token, we take its value directly from the lexer in case @@ -708,11 +744,11 @@ the error is caused by a generated token which might refer to its origin.
-The second argument has a loc
property, which should have the location
data for this token. Unfortunately, Jison seems to send an outdated loc
@@ -726,11 +762,11 @@ from the lexer.
Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js Modified to handle sourceMap
@@ -758,11 +794,11 @@ Modified to handle sourceMap -Check for a sourceMap position
@@ -805,11 +841,11 @@ Modified to handle sourceMap -Skip files that we didn’t compile, like Node system files that appear in the stack trace, as they never have source maps.
@@ -824,11 +860,11 @@ the stack trace, as they never have source maps. -CoffeeScript compiled in a browser or via CoffeeScript.compile
or .run
may get compiled with options.filename
that’s missing, which becomes
@@ -843,11 +879,11 @@ filename of the script file. See if we have a source map cached under
-
Work backwards from the most recent anonymous source maps, until we find one that works. This isn’t foolproof; there is a chance that multiple @@ -864,11 +900,11 @@ and it’s not foolproof either.
-If all else fails, recompile this source to get a source map. We need the
previous section (for <anonymous>
) despite this option, because after it
@@ -891,11 +927,11 @@ time the source map we want is the last one.
Based on michaelficarra/CoffeeScriptRedux
NodeJS / V8 have no support for transforming positions in stack traces using
diff --git a/docs/v2/annotated-source/command.html b/docs/v2/annotated-source/command.html
index c98d4a44..47705d6c 100644
--- a/docs/v2/annotated-source/command.html
+++ b/docs/v2/annotated-source/command.html
@@ -199,12 +199,14 @@ useWinPathSep = path.sep is SWITCHES = [
+ [ '--ast', 'generate an abstract syntax tree of nodes']
['-b', '--bare', 'compile without a top-level function wrapper']
['-c', '--compile', 'compile to JavaScript and save as .js files']
['-e', '--eval', 'pass a string from the command line as input']
['-h', '--help', 'display this help message']
['-i', '--interactive', 'run an interactive CoffeeScript REPL']
['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling']
+ ['-l', '--literate', 'treat stdio as literate style coffeescript']
['-m', '--map', 'generate source map and save as .js.map files']
['-M', '--inline-map', 'generate source map and include it directly in output']
['-n', '--nodes', 'print out the parse tree that the parser produces']
@@ -214,7 +216,6 @@ useWinPathSep = path.sep is '-p', '--print', 'print out the compiled JavaScript']
['-r', '--require [MODULE*]', 'require the given module before eval or REPL']
['-s', '--stdio', 'listen for and compile scripts over stdio']
- ['-l', '--literate', 'treat stdio as literate style coffeescript']
['-t', '--transpile', 'pipe generated JavaScript through Babel']
[ '--tokens', 'print out the tokens that the lexer/rewriter produce']
['-v', '--version', 'display the version number']
@@ -460,6 +461,9 @@ requested options. If evaluating the script directly, set
__filename
else if opts.nodes
printLine CoffeeScript.nodes(task.input, task.options).toString().trim()
+ else if opts.ast
+ compiled = CoffeeScript.compile task.input, task.options
+ printLine JSON.stringify(compiled, null, 2)
else if opts.run
CoffeeScript.register()
CoffeeScript.eval opts.prelude, task.options if opts.prelude
@@ -960,6 +964,7 @@ along.
transpile: opts.transpile
sourceMap: opts.map
inlineMap: opts['inline-map']
+ ast: opts.ast
if filename
if base
diff --git a/docs/v2/annotated-source/grammar.html b/docs/v2/annotated-source/grammar.html
index 57d97607..e9a2fbad 100644
--- a/docs/v2/annotated-source/grammar.html
+++ b/docs/v2/annotated-source/grammar.html
@@ -244,12 +244,13 @@ If the parameter is not a node, it will just be passed through unaffected.
getAddDataToNodeFunctionString = (first, last) ->
- "yy.addDataToNode(yy, @#{first}#{if last then ", @#{last}" else ''})"
+
getAddDataToNodeFunctionString = (first, last, forceUpdateLocation = yes) ->
+ "yy.addDataToNode(yy, @#{first}, #{if first[0] is '$' then '$$' else '$'}#{first}, #{if last then "@#{last}, #{if last[0] is '$' then '$$' else '$'}#{last}" else 'null, null'}, #{if forceUpdateLocation then 'true' else 'false'})"
+ returnsLoc = /^LOC/.test action
action = action.replace /LOC\(([0-9]*)\)/g, getAddDataToNodeFunctionString('$1')
action = action.replace /LOC\(([0-9]*),\s*([0-9]*)\)/g, getAddDataToNodeFunctionString('$1', '$2')
- performActionFunctionString = "$$ = #{getAddDataToNodeFunctionString(1, patternCount)}(#{action});"
+ performActionFunctionString = "$$ = #{getAddDataToNodeFunctionString(1, patternCount, not returnsLoc)}(#{action});"
else
performActionFunctionString = '$$ = $1;'
@@ -317,8 +318,8 @@ all parsing must end here.
Root: [
- o '', -> new Block
- o 'Body'
+ o '', -> new Root new Block
+ o 'Body', -> new Root $1
]
AlphaNumeric: [
- o 'NUMBER', -> new NumberLiteral $1
+ o 'NUMBER', -> new NumberLiteral $1.toString(), parsedValue: $1.parsedValue
o 'String'
]
String: [
- o 'STRING', -> new StringLiteral $1
- o 'STRING_START Body STRING_END', -> new StringWithInterpolations $2
+ o 'STRING', ->
+ new StringLiteral(
+ $1.slice 1, -1 # strip artificial quotes and unwrap to primitive string
+ quote: $1.quote
+ initialChunk: $1.initialChunk
+ finalChunk: $1.finalChunk
+ indent: $1.indent
+ double: $1.double
+ heregex: $1.heregex
+ )
+ o 'STRING_START Interpolations STRING_END', -> new StringWithInterpolations Block.wrap($2), quote: $1.quote, startQuote: LOC(1)(new Literal $1.toString())
]
- Regex: [
- o 'REGEX', -> new RegexLiteral $1
- o 'REGEX_START Invocation REGEX_END', -> new RegexWithInterpolations $2.args
+ Interpolations: [
+ o 'InterpolationChunk', -> [$1]
+ o 'Interpolations InterpolationChunk', -> $1.concat $2
+ ]
+
+ InterpolationChunk: [
+ o 'INTERPOLATION_START Body INTERPOLATION_END', -> new Interpolation $2
+ o 'INTERPOLATION_START INDENT Body OUTDENT INTERPOLATION_END', -> new Interpolation $3
+ o 'INTERPOLATION_START INTERPOLATION_END', -> new Interpolation
+ o 'String', -> $1
]
All of our immediate values. Generally these can be passed straight -through and printed to JavaScript.
+The .toString() calls here and elsewhere are to convert String
objects
+back to primitive strings now that we’ve retrieved stowaway extra properties
Literal: [
- o 'AlphaNumeric'
- o 'JS', -> new PassthroughLiteral $1
- o 'Regex'
- o 'UNDEFINED', -> new UndefinedLiteral $1
- o 'NULL', -> new NullLiteral $1
- o 'BOOL', -> new BooleanLiteral $1
- o 'INFINITY', -> new InfinityLiteral $1
- o 'NAN', -> new NaNLiteral $1
+ Regex: [
+ o 'REGEX', -> new RegexLiteral $1.toString(), delimiter: $1.delimiter, heregexCommentTokens: $1.heregexCommentTokens
+ o 'REGEX_START Invocation REGEX_END', -> new RegexWithInterpolations $2, heregexCommentTokens: $3.heregexCommentTokens
]
@@ -538,6 +554,31 @@ through and printed to JavaScript.
+ All of our immediate values. Generally these can be passed straight
+through and printed to JavaScript.
+
+
Literal: [
+ o 'AlphaNumeric'
+ o 'JS', -> new PassthroughLiteral $1.toString(), here: $1.here, generated: $1.generated
+ o 'Regex'
+ o 'UNDEFINED', -> new UndefinedLiteral $1
+ o 'NULL', -> new NullLiteral $1
+ o 'BOOL', -> new BooleanLiteral $1.toString(), originalValue: $1.original
+ o 'INFINITY', -> new InfinityLiteral $1.toString(), originalValue: $1.original
+ o 'NAN', -> new NaNLiteral $1
+ ]
Assignment of a variable, property, or index to a value.
Assignment when it happens within an object literal. The difference from the ordinary Assign is that these allow numbers and strings as keys.
@@ -586,17 +627,18 @@ the ordinary Assign is that these allow numbers and strings as ObjAssignable: [ o 'SimpleObjAssignable' o '[ Expression ]', -> new Value new ComputedPropertyName $2 + o '@ [ Expression ]', -> new Value LOC(1)(new ThisLiteral $1), [LOC(3)(new ComputedPropertyName($3))], 'this' o 'AlphaNumeric' ]Object literal spread properties.
@@ -604,9 +646,9 @@ the ordinary Assign is that these allow numbers and strings as ObjRestValue: [
o 'SimpleObjAssignable ...', -> new Splat new Value $1
- o '... SimpleObjAssignable', -> new Splat new Value $2
+ o '... SimpleObjAssignable', -> new Splat new Value($2), postfix: no
o 'ObjSpreadExpr ...', -> new Splat $1
- o '... ObjSpreadExpr', -> new Splat $2
+ o '... ObjSpreadExpr', -> new Splat $2, postfix: no
]
ObjSpreadExpr: [
@@ -627,18 +669,19 @@ the ordinary Assign is that these allow numbers and strings as
]
ObjSpreadAccessor: [
- o '. Property', -> new Access $2
- o 'INDEX_START IndexValue INDEX_END', -> $2
+ o '. Property', -> new Access $2
+ o 'INDEX_START IndexValue INDEX_END', -> $2
+ o 'INDEX_START INDENT IndexValue OUTDENT INDEX_END', -> $3
]
A return statement from a function body.
@@ -651,23 +694,23 @@ the ordinary Assign is that these allow numbers and strings as ] YieldReturn: [ - o 'YIELD RETURN Expression', -> new YieldReturn $3 - o 'YIELD RETURN', -> new YieldReturn + o 'YIELD RETURN Expression', -> new YieldReturn $3, returnKeyword: LOC(2)(new Literal $2) + o 'YIELD RETURN', -> new YieldReturn null, returnKeyword: LOC(2)(new Literal $2) ] AwaitReturn: [ - o 'AWAIT RETURN Expression', -> new AwaitReturn $3 - o 'AWAIT RETURN', -> new AwaitReturn + o 'AWAIT RETURN Expression', -> new AwaitReturn $3, returnKeyword: LOC(2)(new Literal $2) + o 'AWAIT RETURN', -> new AwaitReturn null, returnKeyword: LOC(2)(new Literal $2) ]The Code node is the function literal. It’s defined by an indented block of Block preceded by a function arrow, with an optional parameter list.
@@ -682,11 +725,11 @@ of Block preceded by a function arrow, with an optional paramet -The Codeline is the Code node with Line instead of indented Block.
@@ -701,11 +744,11 @@ of Block preceded by a function arrow, with an optional paramet -CoffeeScript has two different symbols for functions. ->
is for ordinary
functions, and =>
is for functions bound to the current value of this.
=>
is for functions bound to the current value of
- An optional, trailing comma.
@@ -738,11 +781,11 @@ functions, and=>
is for functions bound to the current value of
- The list of parameters that a function accepts can be of any length.
@@ -759,11 +802,11 @@ functions, and=>
is for functions bound to the current value of
- A single parameter in a function definition can be ordinary, or a splat that hoovers up the remaining arguments.
@@ -773,7 +816,7 @@ that hoovers up the remaining arguments. Param: [
o 'ParamVar', -> new Param $1
o 'ParamVar ...', -> new Param $1, null, on
- o '... ParamVar', -> new Param $2, null, on
+ o '... ParamVar', -> new Param $2, null, postfix: no
o 'ParamVar = Expression', -> new Param $1, $3
o '...', -> new Expansion
]
Function Parameters
@@ -801,11 +844,11 @@ that hoovers up the remaining arguments. -A splat that occurs outside of a parameter list.
@@ -813,17 +856,17 @@ that hoovers up the remaining arguments. Splat: [
o 'Expression ...', -> new Splat $1
- o '... Expression', -> new Splat $2
+ o '... Expression', -> new Splat $2, {postfix: no}
]
Variables and properties that can be assigned to.
@@ -839,11 +882,11 @@ that hoovers up the remaining arguments. -Everything that can be assigned to.
@@ -858,11 +901,11 @@ that hoovers up the remaining arguments. -The types of things that can be treated as values – assigned to, invoked as functions, indexed into, named as a class, etc.
@@ -875,26 +918,10 @@ as functions, indexed into, named as a class, etc. o 'Parenthetical', -> new Value $1 o 'Range', -> new Value $1 o 'Invocation', -> new Value $1 + o 'DoIife', -> new Value $1 o 'This' o 'Super', -> new Value $1 - ]A super
-based expression that can be used as a value.
Super: [
- o 'SUPER . Property', -> new Super LOC(3)(new Access $3), [], no, $1
- o 'SUPER INDEX_START Expression INDEX_END', -> new Super LOC(3)(new Index $3), [], no, $1
+ o 'MetaProperty', -> new Value $1
]
The general group of accessors into an object, by property, by prototype -or by array index or slice.
+A super
-based expression that can be used as a value.
Accessor: [
- o '. Property', -> new Access $2
- o '?. Property', -> new Access $2, 'soak'
- o ':: Property', -> [LOC(1)(new Access new PropertyName('prototype')), LOC(2)(new Access $2)]
- o '?:: Property', -> [LOC(1)(new Access new PropertyName('prototype'), 'soak'), LOC(2)(new Access $2)]
- o '::', -> new Access new PropertyName 'prototype'
- o '?::', -> new Access new PropertyName('prototype'), 'soak'
- o 'Index'
+ Super: [
+ o 'SUPER . Property', -> new Super LOC(3)(new Access $3), LOC(1)(new Literal $1)
+ o 'SUPER INDEX_START Expression INDEX_END', -> new Super LOC(3)(new Index $3), LOC(1)(new Literal $1)
+ o 'SUPER INDEX_START INDENT Expression OUTDENT INDEX_END', -> new Super LOC(4)(new Index $4), LOC(1)(new Literal $1)
]
@@ -930,18 +952,12 @@ or by array index or slice.
- Indexing into an object or array using bracket notation.
+ A “meta-property” access e.g. new.target
Index: [
- o 'INDEX_START IndexValue INDEX_END', -> $2
- o 'INDEX_SOAK Index', -> extend $2, soak: yes
- ]
-
- IndexValue: [
- o 'Expression', -> new Index $1
- o 'Slice', -> new Slice $1
+ MetaProperty: [
+ o 'NEW_TARGET . Property', -> new MetaProperty LOC(1)(new IdentifierLiteral $1), LOC(3)(new Access $3)
]
@@ -953,6 +969,54 @@ or by array index or slice.
+ The general group of accessors into an object, by property, by prototype
+or by array index or slice.
+
+
Accessor: [
+ o '. Property', -> new Access $2
+ o '?. Property', -> new Access $2, soak: yes
+ o ':: Property', -> [LOC(1)(new Access new PropertyName('prototype'), shorthand: yes), LOC(2)(new Access $2)]
+ o '?:: Property', -> [LOC(1)(new Access new PropertyName('prototype'), shorthand: yes, soak: yes), LOC(2)(new Access $2)]
+ o '::', -> new Access new PropertyName('prototype'), shorthand: yes
+ o '?::', -> new Access new PropertyName('prototype'), shorthand: yes, soak: yes
+ o 'Index'
+ ]
Indexing into an object or array using bracket notation.
+ + Index: [
+ o 'INDEX_START IndexValue INDEX_END', -> $2
+ o 'INDEX_START INDENT IndexValue OUTDENT INDEX_END', -> $3
+ o 'INDEX_SOAK Index', -> extend $2, soak: yes
+ ]
+
+ IndexValue: [
+ o 'Expression', -> new Index $1
+ o 'Slice', -> new Slice $1
+ ]
In CoffeeScript, an object literal is simply a list of assignments.
Assignment of properties within an object literal can be separated by comma, as in JavaScript, or simply by newline.
@@ -986,11 +1050,11 @@ comma, as in JavaScript, or simply by newline. -Class definitions have optional bodies of prototype property assignments, and optional references to the superclass.
@@ -1029,8 +1093,8 @@ and optional references to the superclass. ImportSpecifier: [ o 'Identifier', -> new ImportSpecifier $1 o 'Identifier AS Identifier', -> new ImportSpecifier $1, $3 - o 'DEFAULT', -> new ImportSpecifier new Literal $1 - o 'DEFAULT AS Identifier', -> new ImportSpecifier new Literal($1), $3 + o 'DEFAULT', -> new ImportSpecifier LOC(1)(new DefaultLiteral $1) + o 'DEFAULT AS Identifier', -> new ImportSpecifier LOC(1)(new DefaultLiteral($1)), $3 ] ImportDefaultSpecifier: [ @@ -1045,15 +1109,16 @@ and optional references to the superclass. o 'EXPORT { }', -> new ExportNamedDeclaration new ExportSpecifierList [] o 'EXPORT { ExportSpecifierList OptComma }', -> new ExportNamedDeclaration new ExportSpecifierList $3 o 'EXPORT Class', -> new ExportNamedDeclaration $2 - o 'EXPORT Identifier = Expression', -> new ExportNamedDeclaration new Assign $2, $4, null, - moduleDeclaration: 'export' - o 'EXPORT Identifier = TERMINATOR Expression', -> new ExportNamedDeclaration new Assign $2, $5, null, - moduleDeclaration: 'export' - o 'EXPORT Identifier = INDENT Expression OUTDENT', -> new ExportNamedDeclaration new Assign $2, $5, null, - moduleDeclaration: 'export' + o 'EXPORT Identifier = Expression', -> new ExportNamedDeclaration LOC(2,4)(new Assign $2, $4, null, + moduleDeclaration: 'export') + o 'EXPORT Identifier = TERMINATOR Expression', -> new ExportNamedDeclaration LOC(2,5)(new Assign $2, $5, null, + moduleDeclaration: 'export') + o 'EXPORT Identifier = INDENT Expression OUTDENT', -> new ExportNamedDeclaration LOC(2,6)(new Assign $2, $5, null, + moduleDeclaration: 'export') o 'EXPORT DEFAULT Expression', -> new ExportDefaultDeclaration $3 o 'EXPORT DEFAULT INDENT Object OUTDENT', -> new ExportDefaultDeclaration new Value $4 o 'EXPORT EXPORT_ALL FROM String', -> new ExportAllDeclaration new Literal($2), $4 + o 'EXPORT { } FROM String', -> new ExportNamedDeclaration new ExportSpecifierList([]), $5 o 'EXPORT { ExportSpecifierList OptComma } FROM String', -> new ExportNamedDeclaration new ExportSpecifierList($3), $7 ] @@ -1068,47 +1133,9 @@ and optional references to the superclass. ExportSpecifier: [ o 'Identifier', -> new ExportSpecifier $1 o 'Identifier AS Identifier', -> new ExportSpecifier $1, $3 - o 'Identifier AS DEFAULT', -> new ExportSpecifier $1, new Literal $3 - o 'DEFAULT', -> new ExportSpecifier new Literal $1 - o 'DEFAULT AS Identifier', -> new ExportSpecifier new Literal($1), $3 - ]Ordinary function invocation, or a chained series of calls.
- - Invocation: [
- o 'Value OptFuncExist String', -> new TaggedTemplateCall $1, $3, $2
- o 'Value OptFuncExist Arguments', -> new Call $1, $3, $2
- o 'SUPER OptFuncExist Arguments', -> new SuperCall LOC(1)(new Super), $3, $2, $1
- o 'DYNAMIC_IMPORT Arguments', -> new DynamicImportCall LOC(1)(new DynamicImport), $2
- ]
An optional existence check on a function.
- - OptFuncExist: [
- o '', -> no
- o 'FUNC_EXIST', -> yes
+ o 'Identifier AS DEFAULT', -> new ExportSpecifier $1, LOC(3)(new DefaultLiteral $3)
+ o 'DEFAULT', -> new ExportSpecifier LOC(1)(new DefaultLiteral $1)
+ o 'DEFAULT AS Identifier', -> new ExportSpecifier LOC(1)(new DefaultLiteral($1)), $3
]
The list of arguments to a function call.
+Ordinary function invocation, or a chained series of calls.
Arguments: [
- o 'CALL_START CALL_END', -> []
- o 'CALL_START ArgList OptComma CALL_END', -> $2
+ Invocation: [
+ o 'Value OptFuncExist String', -> new TaggedTemplateCall $1, $3, $2.soak
+ o 'Value OptFuncExist Arguments', -> new Call $1, $3, $2.soak
+ o 'SUPER OptFuncExist Arguments', -> new SuperCall LOC(1)(new Super), $3, $2.soak, $1
+ o 'DYNAMIC_IMPORT Arguments', -> new DynamicImportCall LOC(1)(new DynamicImport), $2
]
@@ -1138,6 +1167,42 @@ and optional references to the superclass.
+ An optional existence check on a function.
+
+
OptFuncExist: [
+ o '', -> soak: no
+ o 'FUNC_EXIST', -> soak: yes
+ ]
The list of arguments to a function call.
+ + Arguments: [
+ o 'CALL_START CALL_END', -> []
+ o 'CALL_START ArgList OptComma CALL_END', -> $2.implicit = $1.generated; $2
+ ]
A reference to the this current object.
A reference to a property on this.
@@ -1167,11 +1232,11 @@ and optional references to the superclass. -The array literal.
@@ -1186,59 +1251,19 @@ and optional references to the superclass. -Inclusive and exclusive range dots.
- - RangeDots: [
- o '..', -> 'inclusive'
- o '...', -> 'exclusive'
- ]
The CoffeeScript range literal.
- - Range: [
- o '[ Expression RangeDots Expression ]', -> new Range $2, $4, $3
- o '[ ExpressionLine RangeDots Expression ]', -> new Range $2, $4, $3
- ]
Slice: [
- o 'Expression RangeDots Expression', -> new Range $1, $3, $2
- o 'Expression RangeDots', -> new Range $1, null, $2
- o 'ExpressionLine RangeDots Expression', -> new Range $1, $3, $2
- o 'ExpressionLine RangeDots', -> new Range $1, null, $2
- o 'RangeDots Expression', -> new Range null, $2, $1
- o 'RangeDots', -> new Range null, null, $1
+ RangeDots: [
+ o '..', -> exclusive: no
+ o '...', -> exclusive: yes
]
@@ -1250,6 +1275,46 @@ and optional references to the superclass.
+ The CoffeeScript range literal.
+
+
Range: [
+ o '[ Expression RangeDots Expression ]', -> new Range $2, $4, if $3.exclusive then 'exclusive' else 'inclusive'
+ o '[ ExpressionLine RangeDots Expression ]', -> new Range $2, $4, if $3.exclusive then 'exclusive' else 'inclusive'
+ ]
Array slice literals.
+ + Slice: [
+ o 'Expression RangeDots Expression', -> new Range $1, $3, if $2.exclusive then 'exclusive' else 'inclusive'
+ o 'Expression RangeDots', -> new Range $1, null, if $2.exclusive then 'exclusive' else 'inclusive'
+ o 'ExpressionLine RangeDots Expression', -> new Range $1, $3, if $2.exclusive then 'exclusive' else 'inclusive'
+ o 'ExpressionLine RangeDots', -> new Range $1, null, if $2.exclusive then 'exclusive' else 'inclusive'
+ o 'RangeDots Expression', -> new Range null, $2, if $1.exclusive then 'exclusive' else 'inclusive'
+ o 'RangeDots', -> new Range null, null, if $1.exclusive then 'exclusive' else 'inclusive'
+ ]
The ArgList is the list of objects passed into a function call (i.e. comma-separated expressions). Newlines work as well.
@@ -1266,11 +1331,11 @@ and optional references to the superclass. -Valid arguments are Blocks or Splats.
@@ -1286,11 +1351,11 @@ and optional references to the superclass. -The ArgElisionList is the list of objects, contents of an array literal (i.e. comma-separated expressions and elisions). Newlines work as well.
@@ -1300,7 +1365,7 @@ and optional references to the superclass. ArgElisionList: [
o 'ArgElision'
o 'ArgElisionList , ArgElision', -> $1.concat $3
- o 'ArgElisionList OptElisions TERMINATOR ArgElision', -> $1.concat $2, $4
+ o 'ArgElisionList OptComma TERMINATOR ArgElision', -> $1.concat $4
o 'INDENT ArgElisionList OptElisions OUTDENT', -> $2.concat $3
o 'ArgElisionList OptElisions INDENT ArgElisionList OptElisions OUTDENT', -> $1.concat $2, $4, $5
]
@@ -1322,16 +1387,17 @@ and optional references to the superclass.
Elision: [
o ',', -> new Elision
+ o 'Elision TERMINATOR', -> $1
]
Just simple, comma-separated, required arguments (no fancy syntax). We need this to be separate from the ArgList for use in Switch blocks, where @@ -1349,11 +1415,11 @@ having the newlines wouldn’t make sense.
-The variants of try/catch/finally exception handling blocks.
@@ -1361,38 +1427,38 @@ having the newlines wouldn’t make sense. Try: [
o 'TRY Block', -> new Try $2
- o 'TRY Block Catch', -> new Try $2, $3[0], $3[1]
- o 'TRY Block FINALLY Block', -> new Try $2, null, null, $4
- o 'TRY Block Catch FINALLY Block', -> new Try $2, $3[0], $3[1], $5
+ o 'TRY Block Catch', -> new Try $2, $3
+ o 'TRY Block FINALLY Block', -> new Try $2, null, $4, LOC(3)(new Literal $3)
+ o 'TRY Block Catch FINALLY Block', -> new Try $2, $3, $5, LOC(4)(new Literal $4)
]
Catch: [
- o 'CATCH Identifier Block', -> [$2, $3]
- o 'CATCH Object Block', -> [LOC(2)(new Value($2)), $3]
- o 'CATCH Block', -> [null, $2]
+ o 'CATCH Identifier Block', -> new Catch $3, $2
+ o 'CATCH Object Block', -> new Catch $3, LOC(2)(new Value($2))
+ o 'CATCH Block', -> new Catch $2
]
Throw an exception object.
@@ -1406,11 +1472,11 @@ having the newlines wouldn’t make sense. -Parenthetical expressions. Note that the Parenthetical is a Value, not an Expression, so if you need to use an expression in a place @@ -1427,11 +1493,11 @@ the trick.
-The condition portion of a while loop.
@@ -1456,11 +1522,11 @@ the trick. -The while loop can either be normal, with a block of expressions to execute, or postfix, with a single expression. There is no do..while.
@@ -1470,24 +1536,24 @@ or postfix, with a single expression. There is no do..while. While: [
o 'WhileSource Block', -> $1.addBody $2
o 'WhileLineSource Block', -> $1.addBody $2
- o 'Statement WhileSource', -> $2.addBody LOC(1) Block.wrap([$1])
- o 'Expression WhileSource', -> $2.addBody LOC(1) Block.wrap([$1])
+ o 'Statement WhileSource', -> (Object.assign $2, postfix: yes).addBody LOC(1) Block.wrap([$1])
+ o 'Expression WhileSource', -> (Object.assign $2, postfix: yes).addBody LOC(1) Block.wrap([$1])
o 'Loop', -> $1
]
Loop: [
- o 'LOOP Block', -> new While(LOC(1) new BooleanLiteral 'true').addBody $2
- o 'LOOP Expression', -> new While(LOC(1) new BooleanLiteral 'true').addBody LOC(2) Block.wrap [$2]
+ o 'LOOP Block', -> new While(LOC(1)(new BooleanLiteral 'true'), isLoop: yes).addBody $2
+ o 'LOOP Expression', -> new While(LOC(1)(new BooleanLiteral 'true'), isLoop: yes).addBody LOC(2) Block.wrap [$2]
]
Array, object, and range comprehensions, at the most generic level. Comprehensions can either be normal, with a block of expressions to execute, @@ -1496,8 +1562,8 @@ or postfix, with a single expression.
For: [
- o 'Statement ForBody', -> $2.addBody $1
- o 'Expression ForBody', -> $2.addBody $1
+ o 'Statement ForBody', -> $2.postfix = yes; $2.addBody $1
+ o 'Expression ForBody', -> $2.postfix = yes; $2.addBody $1
o 'ForBody Block', -> $1.addBody $2
o 'ForLineBody Block', -> $1.addBody $2
]
@@ -1526,11 +1592,11 @@ or postfix, with a single expression.
-
An array of all accepted values for a variable inside the loop. This enables support for pattern matching.
@@ -1547,11 +1613,11 @@ This enables support for pattern matching. -An array or range comprehension has variables for the current element and (optional) reference to the current index. Or, key, value, in the case @@ -1567,11 +1633,11 @@ of object comprehensions.
-The source of a comprehension is an array or object with an optional guard clause. If it’s an array comprehension, you can also choose to step through @@ -1626,43 +1692,43 @@ in fixed-size increments.
Switch: [ o 'SWITCH Expression INDENT Whens OUTDENT', -> new Switch $2, $4 o 'SWITCH ExpressionLine INDENT Whens OUTDENT', -> new Switch $2, $4 - o 'SWITCH Expression INDENT Whens ELSE Block OUTDENT', -> new Switch $2, $4, $6 - o 'SWITCH ExpressionLine INDENT Whens ELSE Block OUTDENT', -> new Switch $2, $4, $6 + o 'SWITCH Expression INDENT Whens ELSE Block OUTDENT', -> new Switch $2, $4, LOC(5,6) $6 + o 'SWITCH ExpressionLine INDENT Whens ELSE Block OUTDENT', -> new Switch $2, $4, LOC(5,6) $6 o 'SWITCH INDENT Whens OUTDENT', -> new Switch null, $3 - o 'SWITCH INDENT Whens ELSE Block OUTDENT', -> new Switch null, $3, $5 + o 'SWITCH INDENT Whens ELSE Block OUTDENT', -> new Switch null, $3, LOC(4,5) $5 ] Whens: [ - o 'When' + o 'When', -> [$1] o 'Whens When', -> $1.concat $2 ] When: [
- o 'LEADING_WHEN SimpleArgs Block', -> [[$2, $3]]
- o 'LEADING_WHEN SimpleArgs Block TERMINATOR', -> [[$2, $3]]
+ o 'LEADING_WHEN SimpleArgs Block', -> new SwitchWhen $2, $3
+ o 'LEADING_WHEN SimpleArgs Block TERMINATOR', -> LOC(1, 3) new SwitchWhen $2, $3
]
The most basic form of if is a condition and an action. The following if-related rules are broken up along these lines in order to avoid @@ -1678,11 +1744,11 @@ ambiguity.
-The full complement of if expressions, including postfix one-liner if and unless.
@@ -1692,8 +1758,8 @@ ambiguity. If: [
o 'IfBlock'
o 'IfBlock ELSE Block', -> $1.addElse $3
- o 'Statement POST_IF Expression', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
- o 'Expression POST_IF Expression', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
+ o 'Statement POST_IF Expression', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, postfix: true
+ o 'Expression POST_IF Expression', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, postfix: true
]
IfBlockLine: [
@@ -1704,18 +1770,18 @@ ambiguity.
IfLine: [
o 'IfBlockLine'
o 'IfBlockLine ELSE Block', -> $1.addElse $3
- o 'Statement POST_IF ExpressionLine', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
- o 'Expression POST_IF ExpressionLine', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
+ o 'Statement POST_IF ExpressionLine', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, postfix: true
+ o 'Expression POST_IF ExpressionLine', -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, postfix: true
]
Arithmetic and logical operators, working on one or more operands. Here they are grouped by order of precedence. The actual precedence rules @@ -1728,11 +1794,14 @@ rules are necessary.
OperationLine: [
o 'UNARY ExpressionLine', -> new Op $1, $2
+ o 'DO ExpressionLine', -> new Op $1, $2
+ o 'DO_IIFE CodeLine', -> new Op $1, $2
]
Operation: [
- o 'UNARY Expression', -> new Op $1 , $2
- o 'UNARY_MATH Expression', -> new Op $1 , $2
+ o 'UNARY Expression', -> new Op $1.toString(), $2, undefined, undefined, originalOperator: $1.original
+ o 'DO Expression', -> new Op $1, $2
+ o 'UNARY_MATH Expression', -> new Op $1, $2
o '- Expression', (-> new Op '-', $2), prec: 'UNARY_MATH'
o '+ Expression', (-> new Op '+', $2), prec: 'UNARY_MATH'
@@ -1747,11 +1816,11 @@ rules are necessary.
-
Precedence
+ Expression', -> new Assign $1, $4, $2.toString(), originalContext: $2.original + ] -Precedence
+ +Operators at the top of this list have higher precedence than the ones lower
down. Following these rules is what makes 2 + 3 * 4
parse as:
2 + (3 * 4)
@@ -1829,11 +1898,12 @@ down. Following these rules is what makes 2 + 3 * 4
parse as:
operators = [
+ ['right', 'DO_IIFE']
['left', '.', '?.', '::', '?::']
['left', 'CALL_START', 'CALL_END']
['nonassoc', '++', '--']
['left', '?']
- ['right', 'UNARY']
+ ['right', 'UNARY', 'DO']
['right', 'AWAIT']
['right', '**']
['right', 'UNARY_MATH']
@@ -1859,11 +1929,11 @@ down. Following these rules is what makes 2 + 3 * 4
parse as:
-
Wrapping Up
@@ -1872,11 +1942,11 @@ down. Following these rules is what makes2 + 3 * 4
parse as:
- 2 + 3 * 4
parse as:
Finally, now that we have our grammar and our operators, we can create our Jison.Parser. We do this by processing all of our rules, recording all @@ -1908,11 +1978,11 @@ as “tokens”.
-Initialize the Parser with our list of terminal tokens, our grammar
rules, and the name of the root. Reverse the operators because Jison orders
diff --git a/docs/v2/annotated-source/helpers.html b/docs/v2/annotated-source/helpers.html
index 6ade4ce1..ae5b52c7 100644
--- a/docs/v2/annotated-source/helpers.html
+++ b/docs/v2/annotated-source/helpers.html
@@ -383,9 +383,12 @@ If last
is not provided, this will simply return first
first_column: first.first_column
last_line: last.last_line
last_column: last.last_column
-
-buildLocationHash = (loc) ->
- "#{loc.first_line}x#{loc.first_column}-#{loc.last_line}x#{loc.last_column}"
last
is not provided, this will simply return first
- Build a dictionary of extra token properties organized by tokens’ locations -used as lookup hashes.
+Build a list of all comments attached to tokens.
buildTokenDataDictionary = (parserState) ->
- tokenData = {}
- for token in parserState.parser.tokens when token.comments
- tokenHash = buildLocationHash token[2]
exports.extractAllCommentTokens = (tokens) ->
+ allCommentsObj = {}
+ for token in tokens when token.comments
+ for comment in token.comments
+ commentKey = comment.locationData.range[0]
+ allCommentsObj[commentKey] = comment
+ sortedKeys = Object.keys(allCommentsObj).sort (a, b) -> a - b
+ for key in sortedKeys
+ allCommentsObj[key]
Get a lookup hash for a token based on its location data. +Multiple tokens might have the same location hash, but using exclusive +location data distinguishes e.g. zero-length generated tokens from +actual source tokens.
+ +buildLocationHash = (loc) ->
+ "#{loc.range[0]}-#{loc.range[1]}"
Build a dictionary of extra token properties organized by tokens’ locations +used as lookup hashes.
+ +exports.buildTokenDataDictionary = buildTokenDataDictionary = (tokens) ->
+ tokenData = {}
+ for token in tokens when token.comments
+ tokenHash = buildLocationHash token[2]
Multiple tokens might have the same location hash, such as the generated
JS
tokens added at the start or end of the token stream to hold
comments that start or end a file.
For “overlapping” tokens, that is tokens with the same location data
and therefore matching tokenHash
es, merge the comments from both/all
@@ -446,11 +491,11 @@ they will get sorted out later.
This returns a function which takes an object as a parameter, and if that object is an AST node, updates that object’s locationData. @@ -458,40 +503,43 @@ The object is returned either way.
exports.addDataToNode = (parserState, first, last) ->
+ exports.addDataToNode = (parserState, firstLocationData, firstValue, lastLocationData, lastValue, forceUpdateLocation = yes) ->
(obj) ->
-
if obj?.updateLocationDataIfMissing? and first?
- obj.updateLocationDataIfMissing buildLocationData(first, last)
locationData = buildLocationData(firstValue?.locationData ? firstLocationData, lastValue?.locationData ? lastLocationData)
+ if obj?.updateLocationDataIfMissing? and firstLocationData?
+ obj.updateLocationDataIfMissing locationData, forceUpdateLocation
+ else
+ obj.locationData = locationData
parserState.tokenData ?= buildTokenDataDictionary parserState
+ parserState.tokenData ?= buildTokenDataDictionary parserState.parser.tokens
if obj.locationData?
objHash = buildLocationHash obj.locationData
if parserState.tokenData[objHash]?.comments?
@@ -506,11 +554,11 @@ exports.attachCommentsToNode = attachCommentsToNode =
+
Convert jison location data to a string.
obj
can be a token, or a locationData.
@@ -530,11 +578,11 @@ exports.attachCommentsToNode = attachCommentsToNode =
+
A .coffee.md
compatible version of basename
, that returns the file sans-extension.
@@ -553,11 +601,11 @@ exports.attachCommentsToNode = attachCommentsToNode =
+
Determine if a filename represents a CoffeeScript file.
@@ -568,11 +616,11 @@ exports.attachCommentsToNode = attachCommentsToNode =
+
Determine if a filename represents a Literate CoffeeScript file.
@@ -583,11 +631,11 @@ exports.attachCommentsToNode = attachCommentsToNode =
+
Throws a SyntaxError from a given location.
The error’s toString
will return an error message following the “standard”
@@ -604,11 +652,11 @@ marker showing where the error is.
-
+
Instead of showing the compiler’s stacktrace, show our custom error message
(this is useful when the error bubbles up in Node.js applications that
@@ -623,11 +671,11 @@ compile CoffeeScript for example).
-
+
Update a compiler SyntaxError with source code information if it didn’t have
it already.
@@ -639,11 +687,11 @@ it already.
-
+
Avoid screwing up the stack
property of other errors (i.e. possible bugs).
@@ -669,11 +717,11 @@ it already.
-
+
Show only the first line on multi-line errors.
@@ -685,11 +733,11 @@ it already.
-
+
+ else string
+
+exports.parseNumber = (string) ->
+ return NaN unless string?
+
+ base = switch string.charAt 1
+ when 'b' then 2
+ when 'o' then 8
+ when 'x' then 16
+ else null
+
+ if base?
+ parseInt string[2..].replace(/_/g, ''), base
+ else
+ parseFloat string.replace(/_/g, '')
+
+exports.isFunction = (obj) -> Object::toString.call(obj) is '[object Function]'
+exports.isNumber = isNumber = (obj) -> Object::toString.call(obj) is '[object Number]'
+exports.isString = isString = (obj) -> Object::toString.call(obj) is '[object String]'
+exports.isBoolean = isBoolean = (obj) -> obj is yes or obj is no or Object::toString.call(obj) is '[object Boolean]'
+exports.isPlainObject = (obj) -> typeof obj is 'object' and !!obj and not Array.isArray(obj) and not isNumber(obj) and not isString(obj) and not isBoolean(obj)
+
+unicodeCodePointToUnicodeEscapes = (codePoint) ->
+ toUnicodeEscape = (val) ->
+ str = val.toString 16
+ "\\u#{repeat '0', 4 - str.length}#{str}"
+ return toUnicodeEscape(codePoint) if codePoint < 0x10000
+
+
+
+
+
+
+
+
+ ¶
+
+ surrogate pair
+
+
+
+ high = Math.floor((codePoint - 0x10000) / 0x400) + 0xD800
+ low = (codePoint - 0x10000) % 0x400 + 0xDC00
+ "#{toUnicodeEscape(high)}#{toUnicodeEscape(low)}"
+
+
+
+
+
+
+
+
+ ¶
+
+ Replace \u{...}
with \uxxxx[\uxxxx]
in regexes without u
flag
+
+
+
+ exports.replaceUnicodeCodePointEscapes = (str, {flags, error, delimiter = ''} = {}) ->
+ shouldReplace = flags? and 'u' not in flags
+ str.replace UNICODE_CODE_POINT_ESCAPE, (match, escapedBackslash, codePointHex, offset) ->
+ return escapedBackslash if escapedBackslash
+
+ codePointDecimal = parseInt codePointHex, 16
+ if codePointDecimal > 0x10ffff
+ error "unicode code point escapes greater than \\u{10ffff} are not allowed",
+ offset: offset + delimiter.length
+ length: codePointHex.length + 4
+ return match unless shouldReplace
+
+ unicodeCodePointToUnicodeEscapes codePointDecimal
+
+UNICODE_CODE_POINT_ESCAPE = ///
+ ( \\\\ ) # Make sure the escape isn’t escaped.
+ |
+ \\u\{ ( [\da-fA-F]+ ) \}
+///g
diff --git a/docs/v2/annotated-source/lexer.html b/docs/v2/annotated-source/lexer.html
index 767b520a..e47dfeb6 100644
--- a/docs/v2/annotated-source/lexer.html
+++ b/docs/v2/annotated-source/lexer.html
@@ -120,7 +120,7 @@ matches against the beginning of the source code. When a match is found,
a token is produced, we consume the match, and start again. Tokens are in the
form:
[tag, value, locationData]
-
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, last_line_exclusive, last_column_exclusive}, which is a
format that can be fed directly into Jison. These
are read by jison in the parser.lexer
function defined in coffeescript.coffee.
@@ -143,7 +143,8 @@ are read by jison in the parser.lexer
function defined in coffeescr
{count, starts, compact, repeat, invertLiterate, merge,
-attachCommentsToNode, locationDataToString, throwSyntaxError} = require './helpers'
+attachCommentsToNode, locationDataToString, throwSyntaxError
+replaceUnicodeCodePointEscapes, flatten, parseNumber} = require './helpers'
@@ -222,13 +223,17 @@ it has consumed.
@seenExport = no # Used to recognize `EXPORT FROM? AS?` tokens.
@importSpecifierList = no # Used to identify when in an `IMPORT {...} FROM? ...`.
@exportSpecifierList = no # Used to identify when in an `EXPORT {...} FROM? ...`.
- @csxDepth = 0 # Used to optimize CSX checks, how deep in CSX we are.
- @csxObjAttribute = {} # Used to detect if CSX attributes is wrapped in {} (<div {props...} />).
+ @jsxDepth = 0 # Used to optimize JSX checks, how deep in JSX we are.
+ @jsxObjAttribute = {} # Used to detect if JSX attributes is wrapped in {} (<div {props...} />).
@chunkLine =
opts.line or 0 # The start line for the current @chunk.
@chunkColumn =
opts.column or 0 # The start column of the current @chunk.
+ @chunkOffset =
+ opts.offset or 0 # The start offset for the current @chunk.
+ @locationDataCompensations =
+ opts.locationDataCompensations or {} # The location data compensations for the current @chunk.
code = @clean code # The stripped, cleaned original source code.
@@ -255,7 +260,7 @@ short-circuiting if any of them succeed. Their order determines precedence:
@lineToken() or
@stringToken() or
@numberToken() or
- @csxToken() or
+ @jsxToken() or
@regexToken() or
@jsToken() or
@literalToken()
@@ -273,7 +278,7 @@ short-circuiting if any of them succeed. Their order determines precedence:
- [@chunkLine, @chunkColumn] = @getLineAndColumnFromChunk consumed
+ [@chunkLine, @chunkColumn, @chunkOffset] = @getLineAndColumnFromChunk consumed
i += consumed
@@ -300,11 +305,21 @@ by removing all lines that aren’t indented by at least four spaces or a tab.
clean: (code) ->
- code = code.slice(1) if code.charCodeAt(0) is BOM
- code = code.replace(/\r/g, '').replace TRAILING_SPACES, ''
+ thusFar = 0
+ if code.charCodeAt(0) is BOM
+ code = code.slice 1
+ @locationDataCompensations[0] = 1
+ thusFar += 1
if WHITESPACE.test code
code = "\n#{code}"
@chunkLine--
+ @locationDataCompensations[0] ?= 0
+ @locationDataCompensations[0] -= 1
+ code = code
+ .replace /\r/g, (match, offset) =>
+ @locationDataCompensations[thusFar + offset] = 1
+ ''
+ .replace TRAILING_SPACES, ''
code = invertLiterate code if @literate
code
@@ -352,8 +367,8 @@ though is
means ===
otherwise.
identifierToken: ->
- inCSXTag = @atCSXTag()
- regex = if inCSXTag then CSX_ATTRIBUTE else IDENTIFIER
+ inJSXTag = @atJSXTag()
+ regex = if inJSXTag then JSX_ATTRIBUTE else IDENTIFIER
return 0 unless match = regex.exec @chunk
[input, id, colon] = match
@@ -416,13 +431,14 @@ though is
means ===
otherwise.
else
'IDENTIFIER'
+ tokenData = {}
if tag is 'IDENTIFIER' and (id in JS_KEYWORDS or id in COFFEE_KEYWORDS) and
not (@exportSpecifierList and id in COFFEE_KEYWORDS)
tag = id.toUpperCase()
if tag is 'WHEN' and @tag() in LINE_BREAK
tag = 'LEADING_WHEN'
else if tag is 'FOR'
- @seenFor = yes
+ @seenFor = {endsLength: @ends.length}
else if tag is 'UNLESS'
tag = 'IF'
else if tag is 'IMPORT'
@@ -439,7 +455,7 @@ though is
means ===
otherwise.
tag = 'RELATION'
if @value() is '!'
poppedToken = @tokens.pop()
- id = '!' + id
+ tokenData.invert = poppedToken.data?.original ? poppedToken[1]
else if tag is 'IDENTIFIER' and @seenFor and id is 'from' and
isForFrom(prev)
tag = 'FORFROM'
@@ -466,7 +482,7 @@ what CoffeeScript would normally interpret as calls to functions named
@error "'#{prev[1]}' cannot be used as a keyword, or as a function call
without parentheses", prev[2]
else if prev[0] is '.' and @tokens.length > 1 and (prevprev = @tokens[@tokens.length - 2])[0] is 'UNARY' and prevprev[1] is 'new'
- prevprev[0] = 'IDENTIFIER'
+ prevprev[0] = 'NEW_TARGET'
else if @tokens.length > 2
prevprev = @tokens[@tokens.length - 2]
if prev[0] in ['@', 'THIS'] and prevprev and prevprev.spaced and
@@ -475,13 +491,14 @@ what CoffeeScript would normally interpret as calls to functions named
@error "'#{prevprev[1]}' cannot be used as a keyword, or as a
function call without parentheses", prevprev[2]
- if tag is 'IDENTIFIER' and id in RESERVED
+ if tag is 'IDENTIFIER' and id in RESERVED and not inJSXTag
@error "reserved word '#{id}'", length: id.length
- unless tag is 'PROPERTY' or @exportSpecifierList
+ unless tag is 'PROPERTY' or @exportSpecifierList or @importSpecifierList
if id in COFFEE_ALIASES
alias = id
id = COFFEE_ALIAS_MAP[id]
+ tokenData.original = alias
tag = switch id
when '!' then 'UNARY'
when '==', '!=' then 'COMPARE'
@@ -491,17 +508,17 @@ what CoffeeScript would normally interpret as calls to functions named
when '&&', '||' then id
else tag
- tagToken = @token tag, id, 0, idLength
+ tagToken = @token tag, id, length: idLength, data: tokenData
tagToken.origin = [tag, alias, tagToken[2]] if alias
if poppedToken
- [tagToken[2].first_line, tagToken[2].first_column] =
- [poppedToken[2].first_line, poppedToken[2].first_column]
+ [tagToken[2].first_line, tagToken[2].first_column, tagToken[2].range[0]] =
+ [poppedToken[2].first_line, poppedToken[2].first_column, poppedToken[2].range[0]]
if colon
- colonOffset = input.lastIndexOf if inCSXTag then '=' else ':'
- colonToken = @token ':', ':', colonOffset, colon.length
- colonToken.csxColon = yes if inCSXTag # used by rewriter
- if inCSXTag and tag is 'IDENTIFIER' and prev[0] isnt ':'
- @token ',', ',', 0, 0, tagToken
+ colonOffset = input.lastIndexOf if inJSXTag then '=' else ':'
+ colonToken = @token ':', ':', offset: colonOffset
+ colonToken.jsxColon = yes if inJSXTag # used by rewriter
+ if inJSXTag and tag is 'IDENTIFIER' and prev[0] isnt ':'
+ @token ',', ',', length: 0, origin: tagToken, generated: yes
input.length
@@ -536,16 +553,15 @@ Be careful not to interfere with ranges in progress.
when /^0\d+/.test number
@error "octal literal '#{number}' must be prefixed with '0o'", length: lexedLength
- base = switch number.charAt 1
- when 'b' then 2
- when 'o' then 8
- when 'x' then 16
- else null
+ parsedValue = parseNumber number
+ tokenData = {parsedValue}
- numberValue = if base? then parseInt(number[2..], base) else parseFloat(number)
-
- tag = if numberValue is Infinity then 'INFINITY' else 'NUMBER'
- @token tag, number, 0, lexedLength
+ tag = if parsedValue is Infinity then 'INFINITY' else 'NUMBER'
+ if tag is 'INFINITY'
+ tokenData.original = number
+ @token tag, number,
+ length: lexedLength
+ data: tokenData
lexedLength
from
.
when '"' then STRING_DOUBLE
when "'''" then HEREDOC_SINGLE
when '"""' then HEREDOC_DOUBLE
- heredoc = quote.length is 3
{tokens, index: end} = @matchWithInterpolations regex, quote
- $ = tokens.length - 1
- delimiter = quote.charAt(0)
+ heredoc = quote.length is 3
if heredocfrom
.
while match = HEREDOC_INDENT.exec doc
attempt = match[1]
indent = attempt if indent is null or 0 < attempt.length < indent.length
- indentRegex = /// \n#{indent} ///g if indent
- @mergeInterpolationTokens tokens, {delimiter}, (value, i) =>
- value = @formatString value, delimiter: quote
- value = value.replace indentRegex, '\n' if indentRegex
- value = value.replace LEADING_BLANK_LINE, '' if i is 0
- value = value.replace TRAILING_BLANK_LINE, '' if i is $
- value
- else
- @mergeInterpolationTokens tokens, {delimiter}, (value, i) =>
- value = @formatString value, delimiter: quotefrom
.
- Remove indentation from multiline single-quoted strings.
+Matches and consumes comments. The comments are taken out of the token +stream and saved for later, to be reinserted into the output after +everything has been parsed and the JavaScript code generated.
value = value.replace SIMPLE_STRING_OMIT, (match, offset) ->
- if (i is 0 and offset is 0) or
- (i is $ and offset + match.length is value.length)
- ''
- else
- ' '
- value
-
- if @atCSXTag()
- @token ',', ',', 0, 0, @prev
-
- end
commentToken: (chunk = @chunk, {heregex, returnCommentTokens = no, offsetInChunk = 0} = {}) ->
+ return 0 unless match = chunk.match COMMENT
+ [commentWithSurroundingWhitespace, hereLeadingWhitespace, hereComment, hereTrailingWhitespace, lineComment] = match
+ contents = null
from
.
- Matches and consumes comments. The comments are taken out of the token -stream and saved for later, to be reinserted into the output after -everything has been parsed and the JavaScript code generated.
+Does this comment follow code on the same line?
commentToken: (chunk = @chunk) ->
- return 0 unless match = chunk.match COMMENT
- [comment, here] = match
- contents = null
leadingNewline = /^\s*\n+\s*#/.test commentWithSurroundingWhitespace
+ if hereComment
+ matchIllegal = HERECOMMENT_ILLEGAL.exec hereComment
+ if matchIllegal
+ @error "block comments cannot contain #{matchIllegal[0]}",
+ offset: '###'.length + matchIllegal.index, length: matchIllegal[0].length
Does this comment follow code on the same line?
+Parse indentation or outdentation as if this block comment didn’t exist.
newLine = /^\s*\n+\s*#/.test comment
- if here
- matchIllegal = HERECOMMENT_ILLEGAL.exec comment
- if matchIllegal
- @error "block comments cannot contain #{matchIllegal[0]}",
- offset: matchIllegal.index, length: matchIllegal[0].length
chunk = chunk.replace "####{hereComment}###", ''
Parse indentation or outdentation as if this block comment didn’t exist.
+Remove leading newlines, like Rewriter::removeLeadingNewlines
, to
+avoid the creation of unwanted TERMINATOR
tokens.
chunk = chunk.replace "####{here}###", ''
chunk = chunk.replace /^\n+/, ''
+ @lineToken {chunk}
Remove leading newlines, like Rewriter::removeLeadingNewlines
, to
-avoid the creation of unwanted TERMINATOR
tokens.
Pull out the ###-style comment’s content, and format it.
chunk = chunk.replace /^\n+/, ''
- @lineToken chunk
content = hereComment
+ contents = [{
+ content
+ length: commentWithSurroundingWhitespace.length - hereLeadingWhitespace.length - hereTrailingWhitespace.length
+ leadingWhitespace: hereLeadingWhitespace
+ }]
+ else
TERMINATOR
tokens.
- Pull out the ###-style comment’s content, and format it.
+The COMMENT
regex captures successive line comments as one token.
+Remove any leading newlines before the first comment, but preserve
+blank lines between line comments.
content = here
- if '\n' in content
- content = content.replace /// \n #{repeat ' ', @indent} ///g, '\n'
- contents = [content]
- else
leadingNewlines = ''
+ content = lineComment.replace /^(\n*)/, (leading) ->
+ leadingNewlines = leading
+ ''
+ precedingNonCommentLines = ''
+ hasSeenFirstCommentLine = no
+ contents =
+ content.split '\n'
+ .map (line, index) ->
+ unless line.indexOf('#') > -1
+ precedingNonCommentLines += "\n#{line}"
+ return
+ leadingWhitespace = ''
+ content = line.replace /^([ |\t]*)#/, (_, whitespace) ->
+ leadingWhitespace = whitespace
+ ''
+ comment = {
+ content
+ length: '#'.length + content.length
+ leadingWhitespace: "#{unless hasSeenFirstCommentLine then leadingNewlines else ''}#{precedingNonCommentLines}#{leadingWhitespace}"
+ precededByBlankLine: !!precedingNonCommentLines
+ }
+ hasSeenFirstCommentLine = yes
+ precedingNonCommentLines = ''
+ comment
+ .filter (comment) -> comment
+
+ getIndentSize = ({leadingWhitespace, nonInitial}) ->
+ lastNewlineIndex = leadingWhitespace.lastIndexOf '\n'
+ if hereComment? or not nonInitial
+ return null unless lastNewlineIndex > -1
+ else
+ lastNewlineIndex ?= -1
+ leadingWhitespace.length - 1 - lastNewlineIndex
+ commentAttachments = for {content, length, leadingWhitespace, precededByBlankLine}, i in contents
+ nonInitial = i isnt 0
+ leadingNewlineOffset = if nonInitial then 1 else 0
+ offsetInChunk += leadingNewlineOffset + leadingWhitespace.length
+ indentSize = getIndentSize {leadingWhitespace, nonInitial}
+ noIndent = not indentSize? or indentSize is -1
+ commentAttachment = {
+ content
+ here: hereComment?
+ newLine: leadingNewline or nonInitial # Line comments after the first one start new lines, by definition.
+ locationData: @makeLocationData {offsetInChunk, length}
+ precededByBlankLine
+ indentSize
+ indented: not noIndent and indentSize > @indent
+ outdented: not noIndent and indentSize < @indent
+ }
+ commentAttachment.heregex = yes if heregex
+ offsetInChunk += length
+ commentAttachment
+
+ prev = @prev()
+ unless prev
TERMINATOR
tokens.
- The COMMENT
regex captures successive line comments as one token.
-Remove any leading newlines before the first comment, but preserve
-blank lines between line comments.
If there’s no previous token, create a placeholder token to attach +this comment to; and follow with a newline.
content = comment.replace /^(\n*)/, ''
- content = content.replace /^([ |\t]*)#/gm, ''
- contents = content.split '\n'
+ commentAttachments[0].newLine = yes
+ @lineToken chunk: @chunk[commentWithSurroundingWhitespace.length..], offset: commentWithSurroundingWhitespace.length # Set the indent.
+ placeholderToken = @makeToken 'JS', '', offset: commentWithSurroundingWhitespace.length, generated: yes
+ placeholderToken.comments = commentAttachments
+ @tokens.push placeholderToken
+ @newlineToken commentWithSurroundingWhitespace.length
+ else
+ attachCommentsToNode commentAttachments, prev
- commentAttachments = for content, i in contents
- content: content
- here: here?
- newLine: newLine or i isnt 0 # Line comments after the first one start new lines, by definition.
-
- prev = @prev()
- unless prev
+ return commentAttachments if returnCommentTokens
+ commentWithSurroundingWhitespace.length
If there’s no previous token, create a placeholder token to attach -this comment to; and follow with a newline.
+Matches JavaScript interpolated directly into the source via backticks.
commentAttachments[0].newLine = yes
- @lineToken @chunk[comment.length..] # Set the indent.
- placeholderToken = @makeToken 'JS', ''
- placeholderToken.generated = yes
- placeholderToken.comments = commentAttachments
- @tokens.push placeholderToken
- @newlineToken 0
- else
- attachCommentsToNode commentAttachments, prev
-
- comment.length
jsToken: ->
+ return 0 unless @chunk.charAt(0) is '`' and
+ (match = (matchedHere = HERE_JSTOKEN.exec(@chunk)) or JSTOKEN.exec(@chunk))
Matches JavaScript interpolated directly into the source via backticks.
+Convert escaped backticks to backticks, and escaped backslashes +just before escaped backticks to backslashes
jsToken: ->
- return 0 unless @chunk.charAt(0) is '`' and
- (match = HERE_JSTOKEN.exec(@chunk) or JSTOKEN.exec(@chunk))
script = match[1]
+ {length} = match[0]
+ @token 'JS', script, {length, data: {here: !!matchedHere}}
+ length
Convert escaped backticks to backticks, and escaped backslashes -just before escaped backticks to backslashes
- - script = match[1].replace /\\+(`|$)/g, (string) ->
string
is always a value like ‘`‘, ‘\`‘, ‘\\`‘, etc.
-By reducing it to its latter half, we turn ‘`‘ to ‘', '\\\
‘ to ‘`‘, etc.
string[-Math.ceil(string.length / 2)..]
- @token 'JS', script, 0, match[0].length
- match[0].length
Matches regular expression literals, as well as multiline extended ones. Lexing regular expressions is difficult to distinguish from division, so we borrow some basic heuristics from JavaScript and Ruby.
@@ -869,8 +888,15 @@ borrow some basic heuristics from JavaScript and Ruby. offset: match.index + match[1].length when match = @matchWithInterpolations HEREGEX, '///' {tokens, index} = match - comments = @chunk[0...index].match /\s+(#(?!{).*)/g - @commentToken comment for comment in comments if comments + comments = [] + while matchedComment = HEREGEX_COMMENT.exec @chunk[0...index] + {index: commentIndex} = matchedComment + [fullMatch, leadingWhitespace, comment] = matchedComment + comments.push {comment, offsetInChunk: commentIndex + leadingWhitespace.length} + commentTokens = flatten( + for commentOpts in comments + @commentToken commentOpts.comment, Object.assign commentOpts, heregex: yes, returnCommentTokens: yes + ) when match = REGEX.exec @chunk [regex, body, closed] = match @validateEscapes body, isRegex: yes, offsetInChunk: 1 @@ -887,38 +913,54 @@ borrow some basic heuristics from JavaScript and Ruby. [flags] = REGEX_FLAGS.exec @chunk[index..] end = index + flags.length - origin = @makeToken 'REGEX', null, 0, end + origin = @makeToken 'REGEX', null, length: end switch when not VALID_FLAGS.test flags @error "invalid regular expression flags #{flags}", offset: index, length: flags.length when regex or tokens.length is 1 - if body - body = @formatRegex body, { flags, delimiter: '/' } - else - body = @formatHeregex tokens[0][1], { flags } - @token 'REGEX', "#{@makeDelimitedLiteral body, delimiter: '/'}#{flags}", 0, end, origin + delimiter = if body then '/' else '///' + body ?= tokens[0][1] + @validateUnicodeCodePointEscapes body, {delimiter} + @token 'REGEX', "/#{body}/#{flags}", {length: end, origin, data: {delimiter}} else - @token 'REGEX_START', '(', 0, 0, origin - @token 'IDENTIFIER', 'RegExp', 0, 0 - @token 'CALL_START', '(', 0, 0 - @mergeInterpolationTokens tokens, {delimiter: '"', double: yes}, (str) => - @formatHeregex str, { flags } + @token 'REGEX_START', '(', {length: 0, origin, generated: yes} + @token 'IDENTIFIER', 'RegExp', length: 0, generated: yes + @token 'CALL_START', '(', length: 0, generated: yes + @mergeInterpolationTokens tokens, {double: yes, heregex: {flags}, endOffset: end - flags.length, quote: '///'}, (str) => + @validateUnicodeCodePointEscapes str, {delimiter} if flags - @token ',', ',', index - 1, 0 - @token 'STRING', '"' + flags + '"', index - 1, flags.length - @token ')', ')', end - 1, 0 - @token 'REGEX_END', ')', end - 1, 0 + @token ',', ',', offset: index - 1, length: 0, generated: yes + @token 'STRING', '"' + flags + '"', offset: index, length: flags.length + @token ')', ')', offset: end, length: 0, generated: yes + @token 'REGEX_END', ')', offset: end, length: 0, generated: yesExplicitly attach any heregex comments to the REGEX/REGEX_END token.
+ + if commentTokens?.length
+ addTokenData @tokens[@tokens.length - 1],
+ heregexCommentTokens: commentTokens
end
Matches newlines, indents, and outdents, and determines which is which. If we can detect that the current line is continued onto the next line, @@ -931,13 +973,13 @@ can close multiple indents, so we need to know how far in we happen to be.
lineToken: (chunk = @chunk) ->
+ lineToken: ({chunk = @chunk, offset = 0} = {}) ->
return 0 unless match = MULTI_DENT.exec chunk
indent = match[0]
prev = @prev()
backslash = prev?[0] is '\\'
- @seenFor = no unless backslash and @seenFor
+ @seenFor = no unless (backslash or @seenFor?.endsLength < @ends.length) and @seenFor
@seenImport = no unless (backslash and @seenImport) or @importSpecifierList
@seenExport = no unless (backslash and @seenExport) or @exportSpecifierList
@@ -955,7 +997,7 @@ can close multiple indents, so we need to know how far in we happen to be.
return indent.length
if size - @indebt is @indent
- if noNewlines then @suppressNewlines() else @newlineToken 0
+ if noNewlines then @suppressNewlines() else @newlineToken offset
return indent.length
if size > @indent
@@ -968,34 +1010,34 @@ can close multiple indents, so we need to know how far in we happen to be.
@indentLiteral = newIndentLiteral
return indent.length
diff = size - @indent + @outdebt
- @token 'INDENT', diff, indent.length - size, size
+ @token 'INDENT', diff, offset: offset + indent.length - size, length: size
@indents.push diff
@ends.push {tag: 'OUTDENT'}
@outdebt = @indebt = 0
@indent = size
@indentLiteral = newIndentLiteral
else if size < @baseIndent
- @error 'missing indentation', offset: indent.length
+ @error 'missing indentation', offset: offset + indent.length
else
@indebt = 0
- @outdentToken @indent - size, noNewlines, indent.length
+ @outdentToken {moveOut: @indent - size, noNewlines, outdentLength: indent.length, offset, indentSize: size}
indent.length
-
Record an outdent token or multiple tokens, if we happen to be moving back inwards past several recorded indents. Sets new @indent value.
outdentToken: (moveOut, noNewlines, outdentLength) ->
+ outdentToken: ({moveOut, noNewlines, outdentLength = 0, offset = 0, indentSize}) ->
decreasedIndent = @indent - moveOut
while moveOut > 0
lastIndent = @indents[@indents.length - 1]
@@ -1014,23 +1056,23 @@ inwards past several recorded indents. Sets new @indent value.
-
+
@pair 'OUTDENT'
- @token 'OUTDENT', moveOut, 0, outdentLength
+ @token 'OUTDENT', moveOut, length: outdentLength, indentSize: indentSize + moveOut - dent
moveOut -= dent
@outdebt -= moveOut if dent
@suppressSemicolons()
- @token 'TERMINATOR', '\n', outdentLength, 0 unless @tag() is 'TERMINATOR' or noNewlines
+ @token 'TERMINATOR', '\n', offset: offset + outdentLength, length: 0 unless @tag() is 'TERMINATOR' or noNewlines
@indent = decreasedIndent
@indentLiteral = @indentLiteral[...decreasedIndent]
this
@@ -1038,11 +1080,11 @@ inwards past several recorded indents. Sets new @indent value.
-
+
Matches and consumes non-meaningful whitespace. Tag the previous token
as being “spaced”, because there are some cases where it makes a difference.
@@ -1059,11 +1101,11 @@ as being “spaced”, because there are some cases where it makes a difference.
-
+
Generate a newline token. Consecutive newlines get merged together.
@@ -1071,17 +1113,17 @@ as being “spaced”, because there are some cases where it makes a difference.
newlineToken: (offset) ->
@suppressSemicolons()
- @token 'TERMINATOR', '\n', offset, 0 unless @tag() is 'TERMINATOR'
+ @token 'TERMINATOR', '\n', {offset, length: 0} unless @tag() is 'TERMINATOR'
this
-
+
Use a \
at a line-ending to suppress the newline.
The slash is removed here once its job is done.
@@ -1096,11 +1138,11 @@ The slash is removed here once its job is done.
-
+
@tokens.length
should be at least 2 (some code, then \
).
If something puts a \
after nothing, they deserve to lose any
@@ -1110,7 +1152,29 @@ comments that trail it.
attachCommentsToNode prev.comments, @tokens[@tokens.length - 2]
@tokens.pop()
- this
+ this
+
+ jsxToken: ->
+ firstChar = @chunk[0]
+
+
+
+
+
+
+
+
+ ¶
+
+ Check the previous token to detect if attribute is spread.
+
+
+
+ prevChar = if @tokens.length > 0 then @tokens[@tokens.length - 1][0] else ''
+ if firstChar is '<'
+ match = JSX_IDENTIFIER.exec(@chunk[1...]) or JSX_FRAGMENT_IDENTIFIER.exec(@chunk[1...])
+ return 0 unless match and (
+ @jsxDepth > 0 or
@@ -1121,12 +1185,52 @@ comments that trail it.
- CSX is like JSX but for CoffeeScript.
+ Not the right hand side of an unspaced comparison (i.e. a<b
).
- csxToken: ->
- firstChar = @chunk[0]
+ not (prev = @prev()) or
+ prev.spaced or
+ prev[0] not in COMPARABLE_LEFT_SIDE
+ )
+ [input, id] = match
+ fullId = id
+ if '.' in id
+ [id, properties...] = id.split '.'
+ else
+ properties = []
+ tagToken = @token 'JSX_TAG', id,
+ length: id.length + 1
+ data:
+ openingBracketToken: @makeToken '<', '<'
+ tagNameToken: @makeToken 'IDENTIFIER', id, offset: 1
+ offset = id.length + 1
+ for property in properties
+ @token '.', '.', {offset}
+ offset += 1
+ @token 'PROPERTY', property, {offset}
+ offset += property.length
+ @token 'CALL_START', '(', generated: yes
+ @token '[', '[', generated: yes
+ @ends.push {tag: '/>', origin: tagToken, name: id, properties}
+ @jsxDepth++
+ return fullId.length + 1
+ else if jsxTag = @atJSXTag()
+ if @chunk[...2] is '/>' # Self-closing tag.
+ @pair '/>'
+ @token ']', ']',
+ length: 2
+ generated: yes
+ @token 'CALL_END', ')',
+ length: 2
+ generated: yes
+ data:
+ selfClosingSlashToken: @makeToken '/', '/'
+ closingBracketToken: @makeToken '>', '>', offset: 1
+ @jsxDepth--
+ return 2
+ else if firstChar is '{'
+ if prevChar is ':'
@@ -1137,15 +1241,15 @@ comments that trail it.
- Check the previous token to detect if attribute is spread.
+ This token represents the start of a JSX attribute value
+that’s an expression (e.g. the {b}
in <div a={b} />
).
+Our grammar represents the beginnings of expressions as (
+tokens, so make this into a (
token that displays as {
.
- prevChar = if @tokens.length > 0 then @tokens[@tokens.length - 1][0] else ''
- if firstChar is '<'
- match = CSX_IDENTIFIER.exec(@chunk[1...]) or CSX_FRAGMENT_IDENTIFIER.exec(@chunk[1...])
- return 0 unless match and (
- @csxDepth > 0 or
+ token = @token '(', '{'
+ @jsxObjAttribute[@jsxDepth] = no
@@ -1156,38 +1260,18 @@ comments that trail it.
- Not the right hand side of an unspaced comparison (i.e. a<b
).
+ tag attribute name as JSX
- not (prev = @prev()) or
- prev.spaced or
- prev[0] not in COMPARABLE_LEFT_SIDE
- )
- [input, id, colon] = match
- origin = @token 'CSX_TAG', id, 1, id.length
- @token 'CALL_START', '('
- @token '[', '['
- @ends.push tag: '/>', origin: origin, name: id
- @csxDepth++
- return id.length + 1
- else if csxTag = @atCSXTag()
- if @chunk[...2] is '/>'
- @pair '/>'
- @token ']', ']', 0, 2
- @token 'CALL_END', ')', 0, 2
- @csxDepth--
- return 2
- else if firstChar is '{'
- if prevChar is ':'
- token = @token '(', '('
- @csxObjAttribute[@csxDepth] = no
+ addTokenData @tokens[@tokens.length - 3],
+ jsx: yes
else
token = @token '{', '{'
- @csxObjAttribute[@csxDepth] = yes
+ @jsxObjAttribute[@jsxDepth] = yes
@ends.push {tag: '}', origin: token}
return 1
- else if firstChar is '>'
+ else if firstChar is '>' # end of opening tag
@@ -1202,18 +1286,22 @@ comments that trail it.
- @pair '/>' # As if the current tag was self-closing.
- origin = @token ']', ']'
- @token ',', ','
+ {origin: openingTagToken} = @pair '/>' # As if the current tag was self-closing.
+ @token ']', ']',
+ generated: yes
+ data:
+ closingBracketToken: @makeToken '>', '>'
+ @token ',', 'JSX_COMMA', generated: yes
{tokens, index: end} =
- @matchWithInterpolations INSIDE_CSX, '>', '</', CSX_INTERPOLATION
- @mergeInterpolationTokens tokens, {delimiter: '"'}, (value, i) =>
- @formatString value, delimiter: '>'
- match = CSX_IDENTIFIER.exec(@chunk[end...]) or CSX_FRAGMENT_IDENTIFIER.exec(@chunk[end...])
- if not match or match[1] isnt csxTag.name
- @error "expected corresponding CSX closing tag for #{csxTag.name}",
- csxTag.origin[2]
- afterTag = end + csxTag.name.length
+ @matchWithInterpolations INSIDE_JSX, '>', '</', JSX_INTERPOLATION
+ @mergeInterpolationTokens tokens, {endOffset: end, jsx: yes}, (value) =>
+ @validateUnicodeCodePointEscapes value, delimiter: '>'
+ match = JSX_IDENTIFIER.exec(@chunk[end...]) or JSX_FRAGMENT_IDENTIFIER.exec(@chunk[end...])
+ if not match or match[1] isnt "#{jsxTag.name}#{(".#{property}" for property in jsxTag.properties).join ''}"
+ @error "expected corresponding JSX closing tag for #{jsxTag.name}",
+ jsxTag.origin.data.tagNameToken[2]
+ [, fullTagName] = match
+ afterTag = end + fullTagName.length
if @chunk[afterTag] isnt '>'
@error "missing closing > after tag name", offset: afterTag, length: 1
@@ -1226,36 +1314,17 @@ comments that trail it.
- +1 for the closing >
.
+ -2/+2 for the opening </
and +1 for the closing >
.
- @token 'CALL_END', ')', end, csxTag.name.length + 1
- @csxDepth--
- return afterTag + 1
- else
- return 0
- else if @atCSXTag 1
- if firstChar is '}'
- @pair firstChar
- if @csxObjAttribute[@csxDepth]
- @token '}', '}'
- @csxObjAttribute[@csxDepth] = no
- else
- @token ')', ')'
- @token ',', ','
- return 1
- else
- return 0
- else
- return 0
-
- atCSXTag: (depth = 0) ->
- return no if @csxDepth is 0
- i = @ends.length - 1
- i-- while @ends[i]?.tag is 'OUTDENT' or depth-- > 0 # Ignore indents.
- last = @ends[i]
- last?.tag is '/>' and last
+ endToken = @token 'CALL_END', ')',
+ offset: end - 2
+ length: fullTagName.length + 3
+ generated: yes
+ data:
+ closingTagOpeningBracketToken: @makeToken '<', '<', offset: end - 2
+ closingTagSlashToken: @makeToken '/', '/', offset: end - 1
@@ -1266,6 +1335,62 @@ comments that trail it.
+ TODO: individual tokens for complex tag name? eg < / A . B >
+
+
+
+ closingTagNameToken: @makeToken 'IDENTIFIER', fullTagName, offset: end
+ closingTagClosingBracketToken: @makeToken '>', '>', offset: end + fullTagName.length
+
+
+
+
+
make the closing tag location data more easily accessible to the grammar
+ + addTokenData openingTagToken, endToken.data
+ @jsxDepth--
+ return afterTag + 1
+ else
+ return 0
+ else if @atJSXTag 1
+ if firstChar is '}'
+ @pair firstChar
+ if @jsxObjAttribute[@jsxDepth]
+ @token '}', '}'
+ @jsxObjAttribute[@jsxDepth] = no
+ else
+ @token ')', '}'
+ @token ',', ',', generated: yes
+ return 1
+ else
+ return 0
+ else
+ return 0
+
+ atJSXTag: (depth = 0) ->
+ return no if @jsxDepth is 0
+ i = @ends.length - 1
+ i-- while @ends[i]?.tag is 'OUTDENT' or depth-- > 0 # Ignore indents.
+ last = @ends[i]
+ last?.tag is '/>' and last
We treat all other single characters as a token. E.g.: ( ) , . !
Multi-character operators are also literal tokens, so that Jison can assign
the proper order of operations. There are some symbols that we tag specially
@@ -1288,6 +1413,13 @@ parentheses that indicate a method call from regular parentheses, and so on.
Token Manipulators
@@ -1353,11 +1485,11 @@ parentheses that indicate a method call from regular parentheses, and so on. -A source of ambiguity in our grammar used to be parameter lists in function definitions versus argument lists in function calls. Walk backwards, tagging @@ -1378,7 +1510,7 @@ parameters specially in order to make things easier for the parser.
tagParameters: ->
- return this if @tag() isnt ')'
+ return @tagDoIife() if @tag() isnt ')'
stack = []
{tokens} = this
i = tokens.length
@@ -1392,7 +1524,7 @@ parameters specially in order to make things easier for the parser.
if stack.length then stack.pop()
else if tok[0] is '('
tok[0] = 'PARAM_START'
- return this
+ return @tagDoIife i - 1
else
paramEndToken[0] = 'CALL_END'
return this
@@ -1401,27 +1533,47 @@ parameters specially in order to make things easier for the parser.
-
Tag do
followed by a function differently than do
followed by eg an
+identifier to allow for different grammar precedence
tagDoIife: (tokenIndex) ->
+ tok = @tokens[tokenIndex ? @tokens.length - 1]
+ return this unless tok?[0] is 'DO'
+ tok[0] = 'DO_IIFE'
+ this
Close up all remaining open blocks at the end of the file.
closeIndentation: ->
- @outdentToken @indent
Match the contents of a delimited token and expand variables and expressions
inside it using Ruby-like notation for substitution of arbitrary
@@ -1434,19 +1586,16 @@ Lexer and tokenize until the {
of #{
is balanced with
#{
if interpolations are desired).
delimiter
is the delimiter of the token. Examples are '
, "
, '''
,
"""
and ///
.closingDelimiter
is different from delimiter
only in CSXinterpolators
matches the start of an interpolation, for CSX it’s both
-{
and <
(i.e. nested CSX tag)closingDelimiter
is different from delimiter
only in JSXinterpolators
matches the start of an interpolation, for JSX it’s both
+{
and <
(i.e. nested JSX tag)This method allows us to have strings within interpolations within strings, ad infinitum.
matchWithInterpolations: (regex, delimiter, closingDelimiter, interpolators) ->
- closingDelimiter ?= delimiter
- interpolators ?= /^#\{/
-
+ matchWithInterpolations: (regex, delimiter, closingDelimiter = delimiter, interpolators = /^#\{/) ->
tokens = []
offsetInChunk = delimiter.length
return null unless @chunk[...offsetInChunk] is delimiter
@@ -1459,17 +1608,17 @@ ad infinitum.
-
+
- tokens.push @makeToken 'NEOSTRING', strPart, offsetInChunk
+ tokens.push @makeToken 'NEOSTRING', strPart, offset: offsetInChunk
str = str[strPart.length..]
offsetInChunk += strPart.length
@@ -1480,30 +1629,30 @@ ad infinitum.
-
+
interpolationOffset = interpolator.length - 1
- [line, column] = @getLineAndColumnFromChunk offsetInChunk + interpolationOffset
+ [line, column, offset] = @getLineAndColumnFromChunk offsetInChunk + interpolationOffset
rest = str[interpolationOffset..]
{tokens: nested, index} =
- new Lexer().tokenize rest, line: line, column: column, untilBalanced: on
+ new Lexer().tokenize rest, {line, column, offset, untilBalanced: on, @locationDataCompensations}
-
+
Account for the #
in #{
.
@@ -1517,11 +1666,11 @@ ad infinitum.
-
+
Turn the leading and trailing {
and }
into parentheses. Unnecessary
parentheses will be removed later.
@@ -1529,18 +1678,25 @@ parentheses will be removed later.
[open, ..., close] = nested
- open[0] = open[1] = '('
- close[0] = close[1] = ')'
+ open[0] = 'INTERPOLATION_START'
+ open[1] = '('
+ open[2].first_column -= interpolationOffset
+ open[2].range = [
+ open[2].range[0] - interpolationOffset
+ open[2].range[1]
+ ]
+ close[0] = 'INTERPOLATION_END'
+ close[1] = ')'
close.origin = ['', 'end of interpolation', close[2]]
-
+
Remove leading 'TERMINATOR'
(if any).
@@ -1551,11 +1707,11 @@ parentheses will be removed later.
-
+
Remove trailing 'INDENT'/'OUTDENT'
pair (if any).
@@ -1568,28 +1724,28 @@ parentheses will be removed later.
-
+
- open = @makeToken '(', '(', offsetInChunk, 0
- close = @makeToken ')', ')', offsetInChunk + index, 0
+ open = @makeToken 'INTERPOLATION_START', '(', offset: offsetInChunk, length: 0, generated: yes
+ close = @makeToken 'INTERPOLATION_END', ')', offset: offsetInChunk + index, length: 0, generated: yes
nested = [open, nested..., close]
-
+
Push a fake 'TOKENS'
token, which will get turned into real tokens later.
@@ -1603,92 +1759,36 @@ parentheses will be removed later.
unless str[...closingDelimiter.length] is closingDelimiter
@error "missing #{closingDelimiter}", length: delimiter.length
- [firstToken, ..., lastToken] = tokens
- firstToken[2].first_column -= delimiter.length
- if lastToken[1].substr(-1) is '\n'
- lastToken[2].last_line += 1
- lastToken[2].last_column = closingDelimiter.length - 1
- else
- lastToken[2].last_column += closingDelimiter.length
- lastToken[2].last_column -= 1 if lastToken[1].length is 0
-
{tokens, index: offsetInChunk + closingDelimiter.length}
-
-
-
-
- ¶
-
- Merge the array tokens
of the fake token types 'TOKENS'
and 'NEOSTRING'
-(as returned by matchWithInterpolations
) into the token stream. The value
-of 'NEOSTRING'
s are converted using fn
and turned into strings using
-options
first.
-
-
-
- mergeInterpolationTokens: (tokens, options, fn) ->
- if tokens.length > 1
- lparen = @token 'STRING_START', '(', 0, 0
-
- firstIndex = @tokens.length
- for token, i in tokens
- [tag, value] = token
- switch tag
- when 'TOKENS'
- if value.length is 2
-
-
-
-
-
-
-
-
- ¶
-
- Optimize out empty interpolations (an empty pair of parentheses).
-
-
-
- continue unless value[0].comments or value[1].comments
-
-
-
-
-
-
-
-
- ¶
-
- There are comments (and nothing else) in this interpolation.
-
-
-
- if @csxDepth is 0
-
-
-
-
- This is an interpolated string, not a CSX tag; and for whatever
-reason `a${/*test*/}b`
is invalid JS. So compile to
-`a${/*test*/''}b`
instead.
+ Merge the array tokens
of the fake token types 'TOKENS'
and 'NEOSTRING'
+(as returned by matchWithInterpolations
) into the token stream. The value
+of 'NEOSTRING'
s are converted using fn
and turned into strings using
+options
first.
- placeholderToken = @makeToken 'STRING', "''"
- else
- placeholderToken = @makeToken 'JS', ''
+ mergeInterpolationTokens: (tokens, options, fn) ->
+ {quote, indent, double, heregex, endOffset, jsx} = options
+
+ if tokens.length > 1
+ lparen = @token 'STRING_START', '(', length: quote?.length ? 0, data: {quote}, generated: not quote?.length
+
+ firstIndex = @tokens.length
+ $ = tokens.length - 1
+ for token, i in tokens
+ [tag, value] = token
+ switch tag
+ when 'TOKENS'
@@ -1699,6 +1799,22 @@ reason `a${/*test*/}b`
is invalid JS. So compile to
+ There are comments (and nothing else) in this interpolation.
+
+
+
+ if value.length is 2 and (value[0].comments or value[1].comments)
+ placeholderToken = @makeToken 'JS', '', generated: yes
+
+
+
+
+
+
+
+
+ ¶
+
Use the same location data as the first parenthesis.
@@ -1712,11 +1828,11 @@ reason `a${/*test*/}b`
is invalid JS. So compile to
-
+
Push all the tokens in the fake 'TOKENS'
token. These already have
sane location data.
@@ -1730,38 +1846,57 @@ sane location data.
-
-
-
-
- ¶
-
- Convert 'NEOSTRING'
into 'STRING'
.
-
-
-
- converted = fn.call this, token[1], i
-
-
-
-
- Optimize out empty strings. We ensure that the tokens stream always
-starts with a string token, though, to make sure that the result
-really is a string.
+ Convert 'NEOSTRING'
into 'STRING'
.
- if converted.length is 0
- if i is 0
- firstEmptyStringIndex = @tokens.length
+ converted = fn.call this, token[1], i
+ addTokenData token, initialChunk: yes if i is 0
+ addTokenData token, finalChunk: yes if i is $
+ addTokenData token, {indent, quote, double}
+ addTokenData token, {heregex} if heregex
+ addTokenData token, {jsx} if jsx
+ token[0] = 'STRING'
+ token[1] = '"' + converted + '"'
+ if tokens.length is 1 and quote?
+ token[2].first_column -= quote.length
+ if token[1].substr(-2, 1) is '\n'
+ token[2].last_line += 1
+ token[2].last_column = quote.length - 1
else
- continue
+ token[2].last_column += quote.length
+ token[2].last_column -= 1 if token[1].length is 2
+ token[2].last_column_exclusive += quote.length
+ token[2].range = [
+ token[2].range[0] - quote.length
+ token[2].range[1] + quote.length
+ ]
+ locationToken = token
+ tokensToPush = [token]
+ @tokens.push tokensToPush...
+
+ if lparen
+ [..., lastToken] = tokens
+ lparen.origin = ['STRING', null,
+ first_line: lparen[2].first_line
+ first_column: lparen[2].first_column
+ last_line: lastToken[2].last_line
+ last_column: lastToken[2].last_column
+ last_line_exclusive: lastToken[2].last_line_exclusive
+ last_column_exclusive: lastToken[2].last_column_exclusive
+ range: [
+ lparen[2].range[0]
+ lastToken[2].range[1]
+ ]
+ ]
+ lparen[2] = lparen.origin[2] unless quote?.length
+ rparen = @token 'STRING_END', ')', offset: endOffset - (quote ? '').length, length: quote?.length ? 0, generated: not quote?.length
@@ -1772,65 +1907,6 @@ really is a string.
- However, there is one case where we can optimize away a starting
-empty string.
-
-
-
- if i is 2 and firstEmptyStringIndex?
- @tokens.splice firstEmptyStringIndex, 2 # Remove empty string and the plus.
- token[0] = 'STRING'
- token[1] = @makeDelimitedLiteral converted, options
- locationToken = token
- tokensToPush = [token]
- if @tokens.length > firstIndex
-
-
-
-
-
-
-
-
- ¶
-
- Create a 0-length +
token.
-
-
-
- plusToken = @token '+', '+'
- plusToken[2] =
- first_line: locationToken[2].first_line
- first_column: locationToken[2].first_column
- last_line: locationToken[2].first_line
- last_column: locationToken[2].first_column
- @tokens.push tokensToPush...
-
- if lparen
- [..., lastToken] = tokens
- lparen.origin = ['STRING', null,
- first_line: lparen[2].first_line
- first_column: lparen[2].first_column
- last_line: lastToken[2].last_line
- last_column: lastToken[2].last_column
- ]
- lparen[2] = lparen.origin[2]
- rparen = @token 'STRING_END', ')'
- rparen[2] =
- first_line: lastToken[2].last_line
- first_column: lastToken[2].last_column
- last_line: lastToken[2].last_line
- last_column: lastToken[2].last_column
-
-
-
-
-
-
-
-
- ¶
-
Pairs up a closing token, ensuring that all listed pairs of tokens are
correctly balanced throughout the course of the token stream.
@@ -1844,11 +1920,11 @@ correctly balanced throughout the course of the token stream.
-
+
Auto-close INDENT
to support syntax like this:
el.click((event) ->
@@ -1857,23 +1933,59 @@ correctly balanced throughout the course of the token stream.
[..., lastIndent] = @indents
- @outdentToken lastIndent, true
+ @outdentToken moveOut: lastIndent, noNewlines: true
return @pair tag
@ends.pop()
+
+
+
+
+ ¶
+
+ Helpers
+
+
+
+
+
+
+
+
+
+
+ ¶
+
+
+
+
+
+
+
- Helpers
+ Compensate for the things we strip out initially (e.g. carriage returns)
+so that location data stays accurate with respect to the original source file.
+ getLocationDataCompensation: (start, end) ->
+ compensation = 0
+ initialEnd = end
+ for index, length of @locationDataCompensations
+ index = parseInt index, 10
+ continue unless start <= index and (index < end or index is end and start is initialEnd)
+ compensation += length
+ end += length
+ compensation
+
@@ -1883,26 +1995,16 @@ correctly balanced throughout the course of the token stream.
-
-
-
-
-
-
-
-
-
-
- ¶
-
Returns the line and column number from an offset into the current chunk.
offset
is a number of characters into @chunk
.
getLineAndColumnFromChunk: (offset) ->
+ compensation = @getLocationDataCompensation @chunkOffset, @chunkOffset + offset
+
if offset is 0
- return [@chunkLine, @chunkColumn]
+ return [@chunkLine, @chunkColumn + compensation, @chunkOffset + compensation]
if offset >= @chunk.length
string = @chunk
@@ -1915,10 +2017,36 @@ correctly balanced throughout the course of the token stream.
if lineCount > 0
[..., lastLine] = string.split '\n'
column = lastLine.length
+ previousLinesCompensation = @getLocationDataCompensation @chunkOffset, @chunkOffset + offset - column
+
+
+
+
+
+
+
+
+ ¶
+
+ Don’t recompensate for initially inserted newline.
+
+
+
+ previousLinesCompensation = 0 if previousLinesCompensation < 0
+ columnCompensation = @getLocationDataCompensation(
+ @chunkOffset + offset + previousLinesCompensation - column
+ @chunkOffset + offset + previousLinesCompensation
+ )
else
column += string.length
+ columnCompensation = compensation
- [@chunkLine + lineCount, column]
+ [@chunkLine + lineCount, column + columnCompensation, @chunkOffset + offset + compensation]
+
+ makeLocationData: ({ offsetInChunk, length }) ->
+ locationData = range: []
+ [locationData.first_line, locationData.first_column, locationData.range[0]] =
+ @getLineAndColumnFromChunk offsetInChunk
@@ -1929,15 +2057,19 @@ correctly balanced throughout the course of the token stream.
- Same as token
, except this just returns the token without adding it
-to the results.
+ Use length - 1 for the final offset - we’re supplying the last_line and the last_column,
+so if last_column == first_column, then we’re looking at a character of length 1.
- makeToken: (tag, value, offsetInChunk = 0, length = value.length, origin) ->
- locationData = {}
- [locationData.first_line, locationData.first_column] =
- @getLineAndColumnFromChunk offsetInChunk
+ lastCharacter = if length > 0 then (length - 1) else 0
+ [locationData.last_line, locationData.last_column, endOffset] =
+ @getLineAndColumnFromChunk offsetInChunk + lastCharacter
+ [locationData.last_line_exclusive, locationData.last_column_exclusive] =
+ @getLineAndColumnFromChunk offsetInChunk + lastCharacter + (if length > 0 then 1 else 0)
+ locationData.range[1] = if length > 0 then endOffset + 1 else endOffset
+
+ locationData
@@ -1948,17 +2080,16 @@ to the results.
- Use length - 1 for the final offset - we’re supplying the last_line and the last_column,
-so if last_column == first_column, then we’re looking at a character of length 1.
+ Same as token
, except this just returns the token without adding it
+to the results.
- lastCharacter = if length > 0 then (length - 1) else 0
- [locationData.last_line, locationData.last_column] =
- @getLineAndColumnFromChunk offsetInChunk + lastCharacter
-
- token = [tag, value, locationData]
+ makeToken: (tag, value, {offset: offsetInChunk = 0, length = value.length, origin, generated, indentSize} = {}) ->
+ token = [tag, value, @makeLocationData {offsetInChunk, length}]
token.origin = origin if origin
+ token.generated = yes if generated
+ token.indentSize = indentSize if indentSize?
token
@@ -1978,8 +2109,9 @@ not specified, the length of value
will be used.
- token: (tag, value, offsetInChunk, length, origin) ->
- token = @makeToken tag, value, offsetInChunk, length, origin
+ token: (tag, value, {offset, length, origin, data, generated, indentSize} = {}) ->
+ token = @makeToken tag, value, {offset, length, origin, generated, indentSize}
+ addTokenData token, data if data
@tokens.push token
token
@@ -2016,7 +2148,7 @@ not specified, the length of value
will be used.
value: (useOrigin = no) ->
[..., token] = @tokens
if useOrigin and token?.origin?
- token.origin?[1]
+ token.origin[1]
else
token?[1]
@@ -2053,20 +2185,8 @@ not specified, the length of value
will be used.
LINE_CONTINUER.test(@chunk) or
@tag() in UNFINISHED
- formatString: (str, options) ->
- @replaceUnicodeCodePointEscapes str.replace(STRING_OMIT, '$1'), options
-
- formatHeregex: (str, options) ->
- @formatRegex str.replace(HEREGEX_OMIT, '$1$2'), merge(options, delimiter: '///')
-
- formatRegex: (str, options) ->
- @replaceUnicodeCodePointEscapes str, options
-
- unicodeCodePointToUnicodeEscapes: (codePoint) ->
- toUnicodeEscape = (val) ->
- str = val.toString 16
- "\\u#{repeat '0', 4 - str.length}#{str}"
- return toUnicodeEscape(codePoint) if codePoint < 0x10000
+ validateUnicodeCodePointEscapes: (str, options) ->
+ replaceUnicodeCodePointEscapes str, merge options, {@error}
@@ -2077,50 +2197,6 @@ not specified, the length of value
will be used.
- surrogate pair
-
-
-
- high = Math.floor((codePoint - 0x10000) / 0x400) + 0xD800
- low = (codePoint - 0x10000) % 0x400 + 0xDC00
- "#{toUnicodeEscape(high)}#{toUnicodeEscape(low)}"
-
-
-
-
-
-
-
-
- ¶
-
- Replace \u{...}
with \uxxxx[\uxxxx]
in regexes without u
flag
-
-
-
- replaceUnicodeCodePointEscapes: (str, options) ->
- shouldReplace = options.flags? and 'u' not in options.flags
- str.replace UNICODE_CODE_POINT_ESCAPE, (match, escapedBackslash, codePointHex, offset) =>
- return escapedBackslash if escapedBackslash
-
- codePointDecimal = parseInt codePointHex, 16
- if codePointDecimal > 0x10ffff
- @error "unicode code point escapes greater than \\u{10ffff} are not allowed",
- offset: offset + options.delimiter.length
- length: codePointHex.length + 4
- return match unless shouldReplace
-
- @unicodeCodePointToUnicodeEscapes codePointDecimal
-
-
-
-
-
-
-
-
- ¶
-
Validates escapes in strings and regexes.
@@ -2142,54 +2218,7 @@ not specified, the length of value
will be used.
invalidEscape = "\\#{octal or hex or unicodeCodePoint or unicode}"
@error "#{message} #{invalidEscape}",
offset: (options.offsetInChunk ? 0) + match.index + before.length
- length: invalidEscape.length
Constructs a string or regex by escaping certain characters.
- - makeDelimitedLiteral: (body, options = {}) ->
- body = '(?:)' if body is '' and options.delimiter is '/'
- regex = ///
- (\\\\) # Escaped backslash.
- | (\\0(?=[1-7])) # Null character mistaken as octal escape.
- | \\?(#{options.delimiter}) # (Possibly escaped) delimiter.
- | \\?(?: (\n)|(\r)|(\u2028)|(\u2029) ) # (Possibly escaped) newlines.
- | (\\.) # Other escapes.
- ///g
- body = body.replace regex, (match, backslash, nul, delimiter, lf, cr, ls, ps, other) -> switch
Ignore escaped backslashes.
- - when backslash then (if options.double then backslash + backslash else backslash)
- when nul then '\\x00'
- when delimiter then "\\#{delimiter}"
- when lf then '\\n'
- when cr then '\\r'
- when ls then '\\u2028'
- when ps then '\\u2029'
- when other then (if options.double then "\\#{other}" else other)
- "#{options.delimiter}#{body}#{options.delimiter}"
+ length: invalidEscape.length
suppressSemicolons: ->
while @value() is ';'
@@ -2199,18 +2228,18 @@ not specified, the length of value
will be used.
-
Throws an error at either a given offset from the current chunk or at the
location of a token (token[2]
).
error: (message, options = {}) ->
+ error: (message, options = {}) =>
location =
if 'first_line' of options
options
@@ -2222,11 +2251,11 @@ location of a token (token[2]
).
-
+
-
+
from
isn’t a CoffeeScript keyword, but it behaves like one in import
and
export
statements (handled above) and in the declaration line of a for
@@ -2278,11 +2307,11 @@ loop. Try to detect when from
is a variable identifier and when it
-
+
for i from iterable
@@ -2294,11 +2323,11 @@ loop. Try to detect when from
is a variable identifier and when it
-
+
for from…
@@ -2310,11 +2339,11 @@ loop. Try to detect when from
is a variable identifier and when it
-
+
-
+
Constants
@@ -2341,11 +2373,11 @@ loop. Try to detect when from
is a variable identifier and when it
-
+
@@ -2353,11 +2385,11 @@ loop. Try to detect when from
is a variable identifier and when it
-
+
-
+
The list of keywords that are reserved by JavaScript, but not used, or are
used by CoffeeScript internally. We throw an error when these are encountered,
@@ -2430,11 +2462,11 @@ STRICT_PROSCRIBED = ['arguments',
+
The superset of both JavaScript keywords and reserved words, none of which may
be used as identifiers or properties.
@@ -2446,11 +2478,11 @@ be used as identifiers or properties.
-
+
The character code of the nasty Microsoft madness otherwise known as the BOM.
@@ -2461,11 +2493,11 @@ be used as identifiers or properties.
-
+
+
+
+
+
+
+
+
+
+ ¶
+
+ Like IDENTIFIER
, but includes -
s
+
+
+
+ JSX_IDENTIFIER_PART = /// (?: (?!\s)[\-$\w\x7f-\uffff] )+ ///.source
+
+
+
+
+
+
+
+
+ ¶
+
+ In https://facebook.github.io/jsx/ spec, JSXElementName can be
+JSXIdentifier, JSXNamespacedName (JSXIdentifier : JSXIdentifier), or
+JSXMemberExpression (two or more JSXIdentifier connected by .
s).
+
+
+
+ JSX_IDENTIFIER = /// ^
+ (?![\d<]) # Must not start with `<`.
+ ( #{JSX_IDENTIFIER_PART}
+ (?: \s* : \s* #{JSX_IDENTIFIER_PART} # JSXNamespacedName
+ | (?: \s* \. \s* #{JSX_IDENTIFIER_PART} )+ # JSXMemberExpression
+ )? )
+///
+
+
+
+
+
+
+
+
+ ¶
+
+ Fragment: <></>
+
+
+
+ JSX_FRAGMENT_IDENTIFIER = /// ^
+ ()> # Ends immediately with `>`.
+///
+
+
+
+
+
+
+
+
+ ¶
+
+ In https://facebook.github.io/jsx/ spec, JSXAttributeName can be either
+JSXIdentifier or JSXNamespacedName which is JSXIdentifier : JSXIdentifier
+
+
+
+ JSX_ATTRIBUTE = /// ^
+ (?!\d)
+ ( #{JSX_IDENTIFIER_PART}
+ (?: \s* : \s* #{JSX_IDENTIFIER_PART} # JSXNamespacedName
+ )? )
+ ( [^\S]* = (?!=) )? # Is this an attribute with a value?
///
-CSX_IDENTIFIER = /// ^
- (?![\d<]) # Must not start with `<`.
- ( (?: (?!\s)[\.\-$\w\x7f-\uffff] )+ ) # Like `IDENTIFIER`, but includes `-`s and `.`s.
-///
+NUMBER = ///
+ ^ 0b[01](?:_?[01])*n? | # binary
+ ^ 0o[0-7](?:_?[0-7])*n? | # octal
+ ^ 0x[\da-f](?:_?[\da-f])*n? | # hex
+ ^ \d+n | # decimal bigint
+ ^ (?:\d(?:_?\d)*)? \.? (?:\d(?:_?\d)*)+ # decimal
+ (?:e[+-]? (?:\d(?:_?\d)*)+ )?
+
@@ -2491,47 +2604,35 @@ CSX_IDENTIFIER = /// ^
- Fragment: <></>
+ decimal without support for numeric literal separators for reference:
+\d*.?\d+ (?:e[+-]?\d+)?
- CSX_FRAGMENT_IDENTIFIER = /// ^
- ()> # Ends immediately with `>`.
-///
+ ///i
-CSX_ATTRIBUTE = /// ^
- (?!\d)
- ( (?: (?!\s)[\-$\w\x7f-\uffff] )+ ) # Like `IDENTIFIER`, but includes `-`s.
- ( [^\S]* = (?!=) )? # Is this an attribute with a value?
-///
-
-NUMBER = ///
- ^ 0b[01]+ | # binary
- ^ 0o[0-7]+ | # octal
- ^ 0x[\da-f]+ | # hex
- ^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal
-///i
-
-OPERATOR = /// ^ (
+OPERATOR = /// ^ (
?: [-=]> # function
- | [-+*/%<>&|^!?=]= # compound assign / compare
+ | [-+*/%<>&|^!?=]= # compound assign / compare
| >>>=? # zero-fill right shift
- | ([-+:])\1 # doubles
- | ([&|<>*/%])\2=? # logic / shift / power / floor division / modulo
+ | ([-+:])\1 # doubles
+ | ([&|<>*/%])\2=? # logic / shift / power / floor division / modulo
| \?(\.|::) # soak access
- | \.{2,3} # range or splat
-) ///
+ | \.{2,3} # range or splat
+) ///
-WHITESPACE = /^[^\n\S]+/
+WHITESPACE = /^[^\n\S]+/
-COMMENT = /^\s*###([^#][\s\S]*?)(?:###[^\n\S]*|###$)|^(?:\s*#(?!##[^#]).*)+/
+COMMENT = /^(\s*)###([^#][\s\S]*?)(?:###([^\n\S]*)|###$)|^((?:\s*#(?!##[^#]).*)+)/
-CODE = /^[-=]>/
+CODE = /^[-=]>/
-MULTI_DENT = /^(?:\n[^\n\S]*)+/
+MULTI_DENT = /^(?:\n[^\n\S]*)+/
-JSTOKEN = ///^ `(?!``) ((?: [^`\\] | \\[\s\S] )*) ` ///
-HERE_JSTOKEN = ///^ ``` ((?: [^`\\] | \\[\s\S] | `(?!``) )*) ``` ///
+JSTOKEN = ///^ `(?!``) ((?: [^`\\] | \\[\s\S] )*) ` ///
+HERE_JSTOKEN = ///^ ``` ((?: [^`\\] | \\[\s\S] | `(?!``) )*) ``` ///
+
+
@@ -2553,22 +2654,17 @@ STRING_DOUBLE = /// ^(?: [^\\"'] | \\[\s\S] | '(?!'') )* ///
HEREDOC_DOUBLE = /// ^(?: [^\\"#] | \\[\s\S] | "(?!"") | \#(?!\{) )* ///
-INSIDE_CSX = /// ^(?:
+INSIDE_JSX = /// ^(?:
[^
\{ # Start of CoffeeScript interpolation.
- < # Maybe CSX tag (`<` not allowed even if bare).
+ < # Maybe JSX tag (`<` not allowed even if bare).
]
)* /// # Similar to `HEREDOC_DOUBLE` but there is no escaping.
-CSX_INTERPOLATION = /// ^(?:
+JSX_INTERPOLATION = /// ^(?:
\{ # CoffeeScript interpolation.
- | <(?!/) # CSX opening tag.
+ | <(?!/) # JSX opening tag.
)///
-STRING_OMIT = ///
- ((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
- | \\[^\S\n]*\n\s* # Remove escaped newlines.
-///g
-SIMPLE_STRING_OMIT = /\s*\n\s*/g
HEREDOC_INDENT = /\n+([^\n\S]*)(?=\S)/g
@@ -2678,11 +2774,7 @@ HEREGEX = /// ^
)*
///
-HEREGEX_OMIT = ///
- ((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
- | \\(\s) # Preserve escaped whitespace.
- | \s+(?:#.*)? # Remove whitespace and comments.
-///g
+HEREGEX_COMMENT = /(\s+)(#(?!{).*)/gm
REGEX_ILLEGAL = /// ^ ( / | /{3}\s*) (\*) ///
@@ -2708,7 +2800,7 @@ LINE_CONTINUER = /// ^ \s* (?: , | \??\.(?![.\d])
STRING_INVALID_ESCAPE = ///
( (?:^|[^\\]) (?:\\\\)* ) # Make sure the escape isn’t escaped.
\\ (
- ?: (0[0-7]|[1-7]) # octal escape
+ ?: (0\d|[1-7]) # octal escape
| (x(?![\da-fA-F]{2}).{0,2}) # hex escape
| (u\{(?![\da-fA-F]{1,}\})[^}]*\}?) # unicode code point escape
| (u(?!\{|[\da-fA-F]{4}).{0,4}) # unicode escape
@@ -2717,22 +2809,13 @@ STRING_INVALID_ESCAPE = ///
REGEX_INVALID_ESCAPE = ///
( (?:^|[^\\]) (?:\\\\)* ) # Make sure the escape isn’t escaped.
\\ (
- ?: (0[0-7]) # octal escape
+ ?: (0\d) # octal escape
| (x(?![\da-fA-F]{2}).{0,2}) # hex escape
| (u\{(?![\da-fA-F]{1,}\})[^}]*\}?) # unicode code point escape
| (u(?!\{|[\da-fA-F]{4}).{0,4}) # unicode escape
)
///
-UNICODE_CODE_POINT_ESCAPE = ///
- ( \\\\ ) # Make sure the escape isn’t escaped.
- |
- \\u\{ ( [\da-fA-F]+ ) \}
-///g
-
-LEADING_BLANK_LINE = /^[^\n\S]*\n/
-TRAILING_BLANK_LINE = /\n[^\n\S]*$/
-
TRAILING_SPACES = /\s+$/
UNARY = ['NEW', 'TYPEOF', 'DELETE', 'DO']
+ UNARY = ['NEW', 'TYPEOF', 'DELETE']
UNARY_MATH = ['!', '~']
@@ -2943,7 +3026,7 @@ avoid an ambiguity in the grammar.
UNFINISHED = ['\\', '.', '?.', '?::', 'UNARY', 'MATH', 'UNARY_MATH', '+', '-',
+ UNFINISHED = ['\\', '.', '?.', '?::', 'UNARY', 'DO', 'DO_IIFE', 'MATH', 'UNARY_MATH', '+', '-',
'**', 'SHIFT', 'RELATION', 'COMPARE', '&', '^', '|', '&&', '||',
'BIN?', 'EXTENDS']
diff --git a/docs/v2/annotated-source/nodes.html b/docs/v2/annotated-source/nodes.html
index a807b392..cf16e233 100644
--- a/docs/v2/annotated-source/nodes.html
+++ b/docs/v2/annotated-source/nodes.html
@@ -143,7 +143,8 @@ Error.stackTraceLimit = Infinity
{compact, flatten, extend, merge, del, starts, ends, some,
addDataToNode, attachCommentsToNode, locationDataToString,
-throwSyntaxError} = require './helpers'
+throwSyntaxError, replaceUnicodeCodePointEscapes,
+isFunction, isPlainObject, isNumber, parseNumber} = require './helpers'
compileClosure: (o) ->
- if jumpNode = @jumps()
- jumpNode.error 'cannot use a pure statement in an expression'
+ @checkForPureStatementInExpression()
o.sharedScope = yes
func = new Code [], Block.wrap [this]
args = []
@@ -579,16 +579,12 @@ call.
Construct a node that returns the current node’s result. Note that this is overridden for smarter behavior for -many statement nodes (e.g. If, For)…
+many statement nodes (e.g.If
, For
).
makeReturn: (res) ->
- me = @unwrapAll()
- if res
- new Call new Literal("#{res}.push"), [me]
- else
- new Return me
makeReturn: (results, mark) ->
+ if mark
Mark this node as implicitly returned, so that it can be part of the +node metadata returned in the AST.
+ + @canBeReturned = yes
+ return
+ node = @unwrapAll()
+ if results
+ new Call new Literal("#{results}.push"), [node]
+ else
+ new Return node
Does this node, or any of its children, contain a node of a certain kind?
Recursively traverses down the children nodes and returns the first one
that verifies pred
. Otherwise return undefined. contains
does not cross
@@ -617,11 +635,11 @@ scope boundaries.
Pull out the last node of a node list.
@@ -633,13 +651,13 @@ scope boundaries. -toString
representation of the node, for inspecting the parse tree.
+
Debugging representation of the node, for inspecting the parse tree.
This is what coffee --nodes
prints out.
coffee --nodes
prints out.
tree = '\n' + idt + name
tree += '?' if @soak
@eachChild (node) -> tree += node.toString idt + TAB
- treePlain JavaScript object representation of the node, that can be serialized
+as JSON. This is what the ast
option in the Node API returns.
+We try to follow the Babel AST spec
+as closely as possible, for improved interoperability with other tools.
+WARNING: DO NOT OVERRIDE THIS METHOD IN CHILD CLASSES.
+Only override the component ast*
methods as needed.
ast: (o, level) ->
Merge level
into o
and perform other universal checks.
o = @astInitialize o, level
Create serializable representation of this node.
+ + astNode = @astNode o
Mark AST nodes that correspond to expressions that (implicitly) return.
+We can’t do this as part of astNode
because we need to assemble child
+nodes first before marking the parent being returned.
if @astNode? and @canBeReturned
+ Object.assign astNode, {returns: yes}
+ astNode
+
+ astInitialize: (o, level) ->
+ o = Object.assign {}, o
+ o.level = level if level?
+ if o.level > LEVEL_TOP
+ @checkForPureStatementInExpression()
@makeReturn
must be called before astProperties
, because the latter may call
+.ast()
for child nodes and those nodes would need the return logic from makeReturn
+already executed by then.
@makeReturn null, yes if @isStatement(o) and o.level isnt LEVEL_TOP and o.scope?
+ o
+
+ astNode: (o) ->
Every abstract syntax tree node object has four categories of properties:
+-
+
- type, stored in the
type
field and a string likeNumberLiteral
.
+ - location data, stored in the
loc
,start
,end
andrange
fields.
+ - properties specific to this node, like
parsedValue
.
+ - properties that are themselves child nodes, like
body
. +These fields are all intermixed in the Babel spec;type
andstart
and +parsedValue
are all top level fields in the AST node object. We have +separate methods for returning each category, that we merge together here.
+
Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()
By default, a node class has no specific properties.
+ + astProperties: -> {}
By default, a node class’s AST type
is its class name.
astType: -> @constructor.name
The AST location data is a rearranged version of our Jison location data, +mutated into the structure that the Babel spec uses.
+ + astLocationData: ->
+ jisonLocationDataToAstLocationData @locationData
Determines whether an AST node needs an ExpressionStatement
wrapper.
+Typically matches our isStatement()
logic but this allows overriding.
isStatementAst: (o) ->
+ @isStatement o
Passes each child to a function, breaking when the function returns false
.
coffee --nodes
prints out.
- 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
.
Default implementations of the common node properties and methods. Nodes will override these with custom logic, if needed.
@@ -730,11 +935,11 @@ will override these with custom logic, if needed. -children
are the properties to recurse into when tree walking. The
children
list is the structure of the AST. The parent
pointer, and
@@ -747,11 +952,11 @@ the pointer to the children
are how you can traverse the tree.
isStatement
has to do with “everything is an expression”. A few things
can’t be expressions, such as break
. Things that isStatement
returns
@@ -766,11 +971,11 @@ in expression position.
Track comments that have been compiled into fragments, to avoid outputting them twice.
@@ -782,11 +987,11 @@ them twice. -includeCommentFragments
lets compileCommentFragments
know whether this node
has special awareness of how to handle comments within its output.
jumps
tells you if an expression, or an internal part of an expression
has a flow control construct (like break
, or continue
, or return
,
@@ -817,11 +1022,11 @@ we have to disallow them.
If node.shouldCache() is false
, it is safe to use node
more than once.
Otherwise you need to store the value of node
in a variable and output
@@ -847,11 +1052,11 @@ for brevity.
Is this node used to assign a certain variable?
@@ -862,18 +1067,19 @@ for brevity. -For this node and all descendents, set the location data to locationData
if the location data is not already set.
updateLocationDataIfMissing: (locationData) ->
+ updateLocationDataIfMissing: (locationData, force) ->
+ @forceUpdateLocation = yes if force
return this if @locationData and not @forceUpdateLocation
delete @forceUpdateLocation
@locationData = locationData
@@ -884,11 +1090,46 @@ if the location data is not already set.
-
+
+
+ withLocationDataFrom: ({locationData}) ->
+ @updateLocationDataIfMissing locationData
+
+
+
+
+
+
+
+
+ ¶
+
+ Add location data and comments from another node
+
+
+
+ withLocationDataAndCommentsFrom: (node) ->
+ @withLocationDataFrom node
+ {comments} = node
+ @comments = comments if comments?.length
+ this
+
+
+
+
+
+
+
+
+ ¶
Throw a SyntaxError associated with this node’s location.
@@ -909,11 +1150,11 @@ if the location data is not already set.
-
+
fragmentsList
is an array of arrays of fragments. Each array in fragmentsList will be
concatenated together, with joinStr
added in between each, to produce a final flat array
@@ -931,11 +1172,11 @@ of fragments.
-
+
HoistTarget
@@ -944,11 +1185,11 @@ of fragments.
-
+
A HoistTargetNode represents the output location in the node tree for a hoisted node.
See Base#hoist.
@@ -960,11 +1201,11 @@ See Base#hoist.
-
+
Expands hoisted fragments in the given array
@@ -981,11 +1222,11 @@ See Base#hoist.
-
+
Holds presentational options to apply when the source node is compiled.
@@ -996,11 +1237,11 @@ See Base#hoist.
-
+
Placeholder fragments to be replaced by the source node’s compilation.
@@ -1014,11 +1255,11 @@ See Base#hoist.
-
+
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
@@ -1032,11 +1273,11 @@ presentational options).
-
+
Copies the target indent and level, and returns the placeholder fragments
@@ -1056,11 +1297,108 @@ presentational options).
-
+
+
+
+
+
+
+
+
+
+ ¶
+
+ The root node of the node tree
+
+
+
+ exports.Root = class Root extends Base
+ constructor: (@body) ->
+ super()
+
+ children: ['body']
+
+
+
+
+
+
+
+
+ ¶
+
+ Wrap everything in a safety closure, unless requested not to. It would be
+better not to generate them in the first place, but for now, clean up
+obvious double-parentheses.
+
+
+
+ compileNode: (o) ->
+ o.indent = if o.bare then '' else TAB
+ o.level = LEVEL_TOP
+ o.compiling = yes
+ @initializeScope o
+ fragments = @body.compileRoot o
+ return fragments if o.bare
+ [].concat @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
+
+ initializeScope: (o) ->
+ o.scope = new Scope null, @body, null, o.referencedVars ? []
+
+
+
+
+
+
+
+
+ ¶
+
+ Mark given local variables in the root scope as parameters so they don’t
+end up being declared on the root block.
+
+
+
+ o.scope.parameter name for name in o.locals or []
+
+ commentsAst: ->
+ @allComments ?=
+ for commentToken in (@allCommentTokens ? []) when not commentToken.heregex
+ if commentToken.here
+ new HereComment commentToken
+ else
+ new LineComment commentToken
+ comment.ast() for comment in @allComments
+
+ astNode: (o) ->
+ o.level = LEVEL_TOP
+ @initializeScope o
+ super o
+
+ astType: -> 'File'
+
+ astProperties: (o) ->
+ @body.isRootBlock = yes
+ return
+ program: Object.assign @body.ast(o), @astLocationData()
+ comments: @commentsAst()
+
+
+
+
+
+
+
+
+ ¶
Block
@@ -1069,11 +1407,11 @@ presentational options).
-
+
The block is the list of expressions that forms the body of an
indented block of code – the implementation of a function, a clause in an
@@ -1092,11 +1430,11 @@ indented block of code – the implementation of a function, a clause in an
-
+
Tack an expression on to the end of this expression list.
@@ -1109,11 +1447,11 @@ indented block of code – the implementation of a function, a clause in an
-
+
Remove and return the last expression of this expression list.
@@ -1125,11 +1463,11 @@ indented block of code – the implementation of a function, a clause in an
-
+
Add an expression at the beginning of this expression list.
@@ -1142,11 +1480,11 @@ indented block of code – the implementation of a function, a clause in an
-
+
If this Block consists of just a single node, unwrap it by pulling
it back out.
@@ -1159,11 +1497,11 @@ it back out.
-
+
Is this an empty block of code?
@@ -1184,18 +1522,18 @@ it back out.
-
+
A Block node does not return its entire body, rather it
ensures that the final expression is returned.
- makeReturn: (res) ->
+ makeReturn: (results, mark) ->
len = @expressions.length
[..., lastExp] = @expressions
lastExp = lastExp?.unwrap() or no
@@ -1203,13 +1541,13 @@ ensures that the final expression is returned.
-
+
- We also need to check that we’re not returning a CSX tag if there’s an
+
We also need to check that we’re not returning a JSX tag if there’s an
adjacent one at the same level; JSX doesn’t allow that.
@@ -1219,39 +1557,31 @@ adjacent one at the same level; JSX doesn’t allow that.
[..., penult, last] = expressions
penult = penult.unwrap()
last = last.unwrap()
- if penult instanceof Call and penult.csx and last instanceof Call and last.csx
+ if penult instanceof JSXElement and last instanceof JSXElement
expressions[expressions.length - 1].error 'Adjacent JSX elements must be wrapped in an enclosing tag'
+ if mark
+ @expressions[len - 1]?.makeReturn results, mark
+ return
while len--
expr = @expressions[len]
- @expressions[len] = expr.makeReturn res
+ @expressions[len] = expr.makeReturn results
@expressions.splice(len, 1) if expr instanceof Return and not expr.expression
break
- this
-
-
-
-
-
-
-
- compileToFragments: (o = {}, level) ->
- if o.scope then super o, level else @compileRoot o
+ compile: (o, lvl) ->
+ return new Root(this).withLocationDataFrom(this).compile o, lvl unless o.scope
+
+ super o, lvl
-
+
Compile all expressions within the Block body. If we need to return
the result, and it’s an expression, simply return it. If it’s a statement,
@@ -1270,11 +1600,11 @@ ask the statement to do so.
-
+
This is a hoisted expression.
We want to compile this and ignore the result.
@@ -1289,11 +1619,11 @@ We want to compile this and ignore the result.
-
+
This is a nested block. We don’t do anything special here like
enclose it in a new scope; we just compile the statements in this
@@ -1322,58 +1652,22 @@ block along with our own.
answer = @joinFragmentArrays(compiledNodes, ', ')
else
answer = [@makeCode 'void 0']
- if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInParentheses answer else answer
-
-
-
-
-
-
-
-
- ¶
-
- If we happen to be the top-level Block, wrap everything in a safety
-closure, unless requested not to. It would be better not to generate them
-in the first place, but for now, clean up obvious double-parentheses.
+ if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInParentheses answer else answer
-
-
- compileRoot: (o) ->
- o.indent = if o.bare then '' else TAB
- o.level = LEVEL_TOP
- @spaced = yes
- o.scope = new Scope null, this, null, o.referencedVars ? []
-
-
-
-
-
-
-
-
- ¶
-
- Mark given local variables in the root scope as parameters so they don’t
-end up being declared on this block.
-
-
-
- o.scope.parameter name for name in o.locals or []
+ compileRoot: (o) ->
+ @spaced = yes
fragments = @compileWithDeclarations o
HoistTarget.expand fragments
- fragments = @compileComments fragments
- return fragments if o.bare
- [].concat @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
+ @compileComments fragments
-
+
Compile the expressions body for the contents of a function, with
declarations of all inner variables pushed up to the top.
@@ -1422,11 +1716,11 @@ declarations of all inner variables pushed up to the top.
-
+
Insert comments into the output at the next or previous newline.
If there are no newlines at which to place comments, create them.
@@ -1438,11 +1732,11 @@ If there are no newlines at which to place comments, create them.
-
+
Determine the indentation level of the fragment that we are about
to insert comments before, and use that indentation level for our
@@ -1475,11 +1769,11 @@ search for a code
property that begins with at least two spaces.
-
+
Keep searching previous fragments until we can’t go back any
further, either because there are no fragments left or we’ve
@@ -1504,11 +1798,11 @@ inside a string.
-
+
Yes, this is awfully similar to the previous if
block, but if you
look closely you’ll find lots of tiny differences that make this
@@ -1521,11 +1815,11 @@ confusing if it were abstracted into a function that both blocks share.
-
+
Does the first trailing comment follow at the end of a line of code,
like ; // Comment
, or does it start a new line after a line of code?
@@ -1538,11 +1832,11 @@ like ; // Comment
, or does it start a new line after a line of code
-
+
Find the indent of the next line of code, if we have any non-trailing
comments to output. We need to first find the next newline, as these
@@ -1570,11 +1864,11 @@ that follows the next newline.
-
+
Is this comment following the indent inserted by bare mode?
If so, there’s no need to indent this further.
@@ -1591,11 +1885,11 @@ If so, there’s no need to indent this further.
-
+
Assemble properly indented comments.
@@ -1615,11 +1909,11 @@ If so, there’s no need to indent this further.
-
+
Keep searching upcoming fragments until we can’t go any
further, either because there are no fragments left or we’ve
@@ -1641,11 +1935,11 @@ inside a string.
-
+
Avoid inserting extra blank lines.
@@ -1661,11 +1955,11 @@ inside a string.
-
+
Wrap up the given nodes as a Block, unless it already happens
to be one.
@@ -1674,16 +1968,162 @@ to be one.
@wrap: (nodes) ->
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
- new Block nodes
+ new Block nodes
+
+ astNode: (o) ->
+ if (o.level? and o.level isnt LEVEL_TOP) and @expressions.length
+ return (new Sequence(@expressions).withLocationDataFrom @).ast o
+
+ super o
+
+ astType: ->
+ if @isRootBlock
+ 'Program'
+ else if @isClassBody
+ 'ClassBody'
+ else
+ 'BlockStatement'
+
+ astProperties: (o) ->
+ checkForDirectives = del o, 'checkForDirectives'
+
+ sniffDirectives @expressions, notFinalExpression: checkForDirectives if @isRootBlock or checkForDirectives
+ directives = []
+ body = []
+ for expression in @expressions
+ expressionAst = expression.ast o
-
+
+
+ if not expressionAst?
+ continue
+ else if expression instanceof Directive
+ directives.push expressionAst
+
+
+
+
+
+
+
+
+ ¶
+
+ If an expression is a statement, it can be added to the body as is.
+
+
+
+ else if expression.isStatementAst o
+ body.push expressionAst
+
+
+
+
+
+
+
+
+ ¶
+
+ Otherwise, we need to wrap it in an ExpressionStatement
AST node.
+
+
+
+ else
+ body.push Object.assign
+ type: 'ExpressionStatement'
+ expression: expressionAst
+ ,
+ expression.astLocationData()
+
+ return {
+
+
+
+
+
+
+
+
+ ¶
+
+ For now, we’re not including sourceType
on the Program
AST node.
+Its value could be either 'script'
or 'module'
, and there’s no way
+for CoffeeScript to always know which it should be. The presence of an
+import
or export
statement in source code would imply that it should
+be a module
, but a project may consist of mostly such files and also
+an outlier file that lacks import
or export
but is still imported
+into the project and therefore expects to be treated as a module
.
+Determining the value of sourceType
is essentially the same challenge
+posed by determining the parse goal of a JavaScript file, also module
+or script
, and so if Node figures out a way to do so for .js
files
+then CoffeeScript can copy Node’s algorithm.
+
+
+
+
+
+
+
+
+
+
+ ¶
+
+ sourceType: ‘module’
+
+
+
+ body, directives
+ }
+
+ astLocationData: ->
+ return if @isRootBlock and not @locationData?
+ super()
+
+
+
+
+
+
+
+
+ ¶
+
+ A directive e.g. ‘use strict’.
+Currently only used during AST generation.
+
+
+
+ exports.Directive = class Directive extends Base
+ constructor: (@value) ->
+ super()
+
+ astProperties: (o) ->
+ return
+ value: Object.assign {},
+ @value.ast o
+ type: 'DirectiveLiteral'
+
+
+
+
+
+
+
+
+ ¶
Literal
@@ -1692,11 +2132,11 @@ to be one.
-
+
Literal
is a base class for static values that can be passed through
directly into JavaScript without translation, such as: strings, numbers,
@@ -1716,16 +2156,20 @@ directly into JavaScript without translation, such as: strings, numbers,
compileNode: (o) ->
[@makeCode @value]
+ astProperties: ->
+ return
+ value: @value
+
toString: ->
-
+
This is only intended for debugging.
@@ -1734,11 +2178,58 @@ directly into JavaScript without translation, such as: strings, numbers,
" #{if @isStatement() then super() else @constructor.name}: #{@value}"
exports.NumberLiteral = class NumberLiteral extends Literal
+ constructor: (@value, {@parsedValue} = {}) ->
+ super()
+ unless @parsedValue?
+ if isNumber @value
+ @parsedValue = @value
+ @value = "#{@value}"
+ else
+ @parsedValue = parseNumber @value
+
+ isBigInt: ->
+ /n$/.test @value
+
+ astType: ->
+ if @isBigInt()
+ 'BigIntLiteral'
+ else
+ 'NumericLiteral'
+
+ astProperties: ->
+ return
+ value:
+ if @isBigInt()
+ @parsedValue.toString()
+ else
+ @parsedValue
+ extra:
+ rawValue:
+ if @isBigInt()
+ @parsedValue.toString()
+ else
+ @parsedValue
+ raw: @value
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
+ constructor: (@value, {@originalValue = 'Infinity'} = {}) ->
+ super()
+
compileNode: ->
[@makeCode '2e308']
+ astNode: (o) ->
+ unless @originalValue is 'Infinity'
+ return new NumberLiteral(@value).withLocationDataFrom(@).ast o
+ super o
+
+ astType: -> 'Identifier'
+
+ astProperties: ->
+ return
+ name: 'Infinity'
+ declaration: no
+
exports.NaNLiteral = class NaNLiteral extends NumberLiteral
constructor: ->
super 'NaN'
@@ -1747,19 +2238,189 @@ exports.NaNLiteral = class
code = [@makeCode '0/0']
if o.level >= LEVEL_OP then @wrapInParentheses code else code
-exports.StringLiteral = class StringLiteral extends Literal
- compileNode: (o) ->
- res = if @csx then [@makeCode @unquote(yes, yes)] else super()
+ astType: -> 'Identifier'
- unquote: (doubleQuote = no, newLine = no) ->
- unquoted = @value[1...-1]
- unquoted = unquoted.replace /\\"/g, '"' if doubleQuote
- unquoted = unquoted.replace /\\n/g, '\n' if newLine
- unquoted
+ astProperties: ->
+ return
+ name: 'NaN'
+ declaration: no
+
+exports.StringLiteral = class StringLiteral extends Literal
+ constructor: (@originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex} = {}) ->
+ super ''
+ @quote = null if @quote is '///'
+ @fromSourceString = @quote?
+ @quote ?= '"'
+ heredoc = @isFromHeredoc()
+
+ val = @originalValue
+ if @heregex
+ val = val.replace HEREGEX_OMIT, '$1$2'
+ val = replaceUnicodeCodePointEscapes val, flags: @heregex.flags
+ else
+ val = val.replace STRING_OMIT, '$1'
+ val =
+ unless @fromSourceString
+ val
+ else if heredoc
+ indentRegex = /// \n#{@indent} ///g if @indent
+
+ val = val.replace indentRegex, '\n' if indentRegex
+ val = val.replace LEADING_BLANK_LINE, '' if @initialChunk
+ val = val.replace TRAILING_BLANK_LINE, '' if @finalChunk
+ val
+ else
+ val.replace SIMPLE_STRING_OMIT, (match, offset) =>
+ if (@initialChunk and offset is 0) or
+ (@finalChunk and offset + match.length is val.length)
+ ''
+ else
+ ' '
+ @delimiter = @quote.charAt 0
+ @value = makeDelimitedLiteral val, {
+ @delimiter
+ @double
+ }
+
+ @unquotedValueForTemplateLiteral = makeDelimitedLiteral val, {
+ delimiter: '`'
+ @double
+ escapeNewlines: no
+ includeDelimiters: no
+ convertTrailingNullEscapes: yes
+ }
+
+ @unquotedValueForJSX = makeDelimitedLiteral val, {
+ @double
+ escapeNewlines: no
+ includeDelimiters: no
+ escapeDelimiter: no
+ }
+
+ compileNode: (o) ->
+ return StringWithInterpolations.fromStringLiteral(@).compileNode o if @shouldGenerateTemplateLiteral()
+ return [@makeCode @unquotedValueForJSX] if @jsx
+ super o
+
+
+
+
+
+
+
+
+ ¶
+
+ StringLiteral
s can represent either entire literal strings
+or pieces of text inside of e.g. an interpolated string.
+When parsed as the former but needing to be treated as the latter
+(e.g. the string part of a tagged template literal), this will return
+a copy of the StringLiteral
with the quotes trimmed from its location
+data (like it would have if parsed as part of an interpolated string).
+
+
+
+ withoutQuotesInLocationData: ->
+ endsWithNewline = @originalValue[-1..] is '\n'
+ locationData = Object.assign {}, @locationData
+ locationData.first_column += @quote.length
+ if endsWithNewline
+ locationData.last_line -= 1
+ locationData.last_column =
+ if locationData.last_line is locationData.first_line
+ locationData.first_column + @originalValue.length - '\n'.length
+ else
+ @originalValue[...-1].length - '\n'.length - @originalValue[...-1].lastIndexOf('\n')
+ else
+ locationData.last_column -= @quote.length
+ locationData.last_column_exclusive -= @quote.length
+ locationData.range = [
+ locationData.range[0] + @quote.length
+ locationData.range[1] - @quote.length
+ ]
+ copy = new StringLiteral @originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex}
+ copy.locationData = locationData
+ copy
+
+ isFromHeredoc: ->
+ @quote.length is 3
+
+ shouldGenerateTemplateLiteral: ->
+ @isFromHeredoc()
+
+ astNode: (o) ->
+ return StringWithInterpolations.fromStringLiteral(@).ast o if @shouldGenerateTemplateLiteral()
+ super o
+
+ astProperties: ->
+ return
+ value: @originalValue
+ extra:
+ raw: "#{@delimiter}#{@originalValue}#{@delimiter}"
exports.RegexLiteral = class RegexLiteral extends Literal
+ constructor: (value, {@delimiter = '/', @heregexCommentTokens = []} = {}) ->
+ super ''
+ heregex = @delimiter is '///'
+ endDelimiterIndex = value.lastIndexOf '/'
+ @flags = value[endDelimiterIndex + 1..]
+ val = @originalValue = value[1...endDelimiterIndex]
+ val = val.replace HEREGEX_OMIT, '$1$2' if heregex
+ val = replaceUnicodeCodePointEscapes val, {@flags}
+ @value = "#{makeDelimitedLiteral val, delimiter: '/'}#{@flags}"
+
+ REGEX_REGEX: /// ^ / (.*) / \w* $ ///
+
+ astType: -> 'RegExpLiteral'
+
+ astProperties: (o) ->
+ [, pattern] = @REGEX_REGEX.exec @value
+ return {
+ value: undefined
+ pattern, @flags, @delimiter
+ originalPattern: @originalValue
+ extra:
+ raw: @value
+ originalRaw: "#{@delimiter}#{@originalValue}#{@delimiter}#{@flags}"
+ rawValue: undefined
+ comments:
+ for heregexCommentToken in @heregexCommentTokens
+ if heregexCommentToken.here
+ new HereComment(heregexCommentToken).ast o
+ else
+ new LineComment(heregexCommentToken).ast o
+ }
exports.PassthroughLiteral = class PassthroughLiteral extends Literal
+ constructor: (@originalValue, {@here, @generated} = {}) ->
+ super ''
+ @value = @originalValue.replace /\\+(`|$)/g, (string) ->
+
+
+
+
+
+
+
+
+ ¶
+
+ string
is always a value like ‘`‘, ‘\`‘, ‘\\`‘, etc.
+By reducing it to its latter half, we turn ‘`‘ to ‘', '\\\
‘ to ‘`‘, etc.
+
+
+
+ string[-Math.ceil(string.length / 2)..]
+
+ astNode: (o) ->
+ return null if @generated
+ super o
+
+ astProperties: ->
+ return {
+ value: @originalValue
+ here: !!@here
+ }
exports.IdentifierLiteral = class IdentifierLiteral extends Literal
isAssignable: YES
@@ -1767,15 +2428,38 @@ exports.IdentifierLiteral =
eachName: (iterator) ->
iterator @
-exports.CSXTag = class CSXTag extends IdentifierLiteral
+ astType: ->
+ if @jsx
+ 'JSXIdentifier'
+ else
+ 'Identifier'
+
+ astProperties: ->
+ return
+ name: @value
+ declaration: !!@isDeclaration
exports.PropertyName = class PropertyName extends Literal
isAssignable: YES
+ astType: ->
+ if @jsx
+ 'JSXIdentifier'
+ else
+ 'Identifier'
+
+ astProperties: ->
+ return
+ name: @value
+ declaration: no
+
exports.ComputedPropertyName = class ComputedPropertyName extends PropertyName
compileNode: (o) ->
[@makeCode('['), @value.compileToFragments(o, LEVEL_LIST)..., @makeCode(']')]
+ astNode: (o) ->
+ @value.ast o
+
exports.StatementLiteral = class StatementLiteral extends Literal
isStatement: YES
@@ -1788,14 +2472,27 @@ exports.StatementLiteral = c
compileNode: (o) ->
[@makeCode "#{@tab}#{@value};"]
+ astType: ->
+ switch @value
+ when 'continue' then 'ContinueStatement'
+ when 'break' then 'BreakStatement'
+ when 'debugger' then 'DebuggerStatement'
+
exports.ThisLiteral = class ThisLiteral extends Literal
- constructor: ->
+ constructor: (value) ->
super 'this'
+ @shorthand = value is '@'
compileNode: (o) ->
code = if o.scope.method?.bound then o.scope.method.context else @value
[@makeCode code]
+ astType: -> 'ThisExpression'
+
+ astProperties: ->
+ return
+ shorthand: @shorthand
+
exports.UndefinedLiteral = class UndefinedLiteral extends Literal
constructor: ->
super 'undefined'
@@ -1803,20 +2500,42 @@ exports.UndefinedLiteral = c
compileNode: (o) ->
[@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
+ astType: -> 'Identifier'
+
+ astProperties: ->
+ return
+ name: @value
+ declaration: no
+
exports.NullLiteral = class NullLiteral extends Literal
constructor: ->
super 'null'
-exports.BooleanLiteral = class BooleanLiteral extends Literal
+exports.BooleanLiteral = class BooleanLiteral extends Literal
+ constructor: (value, {@originalValue} = {}) ->
+ super value
+ @originalValue ?= @value
+
+ astProperties: ->
+ value: if @value is 'true' then yes else no
+ name: @originalValue
+
+exports.DefaultLiteral = class DefaultLiteral extends Literal
+ astType: -> 'Identifier'
+
+ astProperties: ->
+ return
+ name: 'default'
+ declaration: no
-
+
Return
@@ -1825,18 +2544,18 @@ exports.BooleanLiteral = cla
-
+
exports.Return = class Return extends Base
- constructor: (@expression) ->
+ constructor: (@expression, {@belongsToFuncDirectiveReturn} = {}) ->
super()
children: ['expression']
@@ -1855,11 +2574,11 @@ exports.BooleanLiteral = cla
-
+
TODO: If we call expression.compile()
here twice, we’ll sometimes
get back different results!
@@ -1873,11 +2592,11 @@ get back different results!
-
+
+ answer
+
+ checkForPureStatementInExpression: ->
-
+
+
+ return if @belongsToFuncDirectiveReturn
+ super()
+
+ astType: -> 'ReturnStatement'
+
+ astProperties: (o) ->
+ argument: @expression?.ast(o, LEVEL_PAREN) ? null
+
+
+
+
+
+
+
+
+ ¶
+
+ Parent class for YieldReturn
/AwaitReturn
.
+
+
+
+ exports.FuncDirectiveReturn = class FuncDirectiveReturn extends Return
+ constructor: (expression, {@returnKeyword}) ->
+ super expression
+
+ compileNode: (o) ->
+ @checkScope o
+ super o
+
+ checkScope: (o) ->
+ unless o.scope.parent?
+ @error "#{@keyword} can only occur inside functions"
+
+ isStatementAst: NO
+
+ astNode: (o) ->
+ @checkScope o
+
+ new Op @keyword,
+ new Return @expression, belongsToFuncDirectiveReturn: yes
+ .withLocationDataFrom(
+ if @expression?
+ locationData: mergeLocationData @returnKeyword.locationData, @expression.locationData
+ else
+ @returnKeyword
+ )
+ .withLocationDataFrom @
+ .ast o
+
+
+
+
+
+
+
+
+ ¶
yield return
works exactly like return
, except that it turns the function
into a generator.
- exports.YieldReturn = class YieldReturn extends Return
- compileNode: (o) ->
- unless o.scope.parent?
- @error 'yield can only occur inside functions'
- super o
+ exports.YieldReturn = class YieldReturn extends FuncDirectiveReturn
+ keyword: 'yield'
-exports.AwaitReturn = class AwaitReturn extends Return
- compileNode: (o) ->
- unless o.scope.parent?
- @error 'await can only occur inside functions'
- super o
+exports.AwaitReturn = class AwaitReturn extends FuncDirectiveReturn
+ keyword: 'await'
-
+
Value
@@ -1938,11 +2715,11 @@ exports.AwaitReturn = class<
-
+
-
+
If this is a @foo =
assignment, if there are comments on @
move them
to be on foo
.
@@ -1980,11 +2758,11 @@ to be on foo
.
-
+
Add a property (or properties ) Access
to the list.
@@ -2004,11 +2782,11 @@ to be on foo
.
-
+
Some boolean checks for the benefit of other nodes.
@@ -2017,7 +2795,7 @@ to be on foo
.
isArray : -> @bareLiteral(Arr)
isRange : -> @bareLiteral(Range)
shouldCache : -> @hasProperties() or @base.shouldCache()
- isAssignable : -> @hasProperties() or @base.isAssignable()
+ isAssignable : (opts) -> @hasProperties() or @base.isAssignable opts
isNumber : -> @bareLiteral(NumberLiteral)
isString : -> @bareLiteral(StringLiteral)
isRegex : -> @bareLiteral(RegexLiteral)
@@ -2026,7 +2804,7 @@ to be on foo
.
isBoolean : -> @bareLiteral(BooleanLiteral)
isAtomic : ->
for node in @properties.concat @base
- return no if node.soak or node instanceof Call
+ return no if node.soak or node instanceof Call or node instanceof Op and node.operator is 'do'
yes
isNotCallable : -> @isNumber() or @isString() or @isRegex() or
@@ -2034,6 +2812,7 @@ to be on foo
.
@isUndefined() or @isNull() or @isBoolean()
isStatement : (o) -> not @properties.length and @base.isStatement o
+ isJSXTag : -> @base instanceof JSXTag
assigns : (name) -> not @properties.length and @base.assigns name
jumps : (o) -> not @properties.length and @base.jumps o
@@ -2046,21 +2825,23 @@ to be on foo
.
@base.hasElision()
isSplice: ->
- [..., lastProp] = @properties
- lastProp instanceof Slice
+ [..., lastProperty] = @properties
+ lastProperty instanceof Slice
looksStatic: (className) ->
- (@this or @base instanceof ThisLiteral or @base.value is className) and
- @properties.length is 1 and @properties[0].name?.value isnt 'prototype'
+ return no unless ((thisLiteral = @base) instanceof ThisLiteral or (name = @base).value is className) and
+ @properties.length is 1 and @properties[0].name?.value isnt 'prototype'
+ return
+ staticClassName: thisLiteral ? name
-
+
The value can be unwrapped as its inner node, if there are no attached
properties.
@@ -2073,11 +2854,11 @@ properties.
-
+
A reference has base part (this
value) and name part.
We cache them separately for compiling complex expressions.
@@ -2103,11 +2884,11 @@ We cache them separately for compiling complex expressions.
-
+
We compile a value to JavaScript by compiling and joining each property.
Things get much more interesting if the chain of properties has soak
@@ -2117,7 +2898,6 @@ evaluate anything twice when building the soak chain.
compileNode: (o) ->
- @checkNewTarget o
@base.front = @front
props = @properties
if props.length and @base.cached?
@@ -2125,11 +2905,11 @@ evaluate anything twice when building the soak chain.
-
+
Cached fragments enable correct order of the compilation,
and reuse of variables in the scope.
@@ -2148,24 +2928,16 @@ Example:
for prop in props
fragments.push (prop.compileToFragments o)...
- fragments
-
- checkNewTarget: (o) ->
- return unless @base instanceof IdentifierLiteral and @base.value is 'new' and @properties.length
- if @properties[0] instanceof Access and @properties[0].name.value is 'target'
- unless o.scope.parent?
- @error "new.target can only occur inside functions"
- else
- @error "the only valid meta property for new is new.target"
+ fragments
-
+
Unfold a soak into an If
: a?.b
-> a.b if a?
@@ -2188,10 +2960,10 @@ Example:
return new If new Existence(fst), snd, soak: on
no
- eachName: (iterator) ->
+ eachName: (iterator, {checkAssignability = yes} = {}) ->
if @hasProperties()
iterator @
- else if @base.isAssignable()
+ else if not checkAssignability or @base.isAssignable()
@base.eachName iterator
else
@error 'tried to assign to unassignable value'
@@ -2199,11 +2971,240 @@ Example:
-
+
+ For AST generation, we need an object
that’s this Value
minus its last
+property, if it has properties.
+
+
+
+ object: ->
+ return @ unless @hasProperties()
+
+
+
+
+
+
+
+
+ ¶
+
+ Get all properties except the last one; for a Value
with only one
+property, initialProperties
is an empty array.
+
+
+
+ initialProperties = @properties[0...@properties.length - 1]
+
+
+
+
+
+
+
+
+ ¶
+
+ Create the object
that becomes the new “base” for the split-off final
+property.
+
+
+
+ object = new Value @base, initialProperties, @tag, @isDefaultValue
+
+
+
+
+
+
+
+
+ ¶
+
+ Add location data to our new node, so that it has correct location data
+for source maps or later conversion into AST location data.
+
+
+
+ object.locationData =
+ if initialProperties.length is 0
+
+
+
+
+
+
+
+
+ ¶
+
+ This new Value
has only one property, so the location data is just
+that of the parent Value
’s base.
+
+
+
+ @base.locationData
+ else
+
+
+
+
+
+
+
+
+ ¶
+
+ This new Value
has multiple properties, so the location data spans
+from the parent Value
’s base to the last property that’s included
+in this new node (a.k.a. the second-to-last property of the parent).
+
+
+
+ mergeLocationData @base.locationData, initialProperties[initialProperties.length - 1].locationData
+ object
+
+ containsSoak: ->
+ return no unless @hasProperties()
+
+ for property in @properties when property.soak
+ return yes
+
+ return yes if @base instanceof Call and @base.soak
+
+ no
+
+ astNode: (o) ->
+
+
+
+
+
+
+
+
+ ¶
+
+ If the Value
has no properties, the AST node is just whatever this
+node’s base
is.
+
+
+
+ return @base.ast o unless @hasProperties()
+
+
+
+
+
+
+
+
+ ¶
+
+ Otherwise, call Base::ast
which in turn calls the astType
and
+astProperties
methods below.
+
+
+
+ super o
+
+ astType: ->
+ if @isJSXTag()
+ 'JSXMemberExpression'
+ else if @containsSoak()
+ 'OptionalMemberExpression'
+ else
+ 'MemberExpression'
+
+
+
+
+
+
+
+
+ ¶
+
+ If this Value
has properties, the last property (e.g. c
in a.b.c
)
+becomes the property
, and the preceding properties (e.g. a.b
) become
+a child Value
node assigned to the object
property.
+
+
+
+ astProperties: (o) ->
+ [..., property] = @properties
+ property.name.jsx = yes if @isJSXTag()
+ computed = property instanceof Index or property.name?.unwrap() not instanceof PropertyName
+ return {
+ object: @object().ast o, LEVEL_ACCESS
+ property: property.ast o, (LEVEL_PAREN if computed)
+ computed
+ optional: !!property.soak
+ shorthand: !!property.shorthand
+ }
+
+ astLocationData: ->
+ return super() unless @isJSXTag()
+
+
+
+
+
+
+
+
+ ¶
+
+ don’t include leading < of JSX tag in location data
+
+
+
+ mergeAstLocationData(
+ jisonLocationDataToAstLocationData(@base.tagNameLocationData),
+ jisonLocationDataToAstLocationData(@properties[@properties.length - 1].locationData)
+ )
+
+exports.MetaProperty = class MetaProperty extends Base
+ constructor: (@meta, @property) ->
+ super()
+
+ children: ['meta', 'property']
+
+ checkValid: (o) ->
+ if @meta.value is 'new'
+ if @property instanceof Access and @property.name.value is 'target'
+ unless o.scope.parent?
+ @error "new.target can only occur inside functions"
+ else
+ @error "the only valid meta property for new is new.target"
+
+ compileNode: (o) ->
+ @checkValid o
+ fragments = []
+ fragments.push @meta.compileToFragments(o, LEVEL_ACCESS)...
+ fragments.push @property.compileToFragments(o)...
+ fragments
+
+ astProperties: (o) ->
+ @checkValid o
+
+ return
+ meta: @meta.ast o, LEVEL_ACCESS
+ property: @property.ast o
+
+
+
+
+
+
+
+
+ ¶
HereComment
@@ -2212,45 +3213,46 @@ Example:
-
+
exports.HereComment = class HereComment extends Base
- constructor: ({ @content, @newLine, @unshift }) ->
+ constructor: ({ @content, @newLine, @unshift, @locationData }) ->
super()
compileNode: (o) ->
- multiline = '\n' in @content
- hasLeadingMarks = /\n\s*[#|\*]/.test @content
- @content = @content.replace /^([ \t]*)#(?=\s)/gm, ' *' if hasLeadingMarks
+ multiline = '\n' in @content
-
+
if multiline
- largestIndent = ''
+ indent = null
for line in @content.split '\n'
leadingWhitespace = /^\s*/.exec(line)[0]
- if leadingWhitespace.length > largestIndent.length
- largestIndent = leadingWhitespace
- @content = @content.replace ///^(#{leadingWhitespace})///gm, ''
+ if not indent or leadingWhitespace.length < indent.length
+ indent = leadingWhitespace
+ @content = @content.replace /// \n #{indent} ///g, '\n' if indent
+
+ hasLeadingMarks = /\n\s*[#|\*]/.test @content
+ @content = @content.replace /^([ \t]*)#(?=\s)/gm, ' *' if hasLeadingMarks
@content = "/*#{@content}#{if hasLeadingMarks then ' ' else ''}*/"
fragment = @makeCode @content
@@ -2261,27 +3263,33 @@ Example:
-
+
fragment.isComment = fragment.isHereComment = yes
- fragment
+ fragment
+
+ astType: -> 'CommentBlock'
+
+ astProperties: ->
+ return
+ value: @content
-
+
LineComment
@@ -2290,22 +3298,22 @@ Example:
-
+
exports.LineComment = class LineComment extends Base
- constructor: ({ @content, @newLine, @unshift }) ->
+ constructor: ({ @content, @newLine, @unshift, @locationData, @precededByBlankLine }) ->
super()
compileNode: (o) ->
- fragment = @makeCode(if /^\s*$/.test @content then '' else "//#{@content}")
+ fragment = @makeCode(if /^\s*$/.test @content then '' else "#{if @precededByBlankLine then "\n#{o.indent}" else ''}//#{@content}")
fragment.newLine = @newLine
fragment.unshift = @unshift
fragment.trail = not @newLine and not @unshift
@@ -2313,27 +3321,435 @@ Example:
-
+
fragment.isComment = fragment.isLineComment = yes
- fragment
+ fragment
+
+ astType: -> 'CommentLine'
+
+ astProperties: ->
+ return
+ value: @content
-
+
+
+
+exports.JSXIdentifier = class JSXIdentifier extends IdentifierLiteral
+ astType: -> 'JSXIdentifier'
+
+exports.JSXTag = class JSXTag extends JSXIdentifier
+ constructor: (value, {
+ @tagNameLocationData
+ @closingTagOpeningBracketLocationData
+ @closingTagSlashLocationData
+ @closingTagNameLocationData
+ @closingTagClosingBracketLocationData
+ }) ->
+ super value
+
+ astProperties: ->
+ return
+ name: @value
+
+exports.JSXExpressionContainer = class JSXExpressionContainer extends Base
+ constructor: (@expression, {locationData} = {}) ->
+ super()
+ @expression.jsxAttribute = yes
+ @locationData = locationData ? @expression.locationData
+
+ children: ['expression']
+
+ compileNode: (o) ->
+ @expression.compileNode(o)
+
+ astProperties: (o) ->
+ return
+ expression: astAsBlockIfNeeded @expression, o
+
+exports.JSXEmptyExpression = class JSXEmptyExpression extends Base
+
+exports.JSXText = class JSXText extends Base
+ constructor: (stringLiteral) ->
+ super()
+ @value = stringLiteral.unquotedValueForJSX
+ @locationData = stringLiteral.locationData
+
+ astProperties: ->
+ return {
+ @value
+ extra:
+ raw: @value
+ }
+
+exports.JSXAttribute = class JSXAttribute extends Base
+ constructor: ({@name, value}) ->
+ super()
+ @value =
+ if value?
+ value = value.base
+ if value instanceof StringLiteral
+ value
+ else
+ new JSXExpressionContainer value
+ else
+ null
+ @value?.comments = value.comments
+
+ children: ['name', 'value']
+
+ compileNode: (o) ->
+ compiledName = @name.compileToFragments o, LEVEL_LIST
+ return compiledName unless @value?
+ val = @value.compileToFragments o, LEVEL_LIST
+ compiledName.concat @makeCode('='), val
+
+ astProperties: (o) ->
+ name = @name
+ if ':' in name.value
+ name = new JSXNamespacedName name
+ return
+ name: name.ast o
+ value: @value?.ast(o) ? null
+
+exports.JSXAttributes = class JSXAttributes extends Base
+ constructor: (arr) ->
+ super()
+ @attributes = []
+ for object in arr.objects
+ @checkValidAttribute object
+ {base} = object
+ if base instanceof IdentifierLiteral
+
+
+
+
+
+
+
+
+ ¶
+
+ attribute with no value eg disabled
+
+
+
+ attribute = new JSXAttribute name: new JSXIdentifier(base.value).withLocationDataAndCommentsFrom base
+ attribute.locationData = base.locationData
+ @attributes.push attribute
+ else if not base.generated
+
+
+
+
+
+
+
+
+ ¶
+
+ object spread attribute eg {…props}
+
+
+
+ attribute = base.properties[0]
+ attribute.jsx = yes
+ attribute.locationData = base.locationData
+ @attributes.push attribute
+ else
+
+
+
+
+
+
+
+
+ ¶
+
+ Obj containing attributes with values eg a=”b” c={d}
+
+
+
+ for property in base.properties
+ {variable, value} = property
+ attribute = new JSXAttribute {
+ name: new JSXIdentifier(variable.base.value).withLocationDataAndCommentsFrom variable.base
+ value
+ }
+ attribute.locationData = property.locationData
+ @attributes.push attribute
+ @locationData = arr.locationData
+
+ children: ['attributes']
+
+
+
+
+
+
+
+
+ ¶
+
+ Catch invalid attributes:
+
+
+
+ checkValidAttribute: (object) ->
+ {base: attribute} = object
+ properties = attribute?.properties or []
+ if not (attribute instanceof Obj or attribute instanceof IdentifierLiteral) or (attribute instanceof Obj and not attribute.generated and (properties.length > 1 or not (properties[0] instanceof Splat)))
+ object.error """
+ Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
+ """
+
+ compileNode: (o) ->
+ fragments = []
+ for attribute in @attributes
+ fragments.push @makeCode ' '
+ fragments.push attribute.compileToFragments(o, LEVEL_TOP)...
+ fragments
+
+ astNode: (o) ->
+ attribute.ast(o) for attribute in @attributes
+
+exports.JSXNamespacedName = class JSXNamespacedName extends Base
+ constructor: (tag) ->
+ super()
+ [namespace, name] = tag.value.split ':'
+ @namespace = new JSXIdentifier(namespace).withLocationDataFrom locationData: extractSameLineLocationDataFirst(namespace.length) tag.locationData
+ @name = new JSXIdentifier(name ).withLocationDataFrom locationData: extractSameLineLocationDataLast(name.length ) tag.locationData
+ @locationData = tag.locationData
+
+ children: ['namespace', 'name']
+
+ astProperties: (o) ->
+ return
+ namespace: @namespace.ast o
+ name: @name.ast o
+
+
+
+
+
+
+
+
+ ¶
+
+ Node for a JSX element
+
+
+
+ exports.JSXElement = class JSXElement extends Base
+ constructor: ({@tagName, @attributes, @content}) ->
+ super()
+
+ children: ['tagName', 'attributes', 'content']
+
+ compileNode: (o) ->
+ @content?.base.jsx = yes
+ fragments = [@makeCode('<')]
+ fragments.push (tag = @tagName.compileToFragments(o, LEVEL_ACCESS))...
+ fragments.push @attributes.compileToFragments(o)...
+ if @content
+ fragments.push @makeCode('>')
+ fragments.push @content.compileNode(o, LEVEL_LIST)...
+ fragments.push [@makeCode('</'), tag..., @makeCode('>')]...
+ else
+ fragments.push @makeCode(' />')
+ fragments
+
+ isFragment: ->
+ !@tagName.base.value.length
+
+ astNode: (o) ->
+
+
+
+
+
+
+
+
+ ¶
+
+ The location data spanning the opening element < … > is captured by
+the generated Arr which contains the element’s attributes
+
+
+
+ @openingElementLocationData = jisonLocationDataToAstLocationData @attributes.locationData
+
+ tagName = @tagName.base
+ tagName.locationData = tagName.tagNameLocationData
+ if @content?
+ @closingElementLocationData = mergeAstLocationData(
+ jisonLocationDataToAstLocationData tagName.closingTagOpeningBracketLocationData
+ jisonLocationDataToAstLocationData tagName.closingTagClosingBracketLocationData
+ )
+
+ super o
+
+ astType: ->
+ if @isFragment()
+ 'JSXFragment'
+ else
+ 'JSXElement'
+
+ elementAstProperties: (o) ->
+ tagNameAst = =>
+ tag = @tagName.unwrap()
+ if tag?.value and ':' in tag.value
+ tag = new JSXNamespacedName tag
+ tag.ast o
+
+ openingElement = Object.assign {
+ type: 'JSXOpeningElement'
+ name: tagNameAst()
+ selfClosing: not @closingElementLocationData?
+ attributes: @attributes.ast o
+ }, @openingElementLocationData
+
+ closingElement = null
+ if @closingElementLocationData?
+ closingElement = Object.assign {
+ type: 'JSXClosingElement'
+ name: Object.assign(
+ tagNameAst(),
+ jisonLocationDataToAstLocationData @tagName.base.closingTagNameLocationData
+ )
+ }, @closingElementLocationData
+ if closingElement.name.type in ['JSXMemberExpression', 'JSXNamespacedName']
+ rangeDiff = closingElement.range[0] - openingElement.range[0] + '/'.length
+ columnDiff = closingElement.loc.start.column - openingElement.loc.start.column + '/'.length
+ shiftAstLocationData = (node) =>
+ node.range = [
+ node.range[0] + rangeDiff
+ node.range[1] + rangeDiff
+ ]
+ node.start += rangeDiff
+ node.end += rangeDiff
+ node.loc.start =
+ line: @closingElementLocationData.loc.start.line
+ column: node.loc.start.column + columnDiff
+ node.loc.end =
+ line: @closingElementLocationData.loc.start.line
+ column: node.loc.end.column + columnDiff
+ if closingElement.name.type is 'JSXMemberExpression'
+ currentExpr = closingElement.name
+ while currentExpr.type is 'JSXMemberExpression'
+ shiftAstLocationData currentExpr unless currentExpr is closingElement.name
+ shiftAstLocationData currentExpr.property
+ currentExpr = currentExpr.object
+ shiftAstLocationData currentExpr
+ else # JSXNamespacedName
+ shiftAstLocationData closingElement.name.namespace
+ shiftAstLocationData closingElement.name.name
+
+ {openingElement, closingElement}
+
+ fragmentAstProperties: (o) ->
+ openingFragment = Object.assign {
+ type: 'JSXOpeningFragment'
+ }, @openingElementLocationData
+
+ closingFragment = Object.assign {
+ type: 'JSXClosingFragment'
+ }, @closingElementLocationData
+
+ {openingFragment, closingFragment}
+
+ contentAst: (o) ->
+ return [] unless @content and not @content.base.isEmpty?()
+
+ content = @content.unwrapAll()
+ children =
+ if content instanceof StringLiteral
+ [new JSXText content]
+ else # StringWithInterpolations
+ for element in @content.unwrapAll().extractElements o, includeInterpolationWrappers: yes, isJsx: yes
+ if element instanceof StringLiteral
+ new JSXText element
+ else # Interpolation
+ {expression} = element
+ unless expression?
+ emptyExpression = new JSXEmptyExpression()
+ emptyExpression.locationData = emptyExpressionLocationData {
+ interpolationNode: element
+ openingBrace: '{'
+ closingBrace: '}'
+ }
+
+ new JSXExpressionContainer emptyExpression, locationData: element.locationData
+ else
+ unwrapped = expression.unwrapAll()
+ if unwrapped instanceof JSXElement and
+
+
+
+
+
+
+
+
+ ¶
+
+ distinguish <a><b /></a>
from <a>{<b />}</a>
+
+
+
+ unwrapped.locationData.range[0] is element.locationData.range[0]
+ unwrapped
+ else
+ new JSXExpressionContainer unwrapped, locationData: element.locationData
+
+ child.ast(o) for child in children when not (child instanceof JSXText and child.value.length is 0)
+
+ astProperties: (o) ->
+ Object.assign(
+ if @isFragment()
+ @fragmentAstProperties o
+ else
+ @elementAstProperties o
+ ,
+ children: @contentAst o
+ )
+
+ astLocationData: ->
+ if @closingElementLocationData?
+ mergeAstLocationData @openingElementLocationData, @closingElementLocationData
+ else
+ @openingElementLocationData
+
+
+
+
+
+
+
+
+ ¶
Call
@@ -2342,11 +3758,11 @@ Example:
-
+
Node for a function invocation.
@@ -2356,20 +3772,26 @@ Example:
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"
- @csx = @variable.base instanceof CSXTag
+ if @variable.base instanceof JSXTag
+ return new JSXElement(
+ tagName: @variable
+ attributes: new JSXAttributes @args[0].base
+ content: @args[1]
+ )
-
+
@variable
never gets output as a result of this node getting created as
part of RegexWithInterpolations
, so for that case move any comments to
@@ -2386,11 +3808,11 @@ the grammar.
-
+
When setting the location, we sometimes need to update the start location to
account for a newly-discovered new
operator to the left of us. This
@@ -2400,12 +3822,24 @@ expands the range on the left, but not the right.
updateLocationDataIfMissing: (locationData) ->
if @locationData and @needsUpdatedStartLocation
- @locationData.first_line = locationData.first_line
- @locationData.first_column = locationData.first_column
+ @locationData = Object.assign {},
+ @locationData,
+ first_line: locationData.first_line
+ first_column: locationData.first_column
+ range: [
+ locationData.range[0]
+ @locationData.range[1]
+ ]
base = @variable?.base or @variable
if base.needsUpdatedStartLocation
- @variable.locationData.first_line = locationData.first_line
- @variable.locationData.first_column = locationData.first_column
+ @variable.locationData = Object.assign {},
+ @variable.locationData,
+ first_line: locationData.first_line
+ first_column: locationData.first_column
+ range: [
+ locationData.range[0]
+ @variable.locationData.range[1]
+ ]
base.updateLocationDataIfMissing locationData
delete @needsUpdatedStartLocation
super locationData
@@ -2413,11 +3847,11 @@ expands the range on the left, but not the right.
-
+
Tag this invocation as creating a new instance.
@@ -2435,11 +3869,11 @@ expands the range on the left, but not the right.
-
+
Soaked chained invocations unfold into if/else ternary structures.
@@ -2480,29 +3914,29 @@ expands the range on the left, but not the right.
-
+
compileNode: (o) ->
- return @compileCSX o if @csx
+ @checkForNewSuper()
@variable?.front = @front
compiledArgs = []
-
+
If variable is Accessor
fragments are cached and used later
in Value::compileNode
to ensure correct order of the compilation,
@@ -2526,59 +3960,49 @@ Example:
fragments = []
if @isNew
- @variable.error "Unsupported reference to 'super'" if @variable instanceof Super
fragments.push @makeCode 'new '
fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
fragments
- compileCSX: (o) ->
- [attributes, content] = @args
- attributes.base.csx = yes
- content?.base.csx = yes
- fragments = [@makeCode('<')]
- fragments.push (tag = @variable.compileToFragments(o, LEVEL_ACCESS))...
- if attributes.base instanceof Arr
- for obj in attributes.base.objects
- attr = obj.base
- attrProps = attr?.properties or []
-
-
-
-
-
-
-
-
- ¶
-
- Catch invalid CSX attributes:
+ checkForNewSuper: ->
+ if @isNew
+ @variable.error "Unsupported reference to 'super'" if @variable instanceof Super
-
-
- if not (attr instanceof Obj or attr instanceof IdentifierLiteral) or (attr instanceof Obj and not attr.generated and (attrProps.length > 1 or not (attrProps[0] instanceof Splat)))
- obj.error """
- Unexpected token. Allowed CSX attributes are: id="val", src={source}, {props...} or attribute.
- """
- obj.base.csx = yes if obj.base instanceof Obj
- fragments.push @makeCode ' '
- fragments.push obj.compileToFragments(o, LEVEL_PAREN)...
- if content
- fragments.push @makeCode('>')
- fragments.push content.compileNode(o, LEVEL_LIST)...
- fragments.push [@makeCode('</'), tag..., @makeCode('>')]...
+ containsSoak: ->
+ return yes if @soak
+ return yes if @variable?.containsSoak?()
+ no
+
+ astNode: (o) ->
+ if @soak and @variable instanceof Super and o.scope.namedMethod()?.ctor
+ @variable.error "Unsupported reference to 'super'"
+ @checkForNewSuper()
+ super o
+
+ astType: ->
+ if @isNew
+ 'NewExpression'
+ else if @containsSoak()
+ 'OptionalCallExpression'
else
- fragments.push @makeCode(' />')
- fragments
+ 'CallExpression'
+
+ astProperties: (o) ->
+ return
+ callee: @variable.ast o, LEVEL_ACCESS
+ arguments: arg.ast(o, LEVEL_LIST) for arg in @args
+ optional: !!@soak
+ implicit: !!@implicit
-
+
Super
@@ -2587,11 +4011,11 @@ Example:
-
+
Takes care of converting super()
calls into calls against the prototype’s
function of the same name.
@@ -2618,11 +4042,11 @@ expression.
-
+
If we might be in an expression we need to cache and return the result
@@ -2635,15 +4059,15 @@ expression.
replacement.compileToFragments o, if o.level is LEVEL_TOP then o.level else LEVEL_LIST
exports.Super = class Super extends Base
- constructor: (@accessor) ->
+ constructor: (@accessor, @superLiteral) ->
super()
children: ['accessor']
compileNode: (o) ->
- method = o.scope.namedMethod()
- @error 'cannot use super outside of an instance method' unless method?.isMethod
+ @checkInInstanceMethod o
+ method = o.scope.namedMethod()
unless method.ctor? or @accessor?
{name, variable} = method
if name.shouldCache() or (name instanceof Index and name.index.isAssignable())
@@ -2656,11 +4080,11 @@ exports.Super = class
-
+
A super()
call gets compiled to e.g. super.method()
, which means
the method
property name gets compiled for the first time here, and
@@ -2678,16 +4102,33 @@ that they’re there for the later compilation.
fragments = (new Value (new Literal 'super'), if @accessor then [ @accessor ] else [])
.compileToFragments o
attachCommentsToNode salvagedComments, @accessor.name if salvagedComments
- fragments
+ fragments
+
+ checkInInstanceMethod: (o) ->
+ method = o.scope.namedMethod()
+ @error 'cannot use super outside of an instance method' unless method?.isMethod
+
+ astNode: (o) ->
+ @checkInInstanceMethod o
+
+ if @accessor?
+ return (
+ new Value(
+ new Super().withLocationDataFrom (@superLiteral ? @)
+ [@accessor]
+ ).withLocationDataFrom @
+ ).ast o
+
+ super o
-
+
RegexWithInterpolations
@@ -2696,29 +4137,46 @@ that they’re there for the later compilation.
-
+
Regexes with interpolations are in fact just a variation of a Call
(a
RegExp()
call to be precise) with a StringWithInterpolations
inside.
- exports.RegexWithInterpolations = class RegexWithInterpolations extends Call
- constructor: (args = []) ->
- super (new Value new IdentifierLiteral 'RegExp'), args, false
+ exports.RegexWithInterpolations = class RegexWithInterpolations extends Base
+ constructor: (@call, {@heregexCommentTokens = []} = {}) ->
+ super()
+
+ children: ['call']
+
+ compileNode: (o) ->
+ @call.compileNode o
+
+ astType: -> 'InterpolatedRegExpLiteral'
+
+ astProperties: (o) ->
+ interpolatedPattern: @call.args[0].ast o
+ flags: @call.args[1]?.unwrap().originalValue ? ''
+ comments:
+ for heregexCommentToken in @heregexCommentTokens
+ if heregexCommentToken.here
+ new HereComment(heregexCommentToken).ast o
+ else
+ new LineComment(heregexCommentToken).ast o
-
+
TaggedTemplateCall
@@ -2727,20 +4185,27 @@ that they’re there for the later compilation.
exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
constructor: (variable, arg, soak) ->
- arg = new StringWithInterpolations Block.wrap([ new Value arg ]) if arg instanceof StringLiteral
+ arg = StringWithInterpolations.fromStringLiteral arg if arg instanceof StringLiteral
super variable, [ arg ], soak
compileNode: (o) ->
- @variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
+ @variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
+
+ astType: -> 'TaggedTemplateExpression'
+
+ astProperties: (o) ->
+ return
+ tag: @variable.ast o, LEVEL_ACCESS
+ quasi: @args[0].ast o, LEVEL_LIST
-
+
Extends
@@ -2749,11 +4214,11 @@ exports.TaggedTemplateCall =
-
+
Node to extend an object’s prototype with an ancestor object.
After goog.inherits
from the
@@ -2770,11 +4235,11 @@ After goog.inherits
from the
-
+
Hooks one constructor into another’s prototype chain.
@@ -2786,11 +4251,11 @@ After goog.inherits
from the
-
+
Access
@@ -2799,11 +4264,11 @@ After goog.inherits
from the
-
+
A .
access into a property of a value, or the ::
shorthand for
an access into the object’s prototype.
@@ -2811,9 +4276,8 @@ an access into the object’s prototype.
exports.Access = class Access extends Base
- constructor: (@name, tag) ->
+ constructor: (@name, {@soak, @shorthand} = {}) ->
super()
- @soak = tag is 'soak'
children: ['name']
@@ -2825,16 +4289,35 @@ an access into the object’s prototype.
else
[@makeCode('['), name..., @makeCode(']')]
- shouldCache: NO
+ shouldCache: NO
+
+ astNode: (o) ->
-
+
+ Babel doesn’t have an AST node for Access
, but rather just includes
+this Access node’s child name
Identifier node as the property
of
+the MemberExpression
node.
+
+
+
+ @name.ast o
+
+
+
+
+
+
+ @index.shouldCache()
+
+ astNode: (o) ->
-
+
+ Babel doesn’t have an AST node for Index
, but rather just includes
+this Index node’s child index
Identifier node as the property
of
+the MemberExpression
node. The fact that the MemberExpression
’s
+property
is an Index means that computed
is true
for the
+MemberExpression
.
+
+
+
+ @index.ast o
+
+
+
+
+
+
+
+
+ ¶
Range
@@ -2881,11 +4385,11 @@ an access into the object’s prototype.
-
+
A range literal. Ranges can be used to extract portions (slices) of arrays,
to specify a range for comprehensions, or as a value, to be expanded into the
@@ -2906,11 +4410,11 @@ corresponding array of integers at runtime.
-
+
Compiles the range’s source variables – where it starts and where it ends.
But only if they need to be cached to avoid double evaluation.
@@ -2923,18 +4427,18 @@ But only if they need to be cached to avoid double evaluation.
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, shouldCache
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, shouldCache
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, shouldCache if step = del o, 'step'
- @fromNum = if @from.isNumber() then Number @fromVar else null
- @toNum = if @to.isNumber() then Number @toVar else null
- @stepNum = if step?.isNumber() then Number @stepVar else null
+ @fromNum = if @from.isNumber() then parseNumber @fromVar else null
+ @toNum = if @to.isNumber() then parseNumber @toVar else null
+ @stepNum = if step?.isNumber() then parseNumber @stepVar else null
-
+
When compiled normally, the range returns the contents of the for loop
needed to iterate over the values in the range. Used by comprehensions.
@@ -2948,11 +4452,11 @@ needed to iterate over the values in the range. Used by comprehensions.
-
+
Set up endpoints.
@@ -2974,11 +4478,11 @@ needed to iterate over the values in the range. Used by comprehensions.
-
+
Generate the condition.
@@ -2989,11 +4493,11 @@ needed to iterate over the values in the range. Used by comprehensions.
-
+
Always check if the step
isn’t zero to avoid the infinite loop.
@@ -3020,11 +4524,11 @@ needed to iterate over the values in the range. Used by comprehensions.
-
+
Generate the step.
@@ -3049,11 +4553,11 @@ needed to iterate over the values in the range. Used by comprehensions.
-
+
The final loop body.
@@ -3064,11 +4568,11 @@ needed to iterate over the values in the range. Used by comprehensions.
-
+
When used as a value, expand the range into the equivalent array.
@@ -3094,16 +4598,23 @@ needed to iterate over the values in the range. Used by comprehensions.
post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
hasArgs = (node) -> node?.contains isLiteralArguments
args = ', arguments' if hasArgs(@from) or hasArgs(@to)
- [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
+ [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
+
+ astProperties: (o) ->
+ return {
+ from: @from?.ast(o) ? null
+ to: @to?.ast(o) ? null
+ @exclusive
+ }
-
+
Slice
@@ -3112,11 +4623,11 @@ needed to iterate over the values in the range. Used by comprehensions.
-
+
An array slice literal. Unlike JavaScript’s Array#slice
, the second parameter
specifies the index of the end of the slice, just as the first parameter
@@ -3134,11 +4645,11 @@ is the index of the beginning.
-
+
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
.
@@ -3152,11 +4663,11 @@ is the index of the beginning.
-
+
+ [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
+
+ astNode: (o) ->
+ @range.ast o
-
+
Obj
@@ -3196,35 +4710,35 @@ is the index of the beginning.
-
+
exports.Obj = class Obj extends Base
- constructor: (props, @generated = no, @lhs = no) ->
+ constructor: (props, @generated = no) ->
super()
@objects = @properties = props or []
children: ['properties']
- isAssignable: ->
+ isAssignable: (opts) ->
for prop in @properties
-
+
Check for reserved words.
@@ -3236,7 +4750,7 @@ is the index of the beginning.
prop = prop.value if prop instanceof Assign and
prop.context is 'object' and
prop.value?.base not instanceof Arr
- return no unless prop.isAssignable()
+ return no unless prop.isAssignable opts
yes
shouldCache: ->
@@ -3245,11 +4759,11 @@ is the index of the beginning.
-
+
Check if object contains splat.
@@ -3262,11 +4776,11 @@ is the index of the beginning.
-
+
Move rest property to the end of the list.
{a, rest..., b} = obj
-> {a, b, rest...} = obj
@@ -3276,8 +4790,7 @@ is the index of the beginning.
reorderProperties: ->
props = @properties
- splatProps = (i for prop, i in props when prop instanceof Splat)
- props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
+ splatProps = @getAndCheckSplatProps()
splatProp = props.splice splatProps[0], 1
@objects = @properties = [].concat props, splatProp
@@ -3294,40 +4807,18 @@ is the index of the beginning.
-
+
- ¶
-
- CSX attributes
-
-
-
- return @compileCSXAttributes o if @csx
-
-
-
-
-
-
- if @lhs
- for prop in props when prop instanceof Assign
- {value} = prop
- unwrappedVal = value.unwrapAll()
- if unwrappedVal instanceof Arr or unwrappedVal instanceof Obj
- unwrappedVal.lhs = yes
- else if unwrappedVal instanceof Assign
- unwrappedVal.nestedLhs = yes
+ @propagateLhs()
isCompact = yes
for prop in @properties
@@ -3368,11 +4859,11 @@ are too.
-
+
{ [foo()] }
output as { [ref = foo()]: ref }
.
@@ -3387,11 +4878,11 @@ are too.
-
+
{ [expression] }
output as { [expression]: expression }
.
@@ -3407,6 +4898,13 @@ are too.
answer = @wrapInBraces answer
if @front then @wrapInParentheses answer else answer
+ getAndCheckSplatProps: ->
+ return unless @hasSplat() and @lhs
+ props = @properties
+ splatProps = (i for prop, i in props when prop instanceof Splat)
+ props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
+ splatProps
+
assigns: (name) ->
for prop in @properties when prop.assigns name then return yes
no
@@ -3415,27 +4913,172 @@ are too.
for prop in @properties
prop = prop.value if prop instanceof Assign and prop.context is 'object'
prop = prop.unwrapAll()
- prop.eachName iterator if prop.eachName?
-
- compileCSXAttributes: (o) ->
- props = @properties
- answer = []
- for prop, i in props
- prop.csx = yes
- join = if i is props.length - 1 then '' else ' '
- prop = new Literal "{#{prop.compile(o)}}" if prop instanceof Splat
- answer.push prop.compileToFragments(o, LEVEL_TOP)...
- answer.push @makeCode join
- if @front then @wrapInParentheses answer else answer
+ prop.eachName iterator if prop.eachName?
-
+
+
+ expandProperty: (property) ->
+ {variable, context, operatorToken} = property
+ key = if property instanceof Assign and context is 'object'
+ variable
+ else if property instanceof Assign
+ operatorToken.error "unexpected #{operatorToken.value}" unless @lhs
+ variable
+ else
+ property
+ if key instanceof Value and key.hasProperties()
+ key.error 'invalid object key' unless context isnt 'object' and key.this
+ if property instanceof Assign
+ return new ObjectProperty fromAssign: property
+ else
+ return new ObjectProperty key: property
+ return new ObjectProperty(fromAssign: property) unless key is property
+ return property if property instanceof Splat
+
+ new ObjectProperty key: property
+
+ expandProperties: ->
+ @expandProperty(property) for property in @properties
+
+ propagateLhs: (setLhs) ->
+ @lhs = yes if setLhs
+ return unless @lhs
+
+ for property in @properties
+ if property instanceof Assign and property.context is 'object'
+ {value} = property
+ unwrappedValue = value.unwrapAll()
+ if unwrappedValue instanceof Arr or unwrappedValue instanceof Obj
+ unwrappedValue.propagateLhs yes
+ else if unwrappedValue instanceof Assign
+ unwrappedValue.nestedLhs = yes
+ else if property instanceof Assign
+
+
+
+
+
+
+
+
+ ¶
+
+ Shorthand property with default, e.g. {a = 1} = b
.
+
+
+
+ property.nestedLhs = yes
+ else if property instanceof Splat
+ property.propagateLhs yes
+
+ astNode: (o) ->
+ @getAndCheckSplatProps()
+ super o
+
+ astType: ->
+ if @lhs
+ 'ObjectPattern'
+ else
+ 'ObjectExpression'
+
+ astProperties: (o) ->
+ return
+ implicit: !!@generated
+ properties:
+ property.ast(o) for property in @expandProperties()
+
+exports.ObjectProperty = class ObjectProperty extends Base
+ constructor: ({key, fromAssign}) ->
+ super()
+ if fromAssign
+ {variable: @key, value, context} = fromAssign
+ if context is 'object'
+
+
+
+
+
+
+
+
+ ¶
+
+ All non-shorthand properties (i.e. includes :
).
+
+
+
+ @value = value
+ else
+
+
+
+
+
+
+
+
+ ¶
+
+ Left-hand-side shorthand with default e.g. {a = 1} = b
.
+
+
+
+ @value = fromAssign
+ @shorthand = yes
+ @locationData = fromAssign.locationData
+ else
+
+
+
+
+
+
+
+
+ ¶
+
+ Shorthand without default e.g. {a}
or {@a}
or {[a]}
.
+
+
+
+ @key = key
+ @shorthand = yes
+ @locationData = key.locationData
+
+ astProperties: (o) ->
+ isComputedPropertyName = (@key instanceof Value and @key.base instanceof ComputedPropertyName) or @key.unwrap() instanceof StringWithInterpolations
+ keyAst = @key.ast o, LEVEL_LIST
+
+ return
+ key:
+ if keyAst?.declaration
+ Object.assign {}, keyAst, declaration: no
+ else
+ keyAst
+ value: @value?.ast(o, LEVEL_LIST) ? keyAst
+ shorthand: !!@shorthand
+ computed: !!isComputedPropertyName
+ method: no
+
+
+
+
+
+
+
+
+ ¶
Arr
@@ -3444,11 +5087,11 @@ are too.
-
+
An array literal.
@@ -3458,6 +5101,7 @@ are too.
constructor: (objs, @lhs = no) ->
super()
@objects = objs or []
+ @propagateLhs()
children: ['objects']
@@ -3465,12 +5109,13 @@ are too.
return yes for obj in @objects when obj instanceof Elision
no
- isAssignable: ->
- return no unless @objects.length
+ isAssignable: (opts) ->
+ {allowExpansion, allowNontrailingSplat, allowEmptyArray = no} = opts ? {}
+ return allowEmptyArray unless @objects.length
for obj, i in @objects
- return no if obj instanceof Splat and i + 1 isnt @objects.length
- return no unless obj.isAssignable() and (not obj.isAtomic or obj.isAtomic())
+ return no if not allowNontrailingSplat and obj instanceof Splat and i + 1 isnt @objects.length
+ return no unless (allowExpansion and obj instanceof Expansion) or (obj.isAssignable(opts) and (not obj.isAtomic or obj.isAtomic()))
yes
shouldCache: ->
@@ -3485,11 +5130,11 @@ are too.
-
+
Detect if Elision
s at the beginning of the array are processed (e.g. [, , , a]).
@@ -3504,11 +5149,11 @@ are too.
-
+
Let compileCommentFragments
know to intersperse block comments
into the fragments created when compiling this array.
@@ -3517,24 +5162,7 @@ into the fragments created when compiling this array.
if unwrappedObj.comments and
unwrappedObj.comments.filter((comment) -> not comment.here).length is 0
- unwrappedObj.includeCommentFragments = YES
-
-
-
-
-
-
-
-
- ¶
-
- If this array is the left-hand side of an assignment, all its children
-are too.
-
-
-
- if @lhs
- unwrappedObj.lhs = yes if unwrappedObj instanceof Arr or unwrappedObj instanceof Obj
+ unwrappedObj.includeCommentFragments = YES
compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
olen = compiledObjs.length
@@ -3542,11 +5170,11 @@ are too.
-
+
If compiledObjs
includes newlines, we will output this as a multiline
array (i.e. with a newline and indentation after the [
). If an element
@@ -3569,11 +5197,11 @@ first element’s line comments get output before or after the array.
-
+
Add ‘, ‘ if all Elisions
from the beginning of the array are processed (e.g. [, , , a]) and
element isn’t Elision
or last element is Elision
(e.g. [a,,b,,])
@@ -3588,7 +5216,7 @@ element isn’t Elision
or last element is Elision
(e.
for fragment, fragmentIndex in answer
if fragment.isHereComment
fragment.code = "#{multident(fragment.code, o.indent, no)}\n#{o.indent}"
- else if fragment.code is ', ' and not fragment?.isElision and fragment.type isnt 'StringLiteral'
+ else if fragment.code is ', ' and not fragment?.isElision and fragment.type not in ['StringLiteral', 'StringWithInterpolations']
fragment.code = ",\n#{o.indent}"
answer.unshift @makeCode "[\n#{o.indent}"
answer.push @makeCode "\n#{@tab}]"
@@ -3611,11 +5239,47 @@ element isn’t Elision
or last element is Elision
(e.
-
+
+
+ propagateLhs: (setLhs) ->
+ @lhs = yes if setLhs
+ return unless @lhs
+ for object in @objects
+ object.lhs = yes if object instanceof Splat or object instanceof Expansion
+ unwrappedObject = object.unwrapAll()
+ if unwrappedObject instanceof Arr or unwrappedObject instanceof Obj
+ unwrappedObject.propagateLhs yes
+ else if unwrappedObject instanceof Assign
+ unwrappedObject.nestedLhs = yes
+
+ astType: ->
+ if @lhs
+ 'ArrayPattern'
+ else
+ 'ArrayExpression'
+
+ astProperties: (o) ->
+ return
+ elements:
+ object.ast(o, LEVEL_LIST) for object in @objects
+
+
+
+
+
+
+
+
+ ¶
Class
@@ -3624,11 +5288,11 @@ element isn’t Elision
or last element is Elision
(e.
-
+
The CoffeeScript class definition.
Initialize a Class with its name, an optional superclass, and a body.
@@ -3639,21 +5303,24 @@ Initialize a Class with its name, an optional superclass, and a
exports.Class = class Class extends Base
children: ['variable', 'parent', 'body']
- constructor: (@variable, @parent, @body = new Block) ->
+ constructor: (@variable, @parent, @body) ->
super()
+ unless @body?
+ @body = new Block
+ @hasGeneratedBody = yes
compileNode: (o) ->
@name = @determineName()
- executableBody = @walkBody()
+ executableBody = @walkBody o
-
+
Special handling to allow class expr.A extends A
declarations
@@ -3671,11 +5338,11 @@ exports.Class = class
-
+
Anonymous classes are only valid in expressions
@@ -3724,11 +5391,11 @@ exports.Class = class
-
+
Figure out the appropriate name for this class
@@ -3749,7 +5416,7 @@ exports.Class = class
@variable.error message if message
if name in JS_FORBIDDEN then "_#{name}" else name
- walkBody: ->
+ walkBody: (o) ->
@ctor = null
@boundMethods = []
executableBody = null
@@ -3767,7 +5434,7 @@ exports.Class = class
pushSlice = -> exprs.push new Value new Obj properties[start...end], true if end > start
while assign = properties[end]
- if initializerExpression = @addInitializerExpression assign
+ if initializerExpression = @addInitializerExpression assign, o
pushSlice()
exprs.push initializerExpression
initializer.push initializerExpression
@@ -3778,7 +5445,7 @@ exports.Class = class
expressions[i..i] = exprs
i += exprs.length
else
- if initializerExpression = @addInitializerExpression expression
+ if initializerExpression = @addInitializerExpression expression, o
initializer.push initializerExpression
expressions[i] = initializerExpression
i += 1
@@ -3792,6 +5459,7 @@ exports.Class = class
else if method.bound
@boundMethods.push method
+ return unless o.compiling
if initializer.length isnt expressions.length
@body.expressions = (expression.hoist() for expression in initializer)
new Block expressions
@@ -3799,11 +5467,11 @@ exports.Class = class
-
+
Add an expression to the class initializer
This is the key method for determining whether an expression in a class
@@ -3821,22 +5489,26 @@ opposed to the Object.defineProperty
method).
- addInitializerExpression: (node) ->
+ addInitializerExpression: (node, o) ->
if node.unwrapAll() instanceof PassthroughLiteral
node
else if @validInitializerMethod node
@addInitializerMethod node
+ else if not o.compiling and @validClassProperty node
+ @addClassProperty node
+ else if not o.compiling and @validClassPrototypeProperty node
+ @addClassPrototypeProperty node
else
null
-
+
Checks if the given node is a valid ES class initializer method.
@@ -3850,18 +5522,18 @@ opposed to the Object.defineProperty
method).
-
+
addInitializerMethod: (assign) ->
- { variable, value: method } = assign
+ { variable, value: method, operatorToken } = assign
method.isMethod = yes
method.isStatic = variable.looksStatic @name
@@ -3871,11 +5543,43 @@ opposed to the Object.defineProperty
method).
methodName = variable.base
method.name = new (if methodName.shouldCache() then Index else Access) methodName
method.name.updateLocationDataIfMissing methodName.locationData
- method.ctor = (if @parent then 'derived' else 'base') if methodName.value is 'constructor'
+ isConstructor =
+ if methodName instanceof StringLiteral
+ methodName.originalValue is 'constructor'
+ else
+ methodName.value is 'constructor'
+ method.ctor = (if @parent then 'derived' else 'base') if isConstructor
method.error 'Cannot define a constructor as a bound (fat arrow) function' if method.bound and method.ctor
+ method.operatorToken = operatorToken
method
+ validClassProperty: (node) ->
+ return no unless node instanceof Assign
+ return node.variable.looksStatic @name
+
+ addClassProperty: (assign) ->
+ {variable, value, operatorToken} = assign
+ {staticClassName} = variable.looksStatic @name
+ new ClassProperty({
+ name: variable.properties[0]
+ isStatic: yes
+ staticClassName
+ value
+ operatorToken
+ }).withLocationDataFrom assign
+
+ validClassPrototypeProperty: (node) ->
+ return no unless node instanceof Assign
+ node.context is 'object' and not node.variable.hasProperties()
+
+ addClassPrototypeProperty: (assign) ->
+ {variable, value} = assign
+ new ClassPrototypeProperty({
+ name: variable.base
+ value
+ }).withLocationDataFrom assign
+
makeDefaultConstructor: ->
ctor = @addInitializerMethod new Assign (new Value new PropertyName 'constructor'), new Code
@body.unshift ctor
@@ -3900,6 +5604,40 @@ opposed to the Object.defineProperty
method).
null
+ declareName: (o) ->
+ return unless (name = @variable?.unwrap()) instanceof IdentifierLiteral
+ alreadyDeclared = o.scope.find name.value
+ name.isDeclaration = not alreadyDeclared
+
+ isStatementAst: -> yes
+
+ astNode: (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"
+ @declareName o
+ @name = @determineName()
+ @body.isClassBody = yes
+ @body.locationData = zeroWidthLocationDataFromEndLocation @locationData if @hasGeneratedBody
+ @walkBody o
+ sniffDirectives @body.expressions
+ @ctor?.noReturn = yes
+
+ super o
+
+ astType: (o) ->
+ if o.level is LEVEL_TOP
+ 'ClassDeclaration'
+ else
+ 'ClassExpression'
+
+ astProperties: (o) ->
+ return
+ id: @variable?.ast(o) ? null
+ superClass: @parent?.ast(o, LEVEL_PAREN) ? null
+ body: @body.ast o, LEVEL_TOP
+
exports.ExecutableClassBody = class ExecutableClassBody extends Base
children: [ 'class', 'body' ]
@@ -3951,11 +5689,11 @@ exports.ExecutableClassBody =
+
Traverse the class’s children and:
@@ -4003,11 +5741,11 @@ exports.ExecutableClassBody =
+ -
Make class/prototype assignments for invalid ES properties
@@ -4027,11 +5765,11 @@ exports.ExecutableClassBody =
+ -
The class scope is not available yet, so return the assignment to update later
@@ -4039,7 +5777,11 @@ exports.ExecutableClassBody = assign = @externalCtor = new Assign new Value, value
else if not assign.variable.this
- name = new (if base.shouldCache() then Index else Access) base
+ name =
+ if base instanceof ComputedPropertyName
+ new Index base.value
+ else
+ new (if base.shouldCache() then Index else Access) base
prototype = new Access new PropertyName 'prototype'
variable = new Value new ThisLiteral(), [ prototype, name ]
@@ -4048,16 +5790,47 @@ exports.ExecutableClassBody = true
assign
- compact result
+ compact result
+
+exports.ClassProperty = class ClassProperty extends Base
+ constructor: ({@name, @isStatic, @staticClassName, @value, @operatorToken}) ->
+ super()
+
+ children: ['name', 'value', 'staticClassName']
+
+ isStatement: YES
+
+ astProperties: (o) ->
+ return
+ key: @name.ast o, LEVEL_LIST
+ value: @value.ast o, LEVEL_LIST
+ static: !!@isStatic
+ computed: @name instanceof Index or @name instanceof ComputedPropertyName
+ operator: @operatorToken?.value ? '='
+ staticClassName: @staticClassName?.ast(o) ? null
+
+exports.ClassPrototypeProperty = class ClassPrototypeProperty extends Base
+ constructor: ({@name, @value}) ->
+ super()
+
+ children: ['name', 'value']
+
+ isStatement: YES
+
+ astProperties: (o) ->
+ return
+ key: @name.ast o, LEVEL_LIST
+ value: @value.ast o, LEVEL_LIST
+ computed: @name instanceof ComputedPropertyName or @name instanceof StringWithInterpolations
-
+
Import and Export
@@ -4079,8 +5852,25 @@ exports.ModuleDeclaration =
if @source? and @source instanceof StringWithInterpolations
@source.error 'the name of the module to be imported from must be an uninterpolated string'
- checkScope: (o, moduleDeclarationType) ->
- if o.indent.length isnt 0
+ checkScope: (o, moduleDeclarationType) ->
+
+
+
+
+
+
+
+
+ ¶
+
+ TODO: would be appropriate to flag this error during AST generation (as
+well as when compiling to JS). But o.indent
isn’t tracked during AST
+generation, and there doesn’t seem to be a current alternative way to track
+whether we’re at the “program top-level”.
+
+
+
+ if o.indent.length isnt 0
@error "#{moduleDeclarationType} statements must be at top-level scope"
exports.ImportDeclaration = class ImportDeclaration extends ModuleDeclaration
@@ -4099,6 +5889,17 @@ exports.ImportDeclaration =
code.push @makeCode ';'
code
+ astNode: (o) ->
+ o.importedSymbols = []
+ super o
+
+ astProperties: (o) ->
+ ret =
+ specifiers: @clause?.ast(o) ? []
+ source: @source.ast o
+ ret.importKind = 'value' if @clause
+ ret
+
exports.ImportClause = class ImportClause extends Base
constructor: (@defaultBinding, @namedImports) ->
super()
@@ -4117,33 +5918,38 @@ exports.ImportClause = class
code
+ astNode: (o) ->
+
+
+
+
+
+
+
+
+ ¶
+
+ The AST for ImportClause
is the non-nested list of import specifiers
+that will be the specifiers
property of an ImportDeclaration
AST
+
+
+
+ compact flatten [
+ @defaultBinding?.ast o
+ @namedImports?.ast o
+ ]
+
exports.ExportDeclaration = class ExportDeclaration extends ModuleDeclaration
compileNode: (o) ->
@checkScope o, 'export'
+ @checkForAnonymousClassExport()
code = []
code.push @makeCode "#{@tab}export "
code.push @makeCode 'default ' if @ instanceof ExportDefaultDeclaration
if @ not instanceof ExportDefaultDeclaration and
- (@clause instanceof Assign or @clause instanceof Class)
-
-
-
-
-
-
-
-
- ¶
-
- Prevent exporting an anonymous class; all exported members must be named
-
-
-
- if @clause instanceof Class and not @clause.variable
- @clause.error 'anonymous classes cannot be exported'
-
+ (@clause instanceof Assign or @clause instanceof Class)
code.push @makeCode 'var '
@clause.moduleDeclaration = 'export'
@@ -4154,13 +5960,53 @@ exports.ExportDeclaration =
code.push @makeCode " from #{@source.value}" if @source?.value?
code.push @makeCode ';'
- code
+ code
+
+
+
+
+
+
+
+
+ ¶
+
+ Prevent exporting an anonymous class; all exported members must be named
+
+
+
+ checkForAnonymousClassExport: ->
+ if @ not instanceof ExportDefaultDeclaration and @clause instanceof Class and not @clause.variable
+ @clause.error 'anonymous classes cannot be exported'
+
+ astNode: (o) ->
+ @checkForAnonymousClassExport()
+ super o
exports.ExportNamedDeclaration = class ExportNamedDeclaration extends ExportDeclaration
+ astProperties: (o) ->
+ ret =
+ source: @source?.ast(o) ? null
+ exportKind: 'value'
+ clauseAst = @clause.ast o
+ if @clause instanceof ExportSpecifierList
+ ret.specifiers = clauseAst
+ ret.declaration = null
+ else
+ ret.specifiers = []
+ ret.declaration = clauseAst
+ ret
exports.ExportDefaultDeclaration = class ExportDefaultDeclaration extends ExportDeclaration
+ astProperties: (o) ->
+ return
+ declaration: @clause.ast o
exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration
+ astProperties: (o) ->
+ return
+ source: @source.ast o
+ exportKind: 'value'
exports.ModuleSpecifierList = class ModuleSpecifierList extends Base
constructor: (@specifiers) ->
@@ -4183,6 +6029,9 @@ exports.ModuleSpecifierList = '{}'
code
+ astNode: (o) ->
+ specifier.ast(o) for specifier in @specifiers
+
exports.ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList
exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList
@@ -4199,11 +6048,11 @@ exports.ModuleSpecifier = cl
-
+
The name of the variable entering the local scope
@@ -4214,26 +6063,33 @@ exports.ModuleSpecifier = cl
children: ['original', 'alias']
compileNode: (o) ->
- o.scope.find @identifier, @moduleDeclarationType
+ @addIdentifierToScope o
code = []
code.push @makeCode @original.value
code.push @makeCode " as #{@alias.value}" if @alias?
code
+ addIdentifierToScope: (o) ->
+ o.scope.find @identifier, @moduleDeclarationType
+
+ astNode: (o) ->
+ @addIdentifierToScope o
+ super o
+
exports.ImportSpecifier = class ImportSpecifier extends ModuleSpecifier
constructor: (imported, local) ->
super imported, local, 'import'
- compileNode: (o) ->
+ addIdentifierToScope: (o) ->
-
+
Per the spec, symbols can’t be imported multiple times
(e.g. import { foo, foo } from 'lib'
is invalid)
@@ -4246,32 +6102,60 @@ exports.ImportSpecifier = cl
o.importedSymbols.push @identifier
super o
+ astProperties: (o) ->
+ originalAst = @original.ast o
+ return
+ imported: originalAst
+ local: @alias?.ast(o) ? originalAst
+ importKind: null
+
exports.ImportDefaultSpecifier = class ImportDefaultSpecifier extends ImportSpecifier
+ astProperties: (o) ->
+ return
+ local: @original.ast o
exports.ImportNamespaceSpecifier = class ImportNamespaceSpecifier extends ImportSpecifier
+ astProperties: (o) ->
+ return
+ local: @alias.ast o
exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier
constructor: (local, exported) ->
super local, exported, 'export'
+ astProperties: (o) ->
+ originalAst = @original.ast o
+ return
+ local: originalAst
+ exported: @alias?.ast(o) ? originalAst
+
exports.DynamicImport = class DynamicImport extends Base
compileNode: ->
[@makeCode 'import']
+ astType: -> 'Import'
+
exports.DynamicImportCall = class DynamicImportCall extends Call
compileNode: (o) ->
+ @checkArguments()
+ super o
+
+ checkArguments: ->
unless @args.length is 1
@error 'import() requires exactly one argument'
+
+ astNode: (o) ->
+ @checkArguments()
super o
-
+
Assign
@@ -4280,11 +6164,11 @@ exports.DynamicImportCall =
-
+
The Assign is used to assign a local variable to value, or to set the
property of an object – including within object literals.
@@ -4294,7 +6178,8 @@ 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
+ {@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options
+ @propagateLhs()
children: ['variable', 'value']
@@ -4303,25 +6188,117 @@ property of an object – including within object literals.
isStatement: (o) ->
o?.level is LEVEL_TOP and @context? and (@moduleDeclaration or "?" in @context)
- checkAssignability: (o, varBase) ->
- if Object::hasOwnProperty.call(o.scope.positions, varBase.value) and
- o.scope.variables[o.scope.positions[varBase.value]].type is 'import'
+ checkNameAssignability: (o, varBase) ->
+ if o.scope.type(varBase.value) is 'import'
varBase.error "'#{varBase.value}' is read-only"
assigns: (name) ->
@[if @context is 'object' then 'value' else 'variable'].assigns name
unfoldSoak: (o) ->
- unfoldSoak o, this, 'variable'
+ unfoldSoak o, this, 'variable'
+
+ addScopeVariables: (o, {
-
+
+ During AST generation, we need to allow assignment to these constructs
+that are considered “unassignable” during compile-to-JS, while still
+flagging things like [null] = b
.
+
+
+
+ allowAssignmentToExpansion = no,
+ allowAssignmentToNontrailingSplat = no,
+ allowAssignmentToEmptyArray = no,
+ allowAssignmentToComplexSplat = no
+ } = {}) ->
+ return unless not @context or @context is '**='
+
+ varBase = @variable.unwrapAll()
+ if not varBase.isAssignable {
+ allowExpansion: allowAssignmentToExpansion
+ allowNontrailingSplat: allowAssignmentToNontrailingSplat
+ allowEmptyArray: allowAssignmentToEmptyArray
+ allowComplexSplat: allowAssignmentToComplexSplat
+ }
+ @variable.error "'#{@variable.compile o}' can't be assigned"
+
+ varBase.eachName (name) =>
+ return if name.hasProperties?()
+
+ message = isUnassignable name.value
+ name.error message if message
+
+
+
+
+
+
+
+
+ ¶
+
+ moduleDeclaration
can be 'import'
or 'export'
.
+
+
+
+ @checkNameAssignability o, name
+ if @moduleDeclaration
+ o.scope.add name.value, @moduleDeclaration
+ name.isDeclaration = yes
+ else if @param
+ o.scope.add name.value,
+ if @param is 'alwaysDeclare'
+ 'var'
+ else
+ 'param'
+ else
+ alreadyDeclared = o.scope.find name.value
+ name.isDeclaration ?= not alreadyDeclared
+
+
+
+
+
+
+
+
+ ¶
+
+ If this assignment identifier has one or more herecomments
+attached, output them as part of the declarations line (unless
+other herecomments are already staged there) for compatibility
+with Flow typing. Don’t do this if this assignment is for a
+class, e.g. ClassName = class ClassName {
, as Flow requires
+the comment to be between the class name and the {
.
+
+
+
+ if name.comments and not o.scope.comments[name.value] and
+ @value not instanceof Class and
+ name.comments.every((comment) -> comment.here and not comment.multiline)
+ commentsNode = new IdentifierLiteral name.value
+ commentsNode.comments = name.comments
+ commentFragments = []
+ @compileCommentFragments o, commentsNode, commentFragments
+ o.scope.comments[name.value] = commentFragments
+
+
+
+
+
+
+
+
+ ¶
Compile an assignment, delegating to compileDestructuring
or
compileSplice
if appropriate. Keep track of the name of the base object
@@ -4337,26 +6314,11 @@ has not been seen yet within the current scope, declare it.
-
+
- ¶
-
- When compiling @variable
, remember if it is part of a function parameter.
-
-
-
- @variable.param = @param
-
-
-
-
-
-
-
-
If @variable
is an array or an object, we’re destructuring;
if it’s also isAssignable()
, the destructuring syntax is supported
@@ -4365,24 +6327,7 @@ and convert this ES-unsupported destructuring into acceptable output.
- if @variable.isArray() or @variable.isObject()
-
-
-
-
-
-
-
-
- ¶
-
- This is the left-hand side of an assignment; let Arr
and Obj
-know that, so that those nodes know that they’re assignable as
-destructured variables.
-
-
-
- @variable.base.lhs = yes
+ if @variable.isArray() or @variable.isObject()
unless @variable.isAssignable()
if @variable.isObject() and @variable.base.hasSplat()
return @compileObjectDestruct o
@@ -4390,72 +6335,10 @@ destructured variables.
return @compileDestructuring o
return @compileSplice o if @variable.isSplice()
- return @compileConditional o if @context in ['||=', '&&=', '?=']
+ return @compileConditional o if @isConditional()
return @compileSpecialMath o if @context in ['//=', '%%=']
- if not @context or @context is '**='
- varBase = @variable.unwrapAll()
- unless varBase.isAssignable()
- @variable.error "'#{@variable.compile o}' can't be assigned"
-
- varBase.eachName (name) =>
- return if name.hasProperties?()
-
- message = isUnassignable name.value
- name.error message if message
-
-
-
-
-
-
-
-
- ¶
-
- moduleDeclaration
can be 'import'
or 'export'
.
-
-
-
- @checkAssignability o, name
- if @moduleDeclaration
- o.scope.add name.value, @moduleDeclaration
- else if @param
- o.scope.add name.value,
- if @param is 'alwaysDeclare'
- 'var'
- else
- 'param'
- else
- o.scope.find name.value
-
-
-
-
-
-
-
-
- ¶
-
- If this assignment identifier has one or more herecomments
-attached, output them as part of the declarations line (unless
-other herecomments are already staged there) for compatibility
-with Flow typing. Don’t do this if this assignment is for a
-class, e.g. ClassName = class ClassName {
, as Flow requires
-the comment to be between the class name and the {
.
-
-
-
- if name.comments and not o.scope.comments[name.value] and
- @value not instanceof Class and
- name.comments.every((comment) -> comment.here and not comment.multiline)
- commentsNode = new IdentifierLiteral name.value
- commentsNode.comments = name.comments
- commentFragments = []
- @compileCommentFragments o, commentsNode, commentFragments
- o.scope.comments[name.value] = commentFragments
-
+ @addScopeVariables o
if @value instanceof Code
if @value.isStatic
@value.name = @variable.properties[0]
@@ -4463,7 +6346,6 @@ the comment to be between the class name and the {
.
[properties..., prototype, name] = @variable.properties
@value.name = name if prototype.name?.value is 'prototype'
- @value.base.csxAttribute = yes if @csx
val = @value.compileToFragments o, LEVEL_LIST
compiledName = @variable.compileToFragments o, LEVEL_LIST
@@ -4471,18 +6353,18 @@ the comment to be between the class name and the {
.
if @variable.shouldCache()
compiledName.unshift @makeCode '['
compiledName.push @makeCode ']'
- return compiledName.concat @makeCode(if @csx then '=' else ': '), val
+ return compiledName.concat @makeCode(': '), val
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
-
+
Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,
if we’re destructuring without declaring, the destructuring assignment must be wrapped in parentheses.
@@ -4499,11 +6381,11 @@ The assignment is wrapped in parentheses if ‘o.level’ has lower precedence t
-
+
Object rest property is not assignable: {{a}...}
@@ -4524,11 +6406,11 @@ The assignment is wrapped in parentheses if ‘o.level’ has lower precedence t
-
+
Brief implementation of recursive pattern matching, when assigning array or
object literals to a value. Peeks at their properties to assign inner names.
@@ -4544,11 +6426,11 @@ object literals to a value. Peeks at their properties to assign inner names.
-
+
Special-case for {} = a
and [] = a
(empty patterns).
Compile to simply a
.
@@ -4558,99 +6440,10 @@ Compile to simply a
.
if olen is 0
code = value.compileToFragments o
return if o.level >= LEVEL_OP then @wrapInParentheses code else code
- [obj] = objects
-
-
-
-
-
-
-
-
- ¶
-
- Disallow [...] = a
for some reason. (Could be equivalent to [] = a
?)
+ [obj] = objects
-
-
- if olen is 1 and obj instanceof Expansion
- obj.error 'Destructuring assignment has no target'
-
-
-
-
-
-
-
-
- ¶
-
- Count all Splats
: [a, b, c…, d, e]
-
-
-
- splats = (i for obj, i in objects when obj instanceof Splat)
-
-
-
-
-
-
-
-
- ¶
-
- Count all Expansions
: [a, b, …, c, d]
-
-
-
- expans = (i for obj, i in objects when obj instanceof Expansion)
-
-
-
-
-
-
-
-
- ¶
-
- Combine splats and expansions.
-
-
-
- splatsAndExpans = [splats..., expans...]
-
-
-
-
-
-
-
-
- ¶
-
- Show error if there is more than one Splat
, or Expansion
.
-Examples: [a, b, c…, d, e, f…], [a, b, …, c, d, …], [a, b, …, c, d, e…]
-
-
-
- if splatsAndExpans.length > 1
-
-
-
-
-
-
-
-
- ¶
-
- Sort ‘splatsAndExpans’ so we can show error at first disallowed token.
-
-
-
- objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
+ @disallowLoneExpansion()
+ {splats, expans, splatsAndExpans} = @getAndCheckSplatsAndExpansions()
isSplat = splats?.length > 0
isExpans = expans?.length > 0
@@ -4671,11 +6464,11 @@ Examples: [a, b, c…, d, e, f…], [a, b, …, c, d, …], [a, b, …, c, d, e
-
+
At this point, there are several things to destructure. So the fn()
in
{a, b} = fn()
must be cached, for example. Make vvar into a simple
@@ -4699,11 +6492,11 @@ variable if it isn’t already.
-
+
Helper which outputs [].slice
code.
@@ -4714,11 +6507,11 @@ variable if it isn’t already.
-
+
Helper which outputs [].splice
code.
@@ -4729,11 +6522,11 @@ variable if it isn’t already.
-
+
Check if objects
array contains any instance of Assign
, e.g. {a:1}.
@@ -4745,11 +6538,11 @@ variable if it isn’t already.
-
+
Check if objects
array contains any unassignable object.
@@ -4762,11 +6555,11 @@ variable if it isn’t already.
-
+
objects
are complex when there is object assign ({a:1}),
unassignable object, or just a single node.
@@ -4779,11 +6572,11 @@ unassignable object, or just a single node.
-
+
“Complex” objects
are processed in a loop.
Examples: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]
@@ -4796,11 +6589,11 @@ Examples: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]
-
+
Elision
can be skipped.
@@ -4811,11 +6604,11 @@ Examples: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]
-
+
If obj
is {a: 1}
@@ -4836,11 +6629,11 @@ Examples: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]
-
+
obj
is [a…], {a…} or a
@@ -4859,11 +6652,11 @@ Examples: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]
-
+
“Simple” objects
can be split and compiled to arrays, [a, b, c] = arr, [a, b, c…] = arr
@@ -4883,11 +6676,11 @@ Examples: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]
-
+
In case there is Splat
or Expansion
in objects
,
we can split array in two simple subarrays.
@@ -4913,11 +6706,11 @@ b) Expansion
-
+
Slice or splice objects
.
@@ -4936,11 +6729,11 @@ b) Expansion
-
+
There is no Splat
or Expansion
in objects
.
@@ -4955,11 +6748,112 @@ b) Expansion
-
+
+
+ disallowLoneExpansion: ->
+ return unless @variable.base instanceof Arr
+ {objects} = @variable.base
+ return unless objects?.length is 1
+ [loneObject] = objects
+ if loneObject instanceof Expansion
+ loneObject.error 'Destructuring assignment has no target'
+
+
+
+
+
+
+
+
+ ¶
+
+ Show error if there is more than one Splat
, or Expansion
.
+Examples: [a, b, c…, d, e, f…], [a, b, …, c, d, …], [a, b, …, c, d, e…]
+
+
+
+ getAndCheckSplatsAndExpansions: ->
+ return {splats: [], expans: [], splatsAndExpans: []} unless @variable.base instanceof Arr
+ {objects} = @variable.base
+
+
+
+
+
+
+
+
+ ¶
+
+ Count all Splats
: [a, b, c…, d, e]
+
+
+
+ splats = (i for obj, i in objects when obj instanceof Splat)
+
+
+
+
+
+
+
+
+ ¶
+
+ Count all Expansions
: [a, b, …, c, d]
+
+
+
+ expans = (i for obj, i in objects when obj instanceof Expansion)
+
+
+
+
+
+
+
+
+ ¶
+
+ Combine splats and expansions.
+
+
+
+ splatsAndExpans = [splats..., expans...]
+ if splatsAndExpans.length > 1
+
+
+
+
+
+
+
+
+ ¶
+
+ Sort ‘splatsAndExpans’ so we can show error at first disallowed token.
+
+
+
+ objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
+ {splats, expans, splatsAndExpans}
+
+
+
+
+
+
+
+
+ ¶
When compiling a conditional assignment, take care to ensure that the
operands are only evaluated once, even though we have to reference them
@@ -4973,11 +6867,11 @@ more than once.
-
+
Disallow conditional assignment of undefined variables.
@@ -4985,7 +6879,7 @@ more than once.
if not left.properties.length and left.base instanceof Literal and
left.base not instanceof ThisLiteral and not o.scope.check left.base.value
- @variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
+ @throwUnassignableConditionalError left.base.value
if "?" in @context
o.isExistentialEquals = true
new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
@@ -4996,11 +6890,11 @@ more than once.
-
+
Convert special math assignment operators like a //= b
to the equivalent
extended form a = a ** b
and then compiles that.
@@ -5014,11 +6908,11 @@ extended form a = a ** b
and then compiles that.
-
+
Compile the assignment from an array splice literal, using JavaScript’s
Array#splice
method.
@@ -5050,16 +6944,72 @@ extended form a = a ** b
and then compiles that.
if o.level > LEVEL_TOP then @wrapInParentheses answer else answer
eachName: (iterator) ->
- @variable.unwrapAll().eachName iterator
+ @variable.unwrapAll().eachName iterator
+
+ isDefaultAssignment: -> @param or @nestedLhs
+
+ propagateLhs: ->
+ return unless @variable?.isArray?() or @variable?.isObject?()
-
+
+ This is the left-hand side of an assignment; let Arr
and Obj
+know that, so that those nodes know that they’re assignable as
+destructured variables.
+
+
+
+ @variable.base.propagateLhs yes
+
+ throwUnassignableConditionalError: (name) ->
+ @variable.error "the variable \"#{name}\" can't be assigned with #{@context} because it has not been declared before"
+
+ isConditional: ->
+ @context in ['||=', '&&=', '?=']
+
+ isStatementAst: NO
+
+ astNode: (o) ->
+ @disallowLoneExpansion()
+ @getAndCheckSplatsAndExpansions()
+ if @isConditional()
+ variable = @variable.unwrap()
+ if variable instanceof IdentifierLiteral and not o.scope.check variable.value
+ @throwUnassignableConditionalError variable.value
+ @addScopeVariables o, allowAssignmentToExpansion: yes, allowAssignmentToNontrailingSplat: yes, allowAssignmentToEmptyArray: yes, allowAssignmentToComplexSplat: yes
+ super o
+
+ astType: ->
+ if @isDefaultAssignment()
+ 'AssignmentPattern'
+ else
+ 'AssignmentExpression'
+
+ astProperties: (o) ->
+ ret =
+ right: @value.ast o, LEVEL_LIST
+ left: @variable.ast o, LEVEL_LIST
+
+ unless @isDefaultAssignment()
+ ret.operator = @originalContext ? '='
+
+ ret
+
+
+
+
+
+
+
+
+ ¶
FuncGlyph
@@ -5073,11 +7023,11 @@ exports.FuncGlyph = class
-
+
Code
@@ -5086,11 +7036,11 @@ exports.FuncGlyph = class
-
+
A function definition. This is the only node that creates a new Scope.
When for the purposes of walking the contents of a function body, the Code
@@ -5117,6 +7067,8 @@ has no children – they’re within the inner scope.
if node instanceof For and node.isAwait()
@isAsync = yes
+ @propagateLhs()
+
children: ['params', 'body']
isStatement: -> @isMethod
@@ -5128,11 +7080,11 @@ has no children – they’re within the inner scope.
-
+
Compilation creates a new scope unless explicitly asked to share with the
outer scope. Handles splat parameters in the parameter list by setting
@@ -5144,44 +7096,37 @@ function body.
compileNode: (o) ->
- if @ctor
- @name.error 'Class constructor may not be async' if @isAsync
- @name.error 'Class constructor may not be a generator' if @isGenerator
+ @checkForAsyncOrGeneratorConstructor()
if @bound
@context = o.scope.method.context if o.scope.method?.bound
@context = 'this' unless @context
- o.scope = del(o, 'classScope') or @makeScope o.scope
- o.scope.shared = del(o, 'sharedScope')
- o.indent += TAB
- delete o.bare
- delete o.isExistentialEquals
+ @updateOptions o
params = []
exprs = []
thisAssignments = @thisAssignments?.slice() ? []
paramsAfterSplat = []
haveSplatParam = no
- haveBodyParam = no
+ haveBodyParam = no
+
+ @checkForDuplicateParams()
+ @disallowLoneExpansionAndMultipleSplats()
-
+
- Check for duplicate parameters and separate this
assignments.
+ Separate this
assignments.
- paramNames = []
- @eachParamName (name, node, param, obj) ->
- node.error "multiple parameters named '#{name}'" if name in paramNames
- paramNames.push name
-
+ @eachParamName (name, node, param, obj) ->
if node.this
name = node.properties[0].name.value
name = "_#{name}" if name in JS_FORBIDDEN
@@ -5190,11 +7135,11 @@ function body.
-
+
Param
is object destructuring with a default value: ({@prop = 1}) ->
In a case when the variable name is already reserved, we have to assign
@@ -5214,11 +7159,11 @@ a new variable name to the destructured variable: ({prop:prop1 = 1}) ->
-
+
Parse the parameters, adding them to the list of parameters to put in the
function definition; and dealing with splats or expansions, including
@@ -5236,23 +7181,18 @@ any non-idempotent parameters are evaluated in the correct order.
-
+
- Was ...
used with this parameter? (Only one such parameter is allowed
-per function.) Splat/expansion parameters cannot have default values,
-so we need not worry about that.
+ Was ...
used with this parameter? Splat/expansion parameters cannot
+have default values, so we need not worry about that.
if param.splat or param instanceof Expansion
- if haveSplatParam
- param.error 'only one splat or expansion parameter is allowed per function definition'
- else if param instanceof Expansion and @params.length is 1
- param.error 'an expansion parameter cannot be the only parameter in a function definition'
haveSplatParam = yes
if param.splat
if param.name instanceof Arr or param.name instanceof Obj
@@ -5260,11 +7200,11 @@ so we need not worry about that.
-
+
Splat arrays are treated oddly by ES; deal with them the legacy
way in the function body. TODO: Should this be handled in the
@@ -5289,11 +7229,11 @@ function parameter list, and if so, how?
-
+
Parse all other parameters; if a splat paramater has not yet been
encountered, add these other parameters to the list to be output in
@@ -5309,11 +7249,11 @@ the function definition.
-
+
This parameter cannot be declared or assigned in the parameter
list. So put a reference in the parameter list and add a statement
@@ -5332,11 +7272,11 @@ to the function body assigning it, e.g.
-
+
If this parameter comes before the splat or expansion, it will go
in the function definition parameter list.
@@ -5348,11 +7288,11 @@ in the function definition parameter list.
-
+
If this parameter has a default value, and it hasn’t already been
set by the shouldCache()
block above, define it as a statement in
@@ -5372,11 +7312,11 @@ so we can’t define its default value in the parameter list.
-
+
Add this parameter’s reference(s) to the function scope.
@@ -5387,11 +7327,11 @@ so we can’t define its default value in the parameter list.
-
+
This parameter is destructured.
@@ -5406,11 +7346,11 @@ so we can’t define its default value in the parameter list.
-
+
This compilation of the parameter is only to get its name to add
to the scope name tracking; since the compilation output here
@@ -5429,11 +7369,11 @@ is compiled.
-
+
If this parameter had a default value, since it’s no longer in the
function parameter list we need to assign its default value
@@ -5449,11 +7389,11 @@ function parameter list we need to assign its default value
-
+
Add this parameter to the scope, since it wouldn’t have been added
yet since it was skipped earlier.
@@ -5465,11 +7405,11 @@ yet since it was skipped earlier.
-
+
If there were parameters after the splat or expansion parameter, those
parameters need to be assigned in the body of the function.
@@ -5481,11 +7421,11 @@ parameters need to be assigned in the body of the function.
-
+
Create a destructured assignment, e.g. [a, b, c] = [args..., b, c]
@@ -5498,17 +7438,19 @@ parameters need to be assigned in the body of the function.
-
+
wasEmpty = @body.isEmpty()
+ @disallowSuperInParamDefaults()
+ @checkSuperCallsInConstructorBody()
@body.expressions.unshift thisAssignments...