Error.stackTraceLimit = Infinity
{Scope} = require './scope'
{isUnassignable, JS_FORBIDDEN} = require './lexer'
nodes.coffee
包含語法樹的所有節點類別。大部分節點都是 語法 中動作的結果,但有些節點是由其他節點作為程式碼產生方法所建立。若要將語法樹轉換成 JavaScript 程式碼字串,請在根目錄上呼叫 compile()
。
Error.stackTraceLimit = Infinity
{Scope} = require './scope'
{isUnassignable, JS_FORBIDDEN} = require './lexer'
匯入我們計畫使用的輔助程式。
{compact, flatten, extend, merge, del, starts, ends, some,
addDataToNode, attachCommentsToNode, locationDataToString,
throwSyntaxError, replaceUnicodeCodePointEscapes,
isFunction, isPlainObject, isNumber, parseNumber} = require './helpers'
剖析器需要的函式。
exports.extend = extend
exports.addDataToNode = addDataToNode
不需要自訂的節點的常數函式。
YES = -> yes
NO = -> no
THIS = -> this
NEGATE = -> @negated = not @negated; this
下面定義的各種節點都編譯成 CodeFragment 物件的集合。CodeFragment 是產生程式碼的區塊,以及程式碼在原始檔案中的位置。CodeFragment 可以組裝成可運作的程式碼,只要依序串接所有 CodeFragment 的 code
片段即可。
exports.CodeFragment = class CodeFragment
constructor: (parent, code) ->
@code = "#{code}"
@type = parent?.constructor?.name or 'unknown'
@locationData = parent?.locationData
@comments = parent?.comments
toString: ->
這僅供除錯使用。
"#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
將 CodeFragment 陣列轉換成字串。
fragmentsToText = (fragments) ->
(fragment.code for fragment in fragments).join('')
Base 是語法樹中所有節點的抽象基底類別。每個子類別都實作 compileNode
方法,該方法執行該節點的程式碼產生。若要將節點編譯成 JavaScript,請在節點上呼叫 compile
,它會將 compileNode
包裝在一些通用的額外智慧中,以了解何時需要將產生的程式碼包裝在封閉中。會傳遞並複製一個選項雜湊,其中包含來自樹中較高層級的環境資訊(例如,周圍函式是否要求傳回值)、目前範圍的資訊以及縮排層級。
exports.Base = class Base
compile: (o, lvl) ->
fragmentsToText @compileToFragments o, lvl
偶爾一個節點會被編譯多次,例如取得變數名稱以加入範圍追蹤。當我們知道「過早」編譯不會導致註解輸出時,將這些註解保留起來,以便在後續會導致註解包含在輸出中的 compile
呼叫中保留這些註解。
compileWithoutComments: (o, lvl, method = 'compile') ->
if @comments
@ignoreTheseCommentsTemporarily = @comments
delete @comments
unwrapped = @unwrapAll()
if unwrapped.comments
unwrapped.ignoreTheseCommentsTemporarily = unwrapped.comments
delete unwrapped.comments
fragments = @[method] o, lvl
if @ignoreTheseCommentsTemporarily
@comments = @ignoreTheseCommentsTemporarily
delete @ignoreTheseCommentsTemporarily
if unwrapped.ignoreTheseCommentsTemporarily
unwrapped.comments = unwrapped.ignoreTheseCommentsTemporarily
delete unwrapped.ignoreTheseCommentsTemporarily
fragments
compileNodeWithoutComments: (o, lvl) ->
@compileWithoutComments o, lvl, 'compileNode'
用於決定在編譯節點之前是否將此節點包覆在閉包中,或直接編譯的共用邏輯。如果此節點是陳述句,且不是純陳述句,而且我們不在區塊的最上層(這將是不必要的),而且我們尚未被要求傳回結果(因為陳述句知道如何傳回結果),則我們需要包覆。
compileToFragments: (o, lvl) ->
o = extend {}, o
o.level = lvl if lvl
node = @unfoldSoak(o) or this
node.tab = o.indent
fragments = if o.level is LEVEL_TOP or not node.isStatement(o)
node.compileNode o
else
node.compileClosure o
@compileCommentFragments o, node, fragments
fragments
compileToFragmentsWithoutComments: (o, lvl) ->
@compileWithoutComments o, lvl, 'compileToFragments'
透過閉包包覆轉換為表達式的陳述句與其父閉包共用一個範圍物件,以保留預期的詞彙範圍。
compileClosure: (o) ->
@checkForPureStatementInExpression()
o.sharedScope = yes
func = new Code [], Block.wrap [this]
args = []
if @contains ((node) -> node instanceof SuperCall)
func.bound = yes
else if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
args = [new ThisLiteral]
if argumentsNode
meth = 'apply'
args.push new IdentifierLiteral 'arguments'
else
meth = 'call'
func = new Value func, [new Access new PropertyName meth]
parts = (new Call func, args).compileNode o
switch
when func.isGenerator or func.base?.isGenerator
parts.unshift @makeCode "(yield* "
parts.push @makeCode ")"
when func.isAsync or func.base?.isAsync
parts.unshift @makeCode "(await "
parts.push @makeCode ")"
parts
compileCommentFragments: (o, node, fragments) ->
return fragments unless node.comments
這是註解(以 comments
屬性附加至節點)變成 CodeFragment
的地方。「內嵌區塊註解」,例如以 /* */
分隔的註解,穿插在程式碼中的一行上,會加入目前的 fragments
串流。所有其他片段會附加為最近前一個或後一個片段的屬性,以保持為偷渡客,直到稍後在 compileComments
中正確輸出為止。
unshiftCommentFragment = (commentFragment) ->
if commentFragment.unshift
找出第一個非註解片段,並在它之前插入 commentFragment
。
unshiftAfterComments fragments, commentFragment
else
if fragments.length isnt 0
precedingFragment = fragments[fragments.length - 1]
if commentFragment.newLine and precedingFragment.code isnt '' and
not /\n\s*$/.test precedingFragment.code
commentFragment.code = "\n#{commentFragment.code}"
fragments.push commentFragment
for comment in node.comments when comment not in @compiledComments
@compiledComments.push comment # Don’t output this comment twice.
對於以 ###
表示的區塊/此處註解,例如內嵌註解,例如 1 + ### comment ### 2
,建立片段並將它們插入片段陣列。否則,目前將註解片段附加至其最近的片段,以便在稍後新增所有換行字元後,將它們插入輸出中。
if comment.here # Block comment, delimited by `###`.
commentFragment = new HereComment(comment).compileNode o
else # Line comment, delimited by `#`.
commentFragment = new LineComment(comment).compileNode o
if (commentFragment.isHereComment and not commentFragment.newLine) or
node.includeCommentFragments()
內嵌區塊註解,例如 1 + /* comment */ 2
,或 compileToFragments
方法具有輸出註解邏輯的節點。
unshiftCommentFragment commentFragment
else
fragments.push @makeCode '' if fragments.length is 0
if commentFragment.unshift
fragments[0].precedingComments ?= []
fragments[0].precedingComments.push commentFragment
else
fragments[fragments.length - 1].followingComments ?= []
fragments[fragments.length - 1].followingComments.push commentFragment
fragments
如果程式碼產生希望在多個地方使用複雜表達式的結果,請確保只評估一次表達式,方法是將其指定給暫時變數。傳遞一個層級以進行預編譯。
如果傳遞了 level
,則傳回 [val, ref]
,其中 val
是編譯值,而 ref
是編譯參考。如果未傳遞 level
,則傳回 [val, ref]
,其中兩個值是尚未編譯的原始節點。
cache: (o, level, shouldCache) ->
complex = if shouldCache? then shouldCache this else @shouldCache()
if complex
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
sub = new Assign ref, this
if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
else
ref = if level then @compileToFragments o, level else this
[ref, ref]
偶爾,可能需要使表達式表現得好像它已「提升」,藉此表達式的結果可在其在來源中的位置之前取得,但表達式的變數範圍對應於來源位置。這廣泛用於處理類別中的可執行類別主體。
呼叫此方法會變異節點,代理 compileNode
和 compileToFragments
方法以儲存其結果,以便稍後取代呼叫傳回的 target
節點。
hoist: ->
@hoisted = yes
target = new HoistTarget @
compileNode = @compileNode
compileToFragments = @compileToFragments
@compileNode = (o) ->
target.update compileNode, o
@compileToFragments = (o) ->
target.update compileToFragments, o
target
cacheToCodeFragments: (cacheValues) ->
[fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
建構傳回目前節點結果的節點。請注意,這會被許多陳述節點(例如 If
、For
)覆寫以產生更聰明的行為。
makeReturn: (results, mark) ->
if mark
將此節點標記為隱含傳回,以便它可以成為 AST 中傳回的節點元資料的一部分。
@canBeReturned = yes
return
node = @unwrapAll()
if results
new Call new Literal("#{results}.push"), [node]
else
new Return node
此節點或其任何子節點是否包含某種節點?遞迴地向下遍歷子節點,並傳回第一個驗證 pred
的節點。否則傳回未定義。contains
不會跨越範圍邊界。
contains: (pred) ->
node = undefined
@traverseChildren no, (n) ->
if pred n
node = n
return no
node
拉出節點清單的最後一個節點。
lastNode: (list) ->
if list.length is 0 then null else list[list.length - 1]
節點的偵錯表示,用於檢查剖析樹。這是 coffee --nodes
印出的內容。
toString: (idt = '', name = @constructor.name) ->
tree = '\n' + idt + name
tree += '?' if @soak
@eachChild (node) -> tree += node.toString idt + TAB
tree
checkForPureStatementInExpression: ->
if jumpNode = @jumps()
jumpNode.error 'cannot use a pure statement in an expression'
節點的純 JavaScript 物件表示,可以序列化為 JSON。這是 Node API 中的 ast
選項所傳回的內容。我們試著盡可能遵循 Babel AST 規範,以改善與其他工具的互通性。警告:不要在子類別中覆寫此方法。只在需要時覆寫組件 ast*
方法。
ast: (o, level) ->
將 level
合併到 o
中,並執行其他通用檢查。
o = @astInitialize o, level
建立此節點的可序列化表示。
astNode = @astNode o
標記對應於(隱含)傳回的表達式的 AST 節點。我們無法在 astNode
中執行此操作,因為我們需要先組裝子節點,然後才能標記要傳回的父節點。
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()
必須在 astProperties
之前呼叫 @makeReturn
,因為後者可能會呼叫子節點的 .ast()
,而這些節點需要 makeReturn
執行的傳回邏輯。
@makeReturn null, yes if @isStatement(o) and o.level isnt LEVEL_TOP and o.scope?
o
astNode: (o) ->
每個抽象語法樹節點物件都有四類別的屬性
type
欄位中,字串類似 NumberLiteral
。loc
、start
、end
和 range
欄位中。parsedValue
。body
。這些欄位全部交錯在 Babel 規格中;type
、start
和 parsedValue
都是 AST 節點物件中的頂層欄位。我們有不同的方法來傳回每個類別,並在此處合併在一起。 Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()
預設情況下,節點類別沒有特定屬性。
astProperties: -> {}
預設情況下,節點類別的 AST type
是其類別名稱。
astType: -> @constructor.name
AST 位置資料是我們 Jison 位置資料的重新排列版本,已轉換成 Babel 規格使用的結構。
astLocationData: ->
jisonLocationDataToAstLocationData @locationData
判斷 AST 節點是否需要 ExpressionStatement
包裹。通常與我們的 isStatement()
邏輯相符,但允許覆寫。
isStatementAst: (o) ->
@isStatement o
將每個子項傳遞給函式,在函式傳回 false
時中斷。
eachChild: (func) ->
return this unless @children
for attr in @children when @[attr]
for child in flatten [@[attr]]
return this if func(child) is false
this
traverseChildren: (crossScope, func) ->
@eachChild (child) ->
recur = func(child)
child.traverseChildren(crossScope, func) unless recur is no
replaceInContext
將遍歷子項,尋找 match
傳回 true 的節點。找到後,符合條件的節點將由呼叫 replacement
的結果取代。
replaceInContext: (match, replacement) ->
return false unless @children
for attr in @children when children = @[attr]
if Array.isArray children
for child, i in children
if match child
children[i..i] = replacement child, @
return true
else
return true if child.replaceInContext match, replacement
else if match children
@[attr] = replacement children, @
return true
else
return true if children.replaceInContext match, replacement
invert: ->
new Op '!', this
unwrapAll: ->
node = this
continue until node is node = node.unwrap()
node
常見節點屬性和方法的預設實作。如有需要,節點將以自訂邏輯覆寫這些屬性和方法。
children
是在樹狀結構中遞迴的屬性。children
清單就是 AST 的結構。parent
指標和 children
指標是您用來遍歷樹狀結構的方式。
children: []
isStatement
與「所有內容都是表達式」有關。少數內容無法成為表達式,例如 break
。isStatement
傳回 true
的內容無法用作表達式。由於陳述式出現在表達式位置,因此 nodes.coffee
會產生一些錯誤訊息。
isStatement: NO
追蹤已編譯成片段的註解,以避免重複輸出。
compiledComments: []
includeCommentFragments
讓 compileCommentFragments
知道這個節點是否特別了解如何在輸出中處理註解。
includeCommentFragments: NO
jumps
告訴您表達式或表達式的內部部分是否具有跳出正常控制流程的流程控制結構(例如 break
、continue
或 return
),且無法用作值。(請注意,throw
不被視為流程控制結構。)這很重要,因為表達式中間的流程控制毫無意義;我們必須禁止它。
jumps: NO
如果 node.shouldCache() 為 false
,則可以安全地多次使用 node
。否則,您需要將 node
的值儲存在變數中,並多次輸出該變數。有點像這樣:不需要快取 5
。但是,returnFive()
可能會因為多次評估而產生副作用,因此我們需要快取它。這個參數命名為 shouldCache
,而不是 mustCache
,因為在某些情況下我們可能不需要快取,但我們希望快取,例如一個很長的表達式,它可能是冪等的,但我們希望快取以簡潔。
shouldCache: YES
isChainable: NO
isAssignable: NO
isNumber: NO
unwrap: THIS
unfoldSoak: NO
這個節點是用來指定特定變數嗎?
assigns: NO
對於此節點及其所有後代,如果尚未設定位置資料,則將位置資料設定為 locationData
。
updateLocationDataIfMissing: (locationData, force) ->
@forceUpdateLocation = yes if force
return this if @locationData and not @forceUpdateLocation
delete @forceUpdateLocation
@locationData = locationData
@eachChild (child) ->
child.updateLocationDataIfMissing locationData
從另一個節點新增位置資料
withLocationDataFrom: ({locationData}) ->
@updateLocationDataIfMissing locationData
從另一個節點新增位置資料和註解
withLocationDataAndCommentsFrom: (node) ->
@withLocationDataFrom node
{comments} = node
@comments = comments if comments?.length
this
拋出與此節點位置相關的 SyntaxError。
error: (message) ->
throwSyntaxError message, @locationData
makeCode: (code) ->
new CodeFragment this, code
wrapInParentheses: (fragments) ->
[@makeCode('('), fragments..., @makeCode(')')]
wrapInBraces: (fragments) ->
[@makeCode('{'), fragments..., @makeCode('}')]
fragmentsList
是片段陣列的陣列。fragmentsList 中的每個陣列會串接在一起,並在每個陣列之間新增 joinStr
,以產生片段的最終平面陣列。
joinFragmentArrays: (fragmentsList, joinStr) ->
answer = []
for fragments, i in fragmentsList
if i then answer.push @makeCode joinStr
answer = answer.concat fragments
answer
HoistTargetNode 代表提升節點在節點樹中的輸出位置。請參閱 Base#hoist。
exports.HoistTarget = class HoistTarget extends Base
擴充給定陣列中提升的片段
@expand = (fragments) ->
for fragment, i in fragments by -1 when fragment.fragments
fragments[i..i] = @expand fragment.fragments
fragments
constructor: (@source) ->
super()
包含在編譯原始節點時套用的簡報選項。
@options = {}
要由原始節點編譯取代的佔位符片段。
@targetFragments = { fragments: [] }
isStatement: (o) ->
@source.isStatement o
使用編譯原始碼的結果來更新目標片段。使用節點和選項 (以目標簡報選項覆寫) 來呼叫給定的編譯函式。
update: (compile, o) ->
@targetFragments.fragments = compile.call @source, merge o, @options
複製目標縮排和層級,並傳回佔位符片段
compileToFragments: (o, level) ->
@options.indent = o.indent
@options.level = level ? o.level
[ @targetFragments ]
compileNode: (o) ->
@compileToFragments o
compileClosure: (o) ->
@compileToFragments o
節點樹的根節點
exports.Root = class Root extends Base
constructor: (@body) ->
super()
@isAsync = (new Code [], @body).isAsync
children: ['body']
將所有內容包在安全閉包中,除非要求不要這麼做。最好一開始就不產生這些內容,但目前先清除明顯的雙括號。
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
functionKeyword = "#{if @isAsync then 'async ' else ''}function"
[].concat @makeCode("(#{functionKeyword}() {\n"), fragments, @makeCode("\n}).call(this);\n")
initializeScope: (o) ->
o.scope = new Scope null, @body, null, o.referencedVars ? []
將根範圍中給定的區域變數標記為參數,以免最後在根區塊中宣告這些變數。
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()
區塊是形成縮排程式碼區塊主體的表達式清單,也就是函式的實作、if
、switch
或 try
中的子句,等等…
exports.Block = class Block extends Base
constructor: (nodes) ->
super()
@expressions = compact flatten nodes or []
children: ['expressions']
將表達式附加到此表達式清單的結尾。
push: (node) ->
@expressions.push node
this
移除並傳回此表達式清單的最後一個表達式。
pop: ->
@expressions.pop()
在此表達式清單的開頭新增一個表達式。
unshift: (node) ->
@expressions.unshift node
this
如果此區塊僅包含一個節點,請將其拉回以展開。
unwrap: ->
if @expressions.length is 1 then @expressions[0] else this
這是一個空的程式碼區塊嗎?
isEmpty: ->
not @expressions.length
isStatement: (o) ->
for exp in @expressions when exp.isStatement o
return yes
no
jumps: (o) ->
for exp in @expressions
return jumpNode if jumpNode = exp.jumps o
區塊節點不會傳回其整個主體,而是確保傳回最後一個表達式。
makeReturn: (results, mark) ->
len = @expressions.length
[..., lastExp] = @expressions
lastExp = lastExp?.unwrap() or no
我們還需要檢查,如果在同一個層級上有相鄰的 JSX 標籤,我們不會傳回該標籤;JSX 不允許這樣做。
if lastExp and lastExp instanceof Parens and lastExp.body.expressions.length > 1
{body:{expressions}} = lastExp
[..., penult, last] = expressions
penult = penult.unwrap()
last = last.unwrap()
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 results
@expressions.splice(len, 1) if expr instanceof Return and not expr.expression
break
this
compile: (o, lvl) ->
return new Root(this).withLocationDataFrom(this).compile o, lvl unless o.scope
super o, lvl
編譯區塊主體中的所有表達式。如果我們需要傳回結果,而且它是表達式,只需傳回它。如果它是陳述式,請要求陳述式這樣做。
compileNode: (o) ->
@tab = o.indent
top = o.level is LEVEL_TOP
compiledNodes = []
for node, index in @expressions
if node.hoisted
這是一個提升的表達式。我們想要編譯這個表達式並忽略結果。
node.compileToFragments o
continue
node = (node.unfoldSoak(o) or node)
if node instanceof Block
這是一個巢狀區塊。我們不會在此執行任何特殊操作,例如將其封閉在新範圍中;我們只會編譯此區塊中的陳述式以及我們自己的陳述式。
compiledNodes.push node.compileNode o
else if top
node.front = yes
fragments = node.compileToFragments o
unless node.isStatement o
fragments = indentInitial fragments, @
[..., lastFragment] = fragments
unless lastFragment.code is '' or lastFragment.isComment
fragments.push @makeCode ';'
compiledNodes.push fragments
else
compiledNodes.push node.compileToFragments o, LEVEL_LIST
if top
if @spaced
return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode('\n')
else
return @joinFragmentArrays(compiledNodes, '\n')
if compiledNodes.length
answer = @joinFragmentArrays(compiledNodes, ', ')
else
answer = [@makeCode 'void 0']
if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInParentheses answer else answer
compileRoot: (o) ->
@spaced = yes
fragments = @compileWithDeclarations o
HoistTarget.expand fragments
@compileComments fragments
編譯函式內容的表達式主體,並將所有內部變數的宣告提升到最上方。
compileWithDeclarations: (o) ->
fragments = []
post = []
for exp, i in @expressions
exp = exp.unwrap()
break unless exp instanceof Literal
o = merge(o, level: LEVEL_TOP)
if i
rest = @expressions.splice i, 9e9
[spaced, @spaced] = [@spaced, no]
[fragments, @spaced] = [@compileNode(o), spaced]
@expressions = rest
post = @compileNode o
{scope} = o
if scope.expressions is this
declars = o.scope.hasDeclarations()
assigns = scope.hasAssignments
if declars or assigns
fragments.push @makeCode '\n' if i
fragments.push @makeCode "#{@tab}var "
if declars
declaredVariables = scope.declaredVariables()
for declaredVariable, declaredVariablesIndex in declaredVariables
fragments.push @makeCode declaredVariable
if Object::hasOwnProperty.call o.scope.comments, declaredVariable
fragments.push o.scope.comments[declaredVariable]...
if declaredVariablesIndex isnt declaredVariables.length - 1
fragments.push @makeCode ', '
if assigns
fragments.push @makeCode ",\n#{@tab + TAB}" if declars
fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
else if fragments.length and post.length
fragments.push @makeCode "\n"
fragments.concat post
compileComments: (fragments) ->
for fragment, fragmentIndex in fragments
在下一行或前一行的新行中將註解插入輸出。如果沒有可以放置註解的新行,請建立新行。
if fragment.precedingComments
確定我們即將在之前插入註解的片段縮排層級,並使用該縮排層級來插入我們的註解。在此時,片段的 code
屬性是產生的輸出 JavaScript,而 CoffeeScript 始終會產生縮排兩個空格的輸出;因此我們只需要搜尋一個至少以兩個空格開頭的 code
屬性。
fragmentIndent = ''
for pastFragment in fragments[0...(fragmentIndex + 1)] by -1
indent = /^ {2,}/m.exec pastFragment.code
if indent
fragmentIndent = indent[0]
break
else if '\n' in pastFragment.code
break
code = "\n#{fragmentIndent}" + (
for commentFragment in fragment.precedingComments
if commentFragment.isHereComment and commentFragment.multiline
multident commentFragment.code, fragmentIndent, no
else
commentFragment.code
).join("\n#{fragmentIndent}").replace /^(\s*)$/gm, ''
for pastFragment, pastFragmentIndex in fragments[0...(fragmentIndex + 1)] by -1
newLineIndex = pastFragment.code.lastIndexOf '\n'
if newLineIndex is -1
持續搜尋前一個片段,直到我們無法再往回,原因可能是沒有片段了,或是我們發現我們在一個內插在字串內的程式碼區塊中。
if pastFragmentIndex is 0
pastFragment.code = '\n' + pastFragment.code
newLineIndex = 0
else if pastFragment.isStringWithInterpolations and pastFragment.code is '{'
code = code[1..] + '\n' # Move newline to end.
newLineIndex = 1
else
continue
delete fragment.precedingComments
pastFragment.code = pastFragment.code[0...newLineIndex] +
code + pastFragment.code[newLineIndex..]
break
是的,這與前一個 if
區塊非常類似,但如果你仔細看,你會發現許多微小的差異,如果將這兩個區塊抽象成一個他們共用的函式,這會造成混淆。
if fragment.followingComments
第一個尾隨註解是否出現在程式碼行的結尾,例如 ; // 註解
,或是在程式碼行之後開始新的一行?
trail = fragment.followingComments[0].trail
fragmentIndent = ''
找出下一行程式碼的縮排,如果我們有任何非尾隨註解要輸出。我們需要先找出下一個換行字元,因為這些註解會在換行字元之後輸出;然後找出下一個換行字元之後的那一行的縮排。
unless trail and fragment.followingComments.length is 1
onNextLine = no
for upcomingFragment in fragments[fragmentIndex...]
unless onNextLine
if '\n' in upcomingFragment.code
onNextLine = yes
else
continue
else
indent = /^ {2,}/m.exec upcomingFragment.code
if indent
fragmentIndent = indent[0]
break
else if '\n' in upcomingFragment.code
break
這個註解是否遵循純粹模式插入的縮排?如果是,就沒有必要再縮排了。
code = if fragmentIndex is 1 and /^\s+$/.test fragments[0].code
''
else if trail
' '
else
"\n#{fragmentIndent}"
組裝適當縮排的註解。
code += (
for commentFragment in fragment.followingComments
if commentFragment.isHereComment and commentFragment.multiline
multident commentFragment.code, fragmentIndent, no
else
commentFragment.code
).join("\n#{fragmentIndent}").replace /^(\s*)$/gm, ''
for upcomingFragment, upcomingFragmentIndex in fragments[fragmentIndex...]
newLineIndex = upcomingFragment.code.indexOf '\n'
if newLineIndex is -1
持續搜尋後面的片段,直到我們無法再往回,原因可能是沒有片段了,或是我們發現我們在一個內插在字串內的程式碼區塊中。
if upcomingFragmentIndex is fragments.length - 1
upcomingFragment.code = upcomingFragment.code + '\n'
newLineIndex = upcomingFragment.code.length
else if upcomingFragment.isStringWithInterpolations and upcomingFragment.code is '}'
code = "#{code}\n"
newLineIndex = 0
else
continue
delete fragment.followingComments
避免插入額外的空白行。
code = code.replace /^\n/, '' if upcomingFragment.code is '\n'
upcomingFragment.code = upcomingFragment.code[0...newLineIndex] +
code + upcomingFragment.code[newLineIndex..]
break
fragments
將給定的節點包裝成一個區塊,除非它已經是一個區塊。
@wrap: (nodes) ->
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
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
忽略生成的 PassthroughLiteral
if not expressionAst?
continue
else if expression instanceof Directive
directives.push expressionAst
如果表達式是一個陳述,它可以按原樣添加到主體中。
else if expression.isStatementAst o
body.push expressionAst
否則,我們需要將它包裝在一個 ExpressionStatement
AST 節點中。
else
body.push Object.assign
type: 'ExpressionStatement'
expression: expressionAst
,
expression.astLocationData()
return {
目前,我們沒有在 Program
AST 節點上包含 sourceType
。它的值可以是 'script'
或 'module'
,而 CoffeeScript 無法總是知道它應該是哪一個。原始碼中存在 import
或 export
陳述意味著它應該是 module
,但一個專案可能主要由此類檔案組成,還有一個異常檔案不包含 import
或 export
,但仍匯入專案中,因此預期將被視為 module
。確定 sourceType
的值本質上與確定 JavaScript 檔案的解析目標(也是 module
或 script
)所帶來的挑戰相同,因此如果 Node 找出對 .js
檔案這樣做的方式,那麼 CoffeeScript 可以複製 Node 的演算法。
sourceType: ‘module’
body, directives
}
astLocationData: ->
return if @isRootBlock and not @locationData?
super()
一個指令,例如 ‘use strict’。目前僅在 AST 生成期間使用。
exports.Directive = class Directive extends Base
constructor: (@value) ->
super()
astProperties: (o) ->
return
value: Object.assign {},
@value.ast o
type: 'DirectiveLiteral'
Literal
是靜態值的基底類別,這些值可以直接傳遞到 JavaScript 中而無需轉換,例如:字串、數字、true
、false
、null
…
exports.Literal = class Literal extends Base
constructor: (@value) ->
super()
shouldCache: NO
assigns: (name) ->
name is @value
compileNode: (o) ->
[@makeCode @value]
astProperties: ->
return
value: @value
toString: ->
這僅供除錯使用。
" #{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'
compileNode: (o) ->
code = [@makeCode '0/0']
if o.level >= LEVEL_OP then @wrapInParentheses code else code
astType: -> 'Identifier'
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
可以表示整個文字字串或內插字串中的文字片段。當解析成前者但需要當成後者處理時(例如標記範本文字的字串部分),這將傳回一個 StringLiteral
的副本,其位置資料中已移除引號(就像解析成內插字串的一部分時一樣)。
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
永遠是類似於 ‘`‘、‘\`‘、‘\\`‘ 等的值。將其簡化為後半部分,我們將 ‘`‘ 轉換為 ‘',‘\\\
‘ 轉換為 ‘`‘ 等。
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
eachName: (iterator) ->
iterator @
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
makeReturn: THIS
jumps: (o) ->
return this if @value is 'break' and not (o?.loop or o?.block)
return this if @value is 'continue' and not o?.loop
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: (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'
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
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
是個 pureStatement,將其包裝在封閉中沒有意義。
exports.Return = class Return extends Base
constructor: (@expression, {@belongsToFuncDirectiveReturn} = {}) ->
super()
children: ['expression']
isStatement: YES
makeReturn: THIS
jumps: THIS
compileToFragments: (o, level) ->
expr = @expression?.makeReturn()
if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
compileNode: (o) ->
answer = []
待辦事項:如果我們在此呼叫 expression.compile()
兩次,有時會得到不同的結果!
if @expression
answer = @expression.compileToFragments o, LEVEL_PAREN
unshiftAfterComments answer, @makeCode "#{@tab}return "
由於 return
已由 @tab
縮排,因此前置的多行註解需要縮排。
for fragment in answer
if fragment.isHereComment and '\n' in fragment.code
fragment.code = multident fragment.code, @tab
else if fragment.isLineComment
fragment.code = "#{@tab}#{fragment.code}"
else
break
else
answer.push @makeCode "#{@tab}return"
answer.push @makeCode ';'
answer
checkForPureStatementInExpression: ->
不要將 return
從 await return
/yield return
標記為無效。
return if @belongsToFuncDirectiveReturn
super()
astType: -> 'ReturnStatement'
astProperties: (o) ->
argument: @expression?.ast(o, LEVEL_PAREN) ? null
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
的運作方式與 return
完全相同,只是會將函式轉換為產生器。
exports.YieldReturn = class YieldReturn extends FuncDirectiveReturn
keyword: 'yield'
exports.AwaitReturn = class AwaitReturn extends FuncDirectiveReturn
keyword: 'await'
一個值、變數或文字,或以括號括住、索引或點入,或純粹的。
exports.Value = class Value extends Base
constructor: (base, props, tag, isDefaultValue = no) ->
super()
return base if not props and base instanceof Value
@base = base
@properties = props or []
@tag = tag
@[tag] = yes if tag
@isDefaultValue = isDefaultValue
如果這是 @foo =
指派,如果 @
上有註解,請將它們移到 foo
上。
if @base?.comments and @base instanceof ThisLiteral and @properties[0]?.name?
moveComments @base, @properties[0].name
children: ['base', 'properties']
將屬性(或 屬性 )Access
新增至清單中。
add: (props) ->
@properties = @properties.concat props
@forceUpdateLocation = yes
this
hasProperties: ->
@properties.length isnt 0
bareLiteral: (type) ->
not @properties.length and @base instanceof type
其他節點受益的一些布林檢查。
isArray : -> @bareLiteral(Arr)
isRange : -> @bareLiteral(Range)
shouldCache : -> @hasProperties() or @base.shouldCache()
isAssignable : (opts) -> @hasProperties() or @base.isAssignable opts
isNumber : -> @bareLiteral(NumberLiteral)
isString : -> @bareLiteral(StringLiteral)
isRegex : -> @bareLiteral(RegexLiteral)
isUndefined : -> @bareLiteral(UndefinedLiteral)
isNull : -> @bareLiteral(NullLiteral)
isBoolean : -> @bareLiteral(BooleanLiteral)
isAtomic : ->
for node in @properties.concat @base
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
@isArray() or @isRange() or @isSplice() or @isObject() or
@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
isObject: (onlyGenerated) ->
return no if @properties.length
(@base instanceof Obj) and (not onlyGenerated or @base.generated)
isElision: ->
return no unless @base instanceof Arr
@base.hasElision()
isSplice: ->
[..., lastProperty] = @properties
lastProperty instanceof Slice
looksStatic: (className) ->
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
如果沒有附加屬性,可以將值解開為其內部節點。
unwrap: ->
if @properties.length then this else @base
參照具有基本部分(this
值)和名稱部分。我們會將它們分別快取,以編譯複雜的表達式。a()[b()] ?= c
-> (_base = a())[_name = b()] ? _base[_name] = c
cacheReference: (o) ->
[..., name] = @properties
if @properties.length < 2 and not @base.shouldCache() and not name?.shouldCache()
return [this, this] # `a` `a.b`
base = new Value @base, @properties[...-1]
if base.shouldCache() # `a().b`
bref = new IdentifierLiteral o.scope.freeVariable 'base'
base = new Value new Parens new Assign bref, base
return [base, bref] unless name # `a()`
if name.shouldCache() # `a[b()]`
nref = new IdentifierLiteral o.scope.freeVariable 'name'
name = new Index new Assign nref, name.index
nref = new Index nref
[base.add(name), new Value(bref or base.base, [nref or name])]
我們透過編譯和合併每個屬性,將值編譯為 JavaScript。如果屬性鏈中有散佈運算子 ?.
穿插,事情會變得更有趣。然後,我們必須小心,在建立散佈鏈時不要意外地兩次評估任何內容。
compileNode: (o) ->
@base.front = @front
props = @properties
if props.length and @base.cached?
快取的片段能正確編譯順序,並重複使用範圍內的變數。範例:a(x = 5).b(-> x = 6)
應與 a(x = 5); b(-> x = 6)
以相同的順序編譯(請參閱問題 #4437,https://github.com/jashkenas/coffeescript/issues/4437)
fragments = @base.cached
else
fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
if props.length and SIMPLENUM.test fragmentsToText fragments
fragments.push @makeCode '.'
for prop in props
fragments.push (prop.compileToFragments o)...
fragments
將散佈展開為 If
:a?.b
-> a.b if a?
unfoldSoak: (o) ->
@unfoldedSoak ?= do =>
ifn = @base.unfoldSoak o
if ifn
ifn.body.properties.push @properties...
return ifn
for prop, i in @properties when prop.soak
prop.soak = off
fst = new Value @base, @properties[...i]
snd = new Value @base, @properties[i..]
if fst.shouldCache()
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
fst = new Parens new Assign ref, fst
snd.base = ref
return new If new Existence(fst), snd, soak: on
no
eachName: (iterator, {checkAssignability = yes} = {}) ->
if @hasProperties()
iterator @
else if not checkAssignability or @base.isAssignable()
@base.eachName iterator
else
@error 'tried to assign to unassignable value'
對於 AST 產生,我們需要一個 object
,如果它有屬性,就是這個 Value
減去其最後一個屬性。
object: ->
return @ unless @hasProperties()
取得除了最後一個以外的所有屬性;對於只有一個屬性的 Value
,initialProperties
是個空陣列。
initialProperties = @properties[0.[email protected] - 1]
建立成為分離的最後一個屬性的新「基本」的 object
。
object = new Value @base, initialProperties, @tag, @isDefaultValue
將位置資料新增至我們的節點,如此一來,它便具有正確的位置資料,以供來源對應或後續轉換成 AST 位置資料。
object.locationData =
if initialProperties.length is 0
這個新的 Value
僅有一個屬性,因此位置資料就是父 Value
的基底。
@base.locationData
else
這個新的 Value
有多個屬性,因此位置資料從父 Value
的基底延伸至此新節點中包含的最後一個屬性 (亦即父節點的倒數第二個屬性)。
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) ->
如果 Value
沒有任何屬性,則 AST 節點就是此節點的 base
。
return @base.ast o unless @hasProperties()
否則,呼叫 Base::ast
,它會依序呼叫下列 astType
和 astProperties
方法。
super o
astType: ->
if @isJSXTag()
'JSXMemberExpression'
else if @containsSoak()
'OptionalMemberExpression'
else
'MemberExpression'
如果這個 Value
有屬性,則最後一個屬性 (例如 a.b.c
中的 c
) 會變成 property
,而前一個屬性 (例如 a.b
) 會變成指派給 object
屬性的子 Value
節點。
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()
不要在位置資料中包含 JSX 標籤的前導 <
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"
else if @meta.value is 'import'
unless @property instanceof Access and @property.name.value is 'meta'
@error "the only valid meta property for import is import.meta"
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
以 ###
分隔的註解 (變成 /* */
)。
exports.HereComment = class HereComment extends Base
constructor: ({ @content, @newLine, @unshift, @locationData }) ->
super()
compileNode: (o) ->
multiline = '\n' in @content
取消縮排多行註解。它們會在稍後重新縮排。
if multiline
indent = null
for line in @content.split '\n'
leadingWhitespace = /^\s*/.exec(line)[0]
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
fragment.newLine = @newLine
fragment.unshift = @unshift
fragment.multiline = multiline
不要依賴 fragment.type
,因為在編譯器縮小時可能會中斷。
fragment.isComment = fragment.isHereComment = yes
fragment
astType: -> 'CommentBlock'
astProperties: ->
return
value: @content
從 #
延伸至行尾的註解 (變成 //
)。
exports.LineComment = class LineComment extends Base
constructor: ({ @content, @newLine, @unshift, @locationData, @precededByBlankLine }) ->
super()
compileNode: (o) ->
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
不要依賴 fragment.type
,因為在編譯器縮小時可能會中斷。
fragment.isComment = fragment.isLineComment = yes
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 and not value.shouldGenerateTemplateLiteral()
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
沒有值的屬性,例如 disabled
attribute = new JSXAttribute name: new JSXIdentifier(base.value).withLocationDataAndCommentsFrom base
attribute.locationData = base.locationData
@attributes.push attribute
else if not base.generated
物件擴充屬性,例如 {…props}
attribute = base.properties[0]
attribute.jsx = yes
attribute.locationData = base.locationData
@attributes.push attribute
else
包含具有值的屬性的物件,例如 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']
捕捉無效屬性:<div {a:”b”, props} {props} “value” />
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
JSX 元素的節點
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: ->
[email protected]
astNode: (o) ->
包含元素屬性的 Arr 會擷取跨越開啟元素 < … > 的位置資料
@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
區分 <a><b /></a>
和 <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
函式呼叫的節點。
exports.Call = class Call extends Base
constructor: (@variable, @args = [], @soak, @token) ->
super()
@implicit = @args.implicit
@isNew = no
if @variable instanceof Value and @variable.isNotCallable()
@variable.error "literal is not a function"
if @variable.base instanceof JSXTag
return new JSXElement(
tagName: @variable
attributes: new JSXAttributes @args[0].base
content: @args[1]
)
@variable
永遠不會因為這個節點作為 RegexWithInterpolations
的一部分而建立而輸出為結果,因此,對於那個情況,將任何註解移至透過語法傳遞到 RegexWithInterpolations
的 args
屬性。
if @variable.base?.value is 'RegExp' and @args.length isnt 0
moveComments @variable, @args[0]
children: ['variable', 'args']
設定位置時,我們有時需要更新開始位置,以考量我們左邊新發現的 new
營運子。這會擴充左邊的範圍,但不會擴充右邊的範圍。
updateLocationDataIfMissing: (locationData) ->
if @locationData and @needsUpdatedStartLocation
@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 = 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
標記此呼叫為建立新執行個體。
newInstance: ->
base = @variable?.base or @variable
if base instanceof Call and not base.isNew
base.newInstance()
else
@isNew = true
@needsUpdatedStartLocation = true
this
浸泡式鏈結呼叫會展開為 if/else 三元結構。
unfoldSoak: (o) ->
if @soak
if @variable instanceof Super
left = new Literal @variable.compile o
rite = new Value left
@variable.error "Unsupported reference to 'super'" unless @variable.accessor?
else
return ifn if ifn = unfoldSoak o, this, 'variable'
[left, rite] = new Value(@variable).cacheReference o
rite = new Call rite, @args
rite.isNew = @isNew
left = new Literal "typeof #{ left.compile o } === \"function\""
return new If left, new Value(rite), soak: yes
call = this
list = []
loop
if call.variable instanceof Call
list.push call
call = call.variable
continue
break unless call.variable instanceof Value
list.push call
break unless (call = call.variable.base) instanceof Call
for call in list.reverse()
if ifn
if call.variable instanceof Call
call.variable = ifn
else
call.variable.base = ifn
ifn = unfoldSoak o, call, 'variable'
ifn
編譯純粹的函式呼叫。
compileNode: (o) ->
@checkForNewSuper()
@variable?.front = @front
compiledArgs = []
如果變數是 Accessor
片段會被快取,並在 Value::compileNode
中稍後使用,以確保編譯的正確順序,以及在範圍內重複使用變數。範例:a(x = 5).b(-> x = 6)
應編譯為與 a(x = 5); b(-> x = 6)
相同的順序(請參閱問題 #4437,https://github.com/jashkenas/coffeescript/issues/4437)
varAccess = @variable?.properties?[0] instanceof Access
argCode = (arg for arg in (@args || []) when arg instanceof Code)
if argCode.length > 0 and varAccess and not @variable.base.cached
[cache] = @variable.base.cache o, LEVEL_ACCESS, -> no
@variable.base.cached = cache
for arg, argIndex in @args
if argIndex then compiledArgs.push @makeCode ", "
compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
fragments = []
if @isNew
fragments.push @makeCode 'new '
fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
fragments
checkForNewSuper: ->
if @isNew
@variable.error "Unsupported reference to 'super'" if @variable instanceof Super
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
'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()
呼叫轉換為對同名原型函式的呼叫。當設定 expressions
時,呼叫將以不改變 SuperCall
表達式回傳值的方式編譯。
exports.SuperCall = class SuperCall extends Call
children: Call::children.concat ['expressions']
isStatement: (o) ->
@expressions?.length and o.level is LEVEL_TOP
compileNode: (o) ->
return super o unless @expressions?.length
superCall = new Literal fragmentsToText super o
replacement = new Block @expressions.slice()
if o.level > LEVEL_TOP
如果我們可能在表達式中,我們需要快取並回傳結果
[superCall, ref] = superCall.cache o, null, YES
replacement.push ref
replacement.unshift superCall
replacement.compileToFragments o, if o.level is LEVEL_TOP then o.level else LEVEL_LIST
exports.Super = class Super extends Base
constructor: (@accessor, @superLiteral) ->
super()
children: ['accessor']
compileNode: (o) ->
@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())
nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
name.index = new Assign nref, name.index
@accessor = if nref? then new Index nref else name
if @accessor?.name?.comments
super()
呼叫會編譯為例如 super.method()
,這表示 method
屬性名稱在此處第一次編譯,並在類別的 method:
屬性編譯時再次編譯。由於此編譯首先發生,附加至 method:
的註解會在 super.method()
附近錯誤輸出,而我們希望它們在第二次傳遞時在 method:
輸出時輸出。因此在此編譯傳遞期間將它們擱置一旁,並將它們放回物件中,以便它們在稍後的編譯中存在。
salvagedComments = @accessor.name.comments
delete @accessor.name.comments
fragments = (new Value (new Literal 'super'), if @accessor then [ @accessor ] else [])
.compileToFragments o
attachCommentsToNode salvagedComments, @accessor.name if salvagedComments
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
帶有內插的正規表示式實際上只是 Call
的變體(精確來說是 RegExp()
呼叫),內含 StringWithInterpolations
。
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
exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
constructor: (variable, arg, soak) ->
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)
astType: -> 'TaggedTemplateExpression'
astProperties: (o) ->
return
tag: @variable.ast o, LEVEL_ACCESS
quasi: @args[0].ast o, LEVEL_LIST
節點用於擴充物件的原型,並使用祖先物件。取自 Closure Library 的 goog.inherits
。
exports.Extends = class Extends extends Base
constructor: (@child, @parent) ->
super()
children: ['child', 'parent']
將一個建構函式掛接到另一個建構函式的原型鏈中。
compileToFragments: (o) ->
new Call(new Value(new Literal utility 'extend', o), [@child, @parent]).compileToFragments o
使用 .
存取值中的屬性,或使用 ::
簡寫方式存取物件的原型。
exports.Access = class Access extends Base
constructor: (@name, {@soak, @shorthand} = {}) ->
super()
children: ['name']
compileToFragments: (o) ->
name = @name.compileToFragments o
node = @name.unwrap()
if node instanceof PropertyName
[@makeCode('.'), name...]
else
[@makeCode('['), name..., @makeCode(']')]
shouldCache: NO
astNode: (o) ->
Babel 沒有 Access
的 AST 節點,而是將此 Access 節點的子節點 name
識別碼節點包含在 MemberExpression
節點的 property
中。
@name.ast o
使用 [ ... ]
編制索引,存取陣列或物件。
exports.Index = class Index extends Base
constructor: (@index) ->
super()
children: ['index']
compileToFragments: (o) ->
[].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
shouldCache: ->
@index.shouldCache()
astNode: (o) ->
Babel 沒有 Index
的 AST 節點,而是將此 Index 節點的子節點 index
識別碼節點包含在 MemberExpression
節點的 property
中。由於 MemberExpression
的 property
是 Index,因此 MemberExpression
的 computed
為 true
。
@index.ast o
範圍文字。範圍可以用於擷取陣列的部分 (切片),指定理解的範圍,或作為值,在執行階段擴充為對應的整數陣列。
exports.Range = class Range extends Base
children: ['from', 'to']
constructor: (@from, @to, tag) ->
super()
@exclusive = tag is 'exclusive'
@equals = if @exclusive then '' else '='
編譯範圍的來源變數,包括開始和結束的位置。但僅在需要快取以避免重複評估時才會編譯。
compileVariables: (o) ->
o = merge o, top: true
shouldCache = del o, 'shouldCache'
[@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 parseNumber @fromVar else null
@toNum = if @to.isNumber() then parseNumber @toVar else null
@stepNum = if step?.isNumber() then parseNumber @stepVar else null
正常編譯時,範圍會傳回迭代範圍內值的 for 迴圈 內容。由理解使用。
compileNode: (o) ->
@compileVariables o unless @fromVar
return @compileArray(o) unless o.index
設定端點。
known = @fromNum? and @toNum?
idx = del o, 'index'
idxName = del o, 'name'
namedIndex = idxName and idxName isnt idx
varPart =
if known and not namedIndex
"var #{idx} = #{@fromC}"
else
"#{idx} = #{@fromC}"
varPart += ", #{@toC}" if @toC isnt @toVar
varPart += ", #{@step}" if @step isnt @stepVar
[lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
產生條件。
[from, to] = [@fromNum, @toNum]
務必檢查 step
是否不為零,以避免無限迴圈。
stepNotZero = "#{ @stepNum ? @stepVar } !== 0"
stepCond = "#{ @stepNum ? @stepVar } > 0"
lowerBound = "#{lt} #{ if known then to else @toVar }"
upperBound = "#{gt} #{ if known then to else @toVar }"
condPart =
if @step?
if @stepNum? and @stepNum isnt 0
if @stepNum > 0 then "#{lowerBound}" else "#{upperBound}"
else
"#{stepNotZero} && (#{stepCond} ? #{lowerBound} : #{upperBound})"
else
if known
"#{ if from <= to then lt else gt } #{to}"
else
"(#{@fromVar} <= #{@toVar} ? #{lowerBound} : #{upperBound})"
cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
產生步驟。
stepPart = if @stepVar
"#{idx} += #{@stepVar}"
else if known
if namedIndex
if from <= to then "++#{idx}" else "--#{idx}"
else
if from <= to then "#{idx}++" else "#{idx}--"
else
if namedIndex
"#{cond} ? ++#{idx} : --#{idx}"
else
"#{cond} ? #{idx}++ : #{idx}--"
varPart = "#{idxName} = #{varPart}" if namedIndex
stepPart = "#{idxName} = #{stepPart}" if namedIndex
最後的迴圈主體。
[@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
當用作值時,將範圍擴展到等效陣列中。
compileArray: (o) ->
known = @fromNum? and @toNum?
if known and Math.abs(@fromNum - @toNum) <= 20
range = [@fromNum..@toNum]
range.pop() if @exclusive
return [@makeCode "[#{ range.join(', ') }]"]
idt = @tab + TAB
i = o.scope.freeVariable 'i', single: true, reserve: no
result = o.scope.freeVariable 'results', reserve: no
pre = "\n#{idt}var #{result} = [];"
if known
o.index = i
body = fragmentsToText @compileNode o
else
vars = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
cond = "#{@fromVar} <= #{@toVar}"
body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
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 ? ''})"]
astProperties: (o) ->
return {
from: @from?.ast(o) ? null
to: @to?.ast(o) ? null
@exclusive
}
陣列切片文字。與 JavaScript 的 Array#slice
不同,第二個參數指定切片的結尾索引,就像第一個參數是開頭的索引一樣。
exports.Slice = class Slice extends Base
children: ['range']
constructor: (@range) ->
super()
嘗試切片到陣列結尾時,我們必須小心,使用 9e9
是因為並非所有實作都尊重 undefined
或 1/0
。9e9
應該是安全的,因為 9e9
> 2**32
,這是最大陣列長度。
compileNode: (o) ->
{to, from} = @range
處理屬性存取中的表達式,例如 a[!b in c..]
。
if from?.shouldCache()
from = new Value new Parens from
if to?.shouldCache()
to = new Value new Parens to
fromCompiled = from?.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
if to
compiled = to.compileToFragments o, LEVEL_PAREN
compiledText = fragmentsToText compiled
if not (not @range.exclusive and +compiledText is -1)
toStr = ', ' + if @range.exclusive
compiledText
else if to.isNumber()
"#{+compiledText + 1}"
else
compiled = to.compileToFragments o, LEVEL_ACCESS
"+#{fragmentsToText compiled} + 1 || 9e9"
[@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
astNode: (o) ->
@range.ast o
物件文字,沒有什麼特別的。
exports.Obj = class Obj extends Base
constructor: (props, @generated = no) ->
super()
@objects = @properties = props or []
children: ['properties']
isAssignable: (opts) ->
for prop in @properties
檢查保留字。
message = isUnassignable prop.unwrapAll().value
prop.error message if message
prop = prop.value if prop instanceof Assign and
prop.context is 'object' and
prop.value?.base not instanceof Arr
return no unless prop.isAssignable opts
yes
shouldCache: ->
not @isAssignable()
檢查物件是否包含展開。
hasSplat: ->
return yes for prop in @properties when prop instanceof Splat
no
將 rest 屬性移到清單結尾。{a, rest..., b} = obj
-> {a, b, rest...} = obj
foo = ({a, rest..., b}) ->
-> foo = {a, b, rest...}) ->
reorderProperties: ->
props = @properties
splatProps = @getAndCheckSplatProps()
splatProp = props.splice splatProps[0], 1
@objects = @properties = [].concat props, splatProp
compileNode: (o) ->
@reorderProperties() if @hasSplat() and @lhs
props = @properties
if @generated
for node in props when node instanceof Value
node.error 'cannot have an implicit value in an implicit object'
idt = o.indent += TAB
lastNode = @lastNode @properties
如果此物件是指定的一側,則其所有子物件也是。
@propagateLhs()
isCompact = yes
for prop in @properties
if prop instanceof Assign and prop.context is 'object'
isCompact = no
answer = []
answer.push @makeCode if isCompact then '' else '\n'
for prop, i in props
join = if i is props.length - 1
''
else if isCompact
', '
else if prop is lastNode
'\n'
else
',\n'
indent = if isCompact then '' else idt
key = if prop instanceof Assign and prop.context is 'object'
prop.variable
else if prop instanceof Assign
prop.operatorToken.error "unexpected #{prop.operatorToken.value}" unless @lhs
prop.variable
else
prop
if key instanceof Value and key.hasProperties()
key.error 'invalid object key' if prop.context is 'object' or not key.this
key = key.properties[0].name
prop = new Assign key, prop, 'object'
if key is prop
if prop.shouldCache()
[key, value] = prop.base.cache o
key = new PropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign key, value, 'object'
else if key instanceof Value and key.base instanceof ComputedPropertyName
{ [foo()] }
輸出為 { [ref = foo()]: ref }
。
if prop.base.value.shouldCache()
[key, value] = prop.base.value.cache o
key = new ComputedPropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign key, value, 'object'
else
{ [expression] }
輸出為 { [expression]: expression }
。
prop = new Assign key, prop.base.value, 'object'
else if not prop.bareLiteral?(IdentifierLiteral) and prop not instanceof Splat
prop = new Assign prop, prop, 'object'
if indent then answer.push @makeCode indent
answer.push prop.compileToFragments(o, LEVEL_TOP)...
if join then answer.push @makeCode join
answer.push @makeCode if isCompact then '' else "\n#{@tab}"
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
eachName: (iterator) ->
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?
將「裸」屬性轉換為 ObjectProperty
(或 Splat
)。
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
具有預設值的簡寫屬性,例如 {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'
所有非簡寫屬性(例如包含 :
)。
@value = value
else
左邊簡寫,具有預設值,例如 {a = 1} = b
。
@value = fromAssign
@shorthand = yes
@locationData = fromAssign.locationData
else
沒有預設值的簡寫,例如 {a}
或 {@a}
或 {[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
陣列文字。
exports.Arr = class Arr extends Base
constructor: (objs, @lhs = no) ->
super()
@objects = objs or []
@propagateLhs()
children: ['objects']
hasElision: ->
return yes for obj in @objects when obj instanceof Elision
no
isAssignable: (opts) ->
{allowExpansion, allowNontrailingSplat, allowEmptyArray = no} = opts ? {}
return allowEmptyArray unless @objects.length
for obj, i in @objects
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: ->
not @isAssignable()
compileNode: (o) ->
return [@makeCode '[]'] unless @objects.length
o.indent += TAB
fragmentIsElision = ([ fragment ]) ->
fragment.type is 'Elision' and fragment.code.trim() is ','
偵測陣列開頭的 Elision
是否已處理(例如 [, , , a])。
passedElision = no
answer = []
for obj, objIndex in @objects
unwrappedObj = obj.unwrapAll()
讓 compileCommentFragments
知道在編譯此陣列時,將區塊註解插入已建立的片段中。
if unwrappedObj.comments and
unwrappedObj.comments.filter((comment) -> not comment.here).length is 0
unwrappedObj.includeCommentFragments = YES
compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
olen = compiledObjs.length
如果 compiledObjs
包含換行符,我們會將其輸出為多行陣列(例如在 [
之後換行並縮排)。如果元素包含行註解,也應觸發多行輸出,因為根據定義,行註解會在我們的輸出中加入換行符。例外情況是只有第一個元素有行註解;在這種情況下,如果我們原本會輸出為精簡形式,則以精簡形式輸出,以便第一個元素的行註解在陣列之前或之後輸出。
includesLineCommentsOnNonFirstElement = no
for fragments, index in compiledObjs
for fragment in fragments
if fragment.isHereComment
fragment.code = fragment.code.trim()
else if index isnt 0 and includesLineCommentsOnNonFirstElement is no and hasLineComments fragment
includesLineCommentsOnNonFirstElement = yes
如果陣列開頭的所有 Elision
已處理(例如 [, , , a]),且元素不是 Elision
或最後一個元素是 Elision
(例如 [a,,b,,]),則加入 ‘, ‘。
if index isnt 0 and passedElision and (not fragmentIsElision(fragments) or index is olen - 1)
answer.push @makeCode ', '
passedElision = passedElision or not fragmentIsElision fragments
answer.push fragments...
if includesLineCommentsOnNonFirstElement or '\n' in fragmentsToText(answer)
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 not in ['StringLiteral', 'StringWithInterpolations']
fragment.code = ",\n#{o.indent}"
answer.unshift @makeCode "[\n#{o.indent}"
answer.push @makeCode "\n#{@tab}]"
else
for fragment in answer when fragment.isHereComment
fragment.code = "#{fragment.code} "
answer.unshift @makeCode '['
answer.push @makeCode ']'
answer
assigns: (name) ->
for obj in @objects when obj.assigns name then return yes
no
eachName: (iterator) ->
for obj in @objects
obj = obj.unwrapAll()
obj.eachName iterator
如果此陣列是指定運算的左側,其所有子項目也都是。
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
CoffeeScript 類別定義。使用其名稱、一個選擇性的超類別和一個主體來初始化一個 類別。
exports.Class = class Class extends Base
children: ['variable', 'parent', 'body']
constructor: (@variable, @parent, @body) ->
super()
unless @body?
@body = new Block
@hasGeneratedBody = yes
compileNode: (o) ->
@name = @determineName()
executableBody = @walkBody o
特殊處理,允許 class expr.A extends A
宣告
parentName = @parent.base.value if @parent instanceof Value and not @parent.hasProperties()
@hasNameClash = @name? and @name is parentName
node = @
if executableBody or @hasNameClash
node = new ExecutableClassBody node, executableBody
else if not @name? and o.level is LEVEL_TOP
匿名類別僅在表達式中有效
node = new Parens node
if @boundMethods.length and @parent
@variable ?= new IdentifierLiteral o.scope.freeVariable '_class'
[@variable, @variableRef] = @variable.cache o unless @variableRef?
if @variable
node = new Assign @variable, node, null, { @moduleDeclaration }
@compileNode = @compileClassDeclaration
try
return node.compileToFragments o
finally
delete @compileNode
compileClassDeclaration: (o) ->
@ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length
@ctor?.noReturn = true
@proxyBoundMethods() if @boundMethods.length
o.indent += TAB
result = []
result.push @makeCode "class "
result.push @makeCode @name if @name
@compileCommentFragments o, @variable, result if @variable?.comments?
result.push @makeCode ' ' if @name
result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent
result.push @makeCode '{'
unless @body.isEmpty()
@body.spaced = true
result.push @makeCode '\n'
result.push @body.compileToFragments(o, LEVEL_TOP)...
result.push @makeCode "\n#{@tab}"
result.push @makeCode '}'
result
找出此類別的適當名稱
determineName: ->
return null unless @variable
[..., tail] = @variable.properties
node = if tail
tail instanceof Access and tail.name
else
@variable.base
unless node instanceof IdentifierLiteral or node instanceof PropertyName
return null
name = node.value
unless tail
message = isUnassignable name
@variable.error message if message
if name in JS_FORBIDDEN then "_#{name}" else name
walkBody: (o) ->
@ctor = null
@boundMethods = []
executableBody = null
initializer = []
{ expressions } = @body
i = 0
for expression in expressions.slice()
if expression instanceof Value and expression.isObject true
{ properties } = expression.base
exprs = []
end = 0
start = 0
pushSlice = -> exprs.push new Value new Obj properties[start...end], true if end > start
while assign = properties[end]
if initializerExpression = @addInitializerExpression assign, o
pushSlice()
exprs.push initializerExpression
initializer.push initializerExpression
start = end + 1
end++
pushSlice()
expressions[i..i] = exprs
i += exprs.length
else
if initializerExpression = @addInitializerExpression expression, o
initializer.push initializerExpression
expressions[i] = initializerExpression
i += 1
for method in initializer when method instanceof Code
if method.ctor
method.error 'Cannot define more than one constructor in a class' if @ctor
@ctor = method
else if method.isStatic and method.bound
method.context = @name
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
將表達式新增到類別初始化程式
這是用於判斷類別主體中的表達式應出現在初始化程式或可執行主體中的關鍵方法。如果指定的 node
在類別主體中有效,則方法會傳回一個(新的、修改的或相同的)節點以包含在類別初始化程式中,否則不會傳回任何內容,而節點將出現在可執行主體中。
在撰寫本文時,只有方法(實例和靜態)在 ES 類別初始化程式中有效。隨著新的 ES 類別功能(例如類別欄位)達到第 4 階段,此方法需要更新以支援它們。我們還允許在初始化程式中使用 PassthroughLiteral
(反引號表達式)作為未實作 ES 功能的跳脫口(例如透過 get
和 set
關鍵字定義的 getter 和 setter,而不是 Object.defineProperty
方法)。
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
檢查指定的節點是否為有效的 ES 類別初始化程式方法。
validInitializerMethod: (node) ->
return no unless node instanceof Assign and node.value instanceof Code
return yes if node.context is 'object' and not node.variable.hasProperties()
return node.variable.looksStatic(@name) and (@name or not node.value.bound)
傳回已設定的類別初始化程式方法
addInitializerMethod: (assign) ->
{ variable, value: method, operatorToken } = assign
method.isMethod = yes
method.isStatic = variable.looksStatic @name
if method.isStatic
method.name = variable.properties[0]
else
methodName = variable.base
method.name = new (if methodName.shouldCache() then Index else Access) methodName
method.name.updateLocationDataIfMissing methodName.locationData
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
if @parent
ctor.body.push new SuperCall new Super, [new Splat new IdentifierLiteral 'arguments']
if @externalCtor
applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ]
applyArgs = [ new ThisLiteral, new IdentifierLiteral 'arguments' ]
ctor.body.push new Call applyCtor, applyArgs
ctor.body.makeReturn()
ctor
proxyBoundMethods: ->
@ctor.thisAssignments = for method in @boundMethods
method.classVariable = @variableRef if @parent
name = new Value(new ThisLiteral, [ method.name ])
new Assign name, new Call(new Value(name, [new Access new PropertyName 'bind']), [new ThisLiteral])
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' ]
defaultClassVariableName: '_Class'
constructor: (@class, @body = new Block) ->
super()
compileNode: (o) ->
if jumpNode = @body.jumps()
jumpNode.error 'Class bodies cannot contain pure statements'
if argumentsNode = @body.contains isLiteralArguments
argumentsNode.error "Class bodies shouldn't reference arguments"
params = []
args = [new ThisLiteral]
wrapper = new Code params, @body
klass = new Parens new Call (new Value wrapper, [new Access new PropertyName 'call']), args
@body.spaced = true
o.classScope = wrapper.makeScope o.scope
@name = @class.name ? o.classScope.freeVariable @defaultClassVariableName
ident = new IdentifierLiteral @name
directives = @walkBody()
@setContext()
if @class.hasNameClash
parent = new IdentifierLiteral o.classScope.freeVariable 'superClass'
wrapper.params.push new Param parent
args.push @class.parent
@class.parent = parent
if @externalCtor
externalCtor = new IdentifierLiteral o.classScope.freeVariable 'ctor', reserve: no
@class.externalCtor = externalCtor
@externalCtor.variable.base = externalCtor
if @name isnt @class.name
@body.expressions.unshift new Assign (new IdentifierLiteral @name), @class
else
@body.expressions.unshift @class
@body.expressions.unshift directives...
@body.push ident
klass.compileToFragments o
walkBody: ->
directives = []
index = 0
while expr = @body.expressions[index]
break unless expr instanceof Value and expr.isString()
if expr.hoisted
index++
else
directives.push @body.expressions.splice(index, 1)...
@traverseChildren false, (child) =>
return false if child instanceof Class or child instanceof HoistTarget
cont = true
if child instanceof Block
for node, i in child.expressions
if node instanceof Value and node.isObject(true)
cont = false
child.expressions[i] = @addProperties node.base.properties
else if node instanceof Assign and node.variable.looksStatic @name
node.value.isStatic = yes
child.expressions = flatten child.expressions
cont
directives
setContext: ->
@body.traverseChildren false, (node) =>
if node instanceof ThisLiteral
node.value = @name
else if node instanceof Code and node.bound and (node.isStatic or not node.name)
node.context = @name
針對無效的 ES 屬性建立類別/原型指派
addProperties: (assigns) ->
result = for assign in assigns
variable = assign.variable
base = variable?.base
value = assign.value
delete assign.context
if base.value is 'constructor'
if value instanceof Code
base.error 'constructors must be defined at the top level of a class body'
類別範圍尚未可用,因此傳回指派以供稍後更新
assign = @externalCtor = new Assign new Value, value
else if not assign.variable.this
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 ]
assign.variable = variable
else if assign.value instanceof Code
assign.value.isStatic = true
assign
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
exports.ModuleDeclaration = class ModuleDeclaration extends Base
constructor: (@clause, @source, @assertions) ->
super()
@checkSource()
children: ['clause', 'source', 'assertions']
isStatement: YES
jumps: THIS
makeReturn: THIS
checkSource: ->
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) ->
待辦事項:在 AST 產生期間(以及編譯到 JS 時)標記此錯誤是適當的。但 o.indent
沒有在 AST 產生期間追蹤,而且似乎沒有目前可用的替代方式來追蹤我們是否在「程式頂層」。
if o.indent.length isnt 0
@error "#{moduleDeclarationType} statements must be at top-level scope"
astAssertions: (o) ->
if @assertions?.properties?
@assertions.properties.map (assertion) =>
{ start, end, loc, left, right } = assertion.ast(o)
{ type: 'ImportAttribute', start, end, loc, key: left, value: right }
else
[]
exports.ImportDeclaration = class ImportDeclaration extends ModuleDeclaration
compileNode: (o) ->
@checkScope o, 'import'
o.importedSymbols = []
code = []
code.push @makeCode "#{@tab}import "
code.push @clause.compileNode(o)... if @clause?
if @source?.value?
code.push @makeCode ' from ' unless @clause is null
code.push @makeCode @source.value
if @assertions?
code.push @makeCode ' assert '
code.push @assertions.compileToFragments(o)...
code.push @makeCode ';'
code
astNode: (o) ->
o.importedSymbols = []
super o
astProperties: (o) ->
ret =
specifiers: @clause?.ast(o) ? []
source: @source.ast o
assertions: @astAssertions(o)
ret.importKind = 'value' if @clause
ret
exports.ImportClause = class ImportClause extends Base
constructor: (@defaultBinding, @namedImports) ->
super()
children: ['defaultBinding', 'namedImports']
compileNode: (o) ->
code = []
if @defaultBinding?
code.push @defaultBinding.compileNode(o)...
code.push @makeCode ', ' if @namedImports?
if @namedImports?
code.push @namedImports.compileNode(o)...
code
astNode: (o) ->
ImportClause
的 AST 是匯入指定項目的非巢狀清單,這些項目將會是 ImportDeclaration
AST 的 specifiers
屬性
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)
code.push @makeCode 'var '
@clause.moduleDeclaration = 'export'
if @clause.body? and @clause.body instanceof Block
code = code.concat @clause.compileToFragments o, LEVEL_TOP
else
code = code.concat @clause.compileNode o
if @source?.value?
code.push @makeCode " from #{@source.value}"
if @assertions?
code.push @makeCode ' assert '
code.push @assertions.compileToFragments(o)...
code.push @makeCode ';'
code
禁止匯出匿名類別;所有匯出的成員都必須命名
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
assertions: @astAssertions(o)
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
assertions: @astAssertions(o)
exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration
astProperties: (o) ->
return
source: @source.ast o
assertions: @astAssertions(o)
exportKind: 'value'
exports.ModuleSpecifierList = class ModuleSpecifierList extends Base
constructor: (@specifiers) ->
super()
children: ['specifiers']
compileNode: (o) ->
code = []
o.indent += TAB
compiledList = (specifier.compileToFragments o, LEVEL_LIST for specifier in @specifiers)
if @specifiers.length isnt 0
code.push @makeCode "{\n#{o.indent}"
for fragments, index in compiledList
code.push @makeCode(",\n#{o.indent}") if index
code.push fragments...
code.push @makeCode "\n}"
else
code.push @makeCode '{}'
code
astNode: (o) ->
specifier.ast(o) for specifier in @specifiers
exports.ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList
exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList
exports.ModuleSpecifier = class ModuleSpecifier extends Base
constructor: (@original, @alias, @moduleDeclarationType) ->
super()
if @original.comments or @alias?.comments
@comments = []
@comments.push @original.comments... if @original.comments
@comments.push @alias.comments... if @alias?.comments
進入區域範圍的變數名稱
@identifier = if @alias? then @alias.value else @original.value
children: ['original', 'alias']
compileNode: (o) ->
@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'
addIdentifierToScope: (o) ->
根據規範,符號無法多次匯入(例如 import { foo, foo } from 'lib'
是無效的)
if @identifier in o.importedSymbols or o.scope.check(@identifier)
@error "'#{@identifier}' has already been declared"
else
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 1 <= @args.length <= 2
@error 'import() accepts either one or two arguments'
astNode: (o) ->
@checkArguments()
super o
指定用於將區域變數指定給值,或設定物件的屬性,包括物件文字中的屬性。
exports.Assign = class Assign extends Base
constructor: (@variable, @value, @context, options = {}) ->
super()
{@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options
@propagateLhs()
children: ['variable', 'value']
isAssignable: YES
isStatement: (o) ->
o?.level is LEVEL_TOP and @context? and (@moduleDeclaration or "?" in @context)
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'
addScopeVariables: (o, {
在 AST 產生期間,我們需要允許指定給這些在編譯到 JS 期間被視為「不可指定」的建構,同時仍標記 [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
可以是 'import'
或 '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
如果此指定識別碼附加一個或多個此處註解,請將它們作為宣告列的一部分輸出(除非其他此處註解已分段放置在那裡),以與 Flow 打字相容。如果此指定是給類別,例如 ClassName = class ClassName {
,請不要這樣做,因為 Flow 要求註解置於類別名稱和 {
之間。
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
編譯指定項目,適當委派給 compileDestructuring
或 compileSplice
。追蹤已指定給我們的基礎物件名稱,以取得正確的內部參照。如果變數尚未在目前範圍內看到,請宣告它。
compileNode: (o) ->
isValue = @variable instanceof Value
if isValue
如果 @variable
是陣列或物件,我們正在解構;如果它也是 isAssignable()
,ES 中支援解構語法,我們可以按原樣輸出;否則,我們 @compileDestructuring
並將這個 ES 不支援的解構轉換為可接受的輸出。
if @variable.isArray() or @variable.isObject()
unless @variable.isAssignable()
if @variable.isObject() and @variable.base.hasSplat()
return @compileObjectDestruct o
else
return @compileDestructuring o
return @compileSplice o if @variable.isSplice()
return @compileConditional o if @isConditional()
return @compileSpecialMath o if @context in ['//=', '%%=']
@addScopeVariables o
if @value instanceof Code
if @value.isStatic
@value.name = @variable.properties[0]
else if @variable.properties?.length >= 2
[properties..., prototype, name] = @variable.properties
@value.name = name if prototype.name?.value is 'prototype'
val = @value.compileToFragments o, LEVEL_LIST
compiledName = @variable.compileToFragments o, LEVEL_LIST
if @context is 'object'
if @variable.shouldCache()
compiledName.unshift @makeCode '['
compiledName.push @makeCode ']'
return compiledName.concat @makeCode(': '), val
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
根據 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration,如果我們在不宣告的情況下解構,解構指定必須包含在括號中。如果 'o.level' 的優先順序低於 LEVEL_LIST (3)(即 LEVEL_COND (4)、LEVEL_OP (5) 或 LEVEL_ACCESS (6)),或者如果我們正在解構物件,例如 {a,b} = obj,則指定會包含在括號中。
if o.level > LEVEL_LIST or isValue and @variable.base instanceof Obj and not @nestedLhs and not (@param is yes)
@wrapInParentheses answer
else
answer
物件 rest 屬性不可指定:{{a}...}
compileObjectDestruct: (o) ->
@variable.base.reorderProperties()
{properties: props} = @variable.base
[..., splat] = props
splatProp = splat.name
assigns = []
refVal = new Value new IdentifierLiteral o.scope.freeVariable 'ref'
props.splice -1, 1, new Splat refVal
assigns.push new Assign(new Value(new Obj props), @value).compileToFragments o, LEVEL_LIST
assigns.push new Assign(new Value(splatProp), refVal).compileToFragments o, LEVEL_LIST
@joinFragmentArrays assigns, ', '
當將陣列或物件文字指定給值時,遞迴模式比對的簡要實作。窺探其屬性以指定內部名稱。
compileDestructuring: (o) ->
top = o.level is LEVEL_TOP
{value} = this
{objects} = @variable.base
olen = objects.length
{} = a
和 [] = a
(空樣式)的特殊情況。編譯為僅 a
。
if olen is 0
code = value.compileToFragments o
return if o.level >= LEVEL_OP then @wrapInParentheses code else code
[obj] = objects
@disallowLoneExpansion()
{splats, expans, splatsAndExpans} = @getAndCheckSplatsAndExpansions()
isSplat = splats?.length > 0
isExpans = expans?.length > 0
vvar = value.compileToFragments o, LEVEL_LIST
vvarText = fragmentsToText vvar
assigns = []
pushAssign = (variable, val) =>
assigns.push new Assign(variable, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
if isSplat
splatVar = objects[splats[0]].name.unwrap()
if splatVar instanceof Arr or splatVar instanceof Obj
splatVarRef = new IdentifierLiteral o.scope.freeVariable 'ref'
objects[splats[0]].name = splatVarRef
splatVarAssign = -> pushAssign new Value(splatVar), splatVarRef
在這個時候,有許多事情要解構。因此,例如 {a, b} = fn()
中的 fn()
必須快取。如果 vvar 不是簡單變數,請將其轉換為簡單變數。
if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
ref = o.scope.freeVariable 'ref'
assigns.push [@makeCode(ref + ' = '), vvar...]
vvar = [@makeCode ref]
vvarText = ref
slicer = (type) -> (vvar, start, end = no) ->
vvar = new IdentifierLiteral vvar unless vvar instanceof Value
args = [vvar, new NumberLiteral(start)]
args.push new NumberLiteral end if end
slice = new Value (new IdentifierLiteral utility type, o), [new Access new PropertyName 'call']
new Value new Call slice, args
輸出 [].slice
程式碼的輔助程式。
compSlice = slicer "slice"
輸出 [].splice
程式碼的輔助程式。
compSplice = slicer "splice"
檢查 objects
陣列是否包含任何 Assign
執行個體,例如 {a:1}。
hasObjAssigns = (objs) ->
(i for obj, i in objs when obj instanceof Assign and obj.context is 'object')
檢查 objects
陣列是否包含任何不可指派的物件。
objIsUnassignable = (objs) ->
return yes for obj in objs when not obj.isAssignable()
no
當物件指派 ({a:1})、不可指派物件或僅單一節點時,objects
會很複雜。
complexObjects = (objs) ->
hasObjAssigns(objs).length or objIsUnassignable(objs) or olen is 1
「複雜」的 objects
會在迴圈中處理。範例:[a, b, {c, r…}, d]、[a, …, {b, r…}, c, d]
loopObjects = (objs, vvar, vvarTxt) =>
for obj, i in objs
可以略過「省略」。
continue if obj instanceof Elision
如果 obj
是 {a: 1}
if obj instanceof Assign and obj.context is 'object'
{variable: {base: idx}, value: vvar} = obj
{variable: vvar} = vvar if vvar instanceof Assign
idx =
if vvar.this
vvar.properties[0].name
else
new PropertyName vvar.unwrap().value
acc = idx.unwrap() instanceof PropertyName
vval = new Value value, [new (if acc then Access else Index) idx]
else
obj
是 [a…], {a…} 或 a
vvar = switch
when obj instanceof Splat then new Value obj.name
else obj
vval = switch
when obj instanceof Splat then compSlice(vvarTxt, i)
else new Value new Literal(vvarTxt), [new Index new NumberLiteral i]
message = isUnassignable vvar.unwrap().value
vvar.error message if message
pushAssign vvar, vval
「簡單」的 objects
可以拆分並編譯成陣列,[a, b, c] = arr、[a, b, c…] = arr
assignObjects = (objs, vvar, vvarTxt) =>
vvar = new Value new Arr(objs, yes)
vval = if vvarTxt instanceof Value then vvarTxt else new Value new Literal(vvarTxt)
pushAssign vvar, vval
processObjects = (objs, vvar, vvarTxt) ->
if complexObjects objs
loopObjects objs, vvar, vvarTxt
else
assignObjects objs, vvar, vvarTxt
如果 objects
中有 Splat
或 Expansion
,我們可以將陣列拆分成兩個簡單的子陣列。Splat
[a, b, c…, d, e] 可以拆分成 [a, b, c…] 和 [d, e]。Expansion
[a, b, …, c, d] 可以拆分成 [a, b] 和 [c, d]。範例:a) Splat
CS: [a, b, c…, d, e] = arr JS: [a, b, …c] = arr、[d, e] = splice.call(c, -2) b) Expansion
CS: [a, b, …, d, e] = arr JS: [a, b] = arr、[d, e] = slice.call(arr, -2)
if splatsAndExpans.length
expIdx = splatsAndExpans[0]
leftObjs = objects.slice 0, expIdx + (if isSplat then 1 else 0)
rightObjs = objects.slice expIdx + 1
processObjects leftObjs, vvar, vvarText if leftObjs.length isnt 0
if rightObjs.length isnt 0
切片或拼接 objects
。
refExp = switch
when isSplat then compSplice new Value(objects[expIdx].name), rightObjs.length * -1
when isExpans then compSlice vvarText, rightObjs.length * -1
if complexObjects rightObjs
restVar = refExp
refExp = o.scope.freeVariable 'ref'
assigns.push [@makeCode(refExp + ' = '), restVar.compileToFragments(o, LEVEL_LIST)...]
processObjects rightObjs, vvar, refExp
else
objects
中沒有 Splat
或 Expansion
。
processObjects objects, vvar, vvarText
splatVarAssign?()
assigns.push vvar unless top or @subpattern
fragments = @joinFragmentArrays assigns, ', '
if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments
由於某些原因,不允許 [...] = a
。(是否等於 [] = a
?)
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'
如果有多個 Splat
或 Expansion
,則顯示錯誤。範例:[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
計算所有 Splats
:[a, b, c…, d, e]
splats = (i for obj, i in objects when obj instanceof Splat)
計算所有 Expansions
:[a, b, …, c, d]
expans = (i for obj, i in objects when obj instanceof Expansion)
結合 splats 和 expansions。
splatsAndExpans = [splats..., expans...]
if splatsAndExpans.length > 1
對 'splatsAndExpans' 排序,以便我們可以在第一個不允許的令牌處顯示錯誤。
objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
{splats, expans, splatsAndExpans}
編譯條件式賦值時,請注意確保運算元只評估一次,即使我們必須參考它們多次。
compileConditional: (o) ->
[left, right] = @variable.cacheReference o
不允許未定義變數的條件式賦值。
if not left.properties.length and left.base instanceof Literal and
left.base not instanceof ThisLiteral and not o.scope.check left.base.value
@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
else
fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
if o.level <= LEVEL_LIST then fragments else @wrapInParentheses fragments
將特殊數學賦值運算子(例如 a //= b
)轉換為等效的延伸形式 a = a ** b
,然後編譯它。
compileSpecialMath: (o) ->
[left, right] = @variable.cacheReference o
new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
使用 JavaScript 的 Array#splice
方法,從陣列切片文字編譯賦值。
compileSplice: (o) ->
{range: {from, to, exclusive}} = @variable.properties.pop()
unwrappedVar = @variable.unwrapAll()
if unwrappedVar.comments
moveComments unwrappedVar, @
delete @variable.comments
name = @variable.compile o
if from
[fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
else
fromDecl = fromRef = '0'
if to
if from?.isNumber() and to.isNumber()
to = to.compile(o) - fromRef
to += 1 unless exclusive
else
to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
to += ' + 1' unless exclusive
else
to = "9e9"
[valDef, valRef] = @value.cache o, LEVEL_LIST
answer = [].concat @makeCode("#{utility 'splice', o}.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
if o.level > LEVEL_TOP then @wrapInParentheses answer else answer
eachName: (iterator) ->
@variable.unwrapAll().eachName iterator
isDefaultAssignment: -> @param or @nestedLhs
propagateLhs: ->
return unless @variable?.isArray?() or @variable?.isObject?()
這是賦值的左側;讓 Arr
和 Obj
知道,以便這些節點知道它們可以作為解構變數指派。
@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
exports.FuncGlyph = class FuncGlyph extends Base
constructor: (@glyph) ->
super()
函式定義。這是唯一建立新 Scope 的節點。當為了遍歷函式主體的內容時,程式碼沒有子節點 – 它們在內部 Scope 中。
exports.Code = class Code extends Base
constructor: (params, body, @funcGlyph, @paramStart) ->
super()
@params = params or []
@body = body or new Block
@bound = @funcGlyph?.glyph is '=>'
@isGenerator = no
@isAsync = no
@isMethod = no
@body.traverseChildren no, (node) =>
if (node instanceof Op and node.isYield()) or node instanceof YieldReturn
@isGenerator = yes
if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
@isAsync = yes
if node instanceof For and node.isAwait()
@isAsync = yes
@propagateLhs()
children: ['params', 'body']
isStatement: -> @isMethod
jumps: NO
makeScope: (parentScope) -> new Scope parentScope, @body, this
編譯會建立一個新的 Scope,除非明確要求與外部 Scope 共用。透過將此類參數設定為函式定義中的最後一個參數,來處理參數清單中的 splat 參數,這是 ES2015 規格的要求。如果 CoffeeScript 函式定義在 splat 之後有參數,則它們會透過函式主體中的表達式宣告。
compileNode: (o) ->
@checkForAsyncOrGeneratorConstructor()
if @bound
@context = o.scope.method.context if o.scope.method?.bound
@context = 'this' unless @context
@updateOptions o
params = []
exprs = []
thisAssignments = @thisAssignments?.slice() ? []
paramsAfterSplat = []
haveSplatParam = no
haveBodyParam = no
@checkForDuplicateParams()
@disallowLoneExpansionAndMultipleSplats()
分離 this
指派。
@eachParamName (name, node, param, obj) ->
if node.this
name = node.properties[0].name.value
name = "_#{name}" if name in JS_FORBIDDEN
target = new IdentifierLiteral o.scope.freeVariable name, reserve: no
Param
是物件解構,帶有預設值:({@prop = 1}) -> 如果變數名稱已被保留,我們必須為解構變數指派新的變數名稱:({prop:prop1 = 1}) ->
replacement =
if param.name instanceof Obj and obj instanceof Assign and
obj.operatorToken.value is '='
new Assign (new IdentifierLiteral name), target, 'object' #, operatorToken: new Literal ':'
else
target
param.renameParam node, replacement
thisAssignments.push new Assign node, target
剖析參數,將它們新增到要放入函數定義的參數清單中;並處理散列或展開,包括將表達式新增到函數主體中,以宣告所有會在散列/展開參數之後的參數變數。如果我們遇到一個參數,需要在函數主體中宣告,例如它已使用 this
解構,也宣告並指派函數主體中所有後續參數,以便任何非冪等參數都能以正確順序評估。
for param, i in @params
是否已對這個參數使用 ...
?散列/展開參數無法有預設值,所以我們不必擔心這個。
if param.splat or param instanceof Expansion
haveSplatParam = yes
if param.splat
if param.name instanceof Arr or param.name instanceof Obj
ES 以奇怪的方式處理散列陣列;在函數主體中以舊式方式處理它們。待辦事項:這是否應該在函數參數清單中處理,如果是,如何處理?
splatParamName = o.scope.freeVariable 'arg'
params.push ref = new Value new IdentifierLiteral splatParamName
exprs.push new Assign new Value(param.name), ref
else
params.push ref = param.asReference o
splatParamName = fragmentsToText ref.compileNodeWithoutComments o
if param.shouldCache()
exprs.push new Assign new Value(param.name), ref
else # `param` is an Expansion
splatParamName = o.scope.freeVariable 'args'
params.push new Value new IdentifierLiteral splatParamName
o.scope.parameter splatParamName
剖析所有其他參數;如果尚未遇到散列參數,將這些其他參數新增到要輸出在函數定義中的清單中。
else
if param.shouldCache() or haveBodyParam
param.assignedInBody = yes
haveBodyParam = yes
這個參數無法在參數清單中宣告或指派。因此,在參數清單中放置一個參考,並新增一個陳述式到函數主體中指派它,例如 (arg) => { var a = arg.a; }
,如果它有一個預設值。
if param.value?
condition = new Op '===', param, new UndefinedLiteral
ifTrue = new Assign new Value(param.name), param.value
exprs.push new If condition, ifTrue
else
exprs.push new Assign new Value(param.name), param.asReference(o), null, param: 'alwaysDeclare'
如果這個參數出現在散列或展開之前,它將會在函數定義參數清單中。
unless haveSplatParam
如果此參數有預設值,且尚未由上述的 shouldCache()
區塊設定,請將其定義為函式主體中的陳述式。此參數位於展開參數之後,因此我們無法在參數清單中定義其預設值。
if param.shouldCache()
ref = param.asReference o
else
if param.value? and not param.assignedInBody
ref = new Assign new Value(param.name), param.value, null, param: yes
else
ref = param
將此參數的參照新增至函式範圍。
if param.name instanceof Arr or param.name instanceof Obj
此參數已解構。
param.name.lhs = yes
unless param.shouldCache()
param.name.eachName (prop) ->
o.scope.parameter prop.value
else
此參數的編譯僅用於取得其名稱以新增至範圍名稱追蹤;由於此處的編譯輸出並未保留供最終輸出,因此請勿在此編譯中包含註解,以免在實際編譯此參數時輸出這些註解。
paramToAddToScope = if param.value? then param else ref
o.scope.parameter fragmentsToText paramToAddToScope.compileToFragmentsWithoutComments o
params.push ref
else
paramsAfterSplat.push param
如果此參數有預設值,由於它不再位於函式參數清單中,因此我們需要將其預設值(如有必要)指定為主體中的表達式。
if param.value? and not param.shouldCache()
condition = new Op '===', param, new UndefinedLiteral
ifTrue = new Assign new Value(param.name), param.value
exprs.push new If condition, ifTrue
將此參數新增至範圍,因為它先前已被略過,因此尚未新增。
o.scope.add param.name.value, 'var', yes if param.name?.value?
如果展開或擴充參數之後有參數,則需要在函式主體中指定這些參數。
if paramsAfterSplat.length isnt 0
建立解構指定,例如 [a, b, c] = [args..., b, c]
exprs.unshift new Assign new Value(
new Arr [new Splat(new IdentifierLiteral(splatParamName)), (param.asReference o for param in paramsAfterSplat)...]
), new Value new IdentifierLiteral splatParamName
將新的表達式新增至函式主體
wasEmpty = @body.isEmpty()
@disallowSuperInParamDefaults()
@checkSuperCallsInConstructorBody()
@body.expressions.unshift thisAssignments... unless @expandCtorSuper thisAssignments
@body.expressions.unshift exprs...
if @isMethod and @bound and not @isStatic and @classVariable
boundMethodCheck = new Value new Literal utility 'boundMethodCheck', o
@body.expressions.unshift new Call(boundMethodCheck, [new Value(new ThisLiteral), @classVariable])
@body.makeReturn() unless wasEmpty or @noReturn
JavaScript 不允許繫結 (=>
) 函式同時也是產生器。這通常會透過 Op::compileContinuation
偵測到,但請仔細檢查
if @bound and @isGenerator
yieldNode = @body.contains (node) -> node instanceof Op and node.operator is 'yield'
(yieldNode or @).error 'yield cannot occur inside bound (fat arrow) functions'
組裝輸出
modifiers = []
modifiers.push 'static' if @isMethod and @isStatic
modifiers.push 'async' if @isAsync
unless @isMethod or @bound
modifiers.push "function#{if @isGenerator then '*' else ''}"
else if @isGenerator
modifiers.push '*'
signature = [@makeCode '(']
函數名稱與 (
之間的區塊註解會輸出在 function
與 (
之間。
if @paramStart?.comments?
@compileCommentFragments o, @paramStart, signature
for param, i in params
signature.push @makeCode ', ' if i isnt 0
signature.push @makeCode '...' if haveSplatParam and i is params.length - 1
編譯此參數,但如果產生任何變數(例如 ref
),請將它們移至父範圍,因為我們無法在函數參數清單中放置 var
行。
scopeVariablesCount = o.scope.variables.length
signature.push param.compileToFragments(o, LEVEL_PAREN)...
if scopeVariablesCount isnt o.scope.variables.length
generatedVariables = o.scope.variables.splice scopeVariablesCount
o.scope.parent.variables.push generatedVariables...
signature.push @makeCode ')'
)
與 ->
/=>
之間的區塊註解會輸出在 )
與 {
之間。
if @funcGlyph?.comments?
comment.unshift = no for comment in @funcGlyph.comments
@compileCommentFragments o, @funcGlyph, signature
body = @body.compileWithDeclarations o unless @body.isEmpty()
我們需要在方法名稱之前編譯主體,以確保處理 super
參照。
if @isMethod
[methodScope, o.scope] = [o.scope, o.scope.parent]
name = @name.compileToFragments o
name.shift() if name[0].code is '.'
o.scope = methodScope
answer = @joinFragmentArrays (@makeCode m for m in modifiers), ' '
answer.push @makeCode ' ' if modifiers.length and name
answer.push name... if name
answer.push signature...
answer.push @makeCode ' =>' if @bound and not @isMethod
answer.push @makeCode ' {'
answer.push @makeCode('\n'), body..., @makeCode("\n#{@tab}") if body?.length
answer.push @makeCode '}'
return indentInitial answer, @ if @isMethod
if @front or (o.level >= LEVEL_ACCESS) then @wrapInParentheses answer else answer
updateOptions: (o) ->
o.scope = del(o, 'classScope') or @makeScope o.scope
o.scope.shared = del(o, 'sharedScope')
o.indent += TAB
delete o.bare
delete o.isExistentialEquals
checkForDuplicateParams: ->
paramNames = []
@eachParamName (name, node, param) ->
node.error "multiple parameters named '#{name}'" if name in paramNames
paramNames.push name
eachParamName: (iterator) ->
param.eachName iterator for param in @params
短路 traverseChildren
方法,以防止它跨越範圍邊界,除非 crossScope
為 true
。
traverseChildren: (crossScope, func) ->
super(crossScope, func) if crossScope
短路 replaceInContext
方法,以防止它跨越內容邊界。繫結函數具有相同的內容。
replaceInContext: (child, replacement) ->
if @bound
super child, replacement
else
false
disallowSuperInParamDefaults: ({forAst} = {}) ->
return false unless @ctor
@eachSuperCall Block.wrap(@params), (superCall) ->
superCall.error "'super' is not allowed in constructor parameter defaults"
, checkForThisBeforeSuper: not forAst
checkSuperCallsInConstructorBody: ->
return false unless @ctor
seenSuper = @eachSuperCall @body, (superCall) =>
superCall.error "'super' is only allowed in derived class constructors" if @ctor is 'base'
seenSuper
flagThisParamInDerivedClassConstructorWithoutCallingSuper: (param) ->
param.error "Can't use @params in derived class constructors without calling super"
checkForAsyncOrGeneratorConstructor: ->
if @ctor
@name.error 'Class constructor may not be async' if @isAsync
@name.error 'Class constructor may not be a generator' if @isGenerator
disallowLoneExpansionAndMultipleSplats: ->
seenSplatParam = no
for param in @params
此參數是否使用 ...
?(每個函數只允許一個此類參數。)
if param.splat or param instanceof Expansion
if seenSplatParam
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'
seenSplatParam = yes
expandCtorSuper: (thisAssignments) ->
return false unless @ctor
seenSuper = @eachSuperCall @body, (superCall) =>
superCall.expressions = thisAssignments
haveThisParam = thisAssignments.length and thisAssignments.length isnt @thisAssignments?.length
if @ctor is 'derived' and not seenSuper and haveThisParam
param = thisAssignments[0].variable
@flagThisParamInDerivedClassConstructorWithoutCallingSuper param
seenSuper
在給定的內容節點中尋找所有 super 呼叫;如果呼叫 iterator
,則傳回 true
。
eachSuperCall: (context, iterator, {checkForThisBeforeSuper = yes} = {}) ->
seenSuper = no
context.traverseChildren yes, (child) =>
if child instanceof SuperCall
建構函數中的 super
(唯一沒有存取器的 super
)不能給予帶有對 this
參照的引數,因為這會在呼叫 super
之前參照 this
。
unless child.variable.accessor
childArgs = child.args.filter (arg) ->
arg not instanceof Class and (arg not instanceof Code or arg.bound)
Block.wrap(childArgs).traverseChildren yes, (node) =>
node.error "Can't call super with @params in derived class constructors" if node.this
seenSuper = yes
iterator child
else if checkForThisBeforeSuper and child instanceof ThisLiteral and @ctor is 'derived' and not seenSuper
child.error "Can't reference 'this' before calling super in derived class constructors"
super
在繫結(箭頭)函數中具有相同的目標,因此也要檢查它們
child not instanceof SuperCall and (child not instanceof Code or child.bound)
seenSuper
propagateLhs: ->
for param in @params
{name} = param
if name instanceof Arr or name instanceof Obj
name.propagateLhs yes
else if param instanceof Expansion
param.lhs = yes
astAddParamsToScope: (o) ->
@eachParamName (name) ->
o.scope.add name, 'param'
astNode: (o) ->
@updateOptions o
@checkForAsyncOrGeneratorConstructor()
@checkForDuplicateParams()
@disallowSuperInParamDefaults forAst: yes
@disallowLoneExpansionAndMultipleSplats()
seenSuper = @checkSuperCallsInConstructorBody()
if @ctor is 'derived' and not seenSuper
@eachParamName (name, node) =>
if node.this
@flagThisParamInDerivedClassConstructorWithoutCallingSuper node
@astAddParamsToScope o
@body.makeReturn null, yes unless @body.isEmpty() or @noReturn
super o
astType: ->
if @isMethod
'ClassMethod'
else if @bound
'ArrowFunctionExpression'
else
'FunctionExpression'
paramForAst: (param) ->
return param if param instanceof Expansion
{name, value, splat} = param
if splat
new Splat name, lhs: yes, postfix: splat.postfix
.withLocationDataFrom param
else if value?
new Assign name, value, null, param: yes
.withLocationDataFrom locationData: mergeLocationData name.locationData, value.locationData
else
name
methodAstProperties: (o) ->
getIsComputed = =>
return yes if @name instanceof Index
return yes if @name instanceof ComputedPropertyName
return yes if @name.name instanceof ComputedPropertyName
no
return
static: !!@isStatic
key: @name.ast o
computed: getIsComputed()
kind:
if @ctor
'constructor'
else
'method'
operator: @operatorToken?.value ? '='
staticClassName: @isStatic.staticClassName?.ast(o) ? null
bound: !!@bound
astProperties: (o) ->
return Object.assign
params: @paramForAst(param).ast(o) for param in @params
body: @body.ast (Object.assign {}, o, checkForDirectives: yes), LEVEL_TOP
generator: !!@isGenerator
async: !!@isAsync
我們從不產生命名函式,因此將 id
指定為 null
,這與匿名函式表達式/箭頭函式的 Babel AST 相符
id: null
hasIndentedBody: @body.locationData.first_line > @funcGlyph?.locationData.first_line
,
if @isMethod then @methodAstProperties o else {}
astLocationData: ->
functionLocationData = super()
return functionLocationData unless @isMethod
astLocationData = mergeAstLocationData @name.astLocationData(), functionLocationData
if @isStatic.staticClassName?
astLocationData = mergeAstLocationData @isStatic.staticClassName.astLocationData(), astLocationData
astLocationData
函式定義中的參數。除了典型的 JavaScript 參數之外,這些參數還可以附加到函式的內容中,也可以是一個展開運算子,將一組參數收集到一個陣列中。
exports.Param = class Param extends Base
constructor: (@name, @value, @splat) ->
super()
message = isUnassignable @name.unwrapAll().value
@name.error message if message
if @name instanceof Obj and @name.generated
token = @name.objects[0].operatorToken
token.error "unexpected #{token.value}"
children: ['name', 'value']
compileToFragments: (o) ->
@name.compileToFragments o, LEVEL_LIST
compileToFragmentsWithoutComments: (o) ->
@name.compileToFragmentsWithoutComments o, LEVEL_LIST
asReference: (o) ->
return @reference if @reference
node = @name
if node.this
name = node.properties[0].name.value
name = "_#{name}" if name in JS_FORBIDDEN
node = new IdentifierLiteral o.scope.freeVariable name
else if node.shouldCache()
node = new IdentifierLiteral o.scope.freeVariable 'arg'
node = new Value node
node.updateLocationDataIfMissing @locationData
@reference = node
shouldCache: ->
@name.shouldCache()
反覆運算 Param
的名稱或名稱。從某種意義上說,解構參數表示多個 JS 參數。此方法允許反覆運算所有參數。iterator
函式將會以 iterator(name, node)
的形式呼叫,其中 name
是參數的名稱,而 node
是對應於該名稱的 AST 節點。
eachName: (iterator, name = @name) ->
checkAssignabilityOfLiteral = (literal) ->
message = isUnassignable literal.value
if message
literal.error message
unless literal.isAssignable()
literal.error "'#{literal.value}' can't be assigned"
atParam = (obj, originalObj = null) => iterator "@#{obj.properties[0].name.value}", obj, @, originalObj
if name instanceof Call
name.error "Function invocation can't be assigned"
foo
if name instanceof Literal
checkAssignabilityOfLiteral name
return iterator name.value, name, @
@foo
return atParam name if name instanceof Value
for obj in name.objects ? []
儲存原始 obj。
nObj = obj
if obj instanceof Assign and not obj.context?
obj = obj.variable
{foo:bar}
if obj instanceof Assign
… 可能具有預設值
if obj.value instanceof Assign
obj = obj.value.variable
else
obj = obj.value
@eachName iterator, obj.unwrap()
[xs...]
else if obj instanceof Splat
node = obj.name.unwrap()
iterator node.value, node, @
else if obj instanceof Value
[{a}]
if obj.isArray() or obj.isObject()
@eachName iterator, obj.base
{@foo}
else if obj.this
atParam obj, nObj
else
checkAssignabilityOfLiteral obj.base
iterator obj.base.value, obj.base, @
else if obj instanceof Elision
obj
else if obj not instanceof Expansion
obj.error "illegal parameter #{obj.compile()}"
return
透過將給定的 AST 節點替換為一個名稱的新節點,來重新命名參數。這需要確保物件解構的來源不會改變。
renameParam: (node, newNode) ->
isNode = (candidate) -> candidate is node
replacement = (node, parent) =>
if parent instanceof Obj
key = node
key = node.properties[0].name if node.this
如果變數尚未保留,則不需要為解構變數指定新變數。範例:({@foo}) ->
應編譯為 ({foo}) { this.foo = foo}
foo = 1; ({@foo}) ->
應編譯為 foo = 1; ({foo:foo1}) { this.foo = foo1 }
if node.this and key.value is newNode.value
new Value newNode
else
new Assign new Value(key), newNode, 'object'
else
newNode
@replaceInContext isNode, replacement
展開,作為函數參數、呼叫參數或解構賦值的一部分。
exports.Splat = class Splat extends Base
constructor: (name, {@lhs, @postfix = true} = {}) ->
super()
@name = if name.compile then name else new Literal name
children: ['name']
shouldCache: -> no
isAssignable: ({allowComplexSplat = no} = {})->
return allowComplexSplat if @name instanceof Obj or @name instanceof Parens
@name.isAssignable() and (not @name.isAtomic or @name.isAtomic())
assigns: (name) ->
@name.assigns name
compileNode: (o) ->
compiledSplat = [@makeCode('...'), @name.compileToFragments(o, LEVEL_OP)...]
return compiledSplat unless @jsx
return [@makeCode('{'), compiledSplat..., @makeCode('}')]
unwrap: -> @name
propagateLhs: (setLhs) ->
@lhs = yes if setLhs
return unless @lhs
@name.propagateLhs? yes
astType: ->
if @jsx
'JSXSpreadAttribute'
else if @lhs
'RestElement'
else
'SpreadElement'
astProperties: (o) -> {
argument: @name.ast o, LEVEL_OP
@postfix
}
用於略過陣列解構(模式比對)或參數清單中的值。
exports.Expansion = class Expansion extends Base
shouldCache: NO
compileNode: (o) ->
@throwLhsError()
asReference: (o) ->
this
eachName: (iterator) ->
throwLhsError: ->
@error 'Expansion must be used inside a destructuring assignment or parameter list'
astNode: (o) ->
unless @lhs
@throwLhsError()
super o
astType: -> 'RestElement'
astProperties: ->
return
argument: null
陣列省略元素(例如 [,a, , , b, , c, ,])。
exports.Elision = class Elision extends Base
isAssignable: YES
shouldCache: NO
compileToFragments: (o, level) ->
fragment = super o, level
fragment.isElision = yes
fragment
compileNode: (o) ->
[@makeCode ', ']
asReference: (o) ->
this
eachName: (iterator) ->
astNode: ->
null
While 迴圈,是 CoffeeScript 公開的唯一一種低階迴圈。透過此迴圈,可以製造所有其他迴圈。在需要比理解更靈活或更快的案例中很有用。
exports.While = class While extends Base
constructor: (@condition, {invert: @inverted, @guard, @isLoop} = {}) ->
super()
children: ['condition', 'guard', 'body']
isStatement: YES
makeReturn: (results, mark) ->
return super(results, mark) if results
@returns = not @jumps()
if mark
@body.makeReturn(results, mark) if @returns
return
this
addBody: (@body) ->
this
jumps: ->
{expressions} = @body
return no unless expressions.length
for node in expressions
return jumpNode if jumpNode = node.jumps loop: yes
no
與 JavaScript while 的主要差異在於,CoffeeScript while 可用作較大表達式的一部分 – while 迴圈可能會傳回包含每個反覆運算結果的陣列。
compileNode: (o) ->
o.indent += TAB
set = ''
{body} = this
if body.isEmpty()
body = @makeCode ''
else
if @returns
body.makeReturn rvar = o.scope.freeVariable 'results'
set = "#{@tab}#{rvar} = [];\n"
if @guard
if body.expressions.length > 1
body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
else
body = Block.wrap [new If @guard, body] if @guard
body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
answer = [].concat @makeCode(set + @tab + "while ("), @processedCondition().compileToFragments(o, LEVEL_PAREN),
@makeCode(") {"), body, @makeCode("}")
if @returns
answer.push @makeCode "\n#{@tab}return #{rvar};"
answer
processedCondition: ->
@processedConditionCache ?= if @inverted then @condition.invert() else @condition
astType: -> 'WhileStatement'
astProperties: (o) ->
return
test: @condition.ast o, LEVEL_PAREN
body: @body.ast o, LEVEL_TOP
guard: @guard?.ast(o) ? null
inverted: !!@inverted
postfix: !!@postfix
loop: !!@isLoop
簡單的算術和邏輯運算。將一些 CoffeeScript 運算轉換為其 JavaScript 等效項。
exports.Op = class Op extends Base
constructor: (op, first, second, flip, {@invertOperator, @originalOperator = op} = {}) ->
super()
if op is 'new'
if ((firstCall = unwrapped = first.unwrap()) instanceof Call or (firstCall = unwrapped.base) instanceof Call) and not firstCall.do and not firstCall.isNew
return new Value firstCall.newInstance(), if firstCall is unwrapped then [] else unwrapped.properties
first = new Parens first unless first instanceof Parens or first.unwrap() instanceof IdentifierLiteral or first.hasProperties?()
call = new Call first, []
call.locationData = @locationData
call.isNew = yes
return call
@operator = CONVERSIONS[op] or op
@first = first
@second = second
@flip = !!flip
if @operator in ['--', '++']
message = isUnassignable @first.unwrapAll().value
@first.error message if message
return this
從 CoffeeScript 轉換為 JavaScript 符號的對應表。
CONVERSIONS =
'==': '==='
'!=': '!=='
'of': 'in'
'yieldfrom': 'yield*'
可逆運算子的對應表。
INVERSIONS =
'!==': '==='
'===': '!=='
children: ['first', 'second']
isNumber: ->
@isUnary() and @operator in ['+', '-'] and
@first instanceof Value and @first.isNumber()
isAwait: ->
@operator is 'await'
isYield: ->
@operator in ['yield', 'yield*']
isUnary: ->
not @second
shouldCache: ->
not @isNumber()
我有能力進行 Python 樣式的比較串接 嗎?
isChainable: ->
@operator in ['<', '>', '>=', '<=', '===', '!==']
isChain: ->
@isChainable() and @first.isChainable()
invert: ->
if @isInOperator()
@invertOperator = '!'
return @
if @isChain()
allInvertable = yes
curr = this
while curr and curr.operator
allInvertable and= (curr.operator of INVERSIONS)
curr = curr.first
return new Parens(this).invert() unless allInvertable
curr = this
while curr and curr.operator
curr.invert = !curr.invert
curr.operator = INVERSIONS[curr.operator]
curr = curr.first
this
else if op = INVERSIONS[@operator]
@operator = op
if @first.unwrap() instanceof Op
@first.invert()
this
else if @second
new Parens(this).invert()
else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
fst.operator in ['!', 'in', 'instanceof']
fst
else
new Op '!', this
unfoldSoak: (o) ->
@operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
generateDo: (exp) ->
passedParams = []
func = if exp instanceof Assign and (ref = exp.value.unwrap()) instanceof Code
ref
else
exp
for param in func.params or []
if param.value
passedParams.push param.value
delete param.value
else
passedParams.push param
call = new Call exp, passedParams
call.do = yes
call
isInOperator: ->
@originalOperator is 'in'
compileNode: (o) ->
if @isInOperator()
inNode = new In @first, @second
return (if @invertOperator then inNode.invert() else inNode).compileNode o
if @invertOperator
@invertOperator = null
return @invert().compileNode(o)
return Op::generateDo(@first).compileNode o if @operator is 'do'
isChain = @isChain()
在串接中,不需要將單獨的 obj 文字包在括號中,因為串接表達式已包覆。
@first.front = @front unless isChain
@checkDeleteOperand o
return @compileContinuation o if @isYield() or @isAwait()
return @compileUnary o if @isUnary()
return @compileChain o if isChain
switch @operator
when '?' then @compileExistence o, @second.isDefaultValue
when '//' then @compileFloorDivision o
when '%%' then @compileModulo o
else
lhs = @first.compileToFragments o, LEVEL_OP
rhs = @second.compileToFragments o, LEVEL_OP
answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
if o.level <= LEVEL_OP then answer else @wrapInParentheses answer
compileChain: (o) ->
[@first.second, shared] = @first.second.cache o
fst = @first.compileToFragments o, LEVEL_OP
fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
(shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP)
@wrapInParentheses fragments
保留對左表達式的參照,除非這是存在賦值
compileExistence: (o, checkOnlyUndefined) ->
if @first.shouldCache()
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
fst = new Parens new Assign ref, @first
else
fst = @first
ref = fst
new If(new Existence(fst, checkOnlyUndefined), ref, type: 'if').addElse(@second).compileToFragments o
編譯一元Op。
compileUnary: (o) ->
parts = []
op = @operator
parts.push [@makeCode op]
if op is '!' and @first instanceof Existence
@first.negated = not @first.negated
return @first.compileToFragments o
if o.level >= LEVEL_ACCESS
return (new Parens this).compileToFragments o
plusMinus = op in ['+', '-']
parts.push [@makeCode(' ')] if op in ['typeof', 'delete'] or
plusMinus and @first instanceof Op and @first.operator is op
if plusMinus and @first instanceof Op
@first = new Parens @first
parts.push @first.compileToFragments o, LEVEL_OP
parts.reverse() if @flip
@joinFragmentArrays parts, ''
compileContinuation: (o) ->
parts = []
op = @operator
@checkContinuation o unless @isAwait()
if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
else
parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
parts.push [@makeCode op]
parts.push [@makeCode " "] if @first.base?.value isnt ''
parts.push @first.compileToFragments o, LEVEL_OP
parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN
@joinFragmentArrays parts, ''
checkContinuation: (o) ->
unless o.scope.parent?
@error "#{@operator} can only occur inside functions"
if o.scope.method?.bound and o.scope.method.isGenerator
@error 'yield cannot occur inside bound (fat arrow) functions'
compileFloorDivision: (o) ->
floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
second = if @second.shouldCache() then new Parens @second else @second
div = new Op '/', @first, second
new Call(floor, [div]).compileToFragments o
compileModulo: (o) ->
mod = new Value new Literal utility 'modulo', o
new Call(mod, [@first, @second]).compileToFragments o
toString: (idt) ->
super idt, @constructor.name + ' ' + @operator
checkDeleteOperand: (o) ->
if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
@error 'delete operand may not be argument or var'
astNode: (o) ->
@checkContinuation o if @isYield()
@checkDeleteOperand o
super o
astType: ->
return 'AwaitExpression' if @isAwait()
return 'YieldExpression' if @isYield()
return 'ChainedComparison' if @isChain()
switch @operator
when '||', '&&', '?' then 'LogicalExpression'
when '++', '--' then 'UpdateExpression'
else
if @isUnary() then 'UnaryExpression'
else 'BinaryExpression'
operatorAst: ->
"#{if @invertOperator then "#{@invertOperator} " else ''}#{@originalOperator}"
chainAstProperties: (o) ->
operators = [@operatorAst()]
operands = [@second]
currentOp = @first
loop
operators.unshift currentOp.operatorAst()
operands.unshift currentOp.second
currentOp = currentOp.first
unless currentOp.isChainable()
operands.unshift currentOp
break
return {
operators
operands: (operand.ast(o, LEVEL_OP) for operand in operands)
}
astProperties: (o) ->
return @chainAstProperties(o) if @isChain()
firstAst = @first.ast o, LEVEL_OP
secondAst = @second?.ast o, LEVEL_OP
operatorAst = @operatorAst()
switch
when @isUnary()
argument =
if @isYield() and @first.unwrap().value is ''
null
else
firstAst
return {argument} if @isAwait()
return {
argument
delegate: @operator is 'yield*'
} if @isYield()
return {
argument
operator: operatorAst
prefix: !@flip
}
else
return
left: firstAst
right: secondAst
operator: operatorAst
exports.In = class In extends Base
constructor: (@object, @array) ->
super()
children: ['object', 'array']
invert: NEGATE
compileNode: (o) ->
if @array instanceof Value and @array.isArray() and @array.base.objects.length
for obj in @array.base.objects when obj instanceof Splat
hasSplat = yes
break
compileOrTest
僅在我們有一個沒有展開運算子的陣列文字時
return @compileOrTest o unless hasSplat
@compileLoopTest o
compileOrTest: (o) ->
[sub, ref] = @object.cache o, LEVEL_OP
[cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
tests = []
for item, i in @array.base.objects
if i then tests.push @makeCode cnj
tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
if o.level < LEVEL_OP then tests else @wrapInParentheses tests
compileLoopTest: (o) ->
[sub, ref] = @object.cache o, LEVEL_LIST
fragments = [].concat @makeCode(utility('indexOf', o) + ".call("), @array.compileToFragments(o, LEVEL_LIST),
@makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0')
return fragments if fragmentsToText(sub) is fragmentsToText(ref)
fragments = sub.concat @makeCode(', '), fragments
if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments
toString: (idt) ->
super idt, @constructor.name + if @negated then '!' else ''
一個經典的try/catch/finally 區塊。
exports.Try = class Try extends Base
constructor: (@attempt, @catch, @ensure, @finallyTag) ->
super()
children: ['attempt', 'catch', 'ensure']
isStatement: YES
jumps: (o) -> @attempt.jumps(o) or @catch?.jumps(o)
makeReturn: (results, mark) ->
if mark
@attempt?.makeReturn results, mark
@catch?.makeReturn results, mark
return
@attempt = @attempt.makeReturn results if @attempt
@catch = @catch .makeReturn results if @catch
this
編譯大致上與你預期的相同 – finally 子句是可選的,catch 則不是。
compileNode: (o) ->
originalIndent = o.indent
o.indent += TAB
tryPart = @attempt.compileToFragments o, LEVEL_TOP
catchPart = if @catch
@catch.compileToFragments merge(o, indent: originalIndent), LEVEL_TOP
else unless @ensure or @catch
generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
[@makeCode(" catch (#{generatedErrorVariableName}) {}")]
else
[]
ensurePart = if @ensure then ([].concat @makeCode(" finally {\n"), @ensure.compileToFragments(o, LEVEL_TOP),
@makeCode("\n#{@tab}}")) else []
[].concat @makeCode("#{@tab}try {\n"),
tryPart,
@makeCode("\n#{@tab}}"), catchPart, ensurePart
astType: -> 'TryStatement'
astProperties: (o) ->
return
block: @attempt.ast o, LEVEL_TOP
handler: @catch?.ast(o) ? null
finalizer:
if @ensure?
Object.assign @ensure.ast(o, LEVEL_TOP),
在位置資料中包含 finally
關鍵字。
mergeAstLocationData(
jisonLocationDataToAstLocationData(@finallyTag.locationData),
@ensure.astLocationData()
)
else
null
exports.Catch = class Catch extends Base
constructor: (@recovery, @errorVariable) ->
super()
@errorVariable?.unwrap().propagateLhs? yes
children: ['recovery', 'errorVariable']
isStatement: YES
jumps: (o) -> @recovery.jumps o
makeReturn: (results, mark) ->
ret = @recovery.makeReturn results, mark
return if mark
@recovery = ret
this
compileNode: (o) ->
o.indent += TAB
generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
placeholder = new IdentifierLiteral generatedErrorVariableName
@checkUnassignable()
if @errorVariable
@recovery.unshift new Assign @errorVariable, placeholder
[].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
@recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
checkUnassignable: ->
if @errorVariable
message = isUnassignable @errorVariable.unwrapAll().value
@errorVariable.error message if message
astNode: (o) ->
@checkUnassignable()
@errorVariable?.eachName (name) ->
alreadyDeclared = o.scope.find name.value
name.isDeclaration = not alreadyDeclared
super o
astType: -> 'CatchClause'
astProperties: (o) ->
return
param: @errorVariable?.ast(o) ? null
body: @recovery.ast o, LEVEL_TOP
拋出例外狀況的簡單節點。
exports.Throw = class Throw extends Base
constructor: (@expression) ->
super()
children: ['expression']
isStatement: YES
jumps: NO
Throw 已經是一種回傳,某種程度上來說…
makeReturn: THIS
compileNode: (o) ->
fragments = @expression.compileToFragments o, LEVEL_LIST
unshiftAfterComments fragments, @makeCode 'throw '
fragments.unshift @makeCode @tab
fragments.push @makeCode ';'
fragments
astType: -> 'ThrowStatement'
astProperties: (o) ->
return
argument: @expression.ast o, LEVEL_LIST
檢查一個變數是否存在 – 不是 null
也不是 undefined
。這類似於 Ruby 中的 .nil?
,而且避免必須查閱 JavaScript 真值表。選擇性地僅檢查一個變數是否不是 undefined
。
exports.Existence = class Existence extends Base
constructor: (@expression, onlyNotUndefined = no) ->
super()
@comparisonTarget = if onlyNotUndefined then 'undefined' else 'null'
salvagedComments = []
@expression.traverseChildren yes, (child) ->
if child.comments
for comment in child.comments
salvagedComments.push comment unless comment in salvagedComments
delete child.comments
attachCommentsToNode salvagedComments, @
moveComments @expression, @
children: ['expression']
invert: NEGATE
compileNode: (o) ->
@expression.front = @front
code = @expression.compile o, LEVEL_OP
if @expression.unwrap() instanceof IdentifierLiteral and not o.scope.check code
[cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
code = "typeof #{code} #{cmp} \"undefined\"" + if @comparisonTarget isnt 'undefined' then " #{cnj} #{code} #{cmp} #{@comparisonTarget}" else ''
else
當與 null
比較時,我們明確地想要使用寬鬆相等(==
),以便存在檢查大致對應於真值檢查。不要 將此變更為 null
的 ===
,因為這將會破壞大量現有程式碼。然而,當僅與 undefined
比較時,我們想要使用 ===
,因為此使用案例是為了與 ES2015+ 預設值相容,而預設值僅在變數為 undefined
(但不是 null
)時才會被指定。
cmp = if @comparisonTarget is 'null'
if @negated then '==' else '!='
else # `undefined`
if @negated then '===' else '!=='
code = "#{code} #{cmp} #{@comparisonTarget}"
[@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
astType: -> 'UnaryExpression'
astProperties: (o) ->
return
argument: @expression.ast o
operator: '?'
prefix: no
exports.Parens = class Parens extends Base
constructor: (@body) ->
super()
children: ['body']
unwrap: -> @body
shouldCache: -> @body.shouldCache()
compileNode: (o) ->
expr = @body.unwrap()
如果這些括號包住一個 IdentifierLiteral
後面接著區塊註解,請輸出括號(或換句話說,不要最佳化移除這些多餘的括號)。這是因為 Flow 在某些情況下需要括號,以區分後面接著基於註解的類型註解的識別碼與 JavaScript 標籤。
shouldWrapComment = expr.comments?.some(
(comment) -> comment.here and not comment.unshift and not comment.newLine)
if expr instanceof Value and expr.isAtomic() and not @jsxAttribute and not shouldWrapComment
expr.front = @front
return expr.compileToFragments o
fragments = expr.compileToFragments o, LEVEL_PAREN
bare = o.level < LEVEL_OP and not shouldWrapComment and (
expr instanceof Op and not expr.isInOperator() or expr.unwrap() instanceof Call or
(expr instanceof For and expr.returns)
) and (o.level < LEVEL_COND or fragments.length <= 3)
return @wrapInBraces fragments if @jsxAttribute
if bare then fragments else @wrapInParentheses fragments
astNode: (o) -> @body.unwrap().ast o, LEVEL_PAREN
exports.StringWithInterpolations = class StringWithInterpolations extends Base
constructor: (@body, {@quote, @startQuote, @jsxAttribute} = {}) ->
super()
@fromStringLiteral: (stringLiteral) ->
updatedString = stringLiteral.withoutQuotesInLocationData()
updatedStringValue = new Value(updatedString).withLocationDataFrom updatedString
new StringWithInterpolations Block.wrap([updatedStringValue]), quote: stringLiteral.quote, jsxAttribute: stringLiteral.jsxAttribute
.withLocationDataFrom stringLiteral
children: ['body']
unwrap
傳回 this
以停止祖先節點深入擷取 @body,並使用 @body.compileNode。StringWithInterpolations.compileNode
是輸出插補字串為程式碼的自訂邏輯。
unwrap: -> this
shouldCache: -> @body.shouldCache()
extractElements: (o, {includeInterpolationWrappers, isJsx} = {}) ->
假設 expr
是 Block
expr = @body.unwrap()
elements = []
salvagedComments = []
expr.traverseChildren no, (node) =>
if node instanceof StringLiteral
if node.comments
salvagedComments.push node.comments...
delete node.comments
elements.push node
return yes
else if node instanceof Interpolation
if salvagedComments.length isnt 0
for comment in salvagedComments
comment.unshift = yes
comment.newLine = yes
attachCommentsToNode salvagedComments, node
if (unwrapped = node.expression?.unwrapAll()) instanceof PassthroughLiteral and unwrapped.generated and not (isJsx and o.compiling)
if o.compiling
commentPlaceholder = new StringLiteral('').withLocationDataFrom node
commentPlaceholder.comments = unwrapped.comments
(commentPlaceholder.comments ?= []).push node.comments... if node.comments
elements.push new Value commentPlaceholder
else
empty = new Interpolation().withLocationDataFrom node
empty.comments = node.comments
elements.push empty
else if node.expression or includeInterpolationWrappers
(node.expression?.comments ?= []).push node.comments... if node.comments
elements.push if includeInterpolationWrappers then node else node.expression
return no
else if node.comments
這個節點將會被捨棄,但保留它的註解。
if elements.length isnt 0 and elements[elements.length - 1] not instanceof StringLiteral
for comment in node.comments
comment.unshift = no
comment.newLine = yes
attachCommentsToNode node.comments, elements[elements.length - 1]
else
salvagedComments.push node.comments...
delete node.comments
return yes
elements
compileNode: (o) ->
@comments ?= @startQuote?.comments
if @jsxAttribute
wrapped = new Parens new StringWithInterpolations @body
wrapped.jsxAttribute = yes
return wrapped.compileNode o
elements = @extractElements o, isJsx: @jsx
fragments = []
fragments.push @makeCode '`' unless @jsx
for element in elements
if element instanceof StringLiteral
unquotedElementValue = if @jsx then element.unquotedValueForJSX else element.unquotedValueForTemplateLiteral
fragments.push @makeCode unquotedElementValue
else
fragments.push @makeCode '$' unless @jsx
code = element.compileToFragments(o, LEVEL_PAREN)
if not @isNestedTag(element) or
code.some((fragment) -> fragment.comments?.some((comment) -> comment.here is no))
code = @wrapInBraces code
將 {
和 }
片段標記為由這個 StringWithInterpolations
節點產生,這樣 compileComments
才知道將它們視為邊界。但如果所有封閉的註解都是 /* */
註解,則大括號是不必要的。不要相信 fragment.type
,它會在這個編譯器縮小時回報縮小的變數名稱。
code[0].isStringWithInterpolations = yes
code[code.length - 1].isStringWithInterpolations = yes
fragments.push code...
fragments.push @makeCode '`' unless @jsx
fragments
isNestedTag: (element) ->
call = element.unwrapAll?()
@jsx and call instanceof JSXElement
astType: -> 'TemplateLiteral'
astProperties: (o) ->
elements = @extractElements o, includeInterpolationWrappers: yes
[..., last] = elements
quasis = []
expressions = []
for element, index in elements
if element instanceof StringLiteral
quasis.push new TemplateElement(
element.originalValue
tail: element is last
).withLocationDataFrom(element).ast o
else # Interpolation
{expression} = element
node =
unless expression?
emptyInterpolation = new EmptyInterpolation()
emptyInterpolation.locationData = emptyExpressionLocationData {
interpolationNode: element
openingBrace: '#{'
closingBrace: '}'
}
emptyInterpolation
else
expression.unwrapAll()
expressions.push astAsBlockIfNeeded node, o
{expressions, quasis, @quote}
exports.TemplateElement = class TemplateElement extends Base
constructor: (@value, {@tail} = {}) ->
super()
astProperties: ->
return
value:
raw: @value
tail: !!@tail
exports.Interpolation = class Interpolation extends Base
constructor: (@expression) ->
super()
children: ['expression']
表示空插補的內容(例如 #{}
)。僅在 AST 產生期間使用。
exports.EmptyInterpolation = class EmptyInterpolation extends Base
constructor: ->
super()
CoffeeScript 取代 for 迴圈的是陣列和物件理解,它們在此編譯成 for 迴圈。它們也作為一個表達式,能夠傳回每個過濾反覆運算的結果。
與 Python 陣列理解不同,它們可以是多行的,而且你可以傳遞迴圈的目前索引作為第二個參數。與 Ruby 區塊不同,你可以在單一傳遞中對應和過濾。
exports.For = class For extends While
constructor: (body, source) ->
super()
@addBody body
@addSource source
children: ['body', 'source', 'guard', 'step']
isAwait: -> @await ? no
addBody: (body) ->
@body = Block.wrap [body]
{expressions} = @body
if expressions.length
@body.locationData ?= mergeLocationData expressions[0].locationData, expressions[expressions.length - 1].locationData
this
addSource: (source) ->
{@source = no} = source
attribs = ["name", "index", "guard", "step", "own", "ownTag", "await", "awaitTag", "object", "from"]
@[attr] = source[attr] ? @[attr] for attr in attribs
return this unless @source
@index.error 'cannot use index with for-from' if @from and @index
@ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object
[@name, @index] = [@index, @name] if @object
@index.error 'index cannot be a pattern matching expression' if @index?.isArray?() or @index?.isObject?()
@awaitTag.error 'await must be used with for-from' if @await and not @from
@range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from
@pattern = @name instanceof Value
@name.unwrap().propagateLhs?(yes) if @pattern
@index.error 'indexes do not apply to range loops' if @range and @index
@name.error 'cannot pattern match over range loops' if @range and @pattern
@returns = no
將「for
行」中的任何註解往上移動,也就是有 for
的程式碼行,從該行的任何子節點往上移動到 for
節點本身,以便輸出這些註解,並在 for
迴圈上方輸出。
for attribute in ['source', 'guard', 'step', 'name', 'index'] when @[attribute]
@[attribute].traverseChildren yes, (node) =>
if node.comments
這些註解埋得很深,所以如果它們碰巧是尾隨註解,當我們完成編譯這個 for
迴圈時,它們尾隨的行將無法辨識;所以只要將它們往上移動到 for
行上方輸出即可。
comment.newLine = comment.unshift = yes for comment in node.comments
moveComments node, @[attribute]
moveComments @[attribute], @
this
歡迎來到 CoffeeScript 中最複雜的方法。處理陣列、物件和範圍理解的內部迴圈、過濾、遞增和結果儲存。部分產生的程式碼可以共用,而有些則不能。
compileNode: (o) ->
body = Block.wrap [@body]
[..., last] = body.expressions
@returns = no if last?.jumps() instanceof Return
source = if @range then @source.base else @source
scope = o.scope
name = @name and (@name.compile o, LEVEL_LIST) if not @pattern
index = @index and (@index.compile o, LEVEL_LIST)
scope.find(name) if name and not @pattern
scope.find(index) if index and @index not instanceof Value
rvar = scope.freeVariable 'results' if @returns
if @from
ivar = scope.freeVariable 'x', single: true if @pattern
else
ivar = (@object and index) or scope.freeVariable 'i', single: true
kvar = ((@range or @from) and name) or index or ivar
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
if @step and not @range
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, shouldCacheOrIsAssignable
stepNum = parseNumber stepVar if @step.isNumber()
name = ivar if @pattern
varPart = ''
guardPart = ''
defPart = ''
idt1 = @tab + TAB
if @range
forPartFragments = source.compileToFragments merge o,
{index: ivar, name, @step, shouldCache: shouldCacheOrIsAssignable}
else
svar = @source.compile o, LEVEL_LIST
if (name or @own) and not @from and @source.unwrap() not instanceof IdentifierLiteral
defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
if name and not @pattern and not @from
namePart = "#{name} = #{svar}[#{kvar}]"
if not @object and not @from
defPart += "#{@tab}#{step};\n" if step isnt stepVar
down = stepNum < 0
lvar = scope.freeVariable 'len' unless @step and stepNum? and down
declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
compare = "#{ivar} < #{lvar}"
compareDown = "#{ivar} >= 0"
if @step
if stepNum?
if down
compare = compareDown
declare = declareDown
else
compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
increment = "#{ivar} += #{stepVar}"
else
increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
if @returns
resultPart = "#{@tab}#{rvar} = [];\n"
returnResult = "\n#{@tab}return #{rvar};"
body.makeReturn rvar
if @guard
if body.expressions.length > 1
body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
else
body = Block.wrap [new If @guard, body] if @guard
if @pattern
body.expressions.unshift new Assign @name, if @from then new IdentifierLiteral kvar else new Literal "#{svar}[#{kvar}]"
varPart = "\n#{idt1}#{namePart};" if namePart
if @object
forPartFragments = [@makeCode("#{kvar} in #{svar}")]
guardPart = "\n#{idt1}if (!#{utility 'hasProp', o}.call(#{svar}, #{kvar})) continue;" if @own
else if @from
if @await
forPartFragments = new Op 'await', new Parens new Literal "#{kvar} of #{svar}"
forPartFragments = forPartFragments.compileToFragments o, LEVEL_TOP
else
forPartFragments = [@makeCode("#{kvar} of #{svar}")]
bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
if bodyFragments and bodyFragments.length > 0
bodyFragments = [].concat @makeCode('\n'), bodyFragments, @makeCode('\n')
fragments = [@makeCode(defPart)]
fragments.push @makeCode(resultPart) if resultPart
forCode = if @await then 'for ' else 'for ('
forClose = if @await then '' else ')'
fragments = fragments.concat @makeCode(@tab), @makeCode( forCode),
forPartFragments, @makeCode("#{forClose} {#{guardPart}#{varPart}"), bodyFragments,
@makeCode(@tab), @makeCode('}')
fragments.push @makeCode(returnResult) if returnResult
fragments
astNode: (o) ->
addToScope = (name) ->
alreadyDeclared = o.scope.find name.value
name.isDeclaration = not alreadyDeclared
@name?.eachName addToScope, checkAssignability: no
@index?.eachName addToScope, checkAssignability: no
super o
astType: -> 'For'
astProperties: (o) ->
return
source: @source?.ast o
body: @body.ast o, LEVEL_TOP
guard: @guard?.ast(o) ? null
name: @name?.ast(o) ? null
index: @index?.ast(o) ? null
step: @step?.ast(o) ? null
postfix: !!@postfix
own: !!@own
await: !!@await
style: switch
when @from then 'from'
when @object then 'of'
when @name then 'in'
else 'range'
JavaScript switch 陳述式。依需求轉換成可傳回的表達式。
exports.Switch = class Switch extends Base
constructor: (@subject, @cases, @otherwise) ->
super()
children: ['subject', 'cases', 'otherwise']
isStatement: YES
jumps: (o = {block: yes}) ->
for {block} in @cases
return jumpNode if jumpNode = block.jumps o
@otherwise?.jumps o
makeReturn: (results, mark) ->
block.makeReturn(results, mark) for {block} in @cases
@otherwise or= new Block [new Literal 'void 0'] if results
@otherwise?.makeReturn results, mark
this
compileNode: (o) ->
idt1 = o.indent + TAB
idt2 = o.indent = idt1 + TAB
fragments = [].concat @makeCode(@tab + "switch ("),
(if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode "false"),
@makeCode(") {\n")
for {conditions, block}, i in @cases
for cond in flatten [conditions]
cond = cond.invert() unless @subject
fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n")
fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0
break if i is @cases.length - 1 and not @otherwise
expr = @lastNode block.expressions
continue if expr instanceof Return or expr instanceof Throw or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
fragments.push cond.makeCode(idt2 + 'break;\n')
if @otherwise and @otherwise.expressions.length
fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n")
fragments.push @makeCode @tab + '}'
fragments
astType: -> 'SwitchStatement'
casesAst: (o) ->
cases = []
for kase, caseIndex in @cases
{conditions: tests, block: consequent} = kase
tests = flatten [tests]
lastTestIndex = tests.length - 1
for test, testIndex in tests
testConsequent =
if testIndex is lastTestIndex
consequent
else
null
caseLocationData = test.locationData
caseLocationData = mergeLocationData caseLocationData, testConsequent.expressions[testConsequent.expressions.length - 1].locationData if testConsequent?.expressions.length
caseLocationData = mergeLocationData caseLocationData, kase.locationData, justLeading: yes if testIndex is 0
caseLocationData = mergeLocationData caseLocationData, kase.locationData, justEnding: yes if testIndex is lastTestIndex
cases.push new SwitchCase(test, testConsequent, trailing: testIndex is lastTestIndex).withLocationDataFrom locationData: caseLocationData
if @otherwise?.expressions.length
cases.push new SwitchCase(null, @otherwise).withLocationDataFrom @otherwise
kase.ast(o) for kase in cases
astProperties: (o) ->
return
discriminant: @subject?.ast(o, LEVEL_PAREN) ? null
cases: @casesAst o
class SwitchCase extends Base
constructor: (@test, @block, {@trailing} = {}) ->
super()
children: ['test', 'block']
astProperties: (o) ->
return
test: @test?.ast(o, LEVEL_PAREN) ? null
consequent: @block?.ast(o, LEVEL_TOP).body ? []
trailing: !!@trailing
exports.SwitchWhen = class SwitchWhen extends Base
constructor: (@conditions, @block) ->
super()
children: ['conditions', 'block']
exports.If = class If extends Base
constructor: (@condition, @body, options = {}) ->
super()
@elseBody = null
@isChain = false
{@soak, @postfix, @type} = options
moveComments @condition, @ if @condition.comments
children: ['condition', 'body', 'elseBody']
bodyNode: -> @body?.unwrap()
elseBodyNode: -> @elseBody?.unwrap()
改寫 If 條件式的鏈,將預設情況新增為最後的 else。
addElse: (elseBody) ->
if @isChain
@elseBodyNode().addElse elseBody
@locationData = mergeLocationData @locationData, @elseBodyNode().locationData
else
@isChain = elseBody instanceof If
@elseBody = @ensureBlock elseBody
@elseBody.updateLocationDataIfMissing elseBody.locationData
@locationData = mergeLocationData @locationData, @elseBody.locationData if @locationData? and @elseBody.locationData?
this
If 條件式只有在任一個主體需要是陳述式時才會編譯成陳述式。否則,條件運算子是安全的。
isStatement: (o) ->
o?.level is LEVEL_TOP or
@bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)
compileNode: (o) ->
if @isStatement o then @compileStatement o else @compileExpression o
makeReturn: (results, mark) ->
if mark
@body?.makeReturn results, mark
@elseBody?.makeReturn results, mark
return
@elseBody or= new Block [new Literal 'void 0'] if results
@body and= new Block [@body.makeReturn results]
@elseBody and= new Block [@elseBody.makeReturn results]
this
ensureBlock: (node) ->
if node instanceof Block then node else new Block [node]
將 If
編譯成一般的 if-else 陳述式。扁平化的鏈會強制內部的 else 主體變成陳述式形式。
compileStatement: (o) ->
child = del o, 'chainChild'
exeq = del o, 'isExistentialEquals'
if exeq
return new If(@processedCondition().invert(), @elseBodyNode(), type: 'if').compileToFragments o
indent = o.indent + TAB
cond = @processedCondition().compileToFragments o, LEVEL_PAREN
body = @ensureBlock(@body).compileToFragments merge o, {indent}
ifPart = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
ifPart.unshift @makeCode @tab unless child
return ifPart unless @elseBody
answer = ifPart.concat @makeCode(' else ')
if @isChain
o.chainChild = yes
answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP
else
answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), @makeCode("\n#{@tab}}")
answer
將 If
編譯成條件運算子。
compileExpression: (o) ->
cond = @processedCondition().compileToFragments o, LEVEL_COND
body = @bodyNode().compileToFragments o, LEVEL_LIST
alt = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
if o.level >= LEVEL_COND then @wrapInParentheses fragments else fragments
unfoldSoak: ->
@soak and this
processedCondition: ->
@processedConditionCache ?= if @type is 'unless' then @condition.invert() else @condition
isStatementAst: (o) ->
o.level is LEVEL_TOP
astType: (o) ->
if @isStatementAst o
'IfStatement'
else
'ConditionalExpression'
astProperties: (o) ->
isStatement = @isStatementAst o
return
test: @condition.ast o, if isStatement then LEVEL_PAREN else LEVEL_COND
consequent:
if isStatement
@body.ast o, LEVEL_TOP
else
@bodyNode().ast o, LEVEL_TOP
alternate:
if @isChain
@elseBody.unwrap().ast o, if isStatement then LEVEL_TOP else LEVEL_COND
else if not isStatement and @elseBody?.expressions?.length is 1
@elseBody.expressions[0].ast o, LEVEL_TOP
else
@elseBody?.ast(o, LEVEL_TOP) ? null
postfix: !!@postfix
inverted: @type is 'unless'
序列表達式,例如 (a; b)
。目前只在 AST 產生期間使用。
exports.Sequence = class Sequence extends Base
children: ['expressions']
constructor: (@expressions) ->
super()
astNode: (o) ->
return @expressions[0].ast(o) if @expressions.length is 1
super o
astType: -> 'SequenceExpression'
astProperties: (o) ->
return
expressions:
expression.ast(o) for expression in @expressions
UTILITIES =
modulo: -> 'function(a, b) { return (+a % (b = +b) + b) % b; }'
boundMethodCheck: -> "
function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new Error('Bound instance method accessed before binding');
}
}
"
捷徑,用於加快原生函式的查詢時間。
hasProp: -> '{}.hasOwnProperty'
indexOf: -> '[].indexOf'
slice : -> '[].slice'
splice : -> '[].splice'
層級表示節點在 AST 中的位置。對於了解括號是否必要或多餘很有用。
LEVEL_TOP = 1 # ...;
LEVEL_PAREN = 2 # (...)
LEVEL_LIST = 3 # [...]
LEVEL_COND = 4 # ... ? x : y
LEVEL_OP = 5 # !...
LEVEL_ACCESS = 6 # ...[0]
縮排為兩個空格,以利美化列印。
TAB = ' '
SIMPLENUM = /^[+-]?\d+(?:_\d+)*$/
SIMPLE_STRING_OMIT = /\s*\n\s*/g
LEADING_BLANK_LINE = /^[^\n\S]*\n/
TRAILING_BLANK_LINE = /\n[^\n\S]*$/
STRING_OMIT = ///
((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
| \\[^\S\n]*\n\s* # Remove escaped newlines.
///g
HEREGEX_OMIT = ///
((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
| \\(\s) # Preserve escaped whitespace.
| \s+(?:#.*)? # Remove whitespace and comments.
///g
輔助函式,用於確保實用函式在頂層指派。
utility = (name, o) ->
{root} = o.scope
if name of root.utilities
root.utilities[name]
else
ref = root.freeVariable name
root.assign ref, UTILITIES[name] o
root.utilities[name] = ref
multident = (code, tab, includingFirstLine = yes) ->
endsWithNewLine = code[code.length - 1] is '\n'
code = (if includingFirstLine then tab else '') + code.replace /\n/g, "$&#{tab}"
code = code.replace /\s+$/, ''
code = code + '\n' if endsWithNewLine
code
在 CoffeeScript 1 中,我們可能會插入 makeCode "#{@tab}"
來縮排一行程式碼,現在我們必須考慮程式碼行前面可能有註解的可能性。如果有此類註解,請縮排此類註解的每一行,然後縮排第一行後續程式碼。
indentInitial = (fragments, node) ->
for fragment, fragmentIndex in fragments
if fragment.isHereComment
fragment.code = multident fragment.code, node.tab
else
fragments.splice fragmentIndex, 0, node.makeCode "#{node.tab}"
break
fragments
hasLineComments = (node) ->
return no unless node.comments
for comment in node.comments
return yes if comment.here is no
return no
將 comments
屬性從一個物件移到另一個物件,並從第一個物件中刪除它。
moveComments = (from, to) ->
return unless from?.comments
attachCommentsToNode from.comments, to
delete from.comments
有時在編譯節點時,我們希望在片段陣列的開頭插入一個片段;但如果開頭有一個或多個註解片段,我們希望在這些片段之後但在任何非註解片段之前插入此片段。
unshiftAfterComments = (fragments, fragmentToInsert) ->
inserted = no
for fragment, fragmentIndex in fragments when not fragment.isComment
fragments.splice fragmentIndex, 0, fragmentToInsert
inserted = yes
break
fragments.push fragmentToInsert unless inserted
fragments
isLiteralArguments = (node) ->
node instanceof IdentifierLiteral and node.value is 'arguments'
isLiteralThis = (node) ->
node instanceof ThisLiteral or (node instanceof Code and node.bound)
shouldCacheOrIsAssignable = (node) -> node.shouldCache() or node.isAssignable?()
如果浸泡,展開節點的子節點,然後將節點塞入已建立的 If
unfoldSoak = (o, parent, name) ->
return unless ifn = parent[name].unfoldSoak o
parent[name] = ifn.body
ifn.body = new Value parent
ifn
透過跳脫某些字元來建構字串或正規表示式。
makeDelimitedLiteral = (body, {delimiter: delimiterOption, escapeNewlines, double, includeDelimiters = yes, escapeDelimiter = yes, convertTrailingNullEscapes} = {}) ->
body = '(?:)' if body is '' and delimiterOption is '/'
escapeTemplateLiteralCurlies = delimiterOption is '`'
regex = ///
(\\\\) # Escaped backslash.
| (\\0(?=\d)) # Null character mistaken as octal escape.
#{
if convertTrailingNullEscapes
/// | (\\0) $ ///.source # Trailing null character that could be mistaken as octal escape.
else
''
}
#{
if escapeDelimiter
/// | \\?(#{delimiterOption}) ///.source # (Possibly escaped) delimiter.
else
''
}
#{
if escapeTemplateLiteralCurlies
/// | \\?(\$\{) ///.source # `${` inside template literals must be escaped.
else
''
}
| \\?(?:
#{if escapeNewlines then '(\n)|' else ''}
(\r)
| (\u2028)
| (\u2029)
) # (Possibly escaped) newlines.
| (\\.) # Other escapes.
///g
body = body.replace regex, (match, backslash, nul, ...args) ->
trailingNullEscape =
args.shift() if convertTrailingNullEscapes
delimiter =
args.shift() if escapeDelimiter
templateLiteralCurly =
args.shift() if escapeTemplateLiteralCurlies
lf =
args.shift() if escapeNewlines
[cr, ls, ps, other] = args
switch
忽略跳脫的反斜線。
when backslash then (if double then backslash + backslash else backslash)
when nul then '\\x00'
when trailingNullEscape then "\\x00"
when delimiter then "\\#{delimiter}"
when templateLiteralCurly then "\\${"
when lf then '\\n'
when cr then '\\r'
when ls then '\\u2028'
when ps then '\\u2029'
when other then (if double then "\\#{other}" else other)
printedDelimiter = if includeDelimiters then delimiterOption else ''
"#{printedDelimiter}#{body}#{printedDelimiter}"
sniffDirectives = (expressions, {notFinalExpression} = {}) ->
index = 0
lastIndex = expressions.length - 1
while index <= lastIndex
break if index is lastIndex and notFinalExpression
expression = expressions[index]
if (unwrapped = expression?.unwrap?()) instanceof PassthroughLiteral and unwrapped.generated
index++
continue
break unless expression instanceof Value and expression.isString() and not expression.unwrap().shouldGenerateTemplateLiteral()
expressions[index] =
new Directive expression
.withLocationDataFrom expression
index++
astAsBlockIfNeeded = (node, o) ->
unwrapped = node.unwrap()
if unwrapped instanceof Block and unwrapped.expressions.length > 1
unwrapped.makeReturn null, yes
unwrapped.ast o, LEVEL_TOP
else
node.ast o, LEVEL_PAREN
以下是 mergeLocationData
和 mergeAstLocationData
的輔助程式。
lesser = (a, b) -> if a < b then a else b
greater = (a, b) -> if a > b then a else b
isAstLocGreater = (a, b) ->
return yes if a.line > b.line
return no unless a.line is b.line
a.column > b.column
isLocationDataStartGreater = (a, b) ->
return yes if a.first_line > b.first_line
return no unless a.first_line is b.first_line
a.first_column > b.first_column
isLocationDataEndGreater = (a, b) ->
return yes if a.last_line > b.last_line
return no unless a.last_line is b.last_line
a.last_column > b.last_column
取得兩個節點的位置資料,並傳回一個新的 locationData
物件,其中包含兩個節點的位置資料。因此新的 first_line
值將是兩個節點的 first_line
值中較早的一個,新的 last_column
是兩個節點的 last_column
值中較晚的一個,依此類推。
如果您只想使用第二個節點的開始或結束位置資料來延伸第一個節點的位置資料,請傳遞 justLeading
或 justEnding
選項。因此,例如,如果 first
的範圍是 [4, 5],而 second
的範圍是 [1, 10],您將取得
mergeLocationData(first, second).range # [1, 10]
mergeLocationData(first, second, justLeading: yes).range # [1, 5]
mergeLocationData(first, second, justEnding: yes).range # [4, 10]
exports.mergeLocationData = mergeLocationData = (locationDataA, locationDataB, {justLeading, justEnding} = {}) ->
return Object.assign(
if justEnding
first_line: locationDataA.first_line
first_column: locationDataA.first_column
else
if isLocationDataStartGreater locationDataA, locationDataB
first_line: locationDataB.first_line
first_column: locationDataB.first_column
else
first_line: locationDataA.first_line
first_column: locationDataA.first_column
,
if justLeading
last_line: locationDataA.last_line
last_column: locationDataA.last_column
last_line_exclusive: locationDataA.last_line_exclusive
last_column_exclusive: locationDataA.last_column_exclusive
else
if isLocationDataEndGreater locationDataA, locationDataB
last_line: locationDataA.last_line
last_column: locationDataA.last_column
last_line_exclusive: locationDataA.last_line_exclusive
last_column_exclusive: locationDataA.last_column_exclusive
else
last_line: locationDataB.last_line
last_column: locationDataB.last_column
last_line_exclusive: locationDataB.last_line_exclusive
last_column_exclusive: locationDataB.last_column_exclusive
,
range: [
if justEnding
locationDataA.range[0]
else
lesser locationDataA.range[0], locationDataB.range[0]
,
if justLeading
locationDataA.range[1]
else
greater locationDataA.range[1], locationDataB.range[1]
]
)
取兩個 AST 節點,或兩個 AST 節點的位置資料物件,並傳回一個新的位置資料物件,其中包含兩個節點的位置資料。因此,新的 start
值將會是兩個節點的 start
值中較早的一個,新的 end
值將會是兩個節點的 end
值中較晚的一個,依此類推。
如果您只想使用第二個節點的開始或結束位置資料來延伸第一個節點的位置資料,請傳遞 justLeading
或 justEnding
選項。因此,例如,如果 first
的範圍是 [4, 5],而 second
的範圍是 [1, 10],您將取得
mergeAstLocationData(first, second).range # [1, 10]
mergeAstLocationData(first, second, justLeading: yes).range # [1, 5]
mergeAstLocationData(first, second, justEnding: yes).range # [4, 10]
exports.mergeAstLocationData = mergeAstLocationData = (nodeA, nodeB, {justLeading, justEnding} = {}) ->
return
loc:
start:
if justEnding
nodeA.loc.start
else
if isAstLocGreater nodeA.loc.start, nodeB.loc.start
nodeB.loc.start
else
nodeA.loc.start
end:
if justLeading
nodeA.loc.end
else
if isAstLocGreater nodeA.loc.end, nodeB.loc.end
nodeA.loc.end
else
nodeB.loc.end
range: [
if justEnding
nodeA.range[0]
else
lesser nodeA.range[0], nodeB.range[0]
,
if justLeading
nodeA.range[1]
else
greater nodeA.range[1], nodeB.range[1]
]
start:
if justEnding
nodeA.start
else
lesser nodeA.start, nodeB.start
end:
if justLeading
nodeA.end
else
greater nodeA.end, nodeB.end
將 Jison 風格的節點類別位置資料轉換為 Babel 風格的位置資料
exports.jisonLocationDataToAstLocationData = jisonLocationDataToAstLocationData = ({first_line, first_column, last_line_exclusive, last_column_exclusive, range}) ->
return
loc:
start:
line: first_line + 1
column: first_column
end:
line: last_line_exclusive + 1
column: last_column_exclusive
range: [
range[0]
range[1]
]
start: range[0]
end: range[1]
產生一個零寬度的位置資料,對應到另一個節點位置的結尾。
zeroWidthLocationDataFromEndLocation = ({range: [, endRange], last_line_exclusive, last_column_exclusive}) -> {
first_line: last_line_exclusive
first_column: last_column_exclusive
last_line: last_line_exclusive
last_column: last_column_exclusive
last_line_exclusive
last_column_exclusive
range: [endRange, endRange]
}
extractSameLineLocationDataFirst = (numChars) -> ({range: [startRange], first_line, first_column}) -> {
first_line
first_column
last_line: first_line
last_column: first_column + numChars - 1
last_line_exclusive: first_line
last_column_exclusive: first_column + numChars
range: [startRange, startRange + numChars]
}
extractSameLineLocationDataLast = (numChars) -> ({range: [, endRange], last_line, last_column, last_line_exclusive, last_column_exclusive}) -> {
first_line: last_line
first_column: last_column - (numChars - 1)
last_line: last_line
last_column: last_column
last_line_exclusive
last_column_exclusive
range: [endRange - numChars, endRange]
}
我們目前沒有對應到內插/JSX 表達式大括號之間的空白的記號,因此透過從內插的位置資料中移除大括號來拼湊位置資料。技術上來說,如果結尾大括號前面有換行,這裡的 last_line/last_column 計算可能會不正確,但 last_line/last_column 根本不會用於 AST 產生。
emptyExpressionLocationData = ({interpolationNode: element, openingBrace, closingBrace}) ->
first_line: element.locationData.first_line
first_column: element.locationData.first_column + openingBrace.length
last_line: element.locationData.last_line
last_column: element.locationData.last_column - closingBrace.length
last_line_exclusive: element.locationData.last_line
last_column_exclusive: element.locationData.last_column
range: [
element.locationData.range[0] + openingBrace.length
element.locationData.range[1] - closingBrace.length
]