• ジャンプ先 … +
    browser.coffee cake.coffee coffeescript.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee register.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee
  • coffeescript.coffee

  • §

    CoffeeScriptは、Node.js/V8ベースのコマンドラインコンパイラとしてサーバー上で使用することも、ブラウザで直接CoffeeScriptを実行することもできます。このモジュールには、CoffeeScriptソースをトークン化、解析、およびJavaScriptにコンパイルするための主要なエントリ関数が含まれています。

    {Lexer}       = require './lexer'
    {parser}      = require './parser'
    helpers       = require './helpers'
    SourceMap     = require './sourcemap'
  • §

    このファイルはlib/coffeescriptから評価されるため、このファイルの2つ上のレベルにあるpackage.jsonをrequireします。

    packageJson   = require '../../package.json'
  • §

    現在のCoffeeScriptのバージョン番号。

    exports.VERSION = packageJson.version
    
    exports.FILE_EXTENSIONS = FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md']
  • §

    テスト用のヘルパーを公開します。

    exports.helpers = helpers
    
    {getSourceMap, registerCompiled} = SourceMap
  • §

    これは、外部モジュールがソースマップのキャッシュを実装できるようにするために公開されています。これは、キャッシュされたソースマップを持つファイルのスタックトレースを調整するためにpatchStackTraceが呼び出された場合にのみ使用されます。

    exports.registerCompiled = registerCompiled
  • §

    nodejsとブラウザの両方でbtoaを許可する関数。

    base64encode = (src) -> switch
      when typeof Buffer is 'function'
        Buffer.from(src).toString('base64')
      when typeof btoa is 'function'
  • §

    <script>ブロックの内容はUTF-16でエンコードされているため、ブロック内で拡張文字が使用されている場合、btoaはUTF-8で最大になるため失敗します。詳細とここで実装されている解決策については、https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problemを参照してください。

        btoa encodeURIComponent(src).replace /%([0-9A-F]{2})/g, (match, p1) ->
          String.fromCharCode '0x' + p1
      else
        throw new Error('Unable to base64 encode inline sourcemap.')
  • §

    字句解析器/パーサー/コンパイラーによってスローされたSyntaxErrorにソースファイル情報を追加するための関数ラッパー。

    withPrettyErrors = (fn) ->
      (code, options = {}) ->
        try
          fn.call @, code, options
        catch err
          throw err if typeof code isnt 'string' # Support `CoffeeScript.nodes(tokens)`.
          throw helpers.updateSyntaxError err, code, options.filename
  • §

    Coffee/Jisonコンパイラーを使用して、CoffeeScriptコードをJavaScriptにコンパイルします。

    options.sourceMapが指定されている場合は、options.filenameも指定する必要があります。SourceMap#generateに渡すことができるすべてのオプションもここに渡すことができます。

    これは、options.sourceMapが渡されない限り、javascript文字列を返します。渡された場合は、{js, v3SourceMap, sourceMap}オブジェクトを返します。ここで、sourceMapは、プログラムによるルックアップに便利なsourcemap.coffee#SourceMapオブジェクトです。

    exports.compile = compile = withPrettyErrors (code, options = {}) ->
  • §

    渡されたoptionsオブジェクトをミューテートしないように、optionsを複製します。

      options = Object.assign {}, options
    
      generateSourceMap = options.sourceMap or options.inlineMap or not options.filename?
      filename = options.filename or helpers.anonymousFileName()
    
      checkShebangLine filename, code
    
      map = new SourceMap if generateSourceMap
    
      tokens = lexer.tokenize code, options
  • §

    生成された変数が同じ名前にならないように、参照された変数のリストを渡します。

      options.referencedVars = (
        token[1] for token in tokens when token[0] is 'IDENTIFIER'
      )
  • §

    インポートまたはエクスポートを確認します。見つかった場合は、強制的にベアモードにします。

      unless options.bare? and options.bare is yes
        for token in tokens
          if token[0] in ['IMPORT', 'EXPORT']
            options.bare = yes
            break
    
      nodes = parser.parse tokens
  • §

    要求されたのがノードのPOJO表現、例えば抽象構文木(AST)のみである場合は、ここで停止してそれを返すことができます(lexerのclean関数によって元のソースからずれている可能性がある、ルート/File»Programノードの位置データを修正した後)。

      if options.ast
        nodes.allCommentTokens = helpers.extractAllCommentTokens tokens
        sourceCodeNumberOfLines = (code.match(/\r?\n/g) or '').length + 1
        sourceCodeLastLine = /.*$/.exec(code)[0] # `.*` matches all but line break characters.
        ast = nodes.ast options
        range = [0, code.length]
        ast.start = ast.program.start = range[0]
        ast.end = ast.program.end = range[1]
        ast.range = ast.program.range = range
        ast.loc.start = ast.program.loc.start = {line: 1, column: 0}
        ast.loc.end.line = ast.program.loc.end.line = sourceCodeNumberOfLines
        ast.loc.end.column = ast.program.loc.end.column = sourceCodeLastLine.length
        ast.tokens = tokens
        return ast
    
      fragments = nodes.compileToFragments options
    
      currentLine = 0
      currentLine += 1 if options.header
      currentLine += 1 if options.shiftLine
      currentColumn = 0
      js = ""
      for fragment in fragments
  • §

    各フラグメントからのデータでソースマップを更新します。

        if generateSourceMap
  • §

    空、空白、またはセミコロンのみのフラグメントを含めないでください。

          if fragment.locationData and not /^[;\s]*$/.test fragment.code
            map.add(
              [fragment.locationData.first_line, fragment.locationData.first_column]
              [currentLine, currentColumn]
              {noReplace: true})
          newLines = helpers.count fragment.code, "\n"
          currentLine += newLines
          if newLines
            currentColumn = fragment.code.length - (fragment.code.lastIndexOf("\n") + 1)
          else
            currentColumn += fragment.code.length
  • §

    各フラグメントから最終的なJavaScriptにコードをコピーします。

        js += fragment.code
    
      if options.header
        header = "Generated by CoffeeScript #{@VERSION}"
        js = "// #{header}\n#{js}"
    
      if generateSourceMap
        v3SourceMap = map.generate options, code
    
      if options.transpile
        if typeof options.transpile isnt 'object'
  • §

    これは、Node APIを介して実行され、transpileがオブジェクト以外のものに設定されている場合にのみ発生します。

          throw new Error 'The transpile option must be given an object with options to pass to Babel'
  • §

    このコンパイラがCLIまたはNode APIを介して実行されている場合に渡されたBabelへの参照を取得します。

        transpiler = options.transpile.transpile
        delete options.transpile.transpile
    
        transpilerOptions = Object.assign {}, options.transpile
  • §

    https://github.com/babel/babel/issues/827#issuecomment-77573107を参照してください。Babelは、inputSourceMapでv3ソースマップオブジェクトを入力として受け取り、その出力で更新されたv3ソースマップオブジェクトを返します。

        if v3SourceMap and not transpilerOptions.inputSourceMap?
          transpilerOptions.inputSourceMap = v3SourceMap
        transpilerOutput = transpiler js, transpilerOptions
        js = transpilerOutput.code
        if v3SourceMap and transpilerOutput.map
          v3SourceMap = transpilerOutput.map
    
      if options.inlineMap
        encoded = base64encode JSON.stringify v3SourceMap
        sourceMapDataURI = "//# sourceMappingURL=data:application/json;base64,#{encoded}"
        sourceURL = "//# sourceURL=#{filename}"
        js = "#{js}\n#{sourceMapDataURI}\n#{sourceURL}"
    
      registerCompiled filename, code, map
    
      if options.sourceMap
        {
          js
          sourceMap: map
          v3SourceMap: JSON.stringify v3SourceMap, null, 2
        }
      else
        js
  • §

    CoffeeScriptコードの文字列をトークン化し、トークンの配列を返します。

    exports.tokens = withPrettyErrors (code, options) ->
      lexer.tokenize code, options
  • §

    CoffeeScriptコードの文字列または字句解析されたトークンの配列を解析し、ASTを返します。その後、ルートで.compile()を呼び出してコンパイルするか、コールバックを使用して.traverseChildren()を使用することでトラバースできます。

    exports.nodes = withPrettyErrors (source, options) ->
      source = lexer.tokenize source, options if typeof source is 'string'
      parser.parse source
  • §

    このファイルは以前これらのメソッドをエクスポートしていました。代わりに警告をスローするスタブを残します。これらのメソッドは、Nodeと非Node環境で別々のエントリポイントを提供するためにindex.coffeeに移動されたため、静的解析ツールが非Node環境用にコンパイルするときにNodeパッケージを詰まらせないようにします。

    exports.run = exports.eval = exports.register = ->
      throw new Error 'require index.coffee, not this file'
  • §

    ここで使用するためにLexerをインスタンス化します。

    lexer = new Lexer
  • §

    実際のLexerは、トークンの一般的なストリームを生成します。このオブジェクトは、Jison APIと互換性のある、その上に薄いラッパーを提供します。その後、それを「Jisonレクサー」として直接渡すことができます。

    parser.lexer =
      yylloc:
        range: []
      options:
        ranges: yes
      lex: ->
        token = parser.tokens[@pos++]
        if token
          [tag, @yytext, @yylloc] = token
          parser.errorToken = token.origin or token
          @yylineno = @yylloc.first_line
        else
          tag = ''
        tag
      setInput: (tokens) ->
        parser.tokens = tokens
        @pos = 0
      upcomingInput: -> ''
  • §

    すべてのASTノードをパーサーに表示します。

    parser.yy = require './nodes'
  • §

    Jisonのデフォルトのエラー処理関数をオーバーライドします。

    parser.yy.parseError = (message, {token}) ->
  • §

    Jisonのメッセージを無視します。冗長な行番号情報が含まれています。トークンを無視します。エラーが、その起源を参照する可能性のある生成されたトークンによって引き起こされた場合に備えて、レクサーから直接値を取得します。

      {errorToken, tokens} = parser
      [errorTag, errorText, errorLoc] = errorToken
    
      errorText = switch
        when errorToken is tokens[tokens.length - 1]
          'end of input'
        when errorTag in ['INDENT', 'OUTDENT']
          'indentation'
        when errorTag in ['IDENTIFIER', 'NUMBER', 'INFINITY', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START']
          errorTag.replace(/_START$/, '').toLowerCase()
        else
          helpers.nameWhitespaceCharacter errorText
  • §

    2番目の引数にはlocプロパティがあり、このトークンの位置データが含まれている必要があります。残念ながら、Jisonは古いloc(前のトークンからの)を送信しているように見えるため、レクサーから直接位置情報を取得します。

      helpers.throwSyntaxError "unexpected #{errorText}", errorLoc
    
    exports.patchStackTrace = ->
  • §

    http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.jsに基づいています。sourceMapを処理するように変更されました

      formatSourcePosition = (frame, getSourceMapping) ->
        filename = undefined
        fileLocation = ''
    
        if frame.isNative()
          fileLocation = "native"
        else
          if frame.isEval()
            filename = frame.getScriptNameOrSourceURL()
            fileLocation = "#{frame.getEvalOrigin()}, " unless filename
          else
            filename = frame.getFileName()
    
          filename or= "<anonymous>"
    
          line = frame.getLineNumber()
          column = frame.getColumnNumber()
  • §

    sourceMapの位置を確認します

          source = getSourceMapping filename, line, column
          fileLocation =
            if source
              "#{filename}:#{source[0]}:#{source[1]}"
            else
              "#{filename}:#{line}:#{column}"
    
        functionName = frame.getFunctionName()
        isConstructor = frame.isConstructor()
        isMethodCall = not (frame.isToplevel() or isConstructor)
    
        if isMethodCall
          methodName = frame.getMethodName()
          typeName = frame.getTypeName()
    
          if functionName
            tp = as = ''
            if typeName and functionName.indexOf typeName
              tp = "#{typeName}."
            if methodName and functionName.indexOf(".#{methodName}") isnt functionName.length - methodName.length - 1
              as = " [as #{methodName}]"
    
            "#{tp}#{functionName}#{as} (#{fileLocation})"
          else
            "#{typeName}.#{methodName or '<anonymous>'} (#{fileLocation})"
        else if isConstructor
          "new #{functionName or '<anonymous>'} (#{fileLocation})"
        else if functionName
          "#{functionName} (#{fileLocation})"
        else
          fileLocation
    
      getSourceMapping = (filename, line, column) ->
        sourceMap = getSourceMap filename, line, column
    
        answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap?
        if answer? then [answer[0] + 1, answer[1] + 1] else null
  • §

    michaelficarra/CoffeeScriptReduxに基づいています。NodeJS / V8は、sourceMapを使用してスタックトレース内の位置を変換することをサポートしていないため、ErrorをモンキーパッチしてCoffeeScriptソースの位置を表示する必要があります。

      Error.prepareStackTrace = (err, stack) ->
        frames = for frame in stack
  • §

    CoffeeScript.runよりも深いスタックフレームを表示しないでください。

          break if frame.getFunction() is exports.run
          "    at #{formatSourcePosition frame, getSourceMapping}"
    
        "#{err.toString()}\n#{frames.join '\n'}\n"
    
    checkShebangLine = (file, input) ->
      firstLine = input.split(/$/m, 1)[0]
      rest = firstLine?.match(/^#!\s*([^\s]+\s*)(.*)/)
      args = rest?[2]?.split(/\s/).filter (s) -> s isnt ''
      if args?.length > 1
        console.error '''
          The script to be run begins with a shebang line with more than one
          argument. This script will fail on platforms such as Linux which only
          allow a single argument.
        '''
        console.error "The shebang line was: '#{firstLine}' in file '#{file}'"
        console.error "The arguments were: #{JSON.stringify args}"