• Jump To … +
    browser.coffee cake.coffee coffee-script.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee
  • coffee-script.coffee

  • ¶

    CoffeeScript can be used both on the server, as a command-line compiler based on Node.js/V8, or to run CoffeeScript directly in the browser. This module contains the main entry functions for tokenizing, parsing, and compiling source CoffeeScript into JavaScript.

    fs            = require 'fs'
    vm            = require 'vm'
    path          = require 'path'
    child_process = require 'child_process'
    {Lexer}       = require './lexer'
    {parser}      = require './parser'
    helpers       = require './helpers'
    SourceMap     = require './sourcemap'
  • ¶

    The current CoffeeScript version number.

    exports.VERSION = '1.6.3'
  • ¶

    Expose helpers for testing.

    exports.helpers = helpers
  • ¶

    Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler.

    If options.sourceMap is specified, then options.filename must also be specified. All options that can be passed to SourceMap#generate may also be passed here.

    This returns a javascript string, unless options.sourceMap is passed, in which case this returns a {js, v3SourceMap, sourceMap} object, where sourceMap is a sourcemap.coffee#SourceMap object, handy for doing programatic lookups.

    exports.compile = compile = (code, options = {}) ->
      {merge} = helpers
    
      if options.sourceMap
        map = new SourceMap
    
      fragments = parser.parse(lexer.tokenize code, options).compileToFragments options
    
      currentLine = 0
      currentLine += 1 if options.header
      currentLine += 1 if options.shiftLine
      currentColumn = 0
      js = ""
      for fragment in fragments
  • ¶

    Update the sourcemap with data from each fragment

        if options.sourceMap
          if fragment.locationData
            map.add(
              [fragment.locationData.first_line, fragment.locationData.first_column]
              [currentLine, currentColumn]
              {noReplace: true})
          newLines = helpers.count fragment.code, "\n"
          currentLine += newLines
          currentColumn = fragment.code.length - (if newLines then fragment.code.lastIndexOf "\n" else 0)
  • ¶

    Copy the code from each fragment into the final JavaScript.

        js += fragment.code
    
      if options.header
        header = "Generated by CoffeeScript #{@VERSION}"
        js = "// #{header}\n#{js}"
    
      if options.sourceMap
        answer = {js}
        answer.sourceMap = map
        answer.v3SourceMap = map.generate(options, code)
        answer
      else
        js
  • ¶

    Tokenize a string of CoffeeScript code, and return the array of tokens.

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

    Parse a string of CoffeeScript code or an array of lexed tokens, and return the AST. You can then compile it by calling .compile() on the root, or traverse it by using .traverseChildren() with a callback.

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

    Compile and execute a string of CoffeeScript (on the server), correctly setting __filename, __dirname, and relative require().

    exports.run = (code, options = {}) ->
      mainModule = require.main
      options.sourceMap ?= true
  • ¶

    Set the filename.

      mainModule.filename = process.argv[1] =
        if options.filename then fs.realpathSync(options.filename) else '.'
  • ¶

    Clear the module cache.

      mainModule.moduleCache and= {}
  • ¶

    Assign paths for node_modules loading

      mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename or '.'
  • ¶

    Compile.

      if not helpers.isCoffee(mainModule.filename) or require.extensions
        answer = compile(code, options)
  • ¶

    Attach sourceMap object to sourceMaps[options.filename] so that it is accessible by Error.prepareStackTrace.

        do patchStackTrace
        sourceMaps[mainModule.filename] = answer.sourceMap
        mainModule._compile answer.js, mainModule.filename
      else
        mainModule._compile code, mainModule.filename
  • ¶

    Compile and evaluate a string of CoffeeScript (in a Node.js-like environment). The CoffeeScript REPL uses this to run the input.

    exports.eval = (code, options = {}) ->
      return unless code = code.trim()
      Script = vm.Script
      if Script
        if options.sandbox?
          if options.sandbox instanceof Script.createContext().constructor
            sandbox = options.sandbox
          else
            sandbox = Script.createContext()
            sandbox[k] = v for own k, v of options.sandbox
          sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox
        else
          sandbox = global
        sandbox.__filename = options.filename || 'eval'
        sandbox.__dirname  = path.dirname sandbox.__filename
  • ¶

    define module/require only if they chose not to specify their own

        unless sandbox isnt global or sandbox.module or sandbox.require
          Module = require 'module'
          sandbox.module  = _module  = new Module(options.modulename || 'eval')
          sandbox.require = _require = (path) ->  Module._load path, _module, true
          _module.filename = sandbox.__filename
          _require[r] = require[r] for r in Object.getOwnPropertyNames require when r isnt 'paths'
  • ¶

    use the same hack node currently uses for their own REPL

          _require.paths = _module.paths = Module._nodeModulePaths process.cwd()
          _require.resolve = (request) -> Module._resolveFilename request, _module
      o = {}
      o[k] = v for own k, v of options
      o.bare = on # ensure return value
      js = compile code, o
      if sandbox is global
        vm.runInThisContext js
      else
        vm.runInContext js, sandbox
  • ¶

    Load and run a CoffeeScript file for Node, stripping any BOMs.

    loadFile = (module, filename) ->
      raw = fs.readFileSync filename, 'utf8'
      stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
      answer = compile(stripped, {filename, sourceMap: true, literate: helpers.isLiterate filename})
      sourceMaps[filename] = answer.sourceMap
      module._compile answer.js, filename
  • ¶

    If the installed version of Node supports require.extensions, register CoffeeScript as an extension.

    if require.extensions
      for ext in ['.coffee', '.litcoffee', '.coffee.md']
        require.extensions[ext] = loadFile
  • ¶

    Patch Node's module loader to be able to handle mult-dot extensions. This is a horrible thing that should not be required. Perhaps, one day, when a truly benevolent dictator comes to rule over the Republik of Node, it won't be.

      Module = require 'module'
    
      findExtension = (filename) ->
        extensions = path.basename(filename).split '.'
  • ¶

    Remove the initial dot from dotfiles.

        extensions.shift() if extensions[0] is ''
  • ¶

    Start with the longest possible extension and work our way shortwards.

        while extensions.shift()
          curExtension = '.' + extensions.join '.'
          return curExtension if Module._extensions[curExtension]
        '.js'
    
      Module::load = (filename) ->
        @filename = filename
        @paths = Module._nodeModulePaths path.dirname filename
        extension = findExtension filename
        Module._extensions[extension](this, filename)
        @loaded = true
  • ¶

    If we're on Node, patch child_process.fork so that Coffee scripts are able to fork both CoffeeScript files, and JavaScript files, directly.

    if child_process
      {fork} = child_process
      child_process.fork = (path, args = [], options = {}) ->
        execPath = if helpers.isCoffee(path) then 'coffee' else null
        if not Array.isArray args
          args = []
          options = args or {}
        options.execPath or= execPath
        fork path, args, options
  • ¶

    Instantiate a Lexer for our use here.

    lexer = new Lexer
  • ¶

    The real Lexer produces a generic stream of tokens. This object provides a thin wrapper around it, compatible with the Jison API. We can then pass it directly as a "Jison lexer".

    parser.lexer =
      lex: ->
        token = @tokens[@pos++]
        if token
          [tag, @yytext, @yylloc] = token
          @yylineno = @yylloc.first_line
        else
          tag = ''
    
        tag
      setInput: (@tokens) ->
        @pos = 0
      upcomingInput: ->
        ""
  • ¶

    Make all the AST nodes visible to the parser.

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

    Override Jison's default error handling function.

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

    Disregard Jison's message, it contains redundant line numer information.

      message = "unexpected #{if token is 1 then 'end of input' else token}"
  • ¶

    The second argument has a loc property, which should have the location data for this token. Unfortunately, Jison seems to send an outdated loc (from the previous token), so we take the location information directly from the lexer.

      helpers.throwSyntaxError message, parser.lexer.yylloc
  • ¶

    Based on michaelficarra/CoffeeScriptRedux NodeJS / V8 have no support for transforming positions in stack traces using sourceMap, so we must monkey-patch Error to display CoffeeScript source positions.

    patched = false
  • ¶

    Map of filenames -> sourceMap object.

    sourceMaps = {}
    
    patchStackTrace = ->
      return if patched
      patched = true
      mainModule = require.main
  • ¶

    (Assigning to a property of the Module object in the normal module cache is unsuitable, because node deletes those objects from the cache if an exception is thrown in the module body.)

      Error.prepareStackTrace = (err, stack) ->
        sourceFiles = {}
    
        getSourceMapping = (filename, line, column) ->
          sourceMap = sourceMaps[filename]
          answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap
          if answer then [answer[0] + 1, answer[1] + 1] else null
    
        frames = for frame in stack
          break if frame.getFunction() is exports.run
          "  at #{formatSourcePosition frame, getSourceMapping}"
    
        "#{err.name}: #{err.message ? ''}\n#{frames.join '\n'}\n"
  • ¶

    Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js Modified to handle 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()
  • ¶

    Check for a sourceMap position

        source = getSourceMapping fileName, line, column
        fileLocation =
          if source
            "#{fileName}:#{source[0]}:#{source[1]}, <js>:#{line}:#{column}"
          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