coffee-script.coffee

- Jump To … +
  browser.coffee cake.coffee coffee-script.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

- # 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'
      {Lexer}       = require './lexer'
      {parser}      = require './parser'
      helpers       = require './helpers'
      SourceMap     = require './sourcemap'

- ¶

  The current CoffeeScript version number.

      exports.VERSION = '1.9.3'

      exports.FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md']

- ¶

  Expose helpers for testing.

      exports.helpers = helpers

- ¶

  Function wrapper to add source file information to SyntaxErrors thrown by the lexer/parser/compiler.

      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

- ¶

  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 = withPrettyErrors (code, options) ->
        {merge, extend} = helpers
        options = extend {}, options

        if options.sourceMap
          map = new SourceMap

        tokens = lexer.tokenize code, options

- ¶

  Pass a list of referenced variables, so that generated variables won’t get the same name.

        options.referencedVars = (
          token[1] for token in tokens when token.variable
        )

        fragments = parser.parse(tokens).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

- ¶

  Do not include empty, whitespace, or semicolon-only fragments.

            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

- ¶

  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 = withPrettyErrors (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 = withPrettyErrors (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

- ¶

  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

        dir = if options.filename
          path.dirname fs.realpathSync options.filename
        else
          fs.realpathSync '.'
        mainModule.paths = require('module')._nodeModulePaths dir

- ¶

  Compile.

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

        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()
        createContext = vm.Script.createContext ? vm.createContext

        isContext = vm.isContext ? (ctx) ->
          options.sandbox instanceof createContext().constructor

        if createContext
          if options.sandbox?
            if isContext options.sandbox
              sandbox = options.sandbox
            else
              sandbox = 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

      exports.register = -> require './register'

- ¶

  Throw error with deprecation warning when depending upon implicit `require.extensions` registration

      if require.extensions
        for ext in @FILE_EXTENSIONS
          require.extensions[ext] ?= ->
            throw new Error """
            Use CoffeeScript.register() or require the coffee-script/register module to require #{ext} files.
            """

      exports._compileFile = (filename, sourceMap = no) ->
        raw = fs.readFileSync filename, 'utf8'
        stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw

        try
          answer = compile(stripped, {filename, sourceMap, literate: helpers.isLiterate filename})
        catch err

- ¶

  As the filename and code of a dynamically loaded file will be different from the original file compiled with CoffeeScript.run, add that information to error so it can be pretty-printed later.

          throw helpers.updateSyntaxError err, stripped, filename

        answer

- ¶

  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 = 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: ->
          ""

- ¶

  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. Disregard the token, we take its value directly from the lexer in case the error is caused by a generated token which might refer to its origin.

        {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', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START']
            errorTag.replace(/_START$/, '').toLowerCase()
          else
            helpers.nameWhitespaceCharacter errorText

- ¶

  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 "unexpected #{errorText}", errorLoc

- ¶

  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]}"
            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

- ¶

  Map of filenames -> sourceMap object.

      sourceMaps = {}

- ¶

  Generates the source map for a coffee file and stores it in the local cache variable.

      getSourceMap = (filename) ->
        return sourceMaps[filename] if sourceMaps[filename]
        return unless path?.extname(filename) in exports.FILE_EXTENSIONS
        answer = exports._compileFile filename, true
        sourceMaps[filename] = answer.sourceMap

- ¶

  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.

      Error.prepareStackTrace = (err, stack) ->
        getSourceMapping = (filename, line, column) ->
          sourceMap = getSourceMap 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.toString()}\n#{frames.join '\n'}\n"