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.
{Lexer} = require './lexer' {parser} = require './parser' helpers = require './helpers' SourceMap = require './sourcemap'
Require package.json
, which is two levels above this file, as this file is evaluated from lib/coffeescript
.
packageJson = require '../../package.json'
The current CoffeeScript version number.
exports.VERSION = packageJson.version exports.FILE_EXTENSIONS = FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md']
Expose helpers for testing.
exports.helpers = helpers {getSourceMap, registerCompiled} = SourceMap
This is exported to enable an external module to implement caching of sourcemaps. This is used only when patchStackTrace
has been called to adjust stack traces for files with cached source maps.
exports.registerCompiled = registerCompiled
Function that allows for btoa in both nodejs and the browser.
base64encode = (src) -> switch when typeof Buffer is 'function' Buffer.from(src).toString('base64') when typeof btoa is 'function'
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.')
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' 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 programmatic lookups.
exports.compile = compile = withPrettyErrors (code, options = {}) ->
Clone options
, to avoid mutating the options
object passed in.
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
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[0] is 'IDENTIFIER' )
Check for import or export; if found, force bare mode.
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
If all that was requested was a POJO representation of the nodes, e.g. the abstract syntax tree (AST), we can stop now and just return that (after fixing the location data for the root/File
»Program
node, which might’ve gotten misaligned from the original source due to the clean
function in the lexer).
if options.ast nodes.allCommentTokens = helpers.extractAllCommentTokens tokens sourceCodeNumberOfLines = (code.match(/\r?\n/g) or '').length + 1 sourceCodeLastLine = /.*$/.exec(code)[0] 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
Update the sourcemap with data from each fragment.
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 generateSourceMap v3SourceMap = map.generate options, code if options.transpile if typeof options.transpile isnt 'object'
This only happens if run via the Node API and transpile
is set to something other than an object.
throw new Error 'The transpile option must be given an object with options to pass to Babel'
Get the reference to Babel that we have been passed if this compiler is run via the CLI or Node API.
transpiler = options.transpile.transpile delete options.transpile.transpile transpilerOptions = Object.assign {}, options.transpile
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
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) -> source = lexer.tokenize source, options if typeof source is 'string' parser.parse source
This file used to export these methods; leave stubs that throw warnings instead. These methods have been moved into index.coffee
to provide separate entrypoints for Node and non-Node environments, so that static analysis tools don’t choke on Node packages when compiling for a non-Node environment.
exports.run = exports.eval = exports.register = -> throw new Error 'require index.coffee, not this file'
Instantiate a Lexer for our use here.
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 = 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: -> ''
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 number 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', 'INFINITY', '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 exports.patchStackTrace = ->
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 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
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) -> frames = for frame in stack
Don’t display stack frames deeper than 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}"
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4