mirror of
https://github.com/sharkdp/bat.git
synced 2025-08-14 01:59:41 +01:00
.github
assets
diagnostics
doc
examples
src
tests
benchmarks
examples
mocked-pagers
scripts
snapshots
syntax-tests
highlighted
source
ARM Assembly
ASP
AWK
ActionScript
Apache
AppleScript
AsciiDoc
Assembly (x86_64)
Bash
BatTestCustomAssets
Batch
BibTeX
C
C-Sharp
CMake
CSS
CSV
Cabal
Clojure
CoffeeScript
LICENSE.md
coffeescript.coffee
Cpp
CpuInfo
Crystal
D
Dart
Diff
Dockerfile
DotENV
Elixir
Elm
Email
Erlang
EtcGroup
F#
Fish
Fortran (Fixed Form)
Fortran (Modern)
Fortran Namelist
Fstab
GLSL
Git Attributes
Git Config
Git Ignore
Go
GraphQL
Graphviz DOT
Groff
Groovy
HTML
Haskell
Hosts
INI
Ignored suffixes
JSON
Java
Java Server Page (JSP)
JavaScript
Jinja2
Julia
Kotlin
LLVM
Lean
Less
Lisp
Literate Haskell
LiveScript
Log
Lua
MATLAB
Makefile
Manpage
Markdown
MediaWiki
MemInfo
NAnt Build File
Ninja
OCaml
Objective-C
Objective-C++
PHP
Pascal
Passwd
Perl
Plaintext
PowerShell
Protocol Buffer
Puppet
PureScript
Python
QML
R
Racket
Rego
Regular Expression
Robot Framework
Ruby
Ruby Haml
Ruby On Rails
Rust
SCSS
SLS
SML
SQL
SSH Config
SSHD Config
Sass
Scala
Slim
Solidity
Strace
Stylus
Svelte
Swift
Syslog
SystemVerilog
TOML
Tcl
TeX
Terraform
Textile
TypeScript
TypeScriptReact
Verilog
VimL
Vue
Vyper
XAML
XML
YAML
Zig
dash
gnuplot
http-request-response
jsonnet
nginx
nim
nix
orgmode
reStructuredText
resolv.conf
varlink
BatTestCustomAssets.sublime-syntax
compare_highlighted_versions.py
create_highlighted_versions.py
regression_test.sh
test_custom_assets.sh
update.sh
utils
.gitattributes
assets.rs
integration_tests.rs
no_duplicate_extensions.rs
snapshot_tests.rs
tester.rs
.gitignore
.gitmodules
CHANGELOG.md
CONTRIBUTING.md
Cargo.lock
Cargo.toml
LICENSE-APACHE
LICENSE-MIT
README.md
build.rs
392 lines
16 KiB
CoffeeScript
Vendored
392 lines
16 KiB
CoffeeScript
Vendored
# 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
|
||
|
||
# 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'
|
||
# The contents of a `<script>` block are encoded via UTF-16, so if any extended
|
||
# characters are used in the block, btoa will fail as it maxes out at UTF-8.
|
||
# See https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
|
||
# for the gory details, and for the solution implemented here.
|
||
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' # Support `CoffeeScript.nodes(tokens)`.
|
||
throw helpers.updateSyntaxError err, code, options.filename
|
||
|
||
# For each compiled file, save its source in memory in case we need to
|
||
# recompile it later. We might need to recompile if the first compilation
|
||
# didn’t create a source map (faster) but something went wrong and we need
|
||
# a stack trace. Assuming that most of the time, code isn’t throwing
|
||
# exceptions, it’s probably more efficient to compile twice only when we
|
||
# need a stack trace, rather than always generating a source map even when
|
||
# it’s not likely to be used. Save in form of `filename`: [`(source)`]
|
||
sources = {}
|
||
# Also save source maps if generated, in form of `(source)`: [`(source map)`].
|
||
sourceMaps = {}
|
||
|
||
# This is exported to enable an external module to implement caching of
|
||
# compilation results. When the compiled js source is loaded from cache, the
|
||
# original coffee code should be added with this method in order to enable the
|
||
# Error.prepareStackTrace below to correctly adjust the stack trace for the
|
||
# corresponding file (the source map will be generated on demand).
|
||
exports.registerCompiled = registerCompiled = (filename, source, sourcemap) ->
|
||
|
||
sources[filename] ?= []
|
||
sources[filename].push source
|
||
|
||
if sourcemap?
|
||
sourceMaps[filename] ?= []
|
||
sourceMaps[filename].push sourcemap
|
||
|
||
# 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
|
||
# Always generate a source map if no filename is passed in, since without a
|
||
# a filename we have no way to retrieve this source later in the event that
|
||
# we need to recompile it to get a source map for `prepareStackTrace`.
|
||
generateSourceMap = options.sourceMap or options.inlineMap or not options.filename?
|
||
filename = options.filename or '<anonymous>'
|
||
|
||
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] # `.*` 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
|
||
# Update the sourcemap with data from each fragment.
|
||
if generateSourceMap
|
||
# 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
|
||
|
||
# See https://github.com/babel/babel/issues/827#issuecomment-77573107:
|
||
# Babel can take a v3 source map object as input in `inputSourceMap`
|
||
# and it will return an *updated* v3 source map object in its output.
|
||
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=#{options.filename ? 'coffeescript'}"
|
||
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.
|
||
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 =
|
||
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
|
||
|
||
# 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
|
||
|
||
getSourceMap = (filename, line, column) ->
|
||
# Skip files that we didn’t compile, like Node system files that appear in
|
||
# the stack trace, as they never have source maps.
|
||
return null unless filename is '<anonymous>' or filename.slice(filename.lastIndexOf('.')) in FILE_EXTENSIONS
|
||
|
||
if filename isnt '<anonymous>' and sourceMaps[filename]?
|
||
return sourceMaps[filename][sourceMaps[filename].length - 1]
|
||
# CoffeeScript compiled in a browser or via `CoffeeScript.compile` or `.run`
|
||
# may get compiled with `options.filename` that’s missing, which becomes
|
||
# `<anonymous>`; but the runtime might request the stack trace with the
|
||
# filename of the script file. See if we have a source map cached under
|
||
# `<anonymous>` that matches the error.
|
||
else if sourceMaps['<anonymous>']?
|
||
# Work backwards from the most recent anonymous source maps, until we find
|
||
# one that works. This isn’t foolproof; there is a chance that multiple
|
||
# source maps will have line/column pairs that match. But we have no other
|
||
# way to match them. `frame.getFunction().toString()` doesn’t always work,
|
||
# and it’s not foolproof either.
|
||
for map in sourceMaps['<anonymous>'] by -1
|
||
sourceLocation = map.sourceLocation [line - 1, column - 1]
|
||
return map if sourceLocation?[0]? and sourceLocation[1]?
|
||
|
||
# If all else fails, recompile this source to get a source map. We need the
|
||
# previous section (for `<anonymous>`) despite this option, because after it
|
||
# gets compiled we will still need to look it up from
|
||
# `sourceMaps['<anonymous>']` in order to find and return it. That’s why we
|
||
# start searching from the end in the previous block, because most of the
|
||
# time the source map we want is the last one.
|
||
if sources[filename]?
|
||
answer = compile sources[filename][sources[filename].length - 1],
|
||
filename: filename
|
||
sourceMap: yes
|
||
literate: helpers.isLiterate filename
|
||
answer.sourceMap
|
||
else
|
||
null
|
||
|
||
# Based on [michaelficarra/CoffeeScriptRedux](http://goo.gl/ZTx1p)
|
||
# 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, line, column
|
||
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"
|
||
|
||
checkShebangLine = (file, input) ->
|
||
firstLine = input.split(/$/m)[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}"
|