jashkenas--coffeescript/lib/coffeescript/coffeescript.js

410 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Generated by CoffeeScript 2.7.0
(function() {
// 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.
var FILE_EXTENSIONS, Lexer, SourceMap, base64encode, checkShebangLine, compile, getSourceMap, helpers, lexer, packageJson, parser, registerCompiled, withPrettyErrors;
({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 = function(src) {
switch (false) {
case typeof Buffer !== 'function':
return Buffer.from(src).toString('base64');
case typeof btoa !== '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.
return btoa(encodeURIComponent(src).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
}));
default:
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 = function(fn) {
return function(code, options = {}) {
var err;
try {
return fn.call(this, code, options);
} catch (error) {
err = error;
if (typeof code !== 'string') { // Support `CoffeeScript.nodes(tokens)`.
throw err;
}
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(function(code, options = {}) {
var ast, currentColumn, currentLine, encoded, filename, fragment, fragments, generateSourceMap, header, i, j, js, len, len1, map, newLines, nodes, range, ref, sourceCodeLastLine, sourceCodeNumberOfLines, sourceMapDataURI, sourceURL, token, tokens, transpiler, transpilerOptions, transpilerOutput, v3SourceMap;
// Clone `options`, to avoid mutating the `options` object passed in.
options = Object.assign({}, options);
generateSourceMap = options.sourceMap || options.inlineMap || (options.filename == null);
filename = options.filename || helpers.anonymousFileName();
checkShebangLine(filename, code);
if (generateSourceMap) {
map = new SourceMap();
}
tokens = lexer.tokenize(code, options);
// Pass a list of referenced variables, so that generated variables wont get
// the same name.
options.referencedVars = (function() {
var i, len, results;
results = [];
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token[0] === 'IDENTIFIER') {
results.push(token[1]);
}
}
return results;
})();
// Check for import or export; if found, force bare mode.
if (!((options.bare != null) && options.bare === true)) {
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if ((ref = token[0]) === 'IMPORT' || ref === 'EXPORT') {
options.bare = true;
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 mightve 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) || '').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;
if (options.header) {
currentLine += 1;
}
if (options.shiftLine) {
currentLine += 1;
}
currentColumn = 0;
js = "";
for (j = 0, len1 = fragments.length; j < len1; j++) {
fragment = fragments[j];
// Update the sourcemap with data from each fragment.
if (generateSourceMap) {
// Do not include empty, whitespace, or semicolon-only fragments.
if (fragment.locationData && !/^[;\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 ${this.VERSION}`;
js = `// ${header}\n${js}`;
}
if (generateSourceMap) {
v3SourceMap = map.generate(options, code);
}
if (options.transpile) {
if (typeof options.transpile !== '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 && (transpilerOptions.inputSourceMap == null)) {
transpilerOptions.inputSourceMap = v3SourceMap;
}
transpilerOutput = transpiler(js, transpilerOptions);
js = transpilerOutput.code;
if (v3SourceMap && 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) {
return {
js,
sourceMap: map,
v3SourceMap: JSON.stringify(v3SourceMap, null, 2)
};
} else {
return js;
}
});
// Tokenize a string of CoffeeScript code, and return the array of tokens.
exports.tokens = withPrettyErrors(function(code, options) {
return 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(function(source, options) {
if (typeof source === 'string') {
source = lexer.tokenize(source, options);
}
return 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 dont choke on Node packages when compiling for a non-Node
// environment.
exports.run = exports.eval = exports.register = function() {
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: true
},
lex: function() {
var tag, token;
token = parser.tokens[this.pos++];
if (token) {
[tag, this.yytext, this.yylloc] = token;
parser.errorToken = token.origin || token;
this.yylineno = this.yylloc.first_line;
} else {
tag = '';
}
return tag;
},
setInput: function(tokens) {
parser.tokens = tokens;
return this.pos = 0;
},
upcomingInput: function() {
return '';
}
};
// Make all the AST nodes visible to the parser.
parser.yy = require('./nodes');
// Override Jison's default error handling function.
parser.yy.parseError = function(message, {token}) {
var errorLoc, errorTag, errorText, errorToken, tokens;
// 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 = (function() {
switch (false) {
case errorToken !== tokens[tokens.length - 1]:
return 'end of input';
case errorTag !== 'INDENT' && errorTag !== 'OUTDENT':
return 'indentation';
case errorTag !== 'IDENTIFIER' && errorTag !== 'NUMBER' && errorTag !== 'INFINITY' && errorTag !== 'STRING' && errorTag !== 'STRING_START' && errorTag !== 'REGEX' && errorTag !== 'REGEX_START':
return errorTag.replace(/_START$/, '').toLowerCase();
default:
return 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.
return helpers.throwSyntaxError(`unexpected ${errorText}`, errorLoc);
};
exports.patchStackTrace = function() {
var formatSourcePosition, getSourceMapping;
// Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js
// Modified to handle sourceMap
formatSourcePosition = function(frame, getSourceMapping) {
var as, column, fileLocation, filename, functionName, isConstructor, isMethodCall, line, methodName, source, tp, typeName;
filename = void 0;
fileLocation = '';
if (frame.isNative()) {
fileLocation = "native";
} else {
if (frame.isEval()) {
filename = frame.getScriptNameOrSourceURL();
if (!filename) {
fileLocation = `${frame.getEvalOrigin()}, `;
}
} else {
filename = frame.getFileName();
}
filename || (filename = "<anonymous>");
line = frame.getLineNumber();
column = frame.getColumnNumber();
// Check for a sourceMap position
source = getSourceMapping(filename, line, column);
fileLocation = source ? `${filename}:${source[0]}:${source[1]}` : `${filename}:${line}:${column}`;
}
functionName = frame.getFunctionName();
isConstructor = frame.isConstructor();
isMethodCall = !(frame.isToplevel() || isConstructor);
if (isMethodCall) {
methodName = frame.getMethodName();
typeName = frame.getTypeName();
if (functionName) {
tp = as = '';
if (typeName && functionName.indexOf(typeName)) {
tp = `${typeName}.`;
}
if (methodName && functionName.indexOf(`.${methodName}`) !== functionName.length - methodName.length - 1) {
as = ` [as ${methodName}]`;
}
return `${tp}${functionName}${as} (${fileLocation})`;
} else {
return `${typeName}.${methodName || '<anonymous>'} (${fileLocation})`;
}
} else if (isConstructor) {
return `new ${functionName || '<anonymous>'} (${fileLocation})`;
} else if (functionName) {
return `${functionName} (${fileLocation})`;
} else {
return fileLocation;
}
};
getSourceMapping = function(filename, line, column) {
var answer, sourceMap;
sourceMap = getSourceMap(filename, line, column);
if (sourceMap != null) {
answer = sourceMap.sourceLocation([line - 1, column - 1]);
}
if (answer != null) {
return [answer[0] + 1, answer[1] + 1];
} else {
return 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.
return Error.prepareStackTrace = function(err, stack) {
var frame, frames;
frames = (function() {
var i, len, results;
results = [];
for (i = 0, len = stack.length; i < len; i++) {
frame = stack[i];
if (frame.getFunction() === exports.run) {
// Dont display stack frames deeper than `CoffeeScript.run`.
break;
}
results.push(` at ${formatSourcePosition(frame, getSourceMapping)}`);
}
return results;
})();
return `${err.toString()}\n${frames.join('\n')}\n`;
};
};
checkShebangLine = function(file, input) {
var args, firstLine, ref, rest;
firstLine = input.split(/$/m)[0];
rest = firstLine != null ? firstLine.match(/^#!\s*([^\s]+\s*)(.*)/) : void 0;
args = rest != null ? (ref = rest[2]) != null ? ref.split(/\s/).filter(function(s) {
return s !== '';
}) : void 0 : void 0;
if ((args != null ? args.length : void 0) > 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}'`);
return console.error(`The arguments were: ${JSON.stringify(args)}`);
}
};
}).call(this);