Merge branch 'master' into ast

# Conflicts:
#	lib/coffeescript/grammar.js
#	lib/coffeescript/nodes.js
#	lib/coffeescript/parser.js
#	package-lock.json
#	src/nodes.coffee
#	test/error_messages.coffee
This commit is contained in:
Geoffrey Booth 2019-03-24 19:22:33 -07:00
commit 0b5bac666f
24 changed files with 1576 additions and 1441 deletions

View File

@ -486,6 +486,9 @@ task 'test:integrations', 'test the module integrated with other libraries and e
webpack = require 'webpack'
webpack {
entry: './'
optimization:
# Webpacks minification causes the CoffeeScript module to fail some tests.
minimize: off
output:
path: tmpdir
filename: 'coffeescript.js'

View File

@ -376,7 +376,7 @@ undeclared variable <code>__</code>.</p>
vm.runInContext js, context, filename
<span class="hljs-function">
<span class="hljs-title">addMultilineHandler</span> = <span class="hljs-params">(repl)</span> -&gt;</span>
{rli, inputStream, outputStream} = repl</pre></div></div>
{inputStream, outputStream} = repl</pre></div></div>
</li>
@ -412,15 +412,15 @@ undeclared variable <code>__</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> nodeLineListener = rli.listeners(<span class="hljs-string">'line'</span>)[<span class="hljs-number">0</span>]
rli.removeListener <span class="hljs-string">'line'</span>, nodeLineListener
rli.<span class="hljs-literal">on</span> <span class="hljs-string">'line'</span>, <span class="hljs-function"><span class="hljs-params">(cmd)</span> -&gt;</span>
<div class="content"><div class='highlight'><pre> nodeLineListener = repl.listeners(<span class="hljs-string">'line'</span>)[<span class="hljs-number">0</span>]
repl.removeListener <span class="hljs-string">'line'</span>, nodeLineListener
repl.<span class="hljs-literal">on</span> <span class="hljs-string">'line'</span>, <span class="hljs-function"><span class="hljs-params">(cmd)</span> -&gt;</span>
<span class="hljs-keyword">if</span> multiline.enabled
multiline.buffer += <span class="hljs-string">"<span class="hljs-subst">#{cmd}</span>\n"</span>
rli.setPrompt multiline.prompt
rli.prompt <span class="hljs-literal">true</span>
repl.setPrompt multiline.prompt
repl.prompt <span class="hljs-literal">true</span>
<span class="hljs-keyword">else</span>
rli.setPrompt origPrompt
repl.setPrompt origPrompt
nodeLineListener cmd
<span class="hljs-keyword">return</span></pre></div></div>
@ -456,8 +456,8 @@ undeclared variable <code>__</code>.</p>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">unless</span> multiline.buffer.match <span class="hljs-regexp">/\n/</span>
multiline.enabled = <span class="hljs-keyword">not</span> multiline.enabled
rli.setPrompt origPrompt
rli.prompt <span class="hljs-literal">true</span>
repl.setPrompt origPrompt
repl.prompt <span class="hljs-literal">true</span>
<span class="hljs-keyword">return</span></pre></div></div>
</li>
@ -473,7 +473,7 @@ undeclared variable <code>__</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> rli.line? <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> rli.line.match <span class="hljs-regexp">/^\s*$/</span></pre></div></div>
<div class="content"><div class='highlight'><pre> <span class="hljs-keyword">return</span> <span class="hljs-keyword">if</span> repl.line? <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> repl.line.match <span class="hljs-regexp">/^\s*$/</span></pre></div></div>
</li>
@ -489,10 +489,10 @@ undeclared variable <code>__</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> multiline.enabled = <span class="hljs-keyword">not</span> multiline.enabled
rli.line = <span class="hljs-string">''</span>
rli.cursor = <span class="hljs-number">0</span>
rli.output.cursorTo <span class="hljs-number">0</span>
rli.output.clearLine <span class="hljs-number">1</span></pre></div></div>
repl.line = <span class="hljs-string">''</span>
repl.cursor = <span class="hljs-number">0</span>
repl.output.cursorTo <span class="hljs-number">0</span>
repl.output.clearLine <span class="hljs-number">1</span></pre></div></div>
</li>
@ -508,12 +508,12 @@ undeclared variable <code>__</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> multiline.buffer = multiline.buffer.replace <span class="hljs-regexp">/\n/g</span>, <span class="hljs-string">'\uFF00'</span>
rli.emit <span class="hljs-string">'line'</span>, multiline.buffer
repl.emit <span class="hljs-string">'line'</span>, multiline.buffer
multiline.buffer = <span class="hljs-string">''</span>
<span class="hljs-keyword">else</span>
multiline.enabled = <span class="hljs-keyword">not</span> multiline.enabled
rli.setPrompt multiline.initialPrompt
rli.prompt <span class="hljs-literal">true</span>
repl.setPrompt multiline.initialPrompt
repl.prompt <span class="hljs-literal">true</span>
<span class="hljs-keyword">return</span></pre></div></div>
</li>
@ -580,7 +580,7 @@ undeclared variable <code>__</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> repl.rli.history = buffer.toString().split(<span class="hljs-string">'\n'</span>).reverse()</pre></div></div>
<div class="content"><div class='highlight'><pre> repl.history = buffer.toString().split(<span class="hljs-string">'\n'</span>).reverse()</pre></div></div>
</li>
@ -595,7 +595,7 @@ undeclared variable <code>__</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> repl.rli.history.pop() <span class="hljs-keyword">if</span> stat.size &gt; maxSize</pre></div></div>
<div class="content"><div class='highlight'><pre> repl.history.pop() <span class="hljs-keyword">if</span> stat.size &gt; maxSize</pre></div></div>
</li>
@ -610,13 +610,13 @@ undeclared variable <code>__</code>.</p>
</div>
<div class="content"><div class='highlight'><pre> repl.rli.history.shift() <span class="hljs-keyword">if</span> repl.rli.history[<span class="hljs-number">0</span>] <span class="hljs-keyword">is</span> <span class="hljs-string">''</span>
repl.rli.historyIndex = <span class="hljs-number">-1</span>
lastLine = repl.rli.history[<span class="hljs-number">0</span>]
<div class="content"><div class='highlight'><pre> repl.history.shift() <span class="hljs-keyword">if</span> repl.history[<span class="hljs-number">0</span>] <span class="hljs-keyword">is</span> <span class="hljs-string">''</span>
repl.historyIndex = <span class="hljs-number">-1</span>
lastLine = repl.history[<span class="hljs-number">0</span>]
fd = fs.openSync filename, <span class="hljs-string">'a'</span>
repl.rli.addListener <span class="hljs-string">'line'</span>, <span class="hljs-function"><span class="hljs-params">(code)</span> -&gt;</span>
repl.addListener <span class="hljs-string">'line'</span>, <span class="hljs-function"><span class="hljs-params">(code)</span> -&gt;</span>
<span class="hljs-keyword">if</span> code <span class="hljs-keyword">and</span> code.length <span class="hljs-keyword">and</span> code <span class="hljs-keyword">isnt</span> <span class="hljs-string">'.history'</span> <span class="hljs-keyword">and</span> code <span class="hljs-keyword">isnt</span> <span class="hljs-string">'.exit'</span> <span class="hljs-keyword">and</span> lastLine <span class="hljs-keyword">isnt</span> code</pre></div></div>
</li>
@ -667,7 +667,7 @@ undeclared variable <code>__</code>.</p>
<div class="content"><div class='highlight'><pre> repl.commands[getCommandId(repl, <span class="hljs-string">'history'</span>)] =
help: <span class="hljs-string">'Show command history'</span>
action: <span class="hljs-function">-&gt;</span>
repl.outputStream.write <span class="hljs-string">"<span class="hljs-subst">#{repl.rli.history[..].reverse().join <span class="hljs-string">'\n'</span>}</span>\n"</span>
repl.outputStream.write <span class="hljs-string">"<span class="hljs-subst">#{repl.history[..].reverse().join <span class="hljs-string">'\n'</span>}</span>\n"</span>
repl.displayPrompt()
<span class="hljs-function">
<span class="hljs-title">getCommandId</span> = <span class="hljs-params">(repl, commandName)</span> -&gt;</span></pre></div></div>
@ -742,7 +742,7 @@ the REPL, the only applicable option is <code>transpile</code>.</p>
opts = merge replDefaults, opts
repl = nodeREPL.start opts
runInContext opts.prelude, repl.context, <span class="hljs-string">'prelude'</span> <span class="hljs-keyword">if</span> opts.prelude
repl.<span class="hljs-literal">on</span> <span class="hljs-string">'exit'</span>, <span class="hljs-function">-&gt;</span> repl.outputStream.write <span class="hljs-string">'\n'</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> repl.rli.closed
repl.<span class="hljs-literal">on</span> <span class="hljs-string">'exit'</span>, <span class="hljs-function">-&gt;</span> repl.outputStream.write <span class="hljs-string">'\n'</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> repl.closed
addMultilineHandler repl
addHistory repl, opts.historyFile, opts.historyMaxInputSize <span class="hljs-keyword">if</span> opts.historyFile</pre></div></div>

View File

@ -4,7 +4,7 @@
// 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, formatSourcePosition, getSourceMap, helpers, lexer, packageJson, parser, sourceMaps, sources, withPrettyErrors,
var FILE_EXTENSIONS, Lexer, SourceMap, base64encode, checkShebangLine, compile, formatSourcePosition, getSourceMap, helpers, lexer, packageJson, parser, registerCompiled, sourceMaps, sources, withPrettyErrors,
indexOf = [].indexOf;
({Lexer} = require('./lexer'));
@ -74,6 +74,24 @@
// 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 = function(filename, source, sourcemap) {
if (sources[filename] == null) {
sources[filename] = [];
}
sources[filename].push(source);
if (sourcemap != null) {
if (sourceMaps[filename] == null) {
sourceMaps[filename] = [];
}
return 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
@ -94,10 +112,6 @@
generateSourceMap = options.sourceMap || options.inlineMap || (options.filename == null);
filename = options.filename || '<anonymous>';
checkShebangLine(filename, code);
if (sources[filename] == null) {
sources[filename] = [];
}
sources[filename].push(code);
if (generateSourceMap) {
map = new SourceMap();
}
@ -177,10 +191,6 @@
}
if (generateSourceMap) {
v3SourceMap = map.generate(options, code);
if (sourceMaps[filename] == null) {
sourceMaps[filename] = [];
}
sourceMaps[filename].push(map);
}
if (options.transpile) {
if (typeof options.transpile !== 'object') {
@ -211,6 +221,7 @@
sourceURL = `//# sourceURL=${(ref1 = options.filename) != null ? ref1 : 'coffeescript'}`;
js = `${js}\n${sourceMapDataURI}\n${sourceURL}`;
}
registerCompiled(filename, code, map);
if (options.sourceMap) {
return {
js,

View File

@ -412,6 +412,11 @@
false,
$1);
}),
o('DYNAMIC_IMPORT Arguments',
function() {
return new DynamicImportCall(LOC(1)(new DynamicImport()),
$2);
}),
o('SimpleObjAssignable Arguments',
function() {
return new Call(new Value($1),
@ -1077,6 +1082,11 @@
$3,
$2,
$1);
}),
o('DYNAMIC_IMPORT Arguments',
function() {
return new DynamicImportCall(LOC(1)(new DynamicImport()),
$2);
})
],
// An optional existence check on a function.
@ -2321,7 +2331,7 @@
// And not:
// (2 + 3) * 4
operators = [['right', 'DO_IIFE'], ['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY', 'DO'], ['right', 'AWAIT'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', '&'], ['left', '^'], ['left', '|'], ['left', '&&'], ['left', '||'], ['left', 'BIN?'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', 'YIELD'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'FORFROM', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT'], ['left', 'POST_IF']];
operators = [['right', 'DO_IIFE'], ['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY', 'DO'], ['right', 'AWAIT'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', '&'], ['left', '^'], ['left', '|'], ['left', '&&'], ['left', '||'], ['left', 'BIN?'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', 'YIELD'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'FORFROM', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT', 'DYNAMIC_IMPORT'], ['left', 'POST_IF']];
// Wrapping Up
// -----------

View File

@ -152,9 +152,8 @@
}
}
CoffeeScript._compileFile = function(filename, options = {}) {
var answer, err, raw, stripped;
raw = fs.readFileSync(filename, 'utf8');
CoffeeScript._compileRawFileContent = function(raw, filename, options = {}) {
var answer, err, stripped;
// Strip the Unicode byte order mark, if this file begins with one.
stripped = raw.charCodeAt(0) === 0xFEFF ? raw.substring(1) : raw;
options = Object.assign({}, options, {
@ -175,6 +174,12 @@
return answer;
};
CoffeeScript._compileFile = function(filename, options = {}) {
var raw;
raw = fs.readFileSync(filename, 'utf8');
return CoffeeScript._compileRawFileContent(raw, filename, options);
};
module.exports = CoffeeScript;
}).call(this);

View File

@ -997,6 +997,9 @@
return value.length;
}
}
if (value === '(' && (prev != null ? prev[0] : void 0) === 'IMPORT') {
prev[0] = 'DYNAMIC_IMPORT';
}
if (value === '{' && this.seenImport) {
this.importSpecifierList = true;
} else if (this.importSpecifierList && value === '}') {
@ -1714,7 +1717,7 @@
// Tokens which could legitimately be invoked or indexed. An opening
// parentheses or bracket following these tokens will be recorded as the start
// of a function invocation or indexing operation.
CALLABLE = ['IDENTIFIER', 'PROPERTY', ')', ']', '?', '@', 'THIS', 'SUPER'];
CALLABLE = ['IDENTIFIER', 'PROPERTY', ')', ']', '?', '@', 'THIS', 'SUPER', 'DYNAMIC_IMPORT'];
INDEXABLE = CALLABLE.concat(['NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']);

View File

@ -4,7 +4,7 @@
// nodes are created as the result of actions in the [grammar](grammar.html),
// but some are created by other nodes as a method of code generation. To convert
// the syntax tree into a string of JavaScript code, call `compile()` on the root.
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXAttribute, CSXAttributes, CSXElement, CSXExpressionContainer, CSXIdentifier, CSXTag, Call, Catch, Class, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Elision, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncDirectiveReturn, FuncGlyph, HEREGEX_OMIT, HereComment, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, Interpolation, JS_FORBIDDEN, LEADING_BLANK_LINE, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, LineComment, Literal, MetaProperty, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, ObjectProperty, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, Root, SIMPLENUM, SIMPLE_STRING_OMIT, STRING_OMIT, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, SwitchCase, SwitchWhen, TAB, THIS, TRAILING_BLANK_LINE, TaggedTemplateCall, TemplateElement, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addDataToNode, attachCommentsToNode, compact, del, ends, extend, flatten, fragmentsToText, greater, hasLineComments, indentInitial, isAstLocGreater, isFunction, isLiteralArguments, isLiteralThis, isLocationDataEndGreater, isLocationDataStartGreater, isNumber, isPlainObject, isUnassignable, jisonLocationDataToAstLocationData, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, mergeLocationData, moveComments, multident, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility,
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXAttribute, CSXAttributes, CSXElement, CSXExpressionContainer, CSXIdentifier, CSXTag, Call, Catch, Class, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, DynamicImport, DynamicImportCall, Elision, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncDirectiveReturn, FuncGlyph, HEREGEX_OMIT, HereComment, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, Interpolation, JS_FORBIDDEN, LEADING_BLANK_LINE, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, LineComment, Literal, MetaProperty, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, ObjectProperty, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, Root, SIMPLENUM, SIMPLE_STRING_OMIT, STRING_OMIT, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, SwitchCase, SwitchWhen, TAB, THIS, TRAILING_BLANK_LINE, TaggedTemplateCall, TemplateElement, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addDataToNode, attachCommentsToNode, compact, del, ends, extend, flatten, fragmentsToText, greater, hasLineComments, indentInitial, isAstLocGreater, isFunction, isLiteralArguments, isLiteralThis, isLocationDataEndGreater, isLocationDataStartGreater, isNumber, isPlainObject, isUnassignable, jisonLocationDataToAstLocationData, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, mergeLocationData, moveComments, multident, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility,
indexOf = [].indexOf,
splice = [].splice,
slice1 = [].slice;
@ -282,7 +282,7 @@
// Occasionally it may be useful to make an expression behave as if it was 'hoisted', whereby the
// result of the expression is available before its location in the source, but the expression's
// variable scope corresponds the source position. This is used extensively to deal with executable
// variable scope corresponds to the source position. This is used extensively to deal with executable
// class bodies in classes.
// Calling this method mutates the node, proxying the `compileNode` and `compileToFragments`
@ -3468,10 +3468,10 @@
return [this.makeCode('[]')];
}
o.indent += TAB;
fragmentIsElision = function(fragment) {
return fragmentsToText(fragment).trim() === ',';
fragmentIsElision = function([fragment]) {
return fragment.type === 'Elision' && fragment.code.trim() === ',';
};
// Detect if `Elisions` at the beginning of the array are processed (e.g. [, , , a]).
// Detect if `Elision`s at the beginning of the array are processed (e.g. [, , , a]).
passedElision = false;
answer = [];
ref1 = this.objects;
@ -3528,7 +3528,7 @@
fragment = answer[fragmentIndex];
if (fragment.isHereComment) {
fragment.code = `${multident(fragment.code, o.indent, false)}\n${o.indent}`;
} else if (fragment.code === ', ' && !(fragment != null ? fragment.isElision : void 0)) {
} else if (fragment.code === ', ' && !(fragment != null ? fragment.isElision : void 0) && fragment.type !== 'StringLiteral') {
fragment.code = `,\n${o.indent}`;
}
}
@ -4013,7 +4013,7 @@
return this.body.traverseChildren(false, (node) => {
if (node instanceof ThisLiteral) {
return node.value = this.name;
} else if (node instanceof Code && node.bound && node.isStatic) {
} else if (node instanceof Code && node.bound && (node.isStatic || !node.name)) {
return node.context = this.name;
}
});
@ -4403,6 +4403,23 @@
};
exports.DynamicImport = DynamicImport = class DynamicImport extends Base {
compileNode() {
return [this.makeCode('import')];
}
};
exports.DynamicImportCall = DynamicImportCall = class DynamicImportCall extends Call {
compileNode(o) {
if (this.args.length !== 1) {
this.error('import() requires exactly one argument');
}
return super.compileNode(o);
}
};
//### Assign
// The **Assign** is used to assign a local variable to value, or to set the
@ -5223,7 +5240,7 @@
// (e.g. `ref`), shift those into the parent scope since we cant put a
// `var` line inside a function parameter list.
scopeVariablesCount = o.scope.variables.length;
signature.push(...param.compileToFragments(o));
signature.push(...param.compileToFragments(o, LEVEL_PAREN));
if (scopeVariablesCount !== o.scope.variables.length) {
generatedVariables = o.scope.variables.splice(scopeVariablesCount);
o.scope.parent.variables.push(...generatedVariables);

File diff suppressed because one or more lines are too long

View File

@ -113,8 +113,8 @@
};
addMultilineHandler = function(repl) {
var inputStream, multiline, nodeLineListener, origPrompt, outputStream, ref, rli;
({rli, inputStream, outputStream} = repl);
var inputStream, multiline, nodeLineListener, origPrompt, outputStream, ref;
({inputStream, outputStream} = repl);
// Node 0.11.12 changed API, prompt is now _prompt.
origPrompt = (ref = repl._prompt) != null ? ref : repl.prompt;
multiline = {
@ -128,15 +128,15 @@
buffer: ''
};
// Proxy node's line listener
nodeLineListener = rli.listeners('line')[0];
rli.removeListener('line', nodeLineListener);
rli.on('line', function(cmd) {
nodeLineListener = repl.listeners('line')[0];
repl.removeListener('line', nodeLineListener);
repl.on('line', function(cmd) {
if (multiline.enabled) {
multiline.buffer += `${cmd}\n`;
rli.setPrompt(multiline.prompt);
rli.prompt(true);
repl.setPrompt(multiline.prompt);
repl.prompt(true);
} else {
rli.setPrompt(origPrompt);
repl.setPrompt(origPrompt);
nodeLineListener(cmd);
}
});
@ -149,28 +149,28 @@
// allow arbitrarily switching between modes any time before multiple lines are entered
if (!multiline.buffer.match(/\n/)) {
multiline.enabled = !multiline.enabled;
rli.setPrompt(origPrompt);
rli.prompt(true);
repl.setPrompt(origPrompt);
repl.prompt(true);
return;
}
// no-op unless the current line is empty
if ((rli.line != null) && !rli.line.match(/^\s*$/)) {
if ((repl.line != null) && !repl.line.match(/^\s*$/)) {
return;
}
// eval, print, loop
multiline.enabled = !multiline.enabled;
rli.line = '';
rli.cursor = 0;
rli.output.cursorTo(0);
rli.output.clearLine(1);
repl.line = '';
repl.cursor = 0;
repl.output.cursorTo(0);
repl.output.clearLine(1);
// XXX: multiline hack
multiline.buffer = multiline.buffer.replace(/\n/g, '\uFF00');
rli.emit('line', multiline.buffer);
repl.emit('line', multiline.buffer);
multiline.buffer = '';
} else {
multiline.enabled = !multiline.enabled;
rli.setPrompt(multiline.initialPrompt);
rli.prompt(true);
repl.setPrompt(multiline.initialPrompt);
repl.prompt(true);
}
});
};
@ -189,20 +189,20 @@
fs.readSync(readFd, buffer, 0, size, stat.size - size);
fs.closeSync(readFd);
// Set the history on the interpreter
repl.rli.history = buffer.toString().split('\n').reverse();
repl.history = buffer.toString().split('\n').reverse();
if (stat.size > maxSize) {
// If the history file was truncated we should pop off a potential partial line
repl.rli.history.pop();
repl.history.pop();
}
if (repl.rli.history[0] === '') {
if (repl.history[0] === '') {
// Shift off the final blank newline
repl.rli.history.shift();
repl.history.shift();
}
repl.rli.historyIndex = -1;
lastLine = repl.rli.history[0];
repl.historyIndex = -1;
lastLine = repl.history[0];
} catch (error) {}
fd = fs.openSync(filename, 'a');
repl.rli.addListener('line', function(code) {
repl.addListener('line', function(code) {
if (code && code.length && code !== '.history' && code !== '.exit' && lastLine !== code) {
// Save the latest command in the file
fs.writeSync(fd, `${code}\n`);
@ -220,7 +220,7 @@
return repl.commands[getCommandId(repl, 'history')] = {
help: 'Show command history',
action: function() {
repl.outputStream.write(`${repl.rli.history.slice(0).reverse().join('\n')}\n`);
repl.outputStream.write(`${repl.history.slice(0).reverse().join('\n')}\n`);
return repl.displayPrompt();
}
};
@ -283,7 +283,7 @@
runInContext(opts.prelude, repl.context, 'prelude');
}
repl.on('exit', function() {
if (!repl.rli.closed) {
if (!repl.closed) {
return repl.outputStream.write('\n');
}
});

2207
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -39,16 +39,16 @@
"url": "git://github.com/jashkenas/coffeescript.git"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-preset-minify": "^0.4.3",
"codemirror": "^5.39.2",
"@babel/core": "^7.4.0",
"@babel/preset-env": "^7.4.2",
"babel-preset-minify": "^0.5.0",
"codemirror": "^5.45.0",
"docco": "~0.8.0",
"highlight.js": "~9.12.0",
"jison": ">=0.4.18",
"highlight.js": "~9.15.6",
"jison": "^0.4.18",
"markdown-it": "~8.4.2",
"underscore": "~1.9.1",
"webpack": "~4.17.2"
"webpack": "~4.29.6"
},
"dependencies": {}
}

View File

@ -54,6 +54,20 @@ 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
@ -75,8 +89,6 @@ exports.compile = compile = withPrettyErrors (code, options = {}) ->
checkShebangLine filename, code
sources[filename] ?= []
sources[filename].push code
map = new SourceMap if generateSourceMap
tokens = lexer.tokenize code, options
@ -141,8 +153,6 @@ exports.compile = compile = withPrettyErrors (code, options = {}) ->
if generateSourceMap
v3SourceMap = map.generate options, code
sourceMaps[filename] ?= []
sourceMaps[filename].push map
if options.transpile
if typeof options.transpile isnt 'object'
@ -173,6 +183,8 @@ exports.compile = compile = withPrettyErrors (code, options = {}) ->
sourceURL = "//# sourceURL=#{options.filename ? 'coffeescript'}"
js = "#{js}\n#{sourceMapDataURI}\n#{sourceURL}"
registerCompiled filename, code, map
if options.sourceMap
{
js

View File

@ -269,6 +269,7 @@ grammar =
o 'Super'
o 'This'
o 'SUPER Arguments', -> new SuperCall LOC(1)(new Super), $2, no, $1
o 'DYNAMIC_IMPORT Arguments', -> new DynamicImportCall LOC(1)(new DynamicImport), $2
o 'SimpleObjAssignable Arguments', -> new Call (new Value $1), $2
o 'ObjSpreadExpr Arguments', -> new Call $1, $2
]
@ -522,6 +523,7 @@ grammar =
o 'Value OptFuncExist String', -> new TaggedTemplateCall $1, $3, $2
o 'Value OptFuncExist Arguments', -> new Call $1, $3, $2
o 'SUPER OptFuncExist Arguments', -> new SuperCall LOC(1)(new Super), $3, $2, $1
o 'DYNAMIC_IMPORT Arguments', -> new DynamicImportCall LOC(1)(new DynamicImport), $2
]
# An optional existence check on a function.
@ -932,7 +934,7 @@ operators = [
['right', 'YIELD']
['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS']
['right', 'FORIN', 'FOROF', 'FORFROM', 'BY', 'WHEN']
['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT']
['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT', 'DYNAMIC_IMPORT']
['left', 'POST_IF']
]

View File

@ -110,8 +110,8 @@ if require.extensions
Use CoffeeScript.register() or require the coffeescript/register module to require #{ext} files.
"""
CoffeeScript._compileFile = (filename, options = {}) ->
raw = fs.readFileSync filename, 'utf8'
CoffeeScript._compileRawFileContent = (raw, filename, options = {}) ->
# Strip the Unicode byte order mark, if this file begins with one.
stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
@ -131,4 +131,9 @@ CoffeeScript._compileFile = (filename, options = {}) ->
answer
CoffeeScript._compileFile = (filename, options = {}) ->
raw = fs.readFileSync filename, 'utf8'
CoffeeScript._compileRawFileContent raw, filename, options
module.exports = CoffeeScript

View File

@ -689,6 +689,9 @@ exports.Lexer = class Lexer
@error message, origin[2] if message
return value.length if skipToken
if value is '(' and prev?[0] is 'IMPORT'
prev[0] = 'DYNAMIC_IMPORT'
if value is '{' and @seenImport
@importSpecifierList = yes
else if @importSpecifierList and value is '}'
@ -1313,7 +1316,7 @@ BOOL = ['TRUE', 'FALSE']
# Tokens which could legitimately be invoked or indexed. An opening
# parentheses or bracket following these tokens will be recorded as the start
# of a function invocation or indexing operation.
CALLABLE = ['IDENTIFIER', 'PROPERTY', ')', ']', '?', '@', 'THIS', 'SUPER']
CALLABLE = ['IDENTIFIER', 'PROPERTY', ')', ']', '?', '@', 'THIS', 'SUPER', 'DYNAMIC_IMPORT']
INDEXABLE = CALLABLE.concat [
'NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_END', 'REGEX', 'REGEX_END'
'BOOL', 'NULL', 'UNDEFINED', '}', '::'

View File

@ -208,7 +208,7 @@ exports.Base = class Base
# Occasionally it may be useful to make an expression behave as if it was 'hoisted', whereby the
# result of the expression is available before its location in the source, but the expression's
# variable scope corresponds the source position. This is used extensively to deal with executable
# variable scope corresponds to the source position. This is used extensively to deal with executable
# class bodies in classes.
#
# Calling this method mutates the node, proxying the `compileNode` and `compileToFragments`
@ -2337,8 +2337,9 @@ exports.Arr = class Arr extends Base
compileNode: (o) ->
return [@makeCode '[]'] unless @objects.length
o.indent += TAB
fragmentIsElision = (fragment) -> fragmentsToText(fragment).trim() is ','
# Detect if `Elisions` at the beginning of the array are processed (e.g. [, , , a]).
fragmentIsElision = ([ fragment ]) ->
fragment.type is 'Elision' and fragment.code.trim() is ','
# Detect if `Elision`s at the beginning of the array are processed (e.g. [, , , a]).
passedElision = no
answer = []
@ -2376,7 +2377,7 @@ exports.Arr = class Arr extends Base
for fragment, fragmentIndex in answer
if fragment.isHereComment
fragment.code = "#{multident(fragment.code, o.indent, no)}\n#{o.indent}"
else if fragment.code is ', ' and not fragment?.isElision
else if fragment.code is ', ' and not fragment?.isElision and fragment.type isnt 'StringLiteral'
fragment.code = ",\n#{o.indent}"
answer.unshift @makeCode "[\n#{o.indent}"
answer.push @makeCode "\n#{@tab}]"
@ -2701,7 +2702,7 @@ exports.ExecutableClassBody = class ExecutableClassBody extends Base
@body.traverseChildren false, (node) =>
if node instanceof ThisLiteral
node.value = @name
else if node instanceof Code and node.bound and node.isStatic
else if node instanceof Code and node.bound and (node.isStatic or not node.name)
node.context = @name
# Make class/prototype assignments for invalid ES properties
@ -2940,6 +2941,16 @@ exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier
local: originalAst
exported: @alias?.ast(o) ? originalAst
exports.DynamicImport = class DynamicImport extends Base
compileNode: ->
[@makeCode 'import']
exports.DynamicImportCall = class DynamicImportCall extends Call
compileNode: (o) ->
unless @args.length is 1
@error 'import() requires exactly one argument'
super o
#### Assign
# The **Assign** is used to assign a local variable to value, or to set the
@ -3528,7 +3539,7 @@ exports.Code = class Code extends Base
# (e.g. `ref`), shift those into the parent scope since we cant put a
# `var` line inside a function parameter list.
scopeVariablesCount = o.scope.variables.length
signature.push param.compileToFragments(o)...
signature.push param.compileToFragments(o, LEVEL_PAREN)...
if scopeVariablesCount isnt o.scope.variables.length
generatedVariables = o.scope.variables.splice scopeVariablesCount
o.scope.parent.variables.push generatedVariables...
@ -5107,7 +5118,7 @@ isLocationDataEndGreater = (a, b) ->
# encompasses the location data of both nodes. So the new `first_line` value
# will be the earlier of the two nodes `first_line` values, the new
# `last_column` the later of the two nodes `last_column` values, etc.
#
#
# If you only want to extend the first nodes location data with the start or
# end location data of the second node, pass the `justLeading` or `justEnding`
# options. So e.g. if `first`s range is [4, 5] and `second`s range is [1, 10],
@ -5164,7 +5175,7 @@ exports.mergeLocationData = mergeLocationData = (locationDataA, locationDataB, {
# location data object that encompasses the location data of both nodes. So the
# new `start` value will be the earlier of the two nodes `start` values, the
# new `end` value will be the later of the two nodes `end` values, etc.
#
#
# If you only want to extend the first nodes location data with the start or
# end location data of the second node, pass the `justLeading` or `justEnding`
# options. So e.g. if `first`s range is [4, 5] and `second`s range is [1, 10],

View File

@ -75,7 +75,7 @@ runInContext = (js, context, filename) ->
vm.runInContext js, context, filename
addMultilineHandler = (repl) ->
{rli, inputStream, outputStream} = repl
{inputStream, outputStream} = repl
# Node 0.11.12 changed API, prompt is now _prompt.
origPrompt = repl._prompt ? repl.prompt
@ -86,15 +86,15 @@ addMultilineHandler = (repl) ->
buffer: ''
# Proxy node's line listener
nodeLineListener = rli.listeners('line')[0]
rli.removeListener 'line', nodeLineListener
rli.on 'line', (cmd) ->
nodeLineListener = repl.listeners('line')[0]
repl.removeListener 'line', nodeLineListener
repl.on 'line', (cmd) ->
if multiline.enabled
multiline.buffer += "#{cmd}\n"
rli.setPrompt multiline.prompt
rli.prompt true
repl.setPrompt multiline.prompt
repl.prompt true
else
rli.setPrompt origPrompt
repl.setPrompt origPrompt
nodeLineListener cmd
return
@ -105,25 +105,25 @@ addMultilineHandler = (repl) ->
# allow arbitrarily switching between modes any time before multiple lines are entered
unless multiline.buffer.match /\n/
multiline.enabled = not multiline.enabled
rli.setPrompt origPrompt
rli.prompt true
repl.setPrompt origPrompt
repl.prompt true
return
# no-op unless the current line is empty
return if rli.line? and not rli.line.match /^\s*$/
return if repl.line? and not repl.line.match /^\s*$/
# eval, print, loop
multiline.enabled = not multiline.enabled
rli.line = ''
rli.cursor = 0
rli.output.cursorTo 0
rli.output.clearLine 1
repl.line = ''
repl.cursor = 0
repl.output.cursorTo 0
repl.output.clearLine 1
# XXX: multiline hack
multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
rli.emit 'line', multiline.buffer
repl.emit 'line', multiline.buffer
multiline.buffer = ''
else
multiline.enabled = not multiline.enabled
rli.setPrompt multiline.initialPrompt
rli.prompt true
repl.setPrompt multiline.initialPrompt
repl.prompt true
return
# Store and load command history from a file
@ -139,17 +139,17 @@ addHistory = (repl, filename, maxSize) ->
fs.readSync readFd, buffer, 0, size, stat.size - size
fs.closeSync readFd
# Set the history on the interpreter
repl.rli.history = buffer.toString().split('\n').reverse()
repl.history = buffer.toString().split('\n').reverse()
# If the history file was truncated we should pop off a potential partial line
repl.rli.history.pop() if stat.size > maxSize
repl.history.pop() if stat.size > maxSize
# Shift off the final blank newline
repl.rli.history.shift() if repl.rli.history[0] is ''
repl.rli.historyIndex = -1
lastLine = repl.rli.history[0]
repl.history.shift() if repl.history[0] is ''
repl.historyIndex = -1
lastLine = repl.history[0]
fd = fs.openSync filename, 'a'
repl.rli.addListener 'line', (code) ->
repl.addListener 'line', (code) ->
if code and code.length and code isnt '.history' and code isnt '.exit' and lastLine isnt code
# Save the latest command in the file
fs.writeSync fd, "#{code}\n"
@ -163,7 +163,7 @@ addHistory = (repl, filename, maxSize) ->
repl.commands[getCommandId(repl, 'history')] =
help: 'Show command history'
action: ->
repl.outputStream.write "#{repl.rli.history[..].reverse().join '\n'}\n"
repl.outputStream.write "#{repl.history[..].reverse().join '\n'}\n"
repl.displayPrompt()
getCommandId = (repl, commandName) ->
@ -212,7 +212,7 @@ module.exports =
opts = merge replDefaults, opts
repl = nodeREPL.start opts
runInContext opts.prelude, repl.context, 'prelude' if opts.prelude
repl.on 'exit', -> repl.outputStream.write '\n' if not repl.rli.closed
repl.on 'exit', -> repl.outputStream.write '\n' if not repl.closed
addMultilineHandler repl
addHistory repl, opts.historyFile, opts.historyMaxInputSize if opts.historyFile
# Adapt help inherited from the node REPL

View File

@ -115,6 +115,12 @@ test "array elisions nested destructuring", ->
deepEqual d, {x:2}
arrayEq w, [1,2,4]
test "#5112: array elisions not detected inside strings", ->
arr = [
str: ", #{3}"
]
eq arr[0].str, ', 3'
# Splats in Array Literals
test "array splat expansions with assignments", ->

View File

@ -1917,3 +1917,16 @@ test "#4609: Support new.target", ->
new Foo()
eq newTarget, yes
test "#5085: Bug: @ reference to class not maintained in do block", ->
thisFoo = 'initial foo'
thisBar = 'initial bar'
fn = (o) -> o.bar()
class A
@foo = 'foo assigned in class'
do => thisFoo = @foo
fn bar: => thisBar = @foo
eq thisFoo, 'foo assigned in class'
eq thisBar, 'foo assigned in class'

View File

@ -1946,3 +1946,29 @@ test "`new.target` cannot be assigned", ->
new.target = b
^
'''
test "#4834: dynamic import requires exactly one argument", ->
assertErrorFormat '''
import()
''', '''
[stdin]:1:1: error: import() requires exactly one argument
import()
^^^^^^^^
'''
assertErrorFormat '''
import('x', {})
''', '''
[stdin]:1:1: error: import() requires exactly one argument
import('x', {})
^^^^^^^^^^^^^^^
'''
test "#4834: dynamic import requires explicit call parentheses", ->
assertErrorFormat '''
promise = import 'foo'
''', '''
[stdin]:1:23: error: unexpected end of input
promise = import 'foo'
^
'''

View File

@ -493,3 +493,7 @@ test "#4657: destructured array parameters", ->
result = f [1, 2, 3, 4]
arrayEq result.a, [1, 2, 3]
eq result.b, 4
test "#5128: default parameters of function in binary operation", ->
foo = yes or (a, b = {}) -> null
eq foo, yes

View File

@ -920,3 +920,24 @@ test "#4874: backslash `export`", ->
min
} from 'underscore';
"""
test "#4834: dynamic import", ->
eqJS """
import('module').then ->
""",
"""
import('module').then(function() {});
"""
eqJS """
foo = ->
bar = await import('bar')
""",
"""
var foo;
foo = async function() {
var bar;
return bar = (await import('bar'));
};
"""

View File

@ -418,3 +418,13 @@ test "#4673: complex destructured object spread variables", ->
g = ({@y...}) ->
eq @y.b, 1
g b: 1
test "#4834: dynamic import can technically be object spread", ->
eqJS """
x = {...import('module')}
""",
"""
var x;
x = {...import('module')};
"""

View File

@ -45,7 +45,7 @@ ctrlV = { ctrl: true, name: 'v'}
testRepl 'reads history file', (input, output, repl) ->
input.emitLine repl.rli.history[0]
input.emitLine repl.history[0]
eq '3', output.lastWrite()
testRepl "starts with coffee prompt", (input, output) ->