AST: numeric separators, BigInt (#5272)

* Revert to more complicated lexing of numbers, as the Number constructor can't handle BigInts or numbers with numeric separators

* Add debugging information to error message test (#5239)

One of the test cases in test/error_messages.coffee fails intermittently
in the Node.js ecosystem-testing tool CITGM. In an effort to help debug
what's going on when this occurs, this adds more information to the
AssertionError message in question.

* Fix #5103: Add support for BigInt literals (#5104)

* Fix #5103: Add support for BigInt literals

* Fix typos found in testing

* Support binary, octal and hex BigInt literals

* Make decimal BigInt test consistent other bases

* Correct test BigInt test names

* Add Node versions to CI

* Numeric literal separators (#5215)

* implement numeric literal separators

* add tests

* Revert changes to package-lock.json

* small regex adjustment

* split tests

* add comment

* Add Node versions to CI

* Fix #5103: Add support for BigInt literals (#5104)

* Fix #5103: Add support for BigInt literals

* Fix typos found in testing

* Support binary, octal and hex BigInt literals

* Make decimal BigInt test consistent other bases

* Correct test BigInt test names

* Add Node versions to CI

* Update output

* Fix style

* support bigint literal with separators

* un-disallow property access on number literal

* Update output

* Refactor numeric literal separator tests to be more like the rest of the tests

* Add test for numeric property with underscore

Co-authored-by: Geoffrey Booth <GeoffreyBooth@users.noreply.github.com>
Co-authored-by: Robert de Forest <guitar.robot@gmail.com>

* Update test style and output

* numeric separator parsed value

* BigInt AST; parseNumber()

Co-authored-by: Geoffrey Booth <GeoffreyBooth@users.noreply.github.com>
Co-authored-by: Rich Trott <rtrott@gmail.com>
Co-authored-by: Robert de Forest <guitar.robot@gmail.com>
Co-authored-by: square <Inve1951@users.noreply.github.com>
This commit is contained in:
Julian Rosse
2019-12-29 17:20:43 -07:00
committed by Geoffrey Booth
parent bdcb2c73af
commit f528e5e754
13 changed files with 221 additions and 51 deletions

View File

@@ -4,6 +4,8 @@ node_js:
- 6
- 8
- 10
- 12
- node # Latest
cache:
directories:

View File

@@ -472,6 +472,8 @@ runTests = (CoffeeScript) ->
skipUnless 'var a = 2 ** 2; a **= 3', ['exponentiation.coffee']
skipUnless 'var {...a} = {}', ['object_rest_spread.coffee']
skipUnless '/foo.bar/s.test("foo\tbar")', ['regex_dotall.coffee']
skipUnless '1_2_3', ['numeric_literal_separators.coffee']
skipUnless '1n', ['numbers_bigint.coffee']
files = fs.readdirSync('test').filter (filename) ->
filename not in testFilesToSkip

View File

@@ -3,7 +3,8 @@ environment:
- nodejs_version: '6'
- nodejs_version: '8'
- nodejs_version: '10'
- nodejs_version: '' # Installs latest.
- nodejs_version: '12'
- nodejs_version: '' # Latest
install:
- ps: Install-Product node $env:nodejs_version

View File

@@ -202,7 +202,7 @@
// Build a dictionary of extra token properties organized by tokens locations
// used as lookup hashes.
exports.buildTokenDataDictionary = buildTokenDataDictionary = function(tokens) {
var base, i, len1, token, tokenData, tokenHash;
var base1, i, len1, token, tokenData, tokenHash;
tokenData = {};
for (i = 0, len1 = tokens.length; i < len1; i++) {
token = tokens[i];
@@ -221,7 +221,7 @@
// and therefore matching `tokenHash`es, merge the comments from both/all
// tokens together into one array, even if there are duplicate comments;
// they will get sorted out later.
((base = tokenData[tokenHash]).comments != null ? base.comments : base.comments = []).push(...token.comments);
((base1 = tokenData[tokenHash]).comments != null ? base1.comments : base1.comments = []).push(...token.comments);
}
}
return tokenData;
@@ -385,6 +385,30 @@ ${marker}`;
}
};
exports.parseNumber = function(string) {
var base;
if (string == null) {
return 0/0;
}
base = (function() {
switch (string.charAt(1)) {
case 'b':
return 2;
case 'o':
return 8;
case 'x':
return 16;
default:
return null;
}
})();
if (base != null) {
return parseInt(string.slice(2).replace(/_/g, ''), base);
} else {
return parseFloat(string.replace(/_/g, ''));
}
};
exports.isFunction = function(obj) {
return Object.prototype.toString.call(obj) === '[object Function]';
};

View File

@@ -10,14 +10,14 @@
// where locationData is {first_line, first_column, last_line, last_column, last_line_exclusive, last_column_exclusive}, which is a
// format that can be fed directly into [Jison](https://github.com/zaach/jison). These
// are read by jison in the `parser.lexer` function defined in coffeescript.coffee.
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_COMMENT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_JSX, INVERSES, JSTOKEN, JSX_ATTRIBUTE, JSX_FRAGMENT_IDENTIFIER, JSX_IDENTIFIER, JSX_IDENTIFIER_PART, JSX_INTERPOLATION, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_SINGLE, STRING_START, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, VALID_FLAGS, WHITESPACE, addTokenData, attachCommentsToNode, compact, count, flatten, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, repeat, replaceUnicodeCodePointEscapes, starts, throwSyntaxError,
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_COMMENT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_JSX, INVERSES, JSTOKEN, JSX_ATTRIBUTE, JSX_FRAGMENT_IDENTIFIER, JSX_IDENTIFIER, JSX_IDENTIFIER_PART, JSX_INTERPOLATION, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_SINGLE, STRING_START, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, VALID_FLAGS, WHITESPACE, addTokenData, attachCommentsToNode, compact, count, flatten, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, parseNumber, repeat, replaceUnicodeCodePointEscapes, starts, throwSyntaxError,
indexOf = [].indexOf,
slice = [].slice;
({Rewriter, INVERSES} = require('./rewriter'));
// Import the helpers we need.
({count, starts, compact, repeat, invertLiterate, merge, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, flatten} = require('./helpers'));
({count, starts, compact, repeat, invertLiterate, merge, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, flatten, parseNumber} = require('./helpers'));
// The Lexer Class
// ---------------
@@ -323,19 +323,7 @@
length: lexedLength
});
}
base = (function() {
switch (number.charAt(1)) {
case 'b':
return 2;
case 'o':
return 8;
case 'x':
return 16;
default:
return null;
}
})();
parsedValue = base != null ? parseInt(number.slice(2), base) : parseFloat(number);
parsedValue = parseNumber(number);
tokenData = {parsedValue};
tag = parsedValue === 2e308 ? 'INFINITY' : 'NUMBER';
if (tag === 'INFINITY') {
@@ -1802,10 +1790,13 @@
// Is this an attribute with a value?
}(?:\\s*:\\s*${JSX_IDENTIFIER_PART})?)([^\\S]*=(?!=))?`);
NUMBER = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i; // binary
NUMBER = /^0b[01](?:_?[01])*n?|^0o[0-7](?:_?[0-7])*n?|^0x[\da-f](?:_?[\da-f])*n?|^\d+n|^(?:\d(?:_?\d)*)?\.?(?:\d(?:_?\d)*)+(?:e[+-]?(?:\d(?:_?\d)*)+)?/i; // binary
// octal
// hex
// decimal bigint
// decimal
// decimal without support for numeric literal separators for reference:
// \d*\.?\d+ (?:e[+-]?\d+)?
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/; // function
// compound assign / compare

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, Call, Catch, Class, ClassProperty, ClassPrototypeProperty, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Directive, DynamicImport, DynamicImportCall, Elision, EmptyInterpolation, 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, JSXAttribute, JSXAttributes, JSXElement, JSXEmptyExpression, JSXExpressionContainer, JSXIdentifier, JSXNamespacedName, JSXTag, JSXText, 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, Sequence, 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, astAsBlockIfNeeded, attachCommentsToNode, compact, del, emptyExpressionLocationData, ends, extend, extractSameLineLocationDataFirst, extractSameLineLocationDataLast, 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, sniffDirectives, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility, zeroWidthLocationDataFromEndLocation,
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Catch, Class, ClassProperty, ClassPrototypeProperty, Code, CodeFragment, ComputedPropertyName, DefaultLiteral, Directive, DynamicImport, DynamicImportCall, Elision, EmptyInterpolation, 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, JSXAttribute, JSXAttributes, JSXElement, JSXEmptyExpression, JSXExpressionContainer, JSXIdentifier, JSXNamespacedName, JSXTag, JSXText, 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, Sequence, 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, astAsBlockIfNeeded, attachCommentsToNode, compact, del, emptyExpressionLocationData, ends, extend, extractSameLineLocationDataFirst, extractSameLineLocationDataLast, flatten, fragmentsToText, greater, hasLineComments, indentInitial, isAstLocGreater, isFunction, isLiteralArguments, isLiteralThis, isLocationDataEndGreater, isLocationDataStartGreater, isNumber, isPlainObject, isUnassignable, jisonLocationDataToAstLocationData, lesser, locationDataToString, makeDelimitedLiteral, merge, mergeAstLocationData, mergeLocationData, moveComments, multident, parseNumber, replaceUnicodeCodePointEscapes, shouldCacheOrIsAssignable, sniffDirectives, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility, zeroWidthLocationDataFromEndLocation,
indexOf = [].indexOf,
splice = [].splice,
slice1 = [].slice;
@@ -16,7 +16,7 @@
({isUnassignable, JS_FORBIDDEN} = require('./lexer'));
// Import the helpers we plan to use.
({compact, flatten, extend, merge, del, starts, ends, some, addDataToNode, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, isFunction, isPlainObject, isNumber} = require('./helpers'));
({compact, flatten, extend, merge, del, starts, ends, some, addDataToNode, attachCommentsToNode, locationDataToString, throwSyntaxError, replaceUnicodeCodePointEscapes, isFunction, isPlainObject, isNumber, parseNumber} = require('./helpers'));
// Functions required by parser.
exports.extend = extend;
@@ -1333,20 +1333,28 @@
this.parsedValue = this.value;
this.value = `${this.value}`;
} else {
this.parsedValue = Number(this.value);
this.parsedValue = parseNumber(this.value);
}
}
}
isBigInt() {
return /n$/.test(this.value);
}
astType() {
return 'NumericLiteral';
if (this.isBigInt()) {
return 'BigIntLiteral';
} else {
return 'NumericLiteral';
}
}
astProperties() {
return {
value: this.parsedValue,
value: this.isBigInt() ? this.parsedValue.toString() : this.parsedValue,
extra: {
rawValue: this.parsedValue,
rawValue: this.isBigInt() ? this.parsedValue.toString() : this.parsedValue,
raw: this.value
}
};
@@ -3432,9 +3440,9 @@
if (step = del(o, 'step')) {
[this.step, this.stepVar] = this.cacheToCodeFragments(step.cache(o, LEVEL_LIST, shouldCache));
}
this.fromNum = this.from.isNumber() ? Number(this.fromVar) : null;
this.toNum = this.to.isNumber() ? Number(this.toVar) : null;
return this.stepNum = (step != null ? step.isNumber() : void 0) ? Number(this.stepVar) : null;
this.fromNum = this.from.isNumber() ? parseNumber(this.fromVar) : null;
this.toNum = this.to.isNumber() ? parseNumber(this.toVar) : null;
return this.stepNum = (step != null ? step.isNumber() : void 0) ? parseNumber(this.stepVar) : null;
}
// When compiled normally, the range returns the contents of the *for loop*
@@ -8076,7 +8084,7 @@
if (this.step && !this.range) {
[step, stepVar] = this.cacheToCodeFragments(this.step.cache(o, LEVEL_LIST, shouldCacheOrIsAssignable));
if (this.step.isNumber()) {
stepNum = Number(stepVar);
stepNum = parseNumber(stepVar);
}
}
if (this.pattern) {

View File

@@ -268,6 +268,20 @@ exports.nameWhitespaceCharacter = (string) ->
when '\t' then 'tab'
else string
exports.parseNumber = (string) ->
return NaN unless string?
base = switch string.charAt 1
when 'b' then 2
when 'o' then 8
when 'x' then 16
else null
if base?
parseInt string[2..].replace(/_/g, ''), base
else
parseFloat string.replace(/_/g, '')
exports.isFunction = (obj) -> Object::toString.call(obj) is '[object Function]'
exports.isNumber = isNumber = (obj) -> Object::toString.call(obj) is '[object Number]'
exports.isString = isString = (obj) -> Object::toString.call(obj) is '[object String]'

View File

@@ -14,7 +14,7 @@
# Import the helpers we need.
{count, starts, compact, repeat, invertLiterate, merge,
attachCommentsToNode, locationDataToString, throwSyntaxError
replaceUnicodeCodePointEscapes, flatten} = require './helpers'
replaceUnicodeCodePointEscapes, flatten, parseNumber} = require './helpers'
# The Lexer Class
# ---------------
@@ -272,13 +272,7 @@ exports.Lexer = class Lexer
when /^0\d+/.test number
@error "octal literal '#{number}' must be prefixed with '0o'", length: lexedLength
base = switch number.charAt 1
when 'b' then 2
when 'o' then 8
when 'x' then 16
else null
parsedValue = if base? then parseInt(number[2..], base) else parseFloat(number)
parsedValue = parseNumber number
tokenData = {parsedValue}
tag = if parsedValue is Infinity then 'INFINITY' else 'NUMBER'
@@ -1302,10 +1296,14 @@ JSX_ATTRIBUTE = /// ^
///
NUMBER = ///
^ 0b[01]+ | # binary
^ 0o[0-7]+ | # octal
^ 0x[\da-f]+ | # hex
^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal
^ 0b[01](?:_?[01])*n? | # binary
^ 0o[0-7](?:_?[0-7])*n? | # octal
^ 0x[\da-f](?:_?[\da-f])*n? | # hex
^ \d+n | # decimal bigint
^ (?:\d(?:_?\d)*)? \.? (?:\d(?:_?\d)*)+ # decimal
(?:e[+-]? (?:\d(?:_?\d)*)+ )?
# decimal without support for numeric literal separators for reference:
# \d*\.?\d+ (?:e[+-]?\d+)?
///i
OPERATOR = /// ^ (

View File

@@ -12,7 +12,7 @@ Error.stackTraceLimit = Infinity
{compact, flatten, extend, merge, del, starts, ends, some,
addDataToNode, attachCommentsToNode, locationDataToString,
throwSyntaxError, replaceUnicodeCodePointEscapes,
isFunction, isPlainObject, isNumber} = require './helpers'
isFunction, isPlainObject, isNumber, parseNumber} = require './helpers'
# Functions required by parser.
exports.extend = extend
@@ -936,15 +936,30 @@ exports.NumberLiteral = class NumberLiteral extends Literal
@parsedValue = @value
@value = "#{@value}"
else
@parsedValue = Number @value
@parsedValue = parseNumber @value
astType: -> 'NumericLiteral'
isBigInt: ->
/n$/.test @value
astType: ->
if @isBigInt()
'BigIntLiteral'
else
'NumericLiteral'
astProperties: ->
return
value: @parsedValue
value:
if @isBigInt()
@parsedValue.toString()
else
@parsedValue
extra:
rawValue: @parsedValue
rawValue:
if @isBigInt()
@parsedValue.toString()
else
@parsedValue
raw: @value
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
@@ -2296,9 +2311,9 @@ exports.Range = class Range extends Base
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, shouldCache
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, shouldCache
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, shouldCache if step = del o, 'step'
@fromNum = if @from.isNumber() then Number @fromVar else null
@toNum = if @to.isNumber() then Number @toVar else null
@stepNum = if step?.isNumber() then Number @stepVar else null
@fromNum = if @from.isNumber() then parseNumber @fromVar else null
@toNum = if @to.isNumber() then parseNumber @toVar else null
@stepNum = if step?.isNumber() then parseNumber @stepVar else null
# When compiled normally, the range returns the contents of the *for loop*
# needed to iterate over the values in the range. Used by comprehensions.
@@ -5362,7 +5377,7 @@ exports.For = class For extends While
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
if @step and not @range
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, shouldCacheOrIsAssignable
stepNum = Number stepVar if @step.isNumber()
stepNum = parseNumber stepVar if @step.isNumber()
name = ivar if @pattern
varPart = ''
guardPart = ''

View File

@@ -177,6 +177,41 @@ test "AST as expected for NumberLiteral node", ->
rawValue: 225
raw: '0xE1'
testExpression '10_000',
type: 'NumericLiteral'
value: 10000
extra:
rawValue: 10000
raw: '10_000'
testExpression '1_2.34_5e6_7',
type: 'NumericLiteral'
value: 12.345e67
extra:
rawValue: 12.345e67
raw: '1_2.34_5e6_7'
testExpression '0o7_7_7',
type: 'NumericLiteral'
value: 0o777
extra:
rawValue: 0o777
raw: '0o7_7_7'
testExpression '42n',
type: 'BigIntLiteral'
value: '42'
extra:
rawValue: '42'
raw: '42n'
testExpression '2e3_08',
type: 'NumericLiteral'
value: Infinity
extra:
rawValue: Infinity
raw: '2e3_08'
test "AST as expected for InfinityLiteral node", ->
testExpression 'Infinity',
type: 'Identifier'

View File

@@ -92,7 +92,7 @@ if require?
eq error.message, 'hello world'
doesNotThrow(-> error.stack)
notEqual error.stack.toString().indexOf(filePath), -1
notEqual error.stack.toString().indexOf(filePath), -1, "Expected " + filePath + "in stack trace: " + error.stack.toString()
test "#4418: stack traces for compiled files reference the correct line number", ->
# The browser is already compiling other anonymous scripts (the tests)

View File

@@ -0,0 +1,17 @@
# BigInt Literals
# ---------------
test "BigInt exists", ->
'object' is typeof BigInt
test "Parser recognizes decimal BigInt literals", ->
eq 42n, BigInt 42
test "Parser recognizes binary BigInt literals", ->
eq 42n, 0b101010n
test "Parser recognizes octal BigInt literals", ->
eq 42n, 0o52n
test "Parser recognizes hexadecimal BigInt literals", ->
eq 42n, 0x2an

View File

@@ -0,0 +1,63 @@
# Numeric Literal Separators
# --------------------------
test 'integer literals with separators', ->
eq 123_456, 123456
eq 12_34_56, 123456
test 'decimal literals with separators', ->
eq 1_2.34_5, 12.345
eq 1_0e1_0, 10e10
eq 1_2.34_5e6_7, 12.345e67
test 'hexadecimal literals with separators', ->
eq 0x1_2_3_4, 0x1234
test 'binary literals with separators', ->
eq 0b10_10, 0b1010
test 'octal literals with separators', ->
eq 0o7_7_7, 0o777
test 'infinity with separator', ->
eq 2e3_08, Infinity
test 'range with separators', ->
range = [10_000...10_002]
eq range.length, 2
eq range[0], 10000
test 'property access on a number', ->
# Somehow, `3..toFixed()` is valid JavaScript; though just `3.toFixed()`
# is not. CoffeeScript has long allowed code like `3.toFixed()` to compile
# into `3..toFixed()`.
eq 3.toFixed(), '3'
# Where this can conflict with numeric literal separators is when the
# property name contains an underscore.
Number::_23 = _23 = 'x'
eq 1._23, 'x'
ok 1._34 is undefined
delete Number::_23
test 'invalid decimal literal separators do not compile', ->
# `1._23` is a valid property access (see previous test)
throwsCompileError '1_.23'
throwsCompileError '1e_2'
throwsCompileError '1e2_'
throwsCompileError '1_'
throwsCompileError '1__2'
test 'invalid hexadecimal literal separators do not compile', ->
throwsCompileError '0x_1234'
throwsCompileError '0x1234_'
throwsCompileError '0x1__34'
test 'invalid binary literal separators do not compile', ->
throwsCompileError '0b_100'
throwsCompileError '0b100_'
throwsCompileError '0b1__1'
test 'invalid octal literal separators do not compile', ->
throwsCompileError '0o_777'
throwsCompileError '0o777_'
throwsCompileError '0o6__6'