1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

Support import.meta and import.meta.* (#5319)

Co-authored-by: Geoffrey Booth <webmaster@geoffreybooth.com>
This commit is contained in:
Aurélio A. Heckert 2020-05-29 22:16:48 -03:00 committed by GitHub
parent 389ce89555
commit 2fd9ee403c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 358 additions and 168 deletions

View file

@ -2,8 +2,7 @@
name: Build and Test
on:
push
on: [push, pull_request]
jobs:
ci:

View file

@ -39,6 +39,9 @@
patternString = patternString.replace(/\s{2,}/g, ' ');
patternCount = patternString.split(' ').length;
if (action) {
// This code block does string replacements in the generated `parser.js`
// file, replacing the calls to the `LOC` function and other strings as
// listed below.
action = (match = unwrap.exec(action)) ? match[1] : `(${action}())`;
// All runtime functions we need are defined on `yy`
action = action.replace(/\bnew /g, '$&yy.');
@ -50,8 +53,33 @@
getAddDataToNodeFunctionString = function(first, last, forceUpdateLocation = true) {
return `yy.addDataToNode(yy, @${first}, ${first[0] === '$' ? '$$' : '$'}${first}, ${last ? `@${last}, ${last[0] === '$' ? '$$' : '$'}${last}` : 'null, null'}, ${forceUpdateLocation ? 'true' : 'false'})`;
};
// This code replaces the calls to `LOC` with the `yy.addDataToNode` string
// defined above. The `LOC` function, when used below in the grammar rules,
// is used to make sure that newly created node class objects get correct
// location data assigned to them. By default, the grammar will assign the
// location data spanned by *all* of the tokens on the left (e.g. a string
// such as `'Body TERMINATOR Line'`) to the “top-level” node returned by
// the grammar rule (the function on the right). But for “inner” node class
// objects created by grammar rules, they wont get correct location data
// assigned to them without adding `LOC`.
// For example, consider the grammar rule `'NEW_TARGET . Property'`, which
// is handled by a function that returns
// `new MetaProperty LOC(1)(new IdentifierLiteral $1), LOC(3)(new Access $3)`.
// The `1` in `LOC(1)` refers to the first token (`NEW_TARGET`) and the `3`
// in `LOC(3)` refers to the third token (`Property`). In order for the
// `new IdentifierLiteral` to get assigned the location data corresponding
// to `new` in the source code, we use
// `LOC(1)(new IdentifierLiteral ...)` to mean “assign the location data of
// the *first* token of this grammar rule (`NEW_TARGET`) to this
// `new IdentifierLiteral`”. The `LOC(3)` means “assign the location data of
// the *third* token of this grammar rule (`Property`) to this
// `new Access`”.
returnsLoc = /^LOC/.test(action);
action = action.replace(/LOC\(([0-9]*)\)/g, getAddDataToNodeFunctionString('$1'));
// A call to `LOC` with two arguments, e.g. `LOC(2,4)`, sets the location
// data for the generated node on both of the referenced tokens (the second
// and fourth in this example).
action = action.replace(/LOC\(([0-9]*),\s*([0-9]*)\)/g, getAddDataToNodeFunctionString('$1', '$2'));
performActionFunctionString = `$$ = ${getAddDataToNodeFunctionString(1, patternCount, !returnsLoc)}(${action});`;
} else {
@ -700,6 +728,11 @@
function() {
return new MetaProperty(LOC(1)(new IdentifierLiteral($1)),
LOC(3)(new Access($3)));
}),
o('IMPORT_META . Property',
function() {
return new MetaProperty(LOC(1)(new IdentifierLiteral($1)),
LOC(3)(new Access($3)));
})
],
// The general group of accessors into an object, by property, by prototype

View file

@ -226,6 +226,9 @@
this.error(`'${prev[1]}' cannot be used as a keyword, or as a function call without parentheses`, prev[2]);
} else if (prev[0] === '.' && this.tokens.length > 1 && (prevprev = this.tokens[this.tokens.length - 2])[0] === 'UNARY' && prevprev[1] === 'new') {
prevprev[0] = 'NEW_TARGET';
} else if (prev[0] === '.' && this.tokens.length > 1 && (prevprev = this.tokens[this.tokens.length - 2])[0] === 'IMPORT' && prevprev[1] === 'import') {
this.seenImport = false;
prevprev[0] = 'IMPORT_META';
} else if (this.tokens.length > 2) {
prevprev = this.tokens[this.tokens.length - 2];
if (((ref11 = prev[0]) === '@' || ref11 === 'THIS') && prevprev && prevprev.spaced && /^[gs]et$/.test(prevprev[1]) && ((ref12 = this.tokens[this.tokens.length - 3][0]) !== '.' && ref12 !== '?.' && ref12 !== '@')) {

View file

@ -2325,6 +2325,10 @@
} else {
return this.error("the only valid meta property for new is new.target");
}
} else if (this.meta.value === 'import') {
if (!(this.property instanceof Access && this.property.name.value === 'meta')) {
return this.error("the only valid meta property for import is import.meta");
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -34,6 +34,9 @@ o = (patternString, action, options) ->
patternString = patternString.replace /\s{2,}/g, ' '
patternCount = patternString.split(' ').length
if action
# This code block does string replacements in the generated `parser.js`
# file, replacing the calls to the `LOC` function and other strings as
# listed below.
action = if match = unwrap.exec action then match[1] else "(#{action}())"
# All runtime functions we need are defined on `yy`
@ -47,8 +50,33 @@ o = (patternString, action, options) ->
getAddDataToNodeFunctionString = (first, last, forceUpdateLocation = yes) ->
"yy.addDataToNode(yy, @#{first}, #{if first[0] is '$' then '$$' else '$'}#{first}, #{if last then "@#{last}, #{if last[0] is '$' then '$$' else '$'}#{last}" else 'null, null'}, #{if forceUpdateLocation then 'true' else 'false'})"
# This code replaces the calls to `LOC` with the `yy.addDataToNode` string
# defined above. The `LOC` function, when used below in the grammar rules,
# is used to make sure that newly created node class objects get correct
# location data assigned to them. By default, the grammar will assign the
# location data spanned by *all* of the tokens on the left (e.g. a string
# such as `'Body TERMINATOR Line'`) to the top-level node returned by
# the grammar rule (the function on the right). But for inner node class
# objects created by grammar rules, they wont get correct location data
# assigned to them without adding `LOC`.
# For example, consider the grammar rule `'NEW_TARGET . Property'`, which
# is handled by a function that returns
# `new MetaProperty LOC(1)(new IdentifierLiteral $1), LOC(3)(new Access $3)`.
# The `1` in `LOC(1)` refers to the first token (`NEW_TARGET`) and the `3`
# in `LOC(3)` refers to the third token (`Property`). In order for the
# `new IdentifierLiteral` to get assigned the location data corresponding
# to `new` in the source code, we use
# `LOC(1)(new IdentifierLiteral ...)` to mean assign the location data of
# the *first* token of this grammar rule (`NEW_TARGET`) to this
# `new IdentifierLiteral`. The `LOC(3)` means assign the location data of
# the *third* token of this grammar rule (`Property`) to this
# `new Access`.
returnsLoc = /^LOC/.test action
action = action.replace /LOC\(([0-9]*)\)/g, getAddDataToNodeFunctionString('$1')
# A call to `LOC` with two arguments, e.g. `LOC(2,4)`, sets the location
# data for the generated node on both of the referenced tokens (the second
# and fourth in this example).
action = action.replace /LOC\(([0-9]*),\s*([0-9]*)\)/g, getAddDataToNodeFunctionString('$1', '$2')
performActionFunctionString = "$$ = #{getAddDataToNodeFunctionString(1, patternCount, not returnsLoc)}(#{action});"
else
@ -396,6 +424,7 @@ grammar =
# A "meta-property" access e.g. `new.target`
MetaProperty: [
o 'NEW_TARGET . Property', -> new MetaProperty LOC(1)(new IdentifierLiteral $1), LOC(3)(new Access $3)
o 'IMPORT_META . Property', -> new MetaProperty LOC(1)(new IdentifierLiteral $1), LOC(3)(new Access $3)
]
# The general group of accessors into an object, by property, by prototype

View file

@ -214,6 +214,9 @@ exports.Lexer = class Lexer
without parentheses", prev[2]
else if prev[0] is '.' and @tokens.length > 1 and (prevprev = @tokens[@tokens.length - 2])[0] is 'UNARY' and prevprev[1] is 'new'
prevprev[0] = 'NEW_TARGET'
else if prev[0] is '.' and @tokens.length > 1 and (prevprev = @tokens[@tokens.length - 2])[0] is 'IMPORT' and prevprev[1] is 'import'
@seenImport = no
prevprev[0] = 'IMPORT_META'
else if @tokens.length > 2
prevprev = @tokens[@tokens.length - 2]
if prev[0] in ['@', 'THIS'] and prevprev and prevprev.spaced and

View file

@ -1576,6 +1576,9 @@ exports.MetaProperty = class MetaProperty extends Base
@error "new.target can only occur inside functions"
else
@error "the only valid meta property for new is new.target"
else if @meta.value is 'import'
unless @property instanceof Access and @property.name.value is 'meta'
@error "the only valid meta property for import is import.meta"
compileNode: (o) ->
@checkValid o

View file

@ -4041,7 +4041,7 @@ test "AST as expected for If node", ->
inverted: no
]
test "AST as expected for MetaProperty node", ->
test "AST as expected for `new.target` MetaProperty node", ->
testExpression '''
-> new.target
''',
@ -4074,6 +4074,25 @@ test "AST as expected for MetaProperty node", ->
computed: no
]
test "AST as expected for `import.meta` MetaProperty node", ->
testExpression '''
import.meta
''',
type: 'MetaProperty'
meta: ID 'import'
property: ID 'meta'
testExpression '''
import.meta.name
''',
type: 'MemberExpression'
object:
type: 'MetaProperty'
meta: ID 'import'
property: ID 'meta'
property: ID 'name'
computed: no
test "AST as expected for dynamic import", ->
testExpression '''
import('a')

View file

@ -6006,7 +6006,7 @@ test "AST as expected for While node", ->
line: 3
column: 5
test "AST location data as expected for MetaProperty node", ->
test "AST location data as expected for `new.target` MetaProperty node", ->
testAstLocationData '''
-> new.target
''',
@ -6058,6 +6058,44 @@ test "AST location data as expected for MetaProperty node", ->
line: 1
column: 13
test "AST location data as expected for `import.meta` MetaProperty node", ->
testAstLocationData '''
import.meta
''',
type: 'MetaProperty'
meta:
start: 0
end: 6
range: [0, 6]
loc:
start:
line: 1
column: 0
end:
line: 1
column: 6
property:
start: 7
end: 11
range: [7, 11]
loc:
start:
line: 1
column: 7
end:
line: 1
column: 11
start: 0
end: 11
range: [0, 11]
loc:
start:
line: 1
column: 0
end:
line: 1
column: 11
test "AST location data as expected for For node", ->
testAstLocationData 'for x, i in arr when x? then return',
type: 'For'

View file

@ -1967,6 +1967,15 @@ test "`new.target` is only allowed meta property", ->
^^^^^^^^^^^^^
'''
test "`import.meta` is only allowed meta property", ->
assertErrorFormat '''
foo = import.something
''', '''
[stdin]:1:7: error: the only valid meta property for import is import.meta
foo = import.something
^^^^^^^^^^^^^^^^
'''
test "`new.target` cannot be assigned", ->
assertErrorFormat '''
->

View file

@ -941,3 +941,53 @@ test "#4834: dynamic import", ->
return bar = (await import('bar'));
};
"""
test "#5317: Support import.meta", ->
eqJS """
foo = import.meta
""",
"""
var foo;
foo = import.meta;
"""
eqJS """
foo = import
.meta
""",
"""
var foo;
foo = import.meta;
"""
eqJS """
foo = import.
meta
""",
"""
var foo;
foo = import.meta;
"""
eqJS """
foo = import.meta.bar
""",
"""
var foo;
foo = import.meta.bar;
"""
eqJS """
foo = import
.meta
.bar
""",
"""
var foo;
foo = import.meta.bar;
"""