Skip to content

Commit 2a4d780

Browse files
authored
feat: add remaining CoffeeScript 1.12.7 syntax features (#313)
Progress toward decaffeinate/decaffeinate#427 Tagged template literals, for...from, and bare yield needed some parser tweaks. The inline JS changes just worked without needing modifications here.
1 parent ebd80de commit 2a4d780

File tree

13 files changed

+376
-24
lines changed

13 files changed

+376
-24
lines changed

src/mappers/mapAny.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
Arr, Assign, Base, Block, Call, Class, Code, Comment, Existence, Expansion, Extends,
3-
For, If, In, Literal, ModuleDeclaration, Obj, Op, Param, Parens, Range, Return, Splat, Switch,
3+
For, If, In, Literal, ModuleDeclaration, Obj, Op, Param, Parens, Range, Return, Splat, Switch, TaggedTemplateCall,
44
Throw, Try, Value, While,
55
} from 'decaffeinate-coffeescript/lib/coffee-script/nodes';
66
import { Node } from '../nodes';
@@ -28,6 +28,7 @@ import mapRange from './mapRange';
2828
import mapReturn from './mapReturn';
2929
import mapSplat from './mapSplat';
3030
import mapSwitch from './mapSwitch';
31+
import mapTaggedTemplateCall from './mapTaggedTemplateCall';
3132
import mapThrow from './mapThrow';
3233
import mapTry from './mapTry';
3334
import mapValue from './mapValue';
@@ -46,6 +47,10 @@ export default function mapAny(context: ParseContext, node: Base): Node {
4647
return mapOp(context, node);
4748
}
4849

50+
if (node instanceof TaggedTemplateCall) {
51+
return mapTaggedTemplateCall(context, node);
52+
}
53+
4954
if (node instanceof Call) {
5055
return mapCall(context, node);
5156
}

src/mappers/mapFor.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { For as CoffeeFor } from 'decaffeinate-coffeescript/lib/coffee-script/nodes';
2-
import { For, ForIn, ForOf } from '../nodes';
2+
import {For, ForFrom, ForIn, ForOf} from '../nodes';
33
import getLocation from '../util/getLocation';
44
import ParseContext from '../util/ParseContext';
55
import mapAny from './mapAny';
@@ -31,16 +31,28 @@ export default function mapFor(context: ParseContext, node: CoffeeFor): For {
3131
isOwn
3232
);
3333
} else {
34-
let step = node.step ? mapAny(context, node.step) : null;
35-
36-
return new ForIn(
37-
line, column, start, end, raw,
38-
keyAssignee,
39-
valAssignee,
40-
target,
41-
filter,
42-
body,
43-
step
44-
);
34+
if (node.from) {
35+
if (keyAssignee) {
36+
throw new Error('Unexpected key assignee in for...from.');
37+
}
38+
return new ForFrom(
39+
line, column, start, end, raw,
40+
valAssignee,
41+
target,
42+
filter,
43+
body,
44+
);
45+
} else {
46+
let step = node.step ? mapAny(context, node.step) : null;
47+
return new ForIn(
48+
line, column, start, end, raw,
49+
keyAssignee,
50+
valAssignee,
51+
target,
52+
filter,
53+
body,
54+
step
55+
);
56+
}
4557
}
4658
}

src/mappers/mapOp.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SourceType } from 'coffee-lex';
2-
import { Op as CoffeeOp } from 'decaffeinate-coffeescript/lib/coffee-script/nodes';
2+
import {Literal, Op as CoffeeOp, Value} from 'decaffeinate-coffeescript/lib/coffee-script/nodes';
33
import { inspect } from 'util';
44
import {
55
BinaryOp, BitAndOp, BitNotOp, BitOrOp, BitXorOp, ChainedComparisonOp, DeleteOp, DivideOp, ExistsOp, ExpOp, EQOp, FloorDivideOp,
@@ -185,10 +185,17 @@ function mapNewOp(context: ParseContext, node: CoffeeOp): NewOp {
185185

186186
function mapYieldOp(context: ParseContext, node: CoffeeOp): YieldReturn | Yield {
187187
let { line, column, start, end, raw } = getLocation(context, node);
188-
return new Yield(
189-
line, column, start, end, raw,
190-
mapAny(context, node.first)
191-
);
188+
if (isBareYield(node)) {
189+
return new Yield(line, column, start, end, raw, null);
190+
} else {
191+
return new Yield(line, column, start, end, raw, mapAny(context, node.first));
192+
}
193+
}
194+
195+
function isBareYield(node: CoffeeOp): boolean {
196+
return node.first instanceof Value &&
197+
node.first.base instanceof Literal &&
198+
node.first.base.value === '';
192199
}
193200

194201
interface IBinaryOp {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {TaggedTemplateCall} from 'decaffeinate-coffeescript/lib/coffee-script/nodes';
2+
import {Node, String, TaggedTemplateLiteral} from '../nodes';
3+
import getLocation from '../util/getLocation';
4+
import ParseContext from '../util/ParseContext';
5+
import mapAny from './mapAny';
6+
7+
export default function mapTaggedTemplateCall(context: ParseContext, node: TaggedTemplateCall): Node {
8+
let { line, column, start, end, raw } = getLocation(context, node);
9+
if (!node.variable) {
10+
throw new Error('Expected tag in tagged template literal.');
11+
}
12+
let tag = mapAny(context, node.variable);
13+
if (node.args.length !== 1) {
14+
throw new Error('Expected tagged template literal call to have exactly one argument.');
15+
}
16+
let template = mapAny(context, node.args[0]);
17+
if (!(template instanceof String)) {
18+
throw new Error('Expected tagged template literal argument to be a string.');
19+
}
20+
return new TaggedTemplateLiteral(line, column, start, end, raw, tag, template);
21+
}

src/nodes.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,24 @@ export class String extends Node {
295295
}
296296
}
297297

298+
export class TaggedTemplateLiteral extends Node {
299+
constructor(
300+
line: number,
301+
column: number,
302+
start: number,
303+
end: number,
304+
raw: string,
305+
readonly tag: Node,
306+
readonly template: String,
307+
) {
308+
super('TaggedTemplateLiteral', line, column, start, end, raw);
309+
}
310+
311+
getChildNames(): Array<keyof this> {
312+
return ['tag', 'template'];
313+
}
314+
}
315+
298316
export class ObjectInitialiser extends Node {
299317
constructor(
300318
line: number,
@@ -495,6 +513,26 @@ export class ForIn extends For {
495513
}
496514
}
497515

516+
export class ForFrom extends For {
517+
constructor(
518+
line: number,
519+
column: number,
520+
start: number,
521+
end: number,
522+
raw: string,
523+
valAssignee: Node | null,
524+
target: Node,
525+
filter: Node | null,
526+
body: Block | null,
527+
) {
528+
super('ForFrom', line, column, start, end, raw, null /* keyAssignee */, valAssignee, target, filter, body);
529+
}
530+
531+
getChildNames(): Array<keyof this> {
532+
return ['keyAssignee', 'valAssignee', 'target', 'filter', 'body'];
533+
}
534+
}
535+
498536
export class Switch extends Node {
499537
constructor(
500538
line: number,
@@ -1517,29 +1555,37 @@ export class DeleteOp extends UnaryOp {
15171555
}
15181556
}
15191557

1520-
export class Yield extends UnaryOp {
1558+
export class Yield extends Node {
15211559
constructor(
15221560
line: number,
15231561
column: number,
15241562
start: number,
15251563
end: number,
15261564
raw: string,
1527-
expression: Node,
1565+
readonly expression: Node | null,
15281566
) {
1529-
super('Yield', line, column, start, end, raw, expression);
1567+
super('Yield', line, column, start, end, raw);
1568+
}
1569+
1570+
getChildNames(): Array<keyof this> {
1571+
return ['expression'];
15301572
}
15311573
}
15321574

1533-
export class YieldFrom extends UnaryOp {
1575+
export class YieldFrom extends Node {
15341576
constructor(
15351577
line: number,
15361578
column: number,
15371579
start: number,
15381580
end: number,
15391581
raw: string,
1540-
expression: Node,
1582+
readonly expression: Node,
15411583
) {
1542-
super('YieldFrom', line, column, start, end, raw, expression);
1584+
super('YieldFrom', line, column, start, end, raw);
1585+
}
1586+
1587+
getChildNames(): Array<keyof this> {
1588+
return ['expression'];
15431589
}
15441590
}
15451591

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
f = ->
2+
yield
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"body": {
3+
"column": 1,
4+
"end": 14,
5+
"inline": false,
6+
"line": 1,
7+
"raw": "f = ->\n yield",
8+
"start": 0,
9+
"statements": [
10+
{
11+
"assignee": {
12+
"column": 1,
13+
"data": "f",
14+
"end": 1,
15+
"line": 1,
16+
"raw": "f",
17+
"start": 0,
18+
"type": "Identifier"
19+
},
20+
"column": 1,
21+
"end": 14,
22+
"expression": {
23+
"body": {
24+
"column": 3,
25+
"end": 14,
26+
"inline": false,
27+
"line": 2,
28+
"raw": "yield",
29+
"start": 9,
30+
"statements": [
31+
{
32+
"column": 3,
33+
"end": 14,
34+
"expression": null,
35+
"line": 2,
36+
"raw": "yield",
37+
"start": 9,
38+
"type": "Yield"
39+
}
40+
],
41+
"type": "Block"
42+
},
43+
"column": 5,
44+
"end": 14,
45+
"line": 1,
46+
"parameters": [
47+
],
48+
"raw": "->\n yield",
49+
"start": 4,
50+
"type": "GeneratorFunction"
51+
},
52+
"line": 1,
53+
"raw": "f = ->\n yield",
54+
"start": 0,
55+
"type": "AssignOp"
56+
}
57+
],
58+
"type": "Block"
59+
},
60+
"column": 1,
61+
"end": 15,
62+
"line": 1,
63+
"raw": "f = ->\n yield\n",
64+
"start": 0,
65+
"type": "Program"
66+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
for a from b
2+
c

test/examples/for-from/output.json

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"body": {
3+
"column": 1,
4+
"end": 16,
5+
"inline": false,
6+
"line": 1,
7+
"raw": "for a from b\n c",
8+
"start": 0,
9+
"statements": [
10+
{
11+
"body": {
12+
"column": 3,
13+
"end": 16,
14+
"inline": false,
15+
"line": 2,
16+
"raw": "c",
17+
"start": 15,
18+
"statements": [
19+
{
20+
"column": 3,
21+
"data": "c",
22+
"end": 16,
23+
"line": 2,
24+
"raw": "c",
25+
"start": 15,
26+
"type": "Identifier"
27+
}
28+
],
29+
"type": "Block"
30+
},
31+
"column": 1,
32+
"end": 16,
33+
"filter": null,
34+
"keyAssignee": null,
35+
"line": 1,
36+
"raw": "for a from b\n c",
37+
"start": 0,
38+
"target": {
39+
"column": 12,
40+
"data": "b",
41+
"end": 12,
42+
"line": 1,
43+
"raw": "b",
44+
"start": 11,
45+
"type": "Identifier"
46+
},
47+
"type": "ForFrom",
48+
"valAssignee": {
49+
"column": 5,
50+
"data": "a",
51+
"end": 5,
52+
"line": 1,
53+
"raw": "a",
54+
"start": 4,
55+
"type": "Identifier"
56+
}
57+
}
58+
],
59+
"type": "Block"
60+
},
61+
"column": 1,
62+
"end": 17,
63+
"line": 1,
64+
"raw": "for a from b\n c\n",
65+
"start": 0,
66+
"type": "Program"
67+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
s = f"a#{b}c"

0 commit comments

Comments
 (0)