Add babel-plugin-syntax-typescript, babel-plugin-transform-typescript, and babel-preset-typescript (#5899)

* Add babel-plugin-syntax-typescript and babel-plugin-transform-typescript

* Add babel-preset-typescript

* Remove unnecessary handler for JSXOpeningElement

* Use `t.isFoo(node)` instead of `node.type === "Foo"`

* Clean up parameter property assignment generation

* Don't use function for `isSuperCall`

* slice -> shift

* Calculate sourceFileHasJsx only if necessary

* Remove `export =` support

* remove some syntax readme newlines [skip ci]
This commit is contained in:
Andy 2017-08-07 08:45:52 -07:00 committed by Henry Zhu
parent 66ec5263a4
commit e37a5eb5eb
86 changed files with 929 additions and 0 deletions

View File

@ -0,0 +1,2 @@
node_modules
src

View File

@ -0,0 +1,33 @@
# babel-plugin-syntax-typescript
## Installation
```sh
npm install --save-dev babel-plugin-syntax-typescript
```
## Usage
### Via `.babelrc` (Recommended)
**.babelrc**
```json
{
"plugins": ["syntax-typescript"]
}
```
### Via CLI
```sh
babel --plugins syntax-typescript script.js
```
### Via Node API
```javascript
require("babel-core").transform("code", {
plugins: ["syntax-typescript"]
});
```

View File

@ -0,0 +1,14 @@
{
"name": "babel-plugin-syntax-typescript",
"version": "7.0.0-alpha.17",
"description": "Allow parsing of TypeScript syntax",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-typescript",
"license": "MIT",
"main": "lib/index.js",
"keywords": [
"babel-plugin",
"typescript"
],
"dependencies": {},
"devDependencies": {}
}

View File

@ -0,0 +1,7 @@
export default function() {
return {
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("typescript");
},
};
}

View File

@ -0,0 +1,3 @@
.gitignore
src
test

View File

@ -0,0 +1,54 @@
# babel-plugin-transform-typescript
> Transform [TypeScript](https://github.com/Microsoft/TypeScript) into ES.next.
Does not type-check its input. For that, you will need to install and set up TypeScript.
Does not support `namespace`s or `const enum`s because those require type information to transpile.
Also does not support `export =` and `import =`, because those cannot be transpiled to ES.next.
## Example
**In**
```javascript
const x: number = 0;
```
**Out**
```javascript
const x = 0;
```
## Installation
```sh
npm install --save-dev babel-plugin-transform-typescript
```
## Usage
### Via `.babelrc` (Recommended)
**.babelrc**
```json
{
"plugins": ["transform-typescript"]
}
```
### Via CLI
```sh
babel --plugins transform-typescript script.js
```
### Via Node API
```javascript
require("babel-core").transform("code", {
plugins: ["transform-typescript"]
});
```

View File

@ -0,0 +1,18 @@
{
"name": "babel-plugin-transform-typescript",
"version": "7.0.0-alpha.17",
"description": "Transform TypeScript into ES.next",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-typescript",
"license": "MIT",
"main": "lib/index.js",
"keywords": [
"babel-plugin",
"typescript"
],
"dependencies": {
"babel-plugin-syntax-typescript": "7.0.0-alpha.17"
},
"devDependencies": {
"babel-helper-plugin-test-runner": "7.0.0-alpha.17"
}
}

View File

@ -0,0 +1,206 @@
export default function transpileEnum(path, t) {
const { node } = path;
if (node.declare) {
path.remove();
return;
}
if (node.const) {
throw path.buildCodeFrameError("'const' enums are not supported.");
}
const name = node.id.name;
const fill = enumFill(path, t, node.id);
switch (path.parent.type) {
case "BlockStatement":
case "Program": {
const isGlobal = t.isProgram(path.parent); // && !path.parent.body.some(t.isModuleDeclaration);
if (seen(path.parentPath)) {
path.replaceWith(fill);
} else {
path.replaceWithMultiple([
makeVar(node.id, t, isGlobal ? "var" : "let"),
fill,
]);
}
break;
}
case "ExportNamedDeclaration": {
path.parentPath.insertAfter(fill);
if (seen(path.parentPath.parentPath)) {
path.remove();
} else {
path.replaceWith(makeVar(node.id, t, "let"));
}
break;
}
default:
throw new Error(`Unexpected enum parent '${path.parent.type}`);
}
function seen(parentPath: Path<Node>) {
if (parentPath.getData(name)) {
return true;
} else {
parentPath.setData(name, true);
return false;
}
}
}
function makeVar(id, t, kind): VariableDeclaration {
return t.variableDeclaration(kind, [t.variableDeclarator(id)]);
}
/**
* Generates the statement that fills in the variable declared by the enum.
* `(function (E) { ... assignments ... })(E || (E = {}));`
*/
function enumFill(path, t, id) {
const x = translateEnumValues(path, t);
const assignments = x.map(([memberName, memberValue]) => {
const inner = t.assignmentExpression(
"=",
t.memberExpression(id, t.stringLiteral(memberName), /*computed*/ true),
memberValue,
);
const outer = t.assignmentExpression(
"=",
t.memberExpression(id, inner, /*computed*/ true),
t.stringLiteral(memberName),
);
return t.expressionStatement(outer);
});
// E || (E = {})
const callArg = t.logicalExpression(
"||",
id,
t.assignmentExpression("=", id, t.objectExpression([])),
);
const body = t.blockStatement(assignments);
const callee = t.functionExpression(null, [id], body);
return t.expressionStatement(t.callExpression(callee, [callArg]));
}
/**
* Maps the name of an enum member to its value.
* We keep track of the previous enum members so you can write code like:
* enum E {
* X = 1 << 0,
* Y = 1 << 1,
* Z = X | Y,
* }
*/
type PreviousEnumMembers = { [name: string]: number | typeof undefined };
function translateEnumValues(path, t) {
const seen: PreviousEnumMembers = Object.create(null);
// Start at -1 so the first enum member is its increment, 0.
let prev: number | typeof undefined = -1;
return path.node.members.map(member => {
const name = t.isIdentifier(member.id) ? member.id.name : member.id.value;
const initializer = member.initializer;
let value: Expression;
if (initializer) {
const constValue = evaluate(initializer, seen);
if (constValue !== undefined) {
value = t.numericLiteral(constValue);
prev = constValue;
} else {
value = initializer;
prev = undefined;
}
} else {
if (prev !== undefined) {
prev++;
value = t.numericLiteral(prev);
} else {
throw path.buildCodeFrameError("Enum member must have initializer.");
}
}
return [name, value];
});
}
// Based on the TypeScript repository's `evalConstant` in `checker.ts`.
function evaluate(expr, seen: PreviousEnumMembers) {
return evalConstant(expr);
function evalConstant(expr) {
switch (expr.type) {
case "UnaryExpression":
return evalUnaryExpression(expr);
case "BinaryExpression":
return evalBinaryExpression(expr);
case "NumericLiteral":
return expr.value;
case "ParenthesizedExpression":
return evalConstant(expr.expression);
case "Identifier":
return seen[expr.name];
default:
return undefined;
}
}
function evalUnaryExpression({ argument, operator }) {
const value = evalConstant(argument);
if (value === undefined) {
return undefined;
}
switch (operator) {
case "+":
return value;
case "-":
return -value;
case "~":
return ~value;
default:
return undefined;
}
}
function evalBinaryExpression(expr) {
const left = evalConstant(expr.left);
if (left === undefined) {
return undefined;
}
const right = evalConstant(expr.right);
if (right === undefined) {
return undefined;
}
switch (expr.operator) {
case "|":
return left | right;
case "&":
return left & right;
case ">>":
return left >> right;
case ">>>":
return left >>> right;
case "<<":
return left << right;
case "^":
return left ^ right;
case "*":
return left * right;
case "/":
return left / right;
case "+":
return left + right;
case "-":
return left - right;
case "%":
return left % right;
default:
return undefined;
}
}
}

View File

@ -0,0 +1,255 @@
import syntaxTypeScript from "babel-plugin-syntax-typescript";
import transpileEnum from "./enum";
function isInType(path) {
switch (path.parent.type) {
case "TSTypeReference":
case "TSQualifiedName":
case "TSExpressionWithTypeArguments":
case "TSTypeQuery":
return true;
default:
return false;
}
}
interface State {
programPath: any,
}
export default function({ types: t }) {
return {
inherits: syntaxTypeScript,
visitor: {
//"Pattern" alias doesn't include Identifier or RestElement.
Pattern: visitPattern,
Identifier: visitPattern,
RestElement: visitPattern,
Program(path, state: State) {
state.programPath = path;
},
ImportDeclaration(path, state: State) {
// Note: this will allow both `import { } from "m"` and `import "m";`.
// In TypeScript, the former would be elided.
if (path.node.specifiers.length === 0) {
return;
}
let allElided = true;
const importsToRemove: Path<Node>[] = [];
for (const specifier of path.node.specifiers) {
const binding = path.scope.getBinding(specifier.local.name);
if (isImportTypeOnly(binding, state.programPath)) {
importsToRemove.push(binding.path);
} else {
allElided = false;
}
}
if (allElided) {
path.remove();
} else {
for (const importPath of importsToRemove) {
importPath.remove();
}
}
},
TSDeclareFunction(path) {
path.remove();
},
TSDeclareMethod(path) {
path.remove();
},
VariableDeclaration(path) {
if (path.node.declare) path.remove();
},
ClassMethod(path) {
const { node } = path;
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.optional) node.optional = null;
if (node.kind !== "constructor") {
return;
}
// Collect parameter properties
const parameterProperties = [];
for (const param of node.params) {
if (param.type === "TSParameterProperty") {
parameterProperties.push(param.parameter);
}
}
if (!parameterProperties.length) {
return;
}
const assigns = parameterProperties.map(p => {
let name;
if (t.isIdentifier(p)) {
name = p.name;
} else if (t.isAssignmentPattern(p) && t.isIdentifier(p.left)) {
name = p.left.name;
} else {
throw path.buildCodeFrameError(
"Parameter properties can not be destructuring patterns.",
);
}
const id = t.identifier(name);
const thisDotName = t.memberExpression(t.thisExpression(), id);
const assign = t.assignmentExpression("=", thisDotName, id);
return t.expressionStatement(assign);
});
const statements = node.body.body;
const first = statements[0];
const startsWithSuperCall =
first !== undefined &&
t.isExpressionStatement(first) &&
t.isCallExpression(first.expression) &&
t.isSuper(first.expression.callee);
// Make sure to put parameter properties *after* the `super` call.
// TypeScript will enforce that a 'super()' call is the first statement
// when there are parameter properties.
node.body.body = startsWithSuperCall
? [first, ...assigns, ...statements.slice(1)]
: [...assigns, ...statements];
// Rest handled by Function visitor
},
TSParameterProperty(path) {
path.replaceWith(path.node.parameter);
},
ClassProperty(path) {
const { node } = path;
if (!node.value) {
path.remove();
return;
}
if (node.accessibility) node.accessibility = null;
if (node.abstract) node.abstract = null;
if (node.optional) node.optional = null;
if (node.typeAnnotation) node.typeAnnotation = null;
},
TSIndexSignature(path) {
path.remove();
},
ClassDeclaration(path) {
const { node } = path;
if (node.declare) {
path.remove();
return;
}
if (node.abstract) node.abstract = null;
},
Class({ node }) {
if (node.typeParameters) node.typeParameters = null;
if (node.superTypeParameters) node.superTypeParameters = null;
if (node.implements) node.implements = null;
},
Function({ node }) {
if (node.typeParameters) node.typeParameters = null;
if (node.returnType) node.returnType = null;
const p0 = node.params[0];
if (p0 && t.isIdentifier(p0) && p0.name === "this") {
node.params.shift();
}
},
TSModuleDeclaration(path) {
if (!path.node.declare && path.node.id.type !== "StringLiteral") {
throw path.buildCodeFrameError("Namespaces are not supported.");
}
path.remove();
},
TSInterfaceDeclaration(path) {
path.remove();
},
TSTypeAliasDeclaration(path) {
path.remove();
},
TSEnumDeclaration(path) {
transpileEnum(path, t);
},
TSImportEqualsDeclaration(path) {
throw path.buildCodeFrameError("`import =` is not supported.");
},
TSExportAssignment(path) {
throw path.buildCodeFrameError("`export =` is not supported.");
},
TSTypeAssertion(path) {
path.replaceWith(path.node.expression);
},
TSAsExpression(path) {
path.replaceWith(path.node.expression);
},
TSNonNullExpression(path) {
path.replaceWith(path.node.expression);
},
CallExpression(path) {
path.node.typeParameters = null;
},
NewExpression(path) {
path.node.typeParameters = null;
},
},
};
function visitPattern({ node }) {
if (node.typeAnnotation) node.typeAnnotation = null;
if (t.isIdentifier(node) && node.optional) node.optional = null;
// 'access' and 'readonly' are only for parameter properties, so constructor visitor will handle them.
}
function isImportTypeOnly(binding, programPath) {
for (const path of binding.referencePaths) {
if (!isInType(path)) {
return false;
}
}
if (binding.identifier.name != "React") {
return true;
}
// "React" is referenced as a value if there are any JSX elements in the code.
let sourceFileHasJsx = false;
programPath.traverse({
JSXElement() {
sourceFileHasJsx = true;
},
});
return !sourceFileHasJsx;
}
}

View File

@ -0,0 +1 @@
x as T;

View File

@ -0,0 +1 @@
abstract class C<T> extends D<T> implements I {}

View File

@ -0,0 +1 @@
class C extends D {}

View File

@ -0,0 +1,3 @@
class C {
[s: string]: number;
}

View File

@ -0,0 +1 @@
class C {}

View File

@ -0,0 +1,4 @@
class C {
m(): void;
public m(x?: number, ...y: number[]): void {}
}

View File

@ -0,0 +1,4 @@
class C {
m(x, ...y) {}
}

View File

@ -0,0 +1,5 @@
class C extends Object {
constructor(public x) {
super();
}
}

View File

@ -0,0 +1,7 @@
class C extends Object {
constructor(x) {
super();
this.x = x;
}
}

View File

@ -0,0 +1,3 @@
class C {
constructor(public x, public y = 0, public z: number = 0) {}
}

View File

@ -0,0 +1,8 @@
class C {
constructor(x, y = 0, z = 0) {
this.x = x;
this.y = y;
this.z = z;
}
}

View File

@ -0,0 +1,4 @@
class C {
public a?: number;
private b: number = 0;
}

View File

@ -0,0 +1,3 @@
class C {
b = 0;
}

View File

@ -0,0 +1,10 @@
declare const x: number;
declare function f(): void;
declare class C {}
declare enum E {}
declare module "m" {}
declare module M {}
declare namespace N {}
export interface I {}
export type T = number;
export class C2 {}

View File

@ -0,0 +1 @@
export class C2 {}

View File

@ -0,0 +1 @@
const enum E {}

View File

@ -0,0 +1 @@
{ "throws": "'const' enums are not supported." }

View File

@ -0,0 +1,10 @@
enum E {
a,
b = 2 + 3,
c = 2 - 3,
d = 2 * 3,
e = 2 / 3,
f = -1,
g = 1 + 2 - 3 * 4 / -5,
h,
}

View File

@ -0,0 +1,12 @@
var E;
(function (E) {
E[E["a"] = 0] = "a";
E[E["b"] = 5] = "b";
E[E["c"] = -1] = "c";
E[E["d"] = 6] = "d";
E[E["e"] = 0.6666666666666666] = "e";
E[E["f"] = -1] = "f";
E[E["g"] = 5.4] = "g";
E[E["h"] = 6.4] = "h";
})(E || (E = {}));

View File

@ -0,0 +1,6 @@
export enum E {
A = 1
}
export enum E {
B = 2
}

View File

@ -0,0 +1,9 @@
export let E;
(function (E) {
E[E["A"] = 1] = "A";
})(E || (E = {}));
(function (E) {
E[E["B"] = 2] = "B";
})(E || (E = {}));

View File

@ -0,0 +1,4 @@
enum E {
x,
y,
}

View File

@ -0,0 +1,6 @@
var E;
(function (E) {
E[E["x"] = 0] = "x";
E[E["y"] = 1] = "y";
})(E || (E = {}));

View File

@ -0,0 +1,4 @@
enum E {
a = Math.sin(1),
b,
}

View File

@ -0,0 +1,3 @@
{
"throws": "Enum member must have initializer."
}

View File

@ -0,0 +1,5 @@
enum E {
x = 1,
"y" = 2,
}
enum E { z = 3 }

View File

@ -0,0 +1,10 @@
var E;
(function (E) {
E[E["x"] = 1] = "x";
E[E["y"] = 2] = "y";
})(E || (E = {}));
(function (E) {
E[E["z"] = 3] = "z";
})(E || (E = {}));

View File

@ -0,0 +1,4 @@
{
// Uses 'let' within a scope
enum E {}
}

View File

@ -0,0 +1,6 @@
{
// Uses 'let' within a scope
let E;
(function (E) {})(E || (E = {}));
}

View File

@ -0,0 +1 @@
export = 0;

View File

@ -0,0 +1,3 @@
{
"throws": "`export =` is not supported."
}

View File

@ -0,0 +1,3 @@
function f(): void;
function f(): void {
}

View File

@ -0,0 +1 @@
function f() {}

View File

@ -0,0 +1,2 @@
function f<T>(x?: T, ...y: T[]): T {}
function g(x: number = 0): number {}

View File

@ -0,0 +1,3 @@
function f(x, ...y) {}
function g(x = 0) {}

View File

@ -0,0 +1,7 @@
function f(this: {}) {}
const o = {
m(this: {}) {}
};
class C {
m(this: {}) {}
}

View File

@ -0,0 +1,11 @@
function f() {}
const o = {
m() {}
};
class C {
m() {}
}

View File

@ -0,0 +1,2 @@
import {} from "a";
import "b";

View File

@ -0,0 +1,2 @@
import "a";
import "b";

View File

@ -0,0 +1,3 @@
// Don't elide React if a JSX element appears somewhere.
import * as React from "react";
<div/>;

View File

@ -0,0 +1,3 @@
// Don't elide React if a JSX element appears somewhere.
import * as React from "react";
<div />;

View File

@ -0,0 +1,3 @@
{
"plugins": ["syntax-jsx"]
}

View File

@ -0,0 +1,3 @@
// Don't elide React if a JSX element appears somewhere.
import * as React from "react";
<div></div>;

View File

@ -0,0 +1,3 @@
// Don't elide React if a JSX element appears somewhere.
import * as React from "react";
<div></div>;

View File

@ -0,0 +1,3 @@
{
"plugins": ["syntax-jsx"]
}

View File

@ -0,0 +1,2 @@
import * as React from "react";
const x: React.T = 0;

View File

@ -0,0 +1 @@
const x = 0;

View File

@ -0,0 +1,2 @@
import { A } from "mod";
const x: typeof A = 0;

View File

@ -0,0 +1 @@
const x = 0;

View File

@ -0,0 +1,6 @@
import { A, B, C, D, E, F, G, H } from "m";
class Class extends A<B> implements C<D> {}
interface Iface extends E<F> {}
const x: G = 0;
const y: H.T = 0;

View File

@ -0,0 +1,6 @@
import { A } from "m";
class Class extends A {}
const x = 0;
const y = 0;

View File

@ -0,0 +1,2 @@
import * as A from "lib";
const x: A.B = 0;

View File

@ -0,0 +1,2 @@
import { A as B } from "lib";
const x: B = 0;

View File

@ -0,0 +1 @@
const x = 0;

View File

@ -0,0 +1,7 @@
import A, { B, Used } from "lib";
import Used2, { C } from "lib";
import * as D from "lib";
import * as Used3 from "lib";
const x: A = Used;
const y: B = Used2;
const z: C | D = Used3;

View File

@ -0,0 +1,6 @@
import { Used } from "lib";
import Used2 from "lib";
import * as Used3 from "lib";
const x = Used;
const y = Used2;
const z = Used3;

View File

@ -0,0 +1,2 @@
import lib = require("lib");
lib();

View File

@ -0,0 +1,3 @@
{
"throws": "`import =` is not supported."
}

View File

@ -0,0 +1 @@
namespace N {}

View File

@ -0,0 +1,3 @@
{
"throws": "Namespaces are not supported."
}

View File

@ -0,0 +1,3 @@
{
"plugins": ["transform-typescript"]
}

View File

@ -0,0 +1 @@
new C<T>();

View File

@ -0,0 +1 @@
new C();

View File

@ -0,0 +1,3 @@
const n: number = 0;
const [a, b]: any = 0;
const { x, y }: any = 0;

View File

@ -0,0 +1,6 @@
const n = 0;
const [a, b] = 0;
const {
x,
y
} = 0;

View File

@ -0,0 +1,2 @@
import runner from "babel-helper-plugin-test-runner";
runner(__dirname);

View File

@ -0,0 +1,54 @@
# babel-preset-typescript
> Babel preset for TypeScript.
This preset includes the following plugins:
- [transform-typescript](https://babeljs.io/docs/plugins/transform-typescript/)
- [syntax-object-rest-spread](https://babeljs.io/docs/plugins/syntax-object-rest-spread/)
## Example
**In**
```javascript
const x: number = 0;
```
**Out**
```javascript
const x = 0;
```
## Installation
```sh
npm install --save-dev babel-preset-typescript
```
## Usage
### Via `.babelrc` (Recommended)
**.babelrc**
```json
{
"presets": ["typescript"]
}
```
### Via CLI
```sh
babel --presets typescript script.ts
```
### Via Node API
```javascript
require("babel-core").transform("code", {
presets: ["typescript"]
});
```

View File

@ -0,0 +1,16 @@
{
"name": "babel-preset-typescript",
"version": "7.0.0-alpha.17",
"description": "Babel preset for TypeScript.",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-preset-typescript",
"license": "MIT",
"main": "lib/index.js",
"keywords": [
"babel-preset",
"typescript"
],
"dependencies": {
"babel-plugin-transform-typescript": "7.0.0-alpha.17",
"babel-plugin-syntax-object-rest-spread": "7.0.0-alpha.17"
}
}

View File

@ -0,0 +1,8 @@
import transformTypeScript from "babel-plugin-transform-typescript";
import syntaxObjectRestSpread from "babel-plugin-syntax-object-rest-spread";
export default function() {
return {
plugins: [transformTypeScript, syntaxObjectRestSpread],
};
}