diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 785639ead9..d2f3f334c1 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -69,6 +69,8 @@ const TSErrors = Object.freeze({ "Type parameters cannot appear on a constructor declaration.", DeclareClassFieldHasInitializer: "'declare' class fields cannot have an initializer", + DeclareFunctionHasImplementation: + "An implementation cannot be declared in ambient contexts.", DuplicateModifier: "Duplicate modifier: '%0'", EmptyHeritageClauseType: "'%0' list cannot be empty.", IndexSignatureHasAbstract: @@ -1469,41 +1471,49 @@ export default (superClass: Class): Class => kind = "let"; } - switch (starttype) { - case tt._function: - return this.parseFunctionStatement( - nany, - /* async */ false, - /* declarationPosition */ true, - ); - case tt._class: - // While this is also set by tsParseExpressionStatement, we need to set it - // before parsing the class declaration to now how to register it in the scope. - nany.declare = true; - return this.parseClass( - nany, - /* isStatement */ true, - /* optionalId */ false, - ); - case tt._const: - if (this.match(tt._const) && this.isLookaheadContextual("enum")) { - // `const enum = 0;` not allowed because "enum" is a strict mode reserved word. - this.expect(tt._const); - this.expectContextual("enum"); - return this.tsParseEnumDeclaration(nany, /* isConst */ true); - } - // falls through - case tt._var: - kind = kind || this.state.value; - return this.parseVarStatement(nany, kind); - case tt.name: { - const value = this.state.value; - if (value === "global") { - return this.tsParseAmbientExternalModuleDeclaration(nany); - } else { - return this.tsParseDeclaration(nany, value, /* next */ true); + const oldIsDeclareContext = this.state.isDeclareContext; + this.state.isDeclareContext = true; + + try { + switch (starttype) { + case tt._function: + nany.declare = true; + return this.parseFunctionStatement( + nany, + /* async */ false, + /* declarationPosition */ true, + ); + case tt._class: + // While this is also set by tsParseExpressionStatement, we need to set it + // before parsing the class declaration to now how to register it in the scope. + nany.declare = true; + return this.parseClass( + nany, + /* isStatement */ true, + /* optionalId */ false, + ); + case tt._const: + if (this.match(tt._const) && this.isLookaheadContextual("enum")) { + // `const enum = 0;` not allowed because "enum" is a strict mode reserved word. + this.expect(tt._const); + this.expectContextual("enum"); + return this.tsParseEnumDeclaration(nany, /* isConst */ true); + } + // falls through + case tt._var: + kind = kind || this.state.value; + return this.parseVarStatement(nany, kind); + case tt.name: { + const value = this.state.value; + if (value === "global") { + return this.tsParseAmbientExternalModuleDeclaration(nany); + } else { + return this.tsParseDeclaration(nany, value, /* next */ true); + } } } + } finally { + this.state.isDeclareContext = oldIsDeclareContext; } } @@ -1764,6 +1774,16 @@ export default (superClass: Class): Class => this.finishNode(node, bodilessType); return; } + if (bodilessType === "TSDeclareFunction" && this.state.isDeclareContext) { + this.raise(node.start, TSErrors.DeclareFunctionHasImplementation); + if ( + // $FlowIgnore + node.declare + ) { + super.parseFunctionBodyAndFinish(node, bodilessType, isMethod); + return; + } + } super.parseFunctionBodyAndFinish(node, type, isMethod); } diff --git a/packages/babel-parser/src/tokenizer/state.js b/packages/babel-parser/src/tokenizer/state.js index c9e46a3c96..730be2a577 100644 --- a/packages/babel-parser/src/tokenizer/state.js +++ b/packages/babel-parser/src/tokenizer/state.js @@ -70,6 +70,7 @@ export default class State { inPropertyName: boolean = false; hasFlowComment: boolean = false; isIterator: boolean = false; + isDeclareContext: boolean = false; // For the smartPipelines plugin: topicContext: TopicContextState = { diff --git a/packages/babel-parser/test/fixtures/typescript/declare/function/input.ts b/packages/babel-parser/test/fixtures/typescript/declare/function/input.ts new file mode 100644 index 0000000000..83677c6aa0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/declare/function/input.ts @@ -0,0 +1 @@ +declare function foo() {} diff --git a/packages/babel-parser/test/fixtures/typescript/declare/function/output.json b/packages/babel-parser/test/fixtures/typescript/declare/function/output.json new file mode 100644 index 0000000000..490c734868 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/declare/function/output.json @@ -0,0 +1,35 @@ +{ + "type": "File", + "start":0,"end":25,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":25}}, + "errors": [ + "SyntaxError: An implementation cannot be declared in ambient contexts. (1:0)" + ], + "program": { + "type": "Program", + "start":0,"end":25,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":25}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSDeclareFunction", + "start":0,"end":25,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":25}}, + "declare": true, + "id": { + "type": "Identifier", + "start":17,"end":20,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":20},"identifierName":"foo"}, + "name": "foo" + }, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":23,"end":25,"loc":{"start":{"line":1,"column":23},"end":{"line":1,"column":25}}, + "body": [], + "directives": [] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/declare/module/input.ts b/packages/babel-parser/test/fixtures/typescript/declare/module/input.ts new file mode 100644 index 0000000000..fca55e911f --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/declare/module/input.ts @@ -0,0 +1,3 @@ +declare module m { + function foo() {} +} diff --git a/packages/babel-parser/test/fixtures/typescript/declare/module/output.json b/packages/babel-parser/test/fixtures/typescript/declare/module/output.json new file mode 100644 index 0000000000..ce844216f0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/declare/module/output.json @@ -0,0 +1,50 @@ +{ + "type": "File", + "start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "errors": [ + "SyntaxError: An implementation cannot be declared in ambient contexts. (2:2)" + ], + "program": { + "type": "Program", + "start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSModuleDeclaration", + "start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "id": { + "type": "Identifier", + "start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16},"identifierName":"m"}, + "name": "m" + }, + "body": { + "type": "TSModuleBlock", + "start":17,"end":40,"loc":{"start":{"line":1,"column":17},"end":{"line":3,"column":1}}, + "body": [ + { + "type": "FunctionDeclaration", + "start":21,"end":38,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":19}}, + "id": { + "type": "Identifier", + "start":30,"end":33,"loc":{"start":{"line":2,"column":11},"end":{"line":2,"column":14},"identifierName":"foo"}, + "name": "foo" + }, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":36,"end":38,"loc":{"start":{"line":2,"column":17},"end":{"line":2,"column":19}}, + "body": [], + "directives": [] + } + } + ] + }, + "declare": true + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/typescript/declare/namespace/input.ts b/packages/babel-parser/test/fixtures/typescript/declare/namespace/input.ts new file mode 100644 index 0000000000..c7f3afddc9 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/declare/namespace/input.ts @@ -0,0 +1,3 @@ +declare namespace n { + function foo() {} +} diff --git a/packages/babel-parser/test/fixtures/typescript/declare/namespace/output.json b/packages/babel-parser/test/fixtures/typescript/declare/namespace/output.json new file mode 100644 index 0000000000..251434a134 --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/declare/namespace/output.json @@ -0,0 +1,50 @@ +{ + "type": "File", + "start":0,"end":43,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "errors": [ + "SyntaxError: An implementation cannot be declared in ambient contexts. (2:2)" + ], + "program": { + "type": "Program", + "start":0,"end":43,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "TSModuleDeclaration", + "start":0,"end":43,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}}, + "id": { + "type": "Identifier", + "start":18,"end":19,"loc":{"start":{"line":1,"column":18},"end":{"line":1,"column":19},"identifierName":"n"}, + "name": "n" + }, + "body": { + "type": "TSModuleBlock", + "start":20,"end":43,"loc":{"start":{"line":1,"column":20},"end":{"line":3,"column":1}}, + "body": [ + { + "type": "FunctionDeclaration", + "start":24,"end":41,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":19}}, + "id": { + "type": "Identifier", + "start":33,"end":36,"loc":{"start":{"line":2,"column":11},"end":{"line":2,"column":14},"identifierName":"foo"}, + "name": "foo" + }, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":39,"end":41,"loc":{"start":{"line":2,"column":17},"end":{"line":2,"column":19}}, + "body": [], + "directives": [] + } + } + ] + }, + "declare": true + } + ], + "directives": [] + } +} \ No newline at end of file