From ae18f9c0d92e2c9067f0a3cdf307d7cf7418b8f9 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 19 Sep 2020 07:35:37 +0900 Subject: [PATCH] Throw a syntax error for a declare function with a body (#12054) --- .../src/plugins/typescript/index.js | 86 ++++++++++++------- packages/babel-parser/src/tokenizer/state.js | 1 + .../typescript/declare/function/input.ts | 1 + .../typescript/declare/function/output.json | 35 ++++++++ .../typescript/declare/module/input.ts | 3 + .../typescript/declare/module/output.json | 50 +++++++++++ .../typescript/declare/namespace/input.ts | 3 + .../typescript/declare/namespace/output.json | 50 +++++++++++ 8 files changed, 196 insertions(+), 33 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/typescript/declare/function/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/declare/function/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/declare/module/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/declare/module/output.json create mode 100644 packages/babel-parser/test/fixtures/typescript/declare/namespace/input.ts create mode 100644 packages/babel-parser/test/fixtures/typescript/declare/namespace/output.json diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 785639ead98c..d2f3f334c1ff 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 c9e46a3c962d..730be2a577b7 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 000000000000..83677c6aa029 --- /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 000000000000..490c73486849 --- /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 000000000000..fca55e911f17 --- /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 000000000000..ce844216f0ed --- /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 000000000000..c7f3afddc9a3 --- /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 000000000000..251434a13486 --- /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