diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow/index.js similarity index 99% rename from packages/babel-parser/src/plugins/flow.js rename to packages/babel-parser/src/plugins/flow/index.js index 0e00291fcfb1..0f606cfd35fa 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -5,26 +5,27 @@ // Error messages are colocated with the plugin. /* eslint-disable @babel/development-internal/dry-error-messages */ -import type Parser from "../parser"; -import { types as tt, type TokenType } from "../tokenizer/types"; -import * as N from "../types"; -import type { Options } from "../options"; -import type { Pos, Position } from "../util/location"; -import type State from "../tokenizer/state"; -import { types as tc } from "../tokenizer/context"; +import type Parser from "../../parser"; +import { types as tt, type TokenType } from "../../tokenizer/types"; +import * as N from "../../types"; +import type { Pos, Position } from "../../util/location"; +import type State from "../../tokenizer/state"; +import { types as tc } from "../../tokenizer/context"; import * as charCodes from "charcodes"; -import { isIteratorStart, isKeyword } from "../util/identifier"; +import { isIteratorStart, isKeyword } from "../../util/identifier"; +import FlowScopeHandler from "./scope"; import { type BindingTypes, BIND_LEXICAL, BIND_VAR, BIND_FUNCTION, + BIND_FLOW_DECLARE_FN, SCOPE_ARROW, SCOPE_FUNCTION, SCOPE_OTHER, -} from "../util/scopeflags"; -import type { ExpressionErrors } from "../parser/util"; -import { Errors } from "../parser/error"; +} from "../../util/scopeflags"; +import type { ExpressionErrors } from "../../parser/util"; +import { Errors } from "../../parser/error"; const reservedTypes = new Set([ "_", @@ -185,11 +186,10 @@ export default (superClass: Class): Class => // The value of the @flow/@noflow pragma. Initially undefined, transitions // to "@flow" or "@noflow" if we see a pragma. Transitions to null if we are // past the initial comment. - flowPragma: void | null | "flow" | "noflow"; + flowPragma: void | null | "flow" | "noflow" = undefined; - constructor(options: ?Options, input: string) { - super(options, input); - this.flowPragma = undefined; + getScopeHandler(): Class { + return FlowScopeHandler; } shouldParseTypes(): boolean { @@ -327,6 +327,8 @@ export default (superClass: Class): Class => this.resetEndLocation(id); this.semicolon(); + this.scope.declareName(node.id.name, BIND_FLOW_DECLARE_FN, node.id.start); + return this.finishNode(node, "DeclareFunction"); } diff --git a/packages/babel-parser/src/plugins/flow/scope.js b/packages/babel-parser/src/plugins/flow/scope.js new file mode 100644 index 000000000000..eb98bdbcc96d --- /dev/null +++ b/packages/babel-parser/src/plugins/flow/scope.js @@ -0,0 +1,56 @@ +// @flow + +import ScopeHandler, { Scope } from "../../util/scope"; +import { + BIND_FLAGS_FLOW_DECLARE_FN, + type ScopeFlags, + type BindingTypes, +} from "../../util/scopeflags"; +import * as N from "../../types"; + +// Reference implementation: https://github.com/facebook/flow/blob/23aeb2a2ef6eb4241ce178fde5d8f17c5f747fb5/src/typing/env.ml#L536-L584 +class FlowScope extends Scope { + // declare function foo(): type; + declareFunctions: string[] = []; +} + +export default class FlowScopeHandler extends ScopeHandler { + createScope(flags: ScopeFlags): FlowScope { + return new FlowScope(flags); + } + + declareName(name: string, bindingType: BindingTypes, pos: number) { + const scope = this.currentScope(); + if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) { + this.checkRedeclarationInScope(scope, name, bindingType, pos); + this.maybeExportDefined(scope, name); + scope.declareFunctions.push(name); + return; + } + + super.declareName(...arguments); + } + + isRedeclaredInScope( + scope: FlowScope, + name: string, + bindingType: BindingTypes, + ): boolean { + if (super.isRedeclaredInScope(...arguments)) return true; + + if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) { + return ( + !scope.declareFunctions.includes(name) && + (scope.lexical.includes(name) || scope.functions.includes(name)) + ); + } + + return false; + } + + checkLocalExport(id: N.Identifier) { + if (this.scopeStack[0].declareFunctions.indexOf(id.name) === -1) { + super.checkLocalExport(id); + } + } +} diff --git a/packages/babel-parser/src/util/scopeflags.js b/packages/babel-parser/src/util/scopeflags.js index f3af9bd3278d..924d94d40b96 100644 --- a/packages/babel-parser/src/util/scopeflags.js +++ b/packages/babel-parser/src/util/scopeflags.js @@ -28,20 +28,21 @@ export type ScopeFlags = // These flags are meant to be _only_ used inside the Scope class (or subclasses). // prettier-ignore -export const BIND_KIND_VALUE = 0b00000_0000_01, - BIND_KIND_TYPE = 0b00000_0000_10, +export const BIND_KIND_VALUE = 0b000000_0000_01, + BIND_KIND_TYPE = 0b000000_0000_10, // Used in checkLVal and declareName to determine the type of a binding - BIND_SCOPE_VAR = 0b00000_0001_00, // Var-style binding - BIND_SCOPE_LEXICAL = 0b00000_0010_00, // Let- or const-style binding - BIND_SCOPE_FUNCTION = 0b00000_0100_00, // Function declaration - BIND_SCOPE_OUTSIDE = 0b00000_1000_00, // Special case for function names as + BIND_SCOPE_VAR = 0b000000_0001_00, // Var-style binding + BIND_SCOPE_LEXICAL = 0b000000_0010_00, // Let- or const-style binding + BIND_SCOPE_FUNCTION = 0b000000_0100_00, // Function declaration + BIND_SCOPE_OUTSIDE = 0b000000_1000_00, // Special case for function names as // bound inside the function // Misc flags - BIND_FLAGS_NONE = 0b00001_0000_00, - BIND_FLAGS_CLASS = 0b00010_0000_00, - BIND_FLAGS_TS_ENUM = 0b00100_0000_00, - BIND_FLAGS_TS_CONST_ENUM = 0b01000_0000_00, - BIND_FLAGS_TS_EXPORT_ONLY = 0b10000_0000_00; + BIND_FLAGS_NONE = 0b000001_0000_00, + BIND_FLAGS_CLASS = 0b000010_0000_00, + BIND_FLAGS_TS_ENUM = 0b000100_0000_00, + BIND_FLAGS_TS_CONST_ENUM = 0b001000_0000_00, + BIND_FLAGS_TS_EXPORT_ONLY = 0b010000_0000_00, + BIND_FLAGS_FLOW_DECLARE_FN = 0b100000_0000_00; // These flags are meant to be _only_ used by Scope consumers // prettier-ignore @@ -60,7 +61,9 @@ export const BIND_CLASS = BIND_KIND_VALUE | BIND_KIND_TYPE | BIND_SCOPE_ BIND_OUTSIDE = BIND_KIND_VALUE | 0 | 0 | BIND_FLAGS_NONE , BIND_TS_CONST_ENUM = BIND_TS_ENUM | BIND_FLAGS_TS_CONST_ENUM, - BIND_TS_NAMESPACE = 0 | 0 | 0 | BIND_FLAGS_TS_EXPORT_ONLY; + BIND_TS_NAMESPACE = 0 | 0 | 0 | BIND_FLAGS_TS_EXPORT_ONLY, + + BIND_FLOW_DECLARE_FN = BIND_FLAGS_FLOW_DECLARE_FN; export type BindingTypes = | typeof BIND_NONE diff --git a/packages/babel-parser/test/fixtures/flow/scope/declare-function-export/input.js b/packages/babel-parser/test/fixtures/flow/scope/declare-function-export/input.js new file mode 100644 index 000000000000..05b292825827 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/scope/declare-function-export/input.js @@ -0,0 +1,2 @@ +declare function foo(): void; +export { foo }; diff --git a/packages/babel-parser/test/fixtures/flow/scope/declare-function-export/output.json b/packages/babel-parser/test/fixtures/flow/scope/declare-function-export/output.json new file mode 100644 index 000000000000..2e2941394998 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/scope/declare-function-export/output.json @@ -0,0 +1,61 @@ +{ + "type": "File", + "start":0,"end":45,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":15}}, + "program": { + "type": "Program", + "start":0,"end":45,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":15}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "DeclareFunction", + "start":0,"end":29,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":29}}, + "id": { + "type": "Identifier", + "start":17,"end":28,"loc":{"start":{"line":1,"column":17},"end":{"line":1,"column":28},"identifierName":"foo"}, + "name": "foo", + "typeAnnotation": { + "type": "TypeAnnotation", + "start":20,"end":28,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":28}}, + "typeAnnotation": { + "type": "FunctionTypeAnnotation", + "start":20,"end":28,"loc":{"start":{"line":1,"column":20},"end":{"line":1,"column":28}}, + "typeParameters": null, + "params": [], + "rest": null, + "returnType": { + "type": "VoidTypeAnnotation", + "start":24,"end":28,"loc":{"start":{"line":1,"column":24},"end":{"line":1,"column":28}} + } + } + } + }, + "predicate": null + }, + { + "type": "ExportNamedDeclaration", + "start":30,"end":45,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":15}}, + "specifiers": [ + { + "type": "ExportSpecifier", + "start":39,"end":42,"loc":{"start":{"line":2,"column":9},"end":{"line":2,"column":12}}, + "local": { + "type": "Identifier", + "start":39,"end":42,"loc":{"start":{"line":2,"column":9},"end":{"line":2,"column":12},"identifierName":"foo"}, + "name": "foo" + }, + "exported": { + "type": "Identifier", + "start":39,"end":42,"loc":{"start":{"line":2,"column":9},"end":{"line":2,"column":12},"identifierName":"foo"}, + "name": "foo" + } + } + ], + "source": null, + "declaration": null, + "exportKind": "value" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/flow/scope/dupl-decl-func-declare-func/input.js b/packages/babel-parser/test/fixtures/flow/scope/dupl-decl-func-declare-func/input.js new file mode 100644 index 000000000000..9d8357d11961 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/scope/dupl-decl-func-declare-func/input.js @@ -0,0 +1,2 @@ +function A() {} +declare function A(): void; diff --git a/packages/babel-parser/test/fixtures/flow/scope/dupl-decl-func-declare-func/output.json b/packages/babel-parser/test/fixtures/flow/scope/dupl-decl-func-declare-func/output.json new file mode 100644 index 000000000000..e088b316d6c6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/flow/scope/dupl-decl-func-declare-func/output.json @@ -0,0 +1,59 @@ +{ + "type": "File", + "start":0,"end":43,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":27}}, + "errors": [ + "SyntaxError: Identifier 'A' has already been declared (2:17)" + ], + "program": { + "type": "Program", + "start":0,"end":43,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":27}}, + "sourceType": "module", + "interpreter": null, + "body": [ + { + "type": "FunctionDeclaration", + "start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}}, + "id": { + "type": "Identifier", + "start":9,"end":10,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":10},"identifierName":"A"}, + "name": "A" + }, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":13,"end":15,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":15}}, + "body": [], + "directives": [] + } + }, + { + "type": "DeclareFunction", + "start":16,"end":43,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":27}}, + "id": { + "type": "Identifier", + "start":33,"end":42,"loc":{"start":{"line":2,"column":17},"end":{"line":2,"column":26},"identifierName":"A"}, + "name": "A", + "typeAnnotation": { + "type": "TypeAnnotation", + "start":34,"end":42,"loc":{"start":{"line":2,"column":18},"end":{"line":2,"column":26}}, + "typeAnnotation": { + "type": "FunctionTypeAnnotation", + "start":34,"end":42,"loc":{"start":{"line":2,"column":18},"end":{"line":2,"column":26}}, + "typeParameters": null, + "params": [], + "rest": null, + "returnType": { + "type": "VoidTypeAnnotation", + "start":38,"end":42,"loc":{"start":{"line":2,"column":22},"end":{"line":2,"column":26}} + } + } + } + }, + "predicate": null + } + ], + "directives": [] + } +} \ No newline at end of file