Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ts] allow redeclaring a var/type with the same name as import #14900

Merged
merged 8 commits into from Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -2712,11 +2712,11 @@ export default abstract class StatementParser extends ExpressionParser {
| N.ImportSpecifier
| N.ImportDefaultSpecifier
| N.ImportNamespaceSpecifier,
>(specifier: Undone<T>, type: T["type"]) {
>(specifier: Undone<T>, type: T["type"], bindingType = BIND_LEXICAL) {
this.checkLVal(specifier.local, {
// @ts-expect-error refine types
in: specifier,
binding: BIND_LEXICAL,
binding: bindingType,
});
return this.finishNode(specifier, type);
}
Expand Down Expand Up @@ -2890,6 +2890,7 @@ export default abstract class StatementParser extends ExpressionParser {
importedIsString,
node.importKind === "type" || node.importKind === "typeof",
isMaybeTypeOnly,
undefined,
);
node.specifiers.push(importSpecifier);
}
Expand All @@ -2902,6 +2903,7 @@ export default abstract class StatementParser extends ExpressionParser {
/* eslint-disable @typescript-eslint/no-unused-vars -- used in TypeScript and Flow parser */
isInTypeOnlyImport: boolean,
isMaybeTypeOnly: boolean,
bindingType: BindingTypes | undefined,
/* eslint-enable @typescript-eslint/no-unused-vars */
): N.ImportSpecifier {
if (this.eatContextual(tt._as)) {
Expand All @@ -2924,7 +2926,11 @@ export default abstract class StatementParser extends ExpressionParser {
specifier.local = cloneIdentifier(imported);
}
}
return this.finishImportSpecifier(specifier, "ImportSpecifier");
return this.finishImportSpecifier(
specifier,
"ImportSpecifier",
bindingType,
);
}

// This is used in flow and typescript plugin
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/plugins/flow/index.ts
Expand Up @@ -2785,6 +2785,8 @@ export default (superClass: typeof Parser) =>
isInTypeOnlyImport: boolean,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
isMaybeTypeOnly: boolean,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bindingType: BindingTypes | undefined,
): N.ImportSpecifier {
const firstIdent = specifier.imported;

Expand Down
14 changes: 11 additions & 3 deletions packages/babel-parser/src/plugins/typescript/index.ts
Expand Up @@ -27,9 +27,11 @@ import {
BIND_TS_INTERFACE,
BIND_TS_AMBIENT,
BIND_TS_NAMESPACE,
BIND_TS_TYPE_IMPORT,
BIND_CLASS,
BIND_LEXICAL,
BIND_NONE,
BIND_FLAGS_TS_IMPORT,
} from "../../util/scopeflags";
import TypeScriptScopeHandler from "./scope";
import * as charCodes from "charcodes";
Expand Down Expand Up @@ -3922,7 +3924,7 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
}

parseExportSpecifier(
node: any,
node: Undone<N.ExportSpecifier>,
isString: boolean,
isInTypeExport: boolean,
isMaybeTypeOnly: boolean,
Expand All @@ -3945,10 +3947,12 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
}

parseImportSpecifier(
specifier: any,
specifier: Undone<N.ImportSpecifier>,
importedIsString: boolean,
isInTypeOnlyImport: boolean,
isMaybeTypeOnly: boolean,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
bindingType: BindingTypes | undefined,
): N.ImportSpecifier {
if (!importedIsString && isMaybeTypeOnly) {
this.parseTypeOnlyImportExportSpecifier(
Expand All @@ -3964,6 +3968,7 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
importedIsString,
isInTypeOnlyImport,
isMaybeTypeOnly,
isInTypeOnlyImport ? BIND_TS_TYPE_IMPORT : BIND_FLAGS_TS_IMPORT,
);
}

Expand Down Expand Up @@ -4059,7 +4064,10 @@ export default (superClass: ClassWithMixin<typeof Parser, IJSXParserMixin>) =>
node[rightOfAsKey] = cloneIdentifier(node[leftOfAsKey]);
}
if (isImport) {
this.checkIdentifier(node[rightOfAsKey], BIND_LEXICAL);
this.checkIdentifier(
node[rightOfAsKey],
hasTypeSpecifier ? BIND_TS_TYPE_IMPORT : BIND_FLAGS_TS_IMPORT,
);
}
}
};
Expand Down
52 changes: 51 additions & 1 deletion packages/babel-parser/src/plugins/typescript/scope.ts
Expand Up @@ -9,8 +9,11 @@ import {
BIND_FLAGS_CLASS,
type ScopeFlags,
type BindingTypes,
BIND_FLAGS_TS_IMPORT,
SCOPE_TS_MODULE,
} from "../../util/scopeflags";
import type * as N from "../../types";
import { Errors } from "../../parse-error";

class TypeScriptScope extends Scope {
types: Set<string> = new Set();
Expand All @@ -35,11 +38,57 @@ class TypeScriptScope extends Scope {
// explanation of how typescript handles scope.

export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope> {
importsStack: Set<string>[] = [];

createScope(flags: ScopeFlags): TypeScriptScope {
this.importsStack.push(new Set()); // Always keep the top-level scope for export checks.

return new TypeScriptScope(flags);
}

enter(flags: number): void {
if (flags == SCOPE_TS_MODULE) {
this.importsStack.push(new Set());
}

super.enter(flags);
}

exit() {
const flags = super.exit();

if (flags == SCOPE_TS_MODULE) {
this.importsStack.pop();
}

return flags;
}

hasImport(name: string, allowShadow?: boolean) {
const len = this.importsStack.length;
if (this.importsStack[len - 1].has(name)) {
return true;
}
if (!allowShadow && len > 1) {
for (let i = 0; i < len - 1; i++) {
if (this.importsStack[i].has(name)) return true;
}
}
return false;
}

declareName(name: string, bindingType: BindingTypes, loc: Position) {
if (bindingType & BIND_FLAGS_TS_IMPORT) {
if (this.hasImport(name, true)) {
this.parser.raise(Errors.VarRedeclaration, {
at: loc,
identifierName: name,
});
}
this.importsStack[this.importsStack.length - 1].add(name);
return;
}

const scope = this.currentScope();
if (bindingType & BIND_FLAGS_TS_EXPORT_ONLY) {
this.maybeExportDefined(scope, name);
Expand Down Expand Up @@ -98,7 +147,8 @@ export default class TypeScriptScopeHandler extends ScopeHandler<TypeScriptScope
const { name } = id;
if (
!topLevelScope.types.has(name) &&
!topLevelScope.exportOnlyBindings.has(name)
!topLevelScope.exportOnlyBindings.has(name) &&
!this.hasImport(name)
) {
super.checkLocalExport(id);
}
Expand Down
5 changes: 3 additions & 2 deletions packages/babel-parser/src/util/scope.ts
Expand Up @@ -93,8 +93,9 @@ export default class ScopeHandler<IScope extends Scope = Scope> {
this.scopeStack.push(this.createScope(flags));
}

exit() {
this.scopeStack.pop();
exit(): ScopeFlags {
const scope = this.scopeStack.pop();
return scope.flags;
}

// The spec says:
Expand Down
18 changes: 10 additions & 8 deletions packages/babel-parser/src/util/scopeflags.ts
Expand Up @@ -35,12 +35,13 @@ export const BIND_KIND_VALUE = 0b000000_0000_01,
BIND_SCOPE_OUTSIDE = 0b000000_1000_00, // Special case for function names as
// bound inside the function
// Misc flags
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;
BIND_FLAGS_NONE = 0b0000001_0000_00,
BIND_FLAGS_CLASS = 0b0000010_0000_00,
BIND_FLAGS_TS_ENUM = 0b0000100_0000_00,
BIND_FLAGS_TS_CONST_ENUM = 0b0001000_0000_00,
BIND_FLAGS_TS_EXPORT_ONLY = 0b0010000_0000_00,
BIND_FLAGS_FLOW_DECLARE_FN = 0b0100000_0000_00,
BIND_FLAGS_TS_IMPORT = 0b1000000_0000_00;

// These flags are meant to be _only_ used by Scope consumers
// prettier-ignore
Expand All @@ -58,8 +59,9 @@ export const BIND_CLASS = BIND_KIND_VALUE | BIND_KIND_TYPE | BIND_SCOPE_
BIND_NONE = 0 | 0 | 0 | BIND_FLAGS_NONE ,
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_CONST_ENUM = BIND_TS_ENUM | BIND_FLAGS_TS_CONST_ENUM ,
BIND_TS_NAMESPACE = 0 | 0 | 0 | BIND_FLAGS_TS_EXPORT_ONLY,
BIND_TS_TYPE_IMPORT= 0 | BIND_KIND_TYPE | 0 | BIND_FLAGS_TS_IMPORT,

BIND_FLOW_DECLARE_FN = BIND_FLAGS_FLOW_DECLARE_FN;

Expand Down
@@ -0,0 +1,7 @@
import React, { Context } from 'react';
import { a } from 'react';
import { a as b } from 'react';

let Context: Context<{}> = React.createContext({});
let a: a = 1;
let b: b = 1;