From a625dec58afca9ede846880abebb772aa1e12bcd Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 29 Nov 2017 16:36:17 -0800 Subject: [PATCH] Elide import namespace from which only const enums are used (#20320) --- src/compiler/checker.ts | 14 +++++++ ...nstEnumNamespaceReferenceCausesNoImport.js | 34 ++++++++++++++++ ...umNamespaceReferenceCausesNoImport.symbols | 40 +++++++++++++++++++ ...EnumNamespaceReferenceCausesNoImport.types | 40 +++++++++++++++++++ ...nstEnumNamespaceReferenceCausesNoImport.ts | 16 ++++++++ 5 files changed, 144 insertions(+) create mode 100644 tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.js create mode 100644 tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.symbols create mode 100644 tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.types create mode 100644 tests/cases/compiler/constEnumNamespaceReferenceCausesNoImport.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5bf441fc0db..a0c77a516bf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15581,6 +15581,8 @@ namespace ts { function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { let propType: Type; + let leftSymbol = getNodeLinks(left) && getNodeLinks(left).resolvedSymbol; + const leftWasReferenced = leftSymbol && getSymbolLinks(leftSymbol).referenced; const leftType = checkNonNullExpression(left); const apparentType = getApparentType(getWidenedType(leftType)); if (isTypeAny(apparentType) || apparentType === silentNeverType) { @@ -15604,6 +15606,13 @@ namespace ts { else { checkPropertyNotUsedBeforeDeclaration(prop, node, right); markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); + // Reset the referenced-ness of the LHS expression if this access refers to a const enum or const enum only module + leftSymbol = getNodeLinks(left) && getNodeLinks(left).resolvedSymbol; + if (leftSymbol && !leftWasReferenced && getSymbolLinks(leftSymbol).referenced && + !(isNonLocalAlias(leftSymbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(node) && !isConstEnumOrConstEnumOnlyModule(prop)) + ) { + getSymbolLinks(leftSymbol).referenced = undefined; + } getNodeLinks(node).resolvedSymbol = prop; checkPropertyAccessibility(node, left, apparentType, prop); if (assignmentKind) { @@ -24691,6 +24700,11 @@ namespace ts { if (symbol && getSymbolLinks(symbol).referenced) { return true; } + const target = getSymbolLinks(symbol).target; + if (target && getModifierFlags(node) & ModifierFlags.Export && target.flags & SymbolFlags.Value) { + // An `export import ... =` of a value symbol is always considered referenced + return true; + } } if (checkChildren) { diff --git a/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.js b/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.js new file mode 100644 index 00000000000..d1ead34bdd5 --- /dev/null +++ b/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.js @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/constEnumNamespaceReferenceCausesNoImport.ts] //// + +//// [foo.ts] +export const enum ConstFooEnum { + Some, + Values, + Here +}; +export function fooFunc(): void { /* removed */ } +//// [index.ts] +import * as Foo from "./foo"; + +function check(x: Foo.ConstFooEnum): void { + switch (x) { + case Foo.ConstFooEnum.Some: + break; + } +} + +//// [foo.js] +"use strict"; +exports.__esModule = true; +; +function fooFunc() { } +exports.fooFunc = fooFunc; +//// [index.js] +"use strict"; +exports.__esModule = true; +function check(x) { + switch (x) { + case 0 /* Some */: + break; + } +} diff --git a/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.symbols b/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.symbols new file mode 100644 index 00000000000..92a85013834 --- /dev/null +++ b/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.symbols @@ -0,0 +1,40 @@ +=== tests/cases/compiler/foo.ts === +export const enum ConstFooEnum { +>ConstFooEnum : Symbol(ConstFooEnum, Decl(foo.ts, 0, 0)) + + Some, +>Some : Symbol(ConstFooEnum.Some, Decl(foo.ts, 0, 32)) + + Values, +>Values : Symbol(ConstFooEnum.Values, Decl(foo.ts, 1, 9)) + + Here +>Here : Symbol(ConstFooEnum.Here, Decl(foo.ts, 2, 11)) + +}; +export function fooFunc(): void { /* removed */ } +>fooFunc : Symbol(fooFunc, Decl(foo.ts, 4, 2)) + +=== tests/cases/compiler/index.ts === +import * as Foo from "./foo"; +>Foo : Symbol(Foo, Decl(index.ts, 0, 6)) + +function check(x: Foo.ConstFooEnum): void { +>check : Symbol(check, Decl(index.ts, 0, 29)) +>x : Symbol(x, Decl(index.ts, 2, 15)) +>Foo : Symbol(Foo, Decl(index.ts, 0, 6)) +>ConstFooEnum : Symbol(Foo.ConstFooEnum, Decl(foo.ts, 0, 0)) + + switch (x) { +>x : Symbol(x, Decl(index.ts, 2, 15)) + + case Foo.ConstFooEnum.Some: +>Foo.ConstFooEnum.Some : Symbol(Foo.ConstFooEnum.Some, Decl(foo.ts, 0, 32)) +>Foo.ConstFooEnum : Symbol(Foo.ConstFooEnum, Decl(foo.ts, 0, 0)) +>Foo : Symbol(Foo, Decl(index.ts, 0, 6)) +>ConstFooEnum : Symbol(Foo.ConstFooEnum, Decl(foo.ts, 0, 0)) +>Some : Symbol(Foo.ConstFooEnum.Some, Decl(foo.ts, 0, 32)) + + break; + } +} diff --git a/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.types b/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.types new file mode 100644 index 00000000000..ce1410a0911 --- /dev/null +++ b/tests/baselines/reference/constEnumNamespaceReferenceCausesNoImport.types @@ -0,0 +1,40 @@ +=== tests/cases/compiler/foo.ts === +export const enum ConstFooEnum { +>ConstFooEnum : ConstFooEnum + + Some, +>Some : ConstFooEnum.Some + + Values, +>Values : ConstFooEnum.Values + + Here +>Here : ConstFooEnum.Here + +}; +export function fooFunc(): void { /* removed */ } +>fooFunc : () => void + +=== tests/cases/compiler/index.ts === +import * as Foo from "./foo"; +>Foo : typeof Foo + +function check(x: Foo.ConstFooEnum): void { +>check : (x: Foo.ConstFooEnum) => void +>x : Foo.ConstFooEnum +>Foo : any +>ConstFooEnum : Foo.ConstFooEnum + + switch (x) { +>x : Foo.ConstFooEnum + + case Foo.ConstFooEnum.Some: +>Foo.ConstFooEnum.Some : Foo.ConstFooEnum.Some +>Foo.ConstFooEnum : typeof Foo.ConstFooEnum +>Foo : typeof Foo +>ConstFooEnum : typeof Foo.ConstFooEnum +>Some : Foo.ConstFooEnum.Some + + break; + } +} diff --git a/tests/cases/compiler/constEnumNamespaceReferenceCausesNoImport.ts b/tests/cases/compiler/constEnumNamespaceReferenceCausesNoImport.ts new file mode 100644 index 00000000000..3df68067ef5 --- /dev/null +++ b/tests/cases/compiler/constEnumNamespaceReferenceCausesNoImport.ts @@ -0,0 +1,16 @@ +// @filename: foo.ts +export const enum ConstFooEnum { + Some, + Values, + Here +}; +export function fooFunc(): void { /* removed */ } +// @filename: index.ts +import * as Foo from "./foo"; + +function check(x: Foo.ConstFooEnum): void { + switch (x) { + case Foo.ConstFooEnum.Some: + break; + } +} \ No newline at end of file